Pouzitie RavenDB

Ahojte,
myslím, že toto je vhodné miesto na otázku ohľadom RavenDB.
Prešiel som si jej featury a mám pocit, že poskytuje obdobné veci ako MongoDB a zároveň o nej nie je veľa zmienok (zaujali ma „materializované pohľady“, automatické indexovanie, TTL, attachments,…).

Ak ste ju použili tak na aké use-case?

Aké vidíte výhody/nevýhody oproti MonboDb?

S akými problémami ste si pri RavenDb prešli, čo nemusia byť začiatočníkovi jasné.

Nerobí problém, že všetky dokumenty v databáze sú ako keby v jednej veľkej kolekcii?

Nerobia RavenDB problém ad-hok dopyty?
(Napríklad, keby chcem robiť aplikáciu na hľadanie logov, tak mi nebude robiť za každým dopytom nový index nad XY GB dát?).

To je veľa otázok, začnem z kraja:

  1. use cases
  • všade tam, kde je predpoklad, že budeš mať množstvo rôznych dát. Typický príklad finstat, dátovych zdrojov sú desiatky. Každý nový začleňovať do sql schémy by bolo neúnosné, takto proste ukladáme jsony
  • všade tam, kde potrebuješ pristupovať ku všetkým dátam naraz a vo veľkom počte, sql je super ak pracuješ nad subsetom, ak máš robiť queries nad miliónmi záznamov, tak potrebuješ riadne železo, tu má RavenDB s lucene indexami troška navrch
  • kde nie je vhodný: všade tam, kde nie je tolerancia, aby bol medzi indexom a raw dátami časový rozdiel. V SQL pristupuješ cez indexy priamo k reálnym dátam v rámci transakcie, v RavenDB sú indexy mimo transakcie, teda ACID je len key-value časť RavenDB, teda vyťahovanie informácii cez ID.
  • ešte scenár nevhodnosti je: ak silno používaš číselníky, teda potrebuješ meniť historickú hodnotu, v RavenDB musíš potom patchovať dáta. Ale ako ja vysvetľujem, lepšie je zmeniť dáta raz za pol roka, ako ich joinovať pri každom query.
  1. Mongo nemá ACID podporu ukladania dát, to zistíš až o ne prídeš (pozeram, že už to pridali), teda musí bežať vždy v clustri. Mongo nemá MapReduce indexy, čo je pre nás must have.
  2. Problémami sme prešli kopec (používame od verzie 2.0), aktuálne ale je ale verzia 5. A tu vidím pre začiatočníkov dva problémy:
  • zmeniť filozofiu myslenia, keď zaučam nových ľudí, tak s tým je najväčší problém. Ľudia čo robia celý život v SQL sa ťažko dostávajú z myslenia normalizovať dáta. Tiež využívajú indexy na to, na čo nie sú určené, teda do nich tlačia raw json dáta.
  • autoindexy - my to vypíname, lebo veľa ľudí si myslí, že za nich autoindexy vyriešia problém. Nie indexy sú ten najdôležitejší nástroj práce s RavenDB.
  • MapReduce - to je koncept, čo ľudia ťažko chápu, ale je to vlastne alternatíva k joinom/group by v sql, akurát je predpočítana a teda násobne rýchlejšia
  1. Kolekcie: Zatiaľ sme s kolekciami nemali problém, sú vynikajúci koncept ak robíš správu db a v RavenDB 5 je každá kolekcia akoby osobitný databázový priestor, nad ktorým je index cez ID. U nás je kolekcia jeden typ root objektu.
  2. Ad-hoc dopyty: Robia. Ako píšem vyššie, už pri návrhu musíš mysliet na indexy. Ak potrebuješ dynamické možnosti queryovania, využi SQL ETL https://ravendb.net/docs/article-page/5.0/Csharp/server/ongoing-tasks/etl/sql

Vdaka za obsiahlu odpoved. Mnohe mi to objasnilo.

S Lucene aj Mongom som uz pracoval, preto som sa pytal na porovnanie prave s nim.

Na SQL ETL sa pozriem a uvidim, ci to bude pre moje use case.

RavenDB ma zaujala, hlavne tym, ze je trochu ina ako Mongo, ktore je propagovane a spominane kam sa clovek pozrie.

Mongo je fajn, lebo je jednoduche a free. Ked ale firmy narastu, tak prechadzaju inde, prave z dovodov, ze Mongo nezapisuje na disk safe, takze ak bezis len jednu instanciu, tak urcite v nejakej faze prides o data.
Ak mas cas a zacinas na zelenej luke, tak by sa ti mohol pacit Marten, chlapik ho vytvoril pred 4 rokmi, lebo nebol spokojny s RavenDB a pacili sa mu jeho koncepty.

Vdaka za spomenutie Marten, mas s nim prakticke skusenosti?

Ja som davnejsie skusal nieco podobne YesSql len pre MS SQL.

No parkrat som pouzil LiteDB co je dotnetova embedded databaza s rovnakym API ako ma Mongo, ale mavyse SQL syntax a tranzakcie. Mam ju v nejakych desktopovych aplikaciach a zatial bez problemov.

IMHO: Na MS SQL som si postavil aj queue (PassiveMQ), ktora kopiruje API Azure Queue storage, bolo to lahsie ako nasadit RabbitMQ.

Dosiaľ som nemal príležitosť. Mám to len v škatuľke, že keď budem musieť, tak viem, kde hľadať. Momentálne sme spokojný a trošku aj vendor locknuty na RavenDB.

Minuly rok o tomto case som si skusal hello world veci na RavenDB.

No teraz som sa pustil do testovacieho “realneho” projektu, kde si chcem vyskusat realnu pracu.

A mam par otazok, ci postujem spravne.

  1. V aplikacii su korpusy dokumentov. V UI chcem pre korpusy pouzivatela zobrazovat kolko dokumentov obsahuju. Spravil som to cez multimap-reduce index, kde sa udaje u korpuse ziskaju s korpusu a skombinuju sa s dokumentami a vypadne mi pocet. Je to tak spravne? Druha moznost je pridavat odkazdy na dokumenty do korpusu (ale nenasiel som ako atomicky pridat polozku do pola v dokumente).

  2. Čo s ID-čkami? Ako sa riesia v realnych webovych aplikaciach? Na restovom API sa zle dava do URL ID vo forme Document/123-A.

  3. Rozmyslam, kedy dokumenty embedovat a kedy na ne pouzit odkaz. Jestvuje na to nejake pravidlo? (Doslo mi, ze embedovat je lepsie, v pripade ak podla udajov v tom dokumente vyhladavam, alebo s nimi casto pracujem.)

Áno, to je správne riešenie. Tiež je riešenie to predpočítať do korpusu, ale nejaká logika by to tam musela updatovať, pri vytvoreni/zmene/zrušení dokumentu. Na to je vhodný Patch príkaz.

Autoincrement kľúče majú presne takýto formát. Ti A je tam preto, aby to bolo rychlé, ak chceš krajšie id, možeš použiť Identities Working with Document Identifiers. Alebo ako my pouzivame predefinované kľúče, napr cez guid.

Tu asi záleží, ako sa bude s dokumentami pracovať. Attachments sú super na binárne súbory, vyhneš sa komplikácií s konverziou do base64. Ak sú dokumenty textové, tak priamo ukladať znamená, že sa ľahšie vyťahujú údaje z nich.

Ešte ma napadlo, keďže nepoznám scenár, ale možno by bolo v tomto prípade najjednoduchšie ukladať korpus ako kolekciu a dokumenty ukladať ako attachmenty k tomu korpusu What are Attachments

Autoincrement kľúče majú presne takýto formát. Ti A je tam preto, aby to bolo rychlé, ak chceš krajšie id, možeš použiť Identities Working with Document Identifiers. Alebo ako my pouzivame predefinované kľúče, napr cez guid.

O tom som vedel, ze ide predefinovat, no nakoniec som to riesil cez HashId kniznicu, no asi to nie je najlepsie rienie. Skor som myslel, ci na to niesu nejake ine odporucania. Ale hram sa a experimentujem.

Tu asi záleží, ako sa bude s dokumentami pracovať. Attachments sú super na binárne súbory, vyhneš sa komplikácií s konverziou do base64. Ak sú dokumenty textové, tak priamo ukladať znamená, že sa ľahšie vyťahujú údaje z nich.

Ono je to trochu blbe na komunikaciu lebo mame dokument ako textovy dokument a dokument v zmysle databazoveho dokumentu (entity).

Asi trosku opisem architekturu a ako to funguje. Mam web api rozhranie, ktore robi jednoduche operacie (vytvorenie korpusu,…) potom mam worker service, ktory cez subscription pocuva commandy (mam len jednu kolekciu na commandy a v nej polymorfizmom konkretne) a vykonava ich… tj. napriklad extrakcia cisteho textu s PDF-ka, alebo spracovanie tohto textu, ci kontrola plagiatorstva nad celym korpusom.

Dokument (domenovy) vytvorim s attachmentom, ktory obsahuje skutocny dokument (najcastejsie PDF-ko). Pri vytvoreni noveho dokumentu sa ulozi aj dokument aj command, ktory na backende spusti procesovanie tohto PDF-ka, vytiahne text, ten ulozi do domenoveho dokumentu kvoli fulltextu a pokracuje v procesovani.

K tomu celemu mam CLI apku na ovladanie. (Keby k tomu spravim Blazor frontened, tak je uplne cely projekt v C#).

Zatial mam pocit, ze co use-case to dalsi index v RavenDb.

A tiez robim veci inak, ako by som ich robil v produkcii (namiesto Subscripcii by som na background processing pouzil Hangfire).

A teraz nejake otazky:

  1. Je nejaky preferovany sposob ako vytiahnut viac dokumentov ako 30? Viem, ze je tam stream, ale ja ich potrebujem vytahovat podla ID. Momentalne si pre kazde vytiahnutie otvorim novu session.

  2. Co mi zatial chyba a nenasiel som to je seekable stream na attachment (stacilo by aspon moznost si vytiahnut vysek binarnych dat).

  3. Commandy (RavenDB commandy) je moznost ich odoslat v jednej transzakcii v save changed (napriklad vytvorenie domenoveho dokumentu a update korpusu)?

load ma override, ktory vracia dictionary vlko/RavenSession.cs at master · vlko/vlko (github.com)

Toto by sa dalo poriešit transformerom, pošleš ID dokumentu, plus poziciu, pripadne nejaku logiku a on si načíta attachment a seekne v ramci neho.

Tu si nie som istý, či patch commandy nemajú osobitnú transakciu, čo by dávalo zmysel, lebo sa spracuvávajú na pozadí. Čo sa týka load/update/delete celej entity, tak tam je session jeden Unit of Work pattern vid prednáška, čo som robil pre kros KROS Dev Meetup #7 -RavenDB is a NoSQL Document Database

Len pri aj pri tomto postupe je asi obmedzenie na maximalny pocet loadnutych dokumentov v session?

Mne zhladiska programu nevadi vytahovat tieto dokumenty po jdnom, lebo v korpuse sa spracovava kazdy s kazdym.

Vies mi napisat na to nejaky priklad? Lebo podla toho co som cital, tak transformeri boli len “pohlady” alebo projekcie a ich API uz v klientskej kniznici nie je (verzia 5.3).

Hej tu prednasku som videl, hned ako bola dostupna online. Vdaka za nu.

Ako zakladnym principom chapem, len si este ujasnujem tie jemne nuancy, na ktore clovek narazi, ked robi nieco “skutocne”.

RavenDB ma error pri pocte requestov na jednu session, co je celkom uzitocne, ak mas v kode zle implementovanu 1:N logiku vztahov (to sa potom riesi cez Include, alebo transformer) Session: How to Change Maximum Number of Requests per Session. Co sa tyka loadovania cez viac ID, tak to ide na jeden request, my pouzivame nacitavanie aj pre 1k batche id.

Normalne vies v transformeri pristupit k attachmentom cez LoadAttachment, tak ako v ked ich indexujes vid Indexes: Indexing Attachments ten ti vrati ravendb/IAttachmentObject.cs a tam uz mas GetContentAsStream a potom to uz je na tebe seek, read.

Vdaka, to som si upravil.

Hej, len to by som si musel ten dokument rozdelit na male kusky (povdzme 4kB) a tak zaindexovat.

To ma uz napadlo.

@vlko Mam dalsiu otazku ohladom indexov.
Mam dynamicky index (tu je cely zdrojak):

 this.Map = logs => from log in logs
                           select new
                           {
                               /// ...
                               _ = log.Properties.Where(t => t.Values != null).Select(t => this.CreateField(t.Name, t.Values, new CreateFieldOptions()
                               {
                                   Indexing = FieldIndexing.Exact | FieldIndexing.Default,
                                   Storage = FieldStorage.No,
                                   TermVector = FieldTermVector.No
                               })),
                           };

A potreboval by som v nom vyhladavat pomocou presnej zhody a aj pomocou startsWith a `search’:

from index 'LogMainIndex'
where startsWith(AppName, "Area52")
order by Timestamp desc
select id() as Id, Timestamp, Message, Level

Co aktualne funguje. No ptreboval by som vyhladavat aj na exaktnu zhodu:

from index 'LogMainIndex'
where exact(AppName == "Area52 Test application")
order by Timestamp desc
select id() as Id, Timestamp, Message, Level

Co mi ale nyvehlada nic.

Nejaky trik co s tym? Ked som skusal nastavit index na FieldIndexing.Exact tak zas nevyhladavalo pomocou `startsWith’.

my používame:

new CreateFieldOptions { Storage = FieldStorage.Yes, Indexing = FieldIndexing.Default, TermVector = FieldTermVector.No }

Nakoniec sa mi to podarilo vyriesit takymto hekom:

                                _Exact = log.Properties.Where(t => t.Values != null).Select(t => this.CreateField(t.Name, t.Values, new CreateFieldOptions()
                               {
                                   Indexing = FieldIndexing.Exact,
                                   Storage = FieldStorage.No,
                                   TermVector = FieldTermVector.No
                               })),
                               _ = log.Properties.Where(t => t.Values != null).Select(t => this.CreateField(t.Name, t.Values, new CreateFieldOptions()
                               {
                                   Indexing = FieldIndexing.Default,
                                   Storage = FieldStorage.No,
                                   TermVector = FieldTermVector.No
                               })),

proste to oindexujem aj tak aj tak.