Pár postrehov o RavenDB

Pred nedávnom som mal možnosť intenzívnejšie sa zoznámiť s RavenDB. Jedna vec je ale písať blog príklady po večeroch, druhá vec snaha aplikovať RavenDB na scenár, na ktorý je šitý. Až pri takomto využívaní na povrch vybublajú skutočné problémy a také súvislosti, o ktorých naviné tutoriál blogy nepíšu.(a často ani Ayendeho dokumentácia)

Stale indexy a kolekcie dokumentov

Vyhodnocovanie aktuálnosti indexov sa neviaže na kolekciu dokumentov, nad ktorou index pracuje, ale na všetky dokumenty v danej databáze. Takže ak máte napríklad v jednej databáze index nad kolekciou do ktorej veľmi intenzívne zapisujete a zároveň index nad inou kolekciou, kde sú zmeny zriedkavé, a pri ktorej čakáte na aktuálne výsledky(cez niektorú z verzií WaitForNonStaleResults), nemusíte sa dočkať tak rýchlo, ako by ste očakávali alebo aj vôbec :-), ak je zápis príliš intenzívny.

To, že opodstatnenosť WaitForNonStaleResultsWhatEver treba vždy zvážiť a často nemá žiadne opodstnenie a že pri potrebe načítania agregátov pre aktualizáciu treba loadovať cez ID [session.Load(id)], rozvádzať nebudem.

Skipped results

Ak stránkujete alebo fetchujete, pozor na skipped výskedky. V skratke tu ide o to, že ak výsledok query hitne dva indexy, ktoré sa viažu na ten istý dokument. Vo výsledku query sa objaví logicky len raz, len pri stránkovaní s tým musíte rátať a zaoberať sa aj preskočenými výsledkami.

Riešením je použiť v rámci definície indexu Store metódu
Store(x => x.MyProperty, FieldStorage.Yes);
nad každým atribútom, ktorý nie je súčasťou indexu a ktorý postrebujeme dostať do výstupu a následne queryovať ako projekciu
session.Query<...,...>().AsProjection()
alebo mať queryované dokumenty naozaj denormalizované.

Denormalizovať alebo nedenormalizovať?

"Ľudové" NoSQL pravidlo v zmysle, že treba denormalizovať všetko čo sa dá, sa mi nepáči. Zmysluplná je denormalizácia všetkých potenciálnych views, ktoré budeme queryovať, čo však neznamená, že entitu nemôžete ukladať primárne v štuktúre, ktorá je výhodná z hľadiska serializácie stavu doménových entít a teda riešiť ich primárnu serializáciu inak, ako po agregátoch.

Veľmi jednoduchý trik ako vyriešiť denormalizáciu je využiť vymoženosti Map/Reduce indexov. Nad kolekciou, kam ukladám primárnu formu agregátu, definujem n rôznych indexov.(pričom využívam map, možno reduce, možno TransformResults…).

Tento prístup sa vôbec neosvedčil a mnohým problémom sa vyhnete, ak denormalizáciu budete riešiť mimo RavenDB. Medzi tie problémy patrí syntaktický problém v pri zápise zložitejsích Map/Reduce indexov cez Linq(napr. práca s nullable hodnotovými typmi), problém so stale resultami a problém so skipped results spomínanými vyššie. Okrem toho, bežne prevláda v aplikáciách počet readov nad writeami a teda je najvýhodnejšie úplne denormalizovať všetko v čase zápisu tak, ako to potrebujeme pri čítaní a nič neriešiť v čase čítania.(napr. typicky TransformResults, ktorý sa síce rieši na strane servera a len pre fetchnuté záznamy, ale aj toto môže znamenať podstanú neefektivitu).

Ako teda na denormalizáciu?

V najjednoduchšom prípade stačí zabezpečiť, aby doménový model generoval "udalosti" vždy ak dôjde ku zmenu stavu v doménovom modely, ktorá nás zaujíma z hľadiska reportingu. Na tieto udalosti počúvajú jednotlivé denormalizačné handlery, z ktorých každý sa stará o udržiavanie práve jedného z views.[@vlko: dá sa to realizovať aj bez message busu aj keď je tam úplne prirodzený] Samozrejme, tu treba riešiť nové problémy napr. s poradím udalostí a tranzakčnosťou.(prípadný EventSourcing nebudem rozvádzať, je mimo zábez príspevku)

Map/Reduce indexy treba používať naozaj len v reportingových scenároch s čistými agregačnými funkciami.

Konzumovanie pamäte

RavenDB(Lucene) je veľmi agresívny z hľadiska pohlcovania pamäte resp. cacheovania v pamäti. Pri väčších aplikáciách počítajte s tým, že 8GB pre RavenDB z ďaleka nestačí a ak nie je perspektíva horizontálneho škálovania cez viac(sharding) uzlov a navždy postačí jedno DB železo, zostaňte pri relačnej DB.(Existuje x spôsobov ako denormalizáciu a ukladanie celých agregátov(bez rozdelenia do viacerých tabuliek pospájaných cez FK) riešiť aj nad nimi, ak je to potrebné.)
Bohužial nastavenia, ktoré by mali ovplyvniť využívanie pamäte mali len obmedzený vplyv a s najväčšou pravdepodobnosťou sú problémy v Lucene.NET. Je možné, že v týchto dňoch sú už problémy úplne odstránené. Treba sledovať relevatné issues na RavenDB fóre.

Všeobecne

Odtraňovanie chýb reportovaných hoci priamo cez RavenDB fórum veľmi rýchle, minimálne vtedy, ak ste schopný napísať a poslať unit test. Dokumentácia je žiaľ stále slabá aj keď sa Ayende zaprisahával, že sa pracuje na lepšej. RavenDB Management studio, aj keď je už stabilnejšie a nepadá, je stále problémové a "konzolovka" a HTTP je lepšia voľba :-) Snáď niečo pozitívne v tomto smere prinesie nové RavenDB Management Studio.  

Toto sú najzásadnejšie veci, ktoré držím v hlave. Bolo toho ale podstatne viac. Priebežne, ak si na niečo ďalšie spomeniem, budem dopĺňať tento blog príspevok.

Zaradené do: ,

Komentáre

# vlko said:

Ty pozeram na to indexovanie a stale je zistovane podla mena indexu: github.com/.../Staleness.cs

a handling ide cez github.com/.../Indexing.cs

ktory je volany vzdy pre index execute, zmena na etagy bola v aprili github.com/.../0b16e1ed29b87fe344b24df194e5255d03387b9a

cize teoreticky ak by si pri svojom kode handloval last write etag per collection (myslim na strane klienta) tak by si vedel celkom dobre identifikovat stale indexy per collection

Friday, November 04, 2011 11:00 AM
# T said:

Yes yes, ale to vyuzijes v nejakych cacheovacich scenaroch ale z hladiska refreshu viewov ale z hladiska toho scenara vyssie a pripadnom rieseni konzitencie nevidim sposob, ako mi to pomoze :-(

Friday, November 04, 2011 1:19 PM
# vlko said:

tak sa zamyslam, ze to co popisujes v index casti myslis to, ze sa dokumenty beru do indexingu podla poradia ako pridu to ravendb a nie podla trebars kolekci, teda aby si kazdy index quernul svoju vlastnu kolekciu pre indexing? Nemala by sa potom takato hi write kolekcia hodit na druhu ravendb instanciu, pripadne db? Pri db ale neviem, ci potom nejde nejak na striedacku, lebo kazda db bezi nad vlastnou document storage (ale netestoval som, tak ruku do ohna nedam:)?

Friday, November 04, 2011 2:06 PM
# vlko said:

Jo pozeram som do kodu, kazda db ma vlastny IndexingExecuter, cize by to take nejake aj singleserver delenie by mohlo fungovat

Friday, November 04, 2011 2:21 PM
# T said:

@vlko:

ano, presne to. To single server delenie/izolacia po databazach funguje urcite, je to odskusane aj posvatene Ayendem :-)

Este je problem v terminologii. Neviem ako nazvat jednotlive DB(default + ostatne vytvorene) v ramci jednej instancie Ravenu tak aby kazdy vedel co myslim :-)

Friday, November 04, 2011 2:51 PM
Prihlásiť | Registrovať | Pomoc