pondělí 10. října 2011

MongoDB za scénou - jak jsme jej použili v GoodData

Jiří Tobolka napsal úvod do notifikačního API, které máme v GoodData platformě. Rád bych vás seznámil s technickými detaily a to především persistentním úložištěm, které jsme pro tento účel použili. V kostce řečeno notifikační API slouží k popisu událostí (změna určité metriky v projektu např. pokles prodeje, nárůst nových objednávek apod.), které nás zajímají a odpovídajících reakcí, které chceme vyvolat (poslání SMS, emailu, zpráva na Twitter apod.), pokud k nim dojde.

V doménovém modelu notifikací máme tři základní entity - ChannelConfiguration (reprezentuje vlastní kanál), Subscription (událost a její popis) a Trigger (způsob vyhodnocení - např. časový spouštěč, napumpování dat). Jejich vztah je zachycen na následujícím obrázku.

Před tím než jsme se pustili do vlastního kódování jsme řešili klasický problém kam s daty. V GoodData máme valnou většinu dat uloženou ve staré dobré relační databázi (RDBMS) se všemi jejími omezeními. Přibližně před rokem jsme dělali PoC, které mělo ukázat, jak složité by bylo držet tato data mimo RDBMS. Neboť charakteristika tohoto typu dat nenahrává relačnímu rozkladu. Zkusím ten problém vysvětlit právě na Notifikacích.

Jak už jsem zmiňoval doménový model se skládá ze tří entit. V případě relační databáze by výsledné relační schéma vypadalo přibližně následujícím způsobem.

Jak si můžete všimnout je zde sedm tabulek pokud počítáme i ty, které slouží k vyjádřenímany-to-many relací. Toto relační schéma přináší několik problémů. S každým novým kanálem (email, Twitter atd.) bychom museli přidat novou tabulku. Kdybychom chtěli získat konkrétní událost (reprezentovaná jako Subscription) s kanály, na které má být provedené její odeslání, museli bychom použít left outer join. Rovněž mazání by muselo probíhat kaskádově díky referenční integritě. Samozřejmě někdo by mohl namítnout, že schéma by šlo modifikovat tím nebo jiným způsobem, ale jeho komplexnost by zůstala přibližně stejná. Další možností by bylo ukládat jednotlivé entity jako dokumenty v BLOBu, ale pak bychom přišli o ten poslední výhodu, kterou by nám RDBMS svět nabídl, a to psaní dotazů např. dej mi všechny notifikace pro konkrétní projekt nebo uživatele.

Mentální kotrmelec, který je potřeba udělat, spočívá v uvědomění si, že ve skutečnosti pracujeme s dokumenty. Čteme, modifikujeme, ukládáme a mažeme dokumenty jako celky. Pokud se tedy bavíme o persistentním úložišti hledáme ve skutečnosti dokumentově orientovanou databázi. Schválně nepoužívám termín NoSQL, protože ne každá databáze z této rodiny, musí být nutně dokumentově orientovaná. Jsou zde řešení jako Riak, Cassandra, Redis a další, ale ty jsou orientované jako key/value úložiště. Při použití úložiště tohoto charakteru bychom museli naše dokumenty rozkládat v podstatě na key/value páry a nezískali bychom v tomto případě výhodu oproti konvenčním RDBMS řešením.

Přestože se bavíme o objektově orientované databázi, některé koncepty jsou velice podobné světu RDBMS. Dokumenty jsou organizované do kolekcí. Kolekce je obdobou tabulky v RDBMS. Kolekce vám umožňuje shromažďovat dokumenty s různou strukturou a nebo dokonce různého typu oproti tabulce, kde je typ ukládaných dat určen sloupci a jejich typy. Dokument se skládá z atributů (fields), které odpovídají sloupečkům, s tím rozdílem že opět neplatí omezení na určitý typ, unikátnost atd. Z této charakteristiky vyplývá, že zde nejsou žádné omezení týkající se struktury a organizace dat. Díky tomu tento typ databází nazýváme schema-less.

To sebou samozřejmě sebou přináší výhody a nevýhody nebo lépe řečeno věci, se kterými musíte počítat. Mezi výhody určitě patří jednoduché rozšiřování dokumentů o nové atributy, protože nemusíte provádět off-line migraci dat. Naopak můžete dokumenty, pokud je to potřeba, migrovat za běhu a tím snížit odstávku serveru. A to se sakra počítá, když někomu poskytujete službu se SLA a garantovanou dostupností. Na druhou stranu v případě konzistence dat a její kontroly vše leží plně na bedrech aplikační logiky.

Pojďme zpět k Notifikacím a jejich uložení do dokumentově orientované databáze. Máme zde dvě kolekce, jednu pro kanály a druhou pro vlastní události resp. jejich definici. Definice události obsahuje trigger (jak často se má kontrolovat), podmínku která se bude vyhodnocovat, zprávu která se pošle na definované kanály, a meta informace vztahující se k dokumentu. Relace na konkrétní kanály, které se mají použít je vyjádřena atributem, ve kterém je identifikátor daného kanálu. Atribut nemá žádný speciální typ, není zde ani žádné omezení na existenci dokumentu. Proto je tato kontrola na aplikační logice.

Pokud se bavíme o dokumentově orientované databází jsou zde dvě prověřená řešení MongoDB a CouchDB. Klíčový rozdíl mezi MongoDB a CouchDB spočívá v přístupu k datům a psaní dotazů. CouchDB používá k dotazování Map/Reduce funkce. Vlastní dotazy jsou speciální dokumenty nazývané view, které obsahují definici Map/Reduce funkcí. V případě CouchDB nejste schopni psát dotazy pokud nedefinujete view. MongoDB se dotazy vyjadřují pomocí strukturovaného JSON objektu, který je více deklarativní oproti programovému Map/Reduce v CouchDB. Pro zvýšení výkonnosti můžete nad atributy definovat indexy, podobně jako v RDBMS. MongoDB umožňuje psát ad-hoc query, tedy dotazy, které mohou dynamicky vzniknout například na základě uživatelského vstupu. MongoDB rovněž podporuje Map/Reduce, ale ta je určená k dávkovému zpracování dat, ne pro vlastní dotazování.

K dokumentům v CouchDB přistupujete přes RESTové rozhraní a HTTP protokol. Stačí vám tedy obyčejný HTTP klient. MongoDB používá síťový binární protokol, to vyžaduje speciální driver pro programovací jazyk, ze kterého budete k datům přistupovat.

Po zvážení všech pro a proti jsme se nakonec rozhodli pro MongoDB a to z několika důvodů:

  • možnosti dotazování - psát dotaz v MongoDB je chápání běžného smrtelníka mnohem bližší a v jistých ohledech velice podobné SQL oproti Map/Reduce funkcím. Nicméně i použití Map/Reduce je stále možné.
  • driver - velice příjemné na použití pro klienta a hlavně výkonné
  • škálovatelnost - MongoDB shardování dat bez nějakého zjevného hacku. To je velice důležité pro SAAS firmu jako jsou GoodData
  • nástroje - backup/restore/monitoring nástroje, administrační konzole, JSON and JavaScript se zde používají pro ovládání těchto nástrojů

MongoDB jsme úspěšně nasadili jak na produkční i na vývojářské instance a neměli jsme zatím žádný závažný problém. MongoDB používáme se zapnutým žurnálem z důvodu konsistence dat ve vlastním enginu přestože to zpomaluje všechny operací typu zápis. Zatím to pro nás není problém vzhledem k tomu, že většina operací je čtecího charakteru. V případě, že se tento poměr změní, můžeme nasadit MongoDB v cluster módu s replikací dat. Pak případné poškození dat na jednom z uzlů clusteru sice vyžaduje manuální zásah našich Ops, ale nemá to vliv na dostupnost služby.