Doplněk k přednášce o CouchDB na Webexpo 2010
Děkuji všem za online i offline reakce na moji přednášku o CouchDB. Snažil jsem se věnovat hodně prostoru obecnějším otázkám spojeným s využitím „netradičních“ databází, které CouchDB dobře reprezentuje a zároveň ryze praktickým zkušenostem, které jsem díky intezivnímu používání CouchDB za poslední rok získal. V tomto článku bych rád dodatečně doplnil či upřesnil některá témata či zodpověděl dotazy.
Příklady využití bezeschémových databází
Za prvé bych chtěl doplnit příklad využití bezeschémových, resp. dokumentových databází. Příklad s adresářem byl, jak doufám, dostatečně ilustrativní: jedná se o nejobvyklejší příklad heterogenních dat: někdo má dva telefony, jiný tři, další má Skype, zatímco jiný má Jabber, a tak dále. Příklad obrázku ke kontaktu navíc hezky ilustruje výhodu CouchDB v přirozeném ukládání binárních dat přímo k dokumentu.
Stejně dobrým příkladem ale může být i to, co každý webový vývojář ve svém životě programoval či potkal tucetkrát: „redakční systém“. Uvozovky jsou záměrné, neboť nemá smysl řešit, co je redakční systém, co publikační systém a co systém na správu obsahu (CMS), proto budeme dále využívat zkratky CMS. Každý, kdo někdy vytvářel CMS ví, že „stránka“ (dokument) v něm není „title a velká textarea s WYWIWYGem“. Naopak, stránka je zpravidla sestavou mnoha elementů, které mohou být textové, obrázkové, mohou odkazovat na další entity (zkrácený výpis novinek) nebo na externí zdroje (video na YouTube).
Oblíbeným školním cvičením každého CMS je pak ukládání hierarchických dat („stromů“) — a jejich efektivní získávání. V dokumentové databázi typu CouchDB mohu „strom“ reprezentovat přímo jako JSON dokument, a obejdu se bez zdlouhavého překládání mezi tabulkovou a hierarchickou reprezentací.Velká výhoda jakékoliv bezeschémové databáze je pak v tom, že mohu stránky modelovat v podstatě ad hoc, bez nutnosti vymýšlet dopředu dostatečně flexibilní schéma – anebo je, muset dodatečně upravovat a ladit. V případě CouchDB se mi opět hodí nativní podpora binárních attachmentů k dokumentu.
Kontinuální „stream“ HTTP notifikací o změnách v databázi
Jedna z nejzajímavějších vlastností CouchDB, které jsme se dotkli jen letmo, je kontinuální „stream“ změn v databázi, tzv. _changes feed. Nejedná se o nějakou zajímavou fíčurku přidanou pro efekt: na základě _changes kanálu funguje celá infrastruktura replikace v CouchDB, nebo indexování ze strany CouchDB-Lucene. Je tedy tak „robustní“, jak jen si lze představit.
Jasnou výhodou _changes kanálu je možnost persistentního spojení mezi klientem a databázovým serverem (s parametrem continuous). Databáze otevře pro každého klienta jedno „vlákno“ v Erlangu a ten ji tedy nezatěžuje pravidelným pollingem, jak je zvykem. Navíc se jedná o push notifikaci, takže klient dostává změny ze strany databáze a neptá se stále „je něco nového? je něco nového?“.
V repositáři s ukázkovou aplikací k přednášce najdete i ukázkovou implementaci klienta pro kontinuální _changes kanál v Ruby. Spustíte ho příkazem rake changes DATABASE=addressbook (viz README), a pak si již jen otevřete Futon v okně nad terminálem, nebo jiným způsobem upravíte data, a v reálném čase můžete sledovat, jak vám databáze tlačí informace o změnách.
Možností využití kontinuálního kanálu je nepřeberně. Krom té, která nás všechny napadne jako první, tedy chatu, je to např. možnost přesouvat a agregovat data mezi databázemi, zejména díky možnosti _changes kanál fitrovat. Ideálně se ale hodí pro všechny možné data enrichment operace, kdy např. chceme po uložení záznamu spustit nějaký asynchronní úkol (konverzi videa, doplnění dokumentu informacemi z webové služby, atd).
A konečně, umožňuje na databázi napojit všemožné externí služby: jako příklad za všechny lze uvést fulltext engine ElasticSearch, jehož podpora pro CouchDB byla přidána pár dní po WebExpo – a to právě napojením na _changes kanál, který mu poskytuje ideální infrastrukturu pro kontinuální indexování záznamů.
CouchDB Lucene
Zaznamenal jsem i několik dotazů na CouchDB-Lucene (CL), technologii pro fulltext indexování a prohledávání dokumentů v CouchDB, příp. i zvláštní názor, že to je přeci „nevýhoda“ CouchDB, když musím pro „složitější“ dotazy využít něco jako fulltext search engine.
Použití CL je velmi jednoduché. Nejprve nadefinujeme indexy pro příslušné atributy dokumentu, které nás zajímají – jako JavaScriptovou funkci:
function(doc) {
var result = new Document();
if (doc.occupation) { result.add(doc.occupation, {"field":"occupation"}) }
return result;
}
Poté, co CL napojíme na notifikace CouchDB, získáme data HTTP dotazem na fulltext index:
$ curl "http://localhost:5984/addressbook/_fti/_design/person/search?q=occupation:supermodel&debug=true"
Lahůdka pro všímavé fanoušky HTTP: ve výsledném JSON opět dostáváme ETag pro konkrétní seznam výsledků. Zkrátka, HTTP od sklepa až na půdu.
Stojí za upozornění, že CouchDB-Lucene není jediným řešením pro fulltextové vyhledávání a ad-hoc dotazy. Vyjma vlastního napojení například na Solr, které je realizovatelné s jakýmkoliv úložištěm, existuje např. experimentální projekt využití Sphinx v CouchDB.
Zvýšenou pozornost si ale zaslouží projekt ElasticSearch zmíněný výše. ElasticSearch totiž nejenom používá JSON, on mu rozumí. Proto nemusíme deklarovat specifické atributy k indexaci, ale prostě necháme ElasticSearch získávat celé dokumenty z _changes kanálu a můžeme se dotazovat rovnou do „hloubky“ JSON dokumentu:
$ curl "http://localhost:9200/addressbook/_search?q=occupation:supermodel AND addresses.work.city:Eichmannburgh"
Konflikty
Několik lidí také bylo překvapených implementací multi version concurrency, která zamezuje tomu, upravit dokument, který nemám v poslední revizi. Jakmile se v CouchDB pokusím uložit dokument, který se mezitím v databázi změnil, dostanu HTTP odpověď 409 Conflict. Mnozí účastníci, s nimiž jsem hovořil, byli nejen překvapení, ale rovnou vyděšeni existencí nějakých „konfliktů“. Jako by to bylo nějaké tabu slovo, jako bych takovou hrůzu měl zmiňovat jen šeptem.
Pomineme nutnost uchovávání revizí a konceptu konfliktu vůbec v silně distribuovaném/decentralizovaném světě jako je CouchDB. Důležitější je jiný ohled: ve skutečnosti to dává mnohem větší smysl, než práce s “tradiční” databází, která tyto děsivé „konflikty“ nemá (a naopak disponuje něčím mnohem děsivějším, jako např. zámky pro čtení a zápis).
Uvažte scénář: uživatel začne upravovat dokument, a provádí nějakou větší změnu, která zabere hodně času. Mezitím, než v aplikaci uloží záznam, však přijde jiný uživatel, a provede změnu menší, kupříkladu pouze opraví telefonní číslo zákazníka v CRM systému. První uživatel ale zformátoval, doplnil, atd. ono původní telefonní číslo (a též ostatní informace o zákazníkovi). Jakmile uloží tyto změny, opravené telefonní číslo se přepíše chybným. To z pohledu uživatele není nijak intuitivní. Ba přímo naopak.
Databáze totiž neví, a nemůže vědět, jak podobnou situaci vyřešit. Ale lidé ano. Programátor aplikace pak může snadno zobrazit obrazovku s rozdíly mezi „mojí“ verzí a verzí v databázi, a nechat uživatele vybrat ta správná data. To je dáno právě tím, že v CouchDB uvažujeme o dokumentu jako o skutečném dokumentu, nikoliv o soustavě relací (tabulek) propojených cizími klíči.
Mimochodem, v tomto bodu také můžeme provést (výjimečně) přímé srovnání s přístupem databáze MongoDB. V MongoDB je možné upravit a uložit jen část dokumentu (tzv. partial update), nebo vložit jeden „dokument“ do druhého (tzv. embedded documents). V závislosti na vašem přesvědčení, znalostech a potřebách se vám taková vlastnost může zdát jako výhoda, nebo jako nevýhoda. Jakto? Protože nemůžete takovou vlastnost posuzovat izolovaně, ve smyslu „jé, to je hezké!“, ale v kontextu celkové architektury a koncepce, a připočíst např. použití nestandardního formátu (BSON), konsekvence pro replikaci, atd. Proto neexistuje asi lepší demonstrace toho, že NoSQL nerozumíte, než mluvit o výběru mezi CouchDB a MongoDB jako o výběru mezi „blondýnkou a brunetkou“. (Prozrazuje to také zřetelné sklony k mužskému šovinismu, ale to se v IT světě nejen toleruje, ale často přímo hýčká.)
Ostatní
V kuloárech také padaly dotazy, které bych chtěl znovu velmi krátce zodpovědět zde.
Za prvé, definice views jsou uloženy v databázi stejně jako ostatní dokumenty, ve speciální variantě označované jako design document (_design/<NAME>).
Za druhé, definice views nemusíte psát v JavaScriptu. Můžete je psát také v Erlangu. Ne, to není vtip :) Můžete je psát rovněž v Ruby či Pythonu, pro které existují experimentální tzv. view servers — ale pro vážné nasazení se v současné době asi budete řídit tím, co dělají ostatní, a budete je psát v JavaScriptu (nebo Erlangu).
Za třetí, pro další uvažování se vám vyplatí nechápat views jako dotazy (query) ve světě *SQL databází: těm daleko lépe odpovídají dotazy pro fulltext, jak bylo vidět výše. Views totiž velmi přesně odpovídají indexům ve světě *SQL databází; což je zjevné, když se nad tím chvíli zamyslíte. Výraz view a index je tedy ve světě CouchDB zaměnitelný.
Máte-li nějaké další dotazy, neváhejte je položit v diskusi pod článkem, či v diskusi na stránce WebExpo.
