Azure Cosmos DB a laditelná konzistence

Ve světě relačních databází obvykle používáte silnou konzistenci (defacto jen jeden node je aktivní v daný čas) a relativně silné oddělení transakcí. To ale znamená i zásadní nevýhody pro škálovatelnost a výkon, na druhou stranu pro některé situace to ideálně reflektuje reálný svět (ale méně často, než si většinou lide myslí). Azure Cosmos DB není relační (má omezené možnosti transakčního zpracování), ale NoSQL – co se týče konzistence nabízí laditelnost, 5 různých stupňů od silné až po eventuální konzistenci.

Jak moc je přísná konzistence obrazem byznysu

Na první pohled se zdá, že systémy musí být silně konzistentní, protože svět a byznys takový je. Určitě? Co když například prodáme jedno sedadlo v letadle dvěma lidem, protože si ho náhodou koupili v úplně stejný čas? Vadí to? Nebo když poslední ledničku na skladě omylem prodáme dvěma lidem, protože systém nereflektoval změnu okamžitě, ale s jistým zpožděním (= nebyl silně konzistentní)?

Pokud má letadlo 200 míst, letecké společnosti už mnoho let nezastaví prodej po dosažení 200 vydaných letenek. Statisticky vědí, že na konkrétním typu letu se s velkou pravděpodobností někdo odhlásí, onemocní či z jiných důvodů let nenastoupí. Tato místa (třeba pět) pak zůstanou neobsazena. Potenciální zájemci měli smůlu a letěli s konkurencí. Letecká společnost je tedy ochotna prodat letenek 205, což ji statisticky umožní mít plně obsazené letadlo, tedy dosáhnout maximální efektivitu a zisk. Může se občas stát, že statistický model selže – možná už se vám to stalo. Společnost nabídne například 100 EUR tomu, kdo se vzdá místa v letadle a poletí o pár hodin později. Je to pakatel v porovnání s tím, kolik strategie overbookingu přináší.

A co ona chybějící lednička? Vybudování silně konzistentního systému, který by situaci předcházel, nemusí dávat smysl. Může znamenat větší náklady (dražší vybavení díky scale-up), horší výkon a tím menší uživatelskou přívětivost, v případě havárie dlouhou dobu výpadku s výraznou ztrátou byznysu. Méně konzistentní systém může být levnější, rychlejší a mít lepší dostupnost. Musím se tedy ptát – kolim mne stojí slabší konzistence byznysově? Odpověď je často je, že vlastně moc ne. Když čas od času prodám ledničku, kterou už nemám, nabídnu zákazníkovi lepší model. On obvykle bude rád souhlasit, ve finále napíše pochvalnou recenzi a mne to stojí jen finanční rozdíl mezi nižším a vyšším modelem.

Jinak řečeno – dočasná nekonzistence může vypadat technicky hrozivě, ale její řešení může být obchodní, snadné a levné (v porovnání se situací s vynucenou konzistencí). Jasně – pokud jde o život nebo velké peníze, potřebujete silnou konzistenci (banka, nemocnice), ale tolerovat slabší konzistenci lze v podstatně více případech, než si techničtí lidé z IT myslí.

Jak moc je přísné transakční zpracování obrazem byznysu

A co transakční zpracování, tedy schopnost běžet několik kroků v transakci tak, že se provede buď celá nebo vůbec? V IT jsme si na to zvykli, ale život takhle nefunguje, protože by to bylo příliš pomalé a neefektivní. Fantistická studie z roku 2005 vysvětluje, proč Starbucks nepoužívá dvoufázový commit: https://www.enterpriseintegrationpatterns.com/docs/IEEE_Software_Design_2PC.pdf

Platba, která neprobíhá z ruky do ruky (na začátku by zákazník musel dát peníze na stůl a společně s pokladním na nich drží ruku dokud kafe není připraveno – prevence objednávky a útěku a ochrana, že za peníze dostanu kafe), asynchronní zpracování ve frontě (prázdné kelímky podle objednávek), competing consumers (více baristů) vede k porušení přísného first-in-first-out zpracování (máme tady kafe mimo pořadí, nutnost na výstupu korelovat podle jména zákazníka) a tak podobně. Starbucks by mohl mít totální konzistenci a transakční zpracování, ale jejich kapacita by dramaticky poklesla – byznysově to nedává smysl.

Konzistence v relačních DB typu Azure SQL

SQL dosahuje vysoké konzistence tím, že v základním nastavení jsou všechny zápisy i čtení do jediného nodu. Pokud v rámci Always On clusteru v jedné lokalitě (vše milisekundu od sebe) přidáte další repliky, tyto jsou řešeny synchronně (tzn. je jim doručen transakční log a po úspěšném zapsání do logu se teprv vrací informace o úspěšném zápisu). Je možné nastavit sekundární repliky do čtecího režimu, takže čtecí operace typu načítání tabulek, reporty či zálohy mohou jít proti sekundární replice. Protože je vše synchronní, je to také konzistentní. V případě Azure SQL se toto děje na pozadí jako služba.

Pokud chcete repliku do ještě do jiného regionu, tam už je řešení eventuálně konzistentní, tedy sekundární regiony budou pozadu. Pokud z nich čtete, musíte počítat s tím, že data nemusí mít konzistenci ve smyslu přečtení poslední hodnoty (je možné, že načtete údaje, které už neplatí). Pokud vám to nevadí, můžete jet proti sekundárnímu regionu reporty, nicméně v případě Azure SQL jde o dva odlišné endpointy (logika kdy zapisuji a kdy čtu musí být v aplikaci).

Transakce v relačních DB typu Azure SQL

Vraťme se do učebnic – co je ACID vlastnost transakcí? Atomicity říká, že je nelze roztrhnout v půlce (odečíst peníze z jednoho účtu, ale už nepřipsat na druhý). Consistency znamená, že celý systém je v každé milisekundě svého života v konzistentním stavu. Můžete dělat cokoli, číst, zapisovat, odkudkoli, jakkoli, kdykoli – vždy bude stav konzistentní v tom smyslu, že bude dodržovat omezující podmínky v systému (sloupec, který musí mít hodnotu, nebude NULL) – což samozřejmě nezaručuje logickou konzistenci (aplikace klidně může správně zapsat hlouposti). Transakce jsou Isolated, takže se nemohou vzájemně ovliňovat (to je složitější, než se zdá a u jednoduchých systémů to má mouchy – například udržení konzistence může znamenat zámky na záznamy a vznik stavu, kdy na sebe dvě transakce donekonečna čekají, tzv. deadlock – řešit to lze, ale tak jednoduché to není). Výsledky transakce jsou Durable, takže i kdyby se po jejich skončení stalo něco nečekaného, třeba se restartoval server, zápisy tam musí trvale zůstat.

Vraťme se ještě k požadavku na izolaci, který je velmi obtížný a vede na masivní dopady ve výkonu. Zejména pokud nebudete mít systém na jednom serveru, ale hned na několika v různých lokalitách – natáhnout zabezpečovací mechanismy mezi nimi často snižuje výkonu hluboko pod možnosti jediného serveru (už jen pro dodržení Atomicity s využitím dvoufázového commitu nebo Paxos vás čeká velká latence navíc po přidání druhého serveru v synchronním režimu). Skutečná izolace je na úrovni obvykle označované jako Serializable. To znamená v mnoha implementacích dát zámečky na zápis, ale i na čtení a dokonce i uzamčení celého range (SELECT s nějakou WHERE klauzulí). Dopady na výkon jsou velmi vysoké. Podobných vlastností dosahuje Snapshot izolace, která problém řeší tak, že v zásadě pracujete v rámci transakce s kopií databáze, která se nakonec zpátky promítne do té hlavní. Pokud vám nevadí menší izolace, můžete použít Repeatable read, tedy garanci, že čtení v rámci jedné transakce dopadne pokaždé stejně (že se hodnota nezmění) – nicméně už nedáváte range zámeček, takže se může objevit fantomové čtení (SELECT s WHERE vrátí jiný počet záznamů). Obvykle se systémy “vyrelaxují” ještě víc a použije se Read commited. Nikdo vám negarantuje, že pokud budete během transakce číst dvakrát to samé, dostanete pokaždé stejnou hodnotu (nedávají se read zámky nebo se uvolní hned po každém SELECT, ne až na konci transakce). Dokonce i write zámek se dá vypnout a pak následují čtení uncommited hodnot (dirty read), ale to už je z pohledu ACID opravdu špinavé.

Obvykle tedy ACID trochu ošidíte…a možná ještě víc. ACID se dá docela dobře udělat v jednom serveru, ale na více uzlech po přidání latence dostáváte horší parametry, než na jednom (čili nám to trochu anti-škáluje). Distribuované řešení mnohdy vede (z důvodu vyřešení jinak problematické škálovatelnosti) na Sharding, kdy si DB rozdělíte na řezy a každý je na jiném serveru (ACID uvnitř řezu je OK, ale pokud chcete ACID napříč hodnotami z různých serverů, máte problém – když nic jiného tak výkonnostní). Mimochodem to vede ke strategii mít “řezy” podle logických celků (faktura, zákazník, …) místo klasického relačního přístupu (no a už máte nakročeno k document oriented NoSQL, což je jeden z typů, který rozebereme na jindy). Pokud už takhle slevujete, co to udělat jinak a nasadit systém s vlastnostmi eventuální konzistence?

Kde použít ACID? Tam, kde transakční vlastnosti jsou klíčem a i sebemenší riziko jejich porušení má fatální následky neopravitelého charakteru – lidské životy nebo hodně velké peníze.

Laditelná konzistence v Cosmos DB

Moderní NoSQL databáze často nabízí konfigurací ovlivnit model konzistence. Cosmos DB v tomto vyniká proto, že nabízí laditelnost jak na výchozí úrovni v rámci DB, tak pro jednotlivé operace (v operaci můžete specifikovat požadovaný model konzistence). Často je konzistence změtí špatně pochopitelných parametrů. Cosmos DB dává 5 dobře definovaných a SLAčkem garantovaných modelů konzistence.

 

Pokračovat ve čtení