Transakce a konzistence dat
Transakce a konzistence dat jsou základními stavebními kameny spolehlivých informačních systémů. Ať už jde o relační databáze, distribuované architektury založené na událostech nebo úložiště NoSQL, správné pochopení transakční sémantiky a zvoleného modelu konzistence zásadně ovlivňuje integritu dat, škálování i uživatelskou zkušenost. Tento článek nabízí podrobný průřez od klasického ACID až po moderní vzory pro distribuované systémy, včetně praktických doporučení pro návrh, provoz a testování.
Transakční model ACID
- Atomicity (atomicita): všechny operace v transakci proběhnou, nebo se celá transakce zruší. Prakticky: rollback vrátí databázi do předchozího konzistentního stavu.
- Consistency (konzistence): transakce převádí databázi z jednoho konzistentního stavu do jiného při dodržení deklarativních pravidel (cizí klíče, kontroly, jedinečnost, triggery).
- Isolation (izolace): souběžné transakce si navzájem nezasahují do výsledků nad rámec povolených jevů definovaných úrovní izolace.
- Durability (trvanlivost): potvrzené změny (commit) přetrvají i po pádu systému; obvykle je to zajištěno žurnálem WAL a synchronním zápisem na disk.
Izolační úrovně a anomálie
Standard SQL specifikuje úrovně izolace a související anomálie. Jejich implementace se mezi jednotlivými systémy liší.
| Úroveň izolace | Popis | Typické povolené/zakázané jevy |
|---|---|---|
| READ UNCOMMITTED | Minimální izolace | Povoluje dirty reads; nepoužívat pro kritická data |
| READ COMMITTED | Každé čtení vidí pouze potvrzená data | Zakazuje dirty reads, povoluje non-repeatable a phantom reads |
| REPEATABLE READ | Řádky načtené v transakci se „nemění“ | Zakazuje non-repeatable reads; phantoms jsou často povoleny (závisí na databázi) |
| SERIALIZABLE | Chování ekvivalentní sériovému provádění | Zakazuje všechny běžné jevy; nejvyšší izolace, omezuje souběžnost |
- Dirty read: čtení nepotvrzených změn jiné transakce.
- Non-repeatable read: opakované čtení téhož řádku vrátí jinou hodnotu.
- Phantom read: stejný dotaz vrátí jinou množinu řádků (nově vložených nebo odstraněných).
- Write skew (past MVCC): pokročilá anomálie při izolaci typu snapshot; dvě transakce načtou konzistentní snímek a provedením navzájem disjunktních zápisů poruší globální invariant.
MVCC vs. zámky: jak databáze zajišťují izolaci
- MVCC (multi-version concurrency control): čtení neblokují zápisy a naopak; databáze udržuje verze řádků. Snapshot poskytuje konzistentní pohled v čase. Příklad: PostgreSQL Snapshot Isolation + Serializable (SSI).
- Pesimistické zamykání: explicitní
SELECT ... FOR UPDATE, zámky na řádcích/stránkách, někdy také zámky rozsahů (key-range) k potlačení jevu phantom. - Detekce a řešení uváznutí: graf čekání, výběr oběti, opakování transakce s exponenciálním prodlením (backoff). Aplikace musí podporovat idempotentní opakování.
Specifika populárních relačních databází
- PostgreSQL: MVCC; READ COMMITTED a REPEATABLE READ (ve skutečnosti snímek); SERIALIZABLE je implementováno jako Serializable Snapshot Isolation (SSI) s predikátovými zámky pro prevenci jevu write skew.
- MySQL/InnoDB: REPEATABLE READ je výchozí úroveň; zámky next-key (řádek + mezera) potlačují jev phantom; pozor na rozdíly mezi RR a RC při použití
SELECT ... FOR UPDATE. - Oracle: silná konzistence čtení (read consistency), verzování v segmentech undo; úroveň „serializable“ má svá specifika (chyby ORA-08177 při konfliktech).
- SQL Server: tradičně používá locking, s možností Snapshot Isolation a Read Committed Snapshot (verzování v tempdb).
Transakce v NoSQL a eventových systémech
- MongoDB: transakce ACID napříč více dokumenty (od verze 4.0), ovšem za cenu vyšší latence; operace s jedním dokumentem jsou atomické.
- Cassandra: lightweight transactions (LWT) přes Paxos pro podmíněné zápisy (compare-and-set); jinak eventual consistency.
- Redis:
MULTI/EXECs optimistickýmWATCH; operace jsou atomické na jednom uzlu; Streams + skupiny konzumentů umožňují idempotentní zpracování. - Kafka: transakční producent + idempotentní publikování; zajištění zpracování exactly-once v rámci definovaného toku (při pečlivé správě offsetů).
Modely konzistence v distribuovaných systémech
- Silná (lineární/sekvenční) konzistence: čtení po zápisu vždy uvidí poslední potvrzenou hodnotu; přináší vyšší latenci a ztěžuje škálování napříč regiony.
- Eventual consistency: repliky se postupem času sjednotí; vyžaduje návrh idempotentních operací a toleranci k dočasným odchylkám.
- Kauzální konzistence: zachovává příčinné vztahy; je silnější než eventual consistency, ale slabší než silná konzistence.
- Read-your-writes, Monotonic reads/writes: uživatelsky přívětivé záruky pro konkrétní relaci.
CAP a PACELC
- CAP: při rozdělení sítě (P) si systém vybírá mezi konzistencí (C) a dostupností (A). V praxi jde o volbu mezi režimem fail-stop a režimem operate-degraded.
- PACELC: i bez rozdělení sítě (Else) volíme mezi latencí (L) a konzistencí (C). Geograficky distribuované systémy často upřednostňují nižší latenci před přísnou konzistencí.
Distribuované transakce: 2PC, 3PC a konsensus
- Two-Phase Commit (2PC): koordinátor provede prepare → commit/abort. Poskytuje silné záruky, ale může blokovat; je citlivý na selhání koordinátora.
- Three-Phase Commit: přidává fázi, která má eliminovat blokování; v praxi se používá zřídka, většina systémů volí protokoly consensus.
- Konsensus (Raft/Paxos): replikace protokolu pomocí kvóra; transakční sémantika vzniká nad deterministickým aplikačním stavem (replikace stavového automatu).
Ságy a kompenzační transakce
V mikroservisních architekturách je plnohodnotné ACID napříč hranicemi služeb nepraktické. Vzor saga rozkládá globální transakci na posloupnost lokálních transakcí s definovanými kompenzačními akcemi.
- Choreografie: události mezi službami; nízká provázanost, vyšší složitost řízení.
- Orchestrace: centrální koordinátor (správce procesů) řídí jednotlivé kroky a kompenzace.
- Idempotence a opakovatelnost: nezbytné pro bezpečné opakování a doručování typu at-least-once.
Vzor Outbox a transakční zasílání zpráv
- Zápis + Outbox (stejná databáze): v rámci jedné lokální transakce zapíšete změnu v doméně a událost do tabulky outbox; samostatný proces pak spolehlivě publikuje událost do zprostředkovatele zpráv.
- Debezium/CDC: zachytávání změn dat streamuje změny z binlogu/WAL do zprostředkovatele zpráv; vyžaduje zpracování exactly-once a deduplikaci u konzumentů.
Idempotence, deduplikace a zpracování právě jednou
- Idempotentní klíče: uživatelský identifikátor požadavku (request-id) ukládaný do databáze pro detekci opakování; sloupec
unique(request_id). - Upsert/merge: atomické „vložit nebo aktualizovat“; eliminuje závody při opakovaných pokusech.
- Tolerance vůči změnám mimo pořadí: uchovávejte verze nebo časová razítka s monotónní logikou a ukládejte pouze „novější“ stav.
Čas, hodiny a monotónnost
- Logické hodiny (Lamportovy), vektorové hodiny: porovnávání kauzality v distribuovaném systému.
- Monotónnost v rámci relace: session stickiness a tokeny konzistence (např. read-your-writes v cloudu).
- Čas podle systémových hodin vs. čas události: audit a SLA měřte obezřetně; odchylky NTP mohou zkreslit „aktuálnost“.
Konzistence schématu a invarianty
- Deklarativní omezení: cizí klíče, jedinečnost, kontrolní omezení; první linie obrany proti porušení invariantů.
- „Transakční DDL“: možnost atomicky migrovat schéma (PostgreSQL); u velkých tabulek je nutné zvolit online postupy.
- Doplnění dat a dvojí zápis: postupné nasazení změn schématu (rozšířit → migrovat → odebrat), kompatibilní serializace (Avro/Protobuf s možností evoluce).
Výkonnostní dopady transakcí
- Delší transakce znamenají delší držení zámků/verzí → více konfliktů a větší zátěž pro VACUUM/GC. Upřednostňujte krátké transakce s jemně vymezeným rozsahem.
- Indexy urychlují čtení, ale prodlužují zápisy; u tabulek s vysokým počtem zápisů pečlivě zvažte sekundární indexy.
- Dávkové zpracování: slučte více logických akcí do menších atomických skupin a snižte režii jednotlivých operací.
Transakce napříč oddíly/shardy
- Klíče vhodné pro dělení na shardy: minimalizujte operace napříč shardy; je-li to nutné, použijte 2PC nebo ságy.
- Globální sekvence: jedinečná ID generujte pomocí schémat snowflake nebo sekvencí s přidělováním bloků; vyhněte se horkým místům.
Testování konzistence a transakcí
- Testy s jehlou (nemesis): chaos engineering – vypínání uzlů, rozdělení sítě typu split-brain, odchylky hodin, zpožďování paketů.
- Testování linearizovatelnosti: ověřování, zda historie operací odpovídá sériovému plánu; nástroje pro generování souběžných scénářů.
- Bankovní test: součet zůstatků se nesmí měnit; klasická zkouška integrity při souběžném zpracování.
- Fuzzy a testy založené na vlastnostech: definujte vlastnosti (invarianty) a nechte generátor vstupů hledat jejich porušení.
Observabilita a provoz
- Transakční metriky: doby potvrzení/zrušení, počet opakování, konfliktů a uváznutí, latence WAL/replikace.
- Logování s korelačními identifikátory: trace-id a span-id pro sledování toků napříč službami; auditní protokol transakcí a změn schématu.
- Výstrahy: nárůst chyb serialization failures, lock timeout, zaostávání replikace, růst bloat.
Praktické návrhové vzory
- Optimistické řízení souběžnosti: sloupec
versiona kontrola vUPDATE ... WHERE id=? AND version=?; při neúspěchu klient obnoví data a operaci zopakuje. - Přísné pořadí operací: určete kanonické pořadí zápisů, abyste předešli uváznutí (např. vždy zamykejte vzestupně podle Account.id).
- Idempotentní koncové body: přijímejte
Idempotency-Key, ukládejte stav zpracování a při opakování vracejte stejnou odpověď. - Try-Confirm/Cancel (TCC): rezervace zdrojů s následným potvrzením nebo zrušením; vhodné pro platební a rezervační scénáře.
Kontrolní seznam pro výběr konzistence a transakční strategie
- Které invarianty musejí být vždy dodrženy (peníze, zásoby) a které mohou být řešeny eventual consistency?
- Jaké jsou požadavky na latenci a dostupnost při rozdělení sítě?
- Je daná oblast vhodná pro lokální ACID + outbox, nebo vyžaduje sagu?
- Jaké izolační úrovně podporuje zvolená databáze a s jakými náklady?
- Podporuje aplikace idempotentní opakování a detekci duplicit?
- Jak budeme testovat konflikty, uváznutí a režimy selhání (chaos/testy s jehlou)?
Příklady (ilustrativní, bez úplného kódu)
- Optimistická aktualizace zůstatku:
UPDATE account SET balance=balance-?, version=version+1 WHERE id=? AND version=?; při 0 řádcích → opakovat s novou verzí. - Outbox: v jedné transakci
INSERT order+INSERT outbox(event); samostatný proces načte nové záznamy outbox a publikuje je do zprostředkovatele zpráv s deduplikací. - Kompenzace v ságách: při neúspěchu kroku „shipment“ zavolat
cancelPaymentareleaseInventory.
Časté chyby a jak se jim vyhnout
- Propojování dlouhých transakcí s interakcí uživatele (čekání na uživatelské rozhraní) → zámky zůstávají držené; používejte krátké atomické kroky.
- Spoléhání na úroveň „serializable“ bez pochopení chování konkrétní databáze → neočekávané chyby serialization failures; zaveďte opakování a idempotenci.
- Ignorování integrity na úrovni databáze (cizí klíče, kontroly) a spoléhání pouze na aplikační logiku → závody a nekonzistence.
- „Distribuované transakce jako výchozí řešení“ → vysoká složitost; upřednostňujte lokální ACID + zasílání zpráv.
Shrnutí
Transakce a konzistence dat představují kompromis mezi bezpečností invariantů, latencí a škálovatelností. V monolitech a relačních databázích využívejte ACID, vhodné izolační úrovně a precizní práci se zámky/MVCC. V distribuovaných systémech kombinujte lokální transakce s outbox/CDC, ságami a idempotentními protokoly. Rozhodnutí vždy podložte výslovně definovanými invarianty, měřením (latence, konflikty, opakování) a důsledným testováním krajních stavů. Takový přístup minimalizuje riziko ztráty konzistence, zachovává výkon a zjednodušuje provoz i audit.
