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é event-driven architektury nebo NoSQL úložiště, 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í hluboký průřez od klasického ACID 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é 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 respektování deklarativních pravidel (FK, check, unikátnost, triggery).
- Isolation (izolace): paralelní transakce si nezasahují do výsledků „nad rámec povolených fenoménů“ definovaných úrovní izolace.
- Durability (trvanlivost): potvrzené změny (commit) přetrvají i při pádu; typicky zajištěno WAL žurnálem a synchronním zápisem na disk.
Izolační úrovně a anomálie
Standard SQL specifikuje úrovně izolace a související anomálie. Implementace se mezi 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í jen 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 často povoleny (závisí na DB) |
| SERIALIZABLE | Chování ekvivalentní sériovému běhu | Zakazuje všechny běžné jevy; nejvyšší izolace, omezuje paralelismus |
- Dirty read: čtení necommitnutý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é/odstraněné).
- Write skew (MVCC past): pokročilá anomálie u snapshot izolace; dvě transakce přečtou konzistentní snapshot a provedením disjunktních zápisů poruší globální invariant.
MVCC vs. zámky: jak DB 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).
- Pessimistické zamykání: explicitní
SELECT ... FOR UPDATE, zámky na řádcích/stránkách, někdy i zámky rozsahů (key-range) pro potlačení phantom jevu. - Detekce a řešení deadlocků: graf čekání, victim selection, opakování transakce s backoff. Aplikace musí umět idempotentní retry.
Specifika populárních relačních databází
- PostgreSQL: MVCC; READ COMMITTED a REPEATABLE READ (ve skutečnosti snapshot); SERIALIZABLE implementováno Serializable Snapshot Isolation (SSI) s predikátovými zámky pro prevenci write skew.
- MySQL/InnoDB: REPEATABLE READ je výchozí; next-key zámky (řádek + mezera) pro potlačení phantoms; pozor na rozdíly mezi RR a RC při
SELECT ... FOR UPDATE. - Oracle: silná read consistency, verzování v undo segmentech; „serializable“ má specifika (chyby ORA-08177 při konfliktech).
- SQL Server: tradičně locking s možností Snapshot Isolation a Read Committed Snapshot (verzování v tempdb).
Transakce v NoSQL a event systémech
- MongoDB: ACID transakce přes více dokumentů (od 4.0), ale s náklady na latenci; single-document operace 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; atomické na jednom uzlu; Streams + consumer groups dovolují idempotentní zpracování. - Kafka: transakční producent + idempotentní publikace; zajištění exactly-once processing v rámci definovaného toku (s pečlivou správou offsetů).
Modely konzistence v distribuovaných systémech
- Silná (lineární/sekvenční) konzistence: čtení po zápisu vždy uvidí poslední potvrzenou hodnotu; vyšší latence, obtížnější škálování přes regiony.
- Eventual consistency: repliky se konvergují „časem“; vyžaduje návrh idempotentních operací a toleranci dočasných odchylek.
- Kauzální konzistence: zachovává příčinné vztahy; silnější než eventual, slabší než silná konzistence.
- Read-your-writes, Monotonic reads/writes: uživatelsky přívětivé záruky pro konkrétní session.
CAP a PACELC
- CAP: při síťovém rozdělení (P) si systém vybírá mezi konzistencí (C) a dostupností (A). Prakticky: volba režimů fail-stop vs. operate-degraded.
- PACELC: i bez rozdělení (Else) volíme mezi latencí (L) a konzistencí (C). Geo-distribuce často preferuje nižší latenci s relaxovanou konzistencí.
Distribuované transakce: 2PC, 3PC a konsensus
- Two-Phase Commit (2PC): koordinátor prepare → commit/abort. Silné záruky, ale blokující; citlivé na selhání koordinátora.
- Three-Phase Commit: přidává fázi pro eliminaci blokování, v praxi zřídka; většina systémů volí consensus protokoly.
- Konsensus (Raft/Paxos): log replikace s kvórem; transakční sémantika vzniká nad deterministickým aplikačním stavem (state machine replication).
Ságy a kompensační transakce
V mikroservisních architekturách je plnohodnotné ACID přes hranice služeb nepraktické. Saga pattern rozkládá globální transakci na sekvenci lokálních transakcí s definovanými kompenzacemi.
- Choreografie: události mezi službami; nízká vazba, vyšší složitost řízení.
- Orchestrace: centrální koordinátor (process manager) řídí kroky a kompenzace.
- Idempotence a opakovatelnost: nezbytné pro bezpečné retry a at-least-once doručení.
Vzor Outbox a Transactional Messaging
- Write + Outbox (stejná DB): v rámci jedné lokální transakce zapíšete doménovou změnu a event do outbox tabulky; separátní relay spolehlivě publikuje do message brokera.
- Debezium/CDC: change data capture streamuje změny z binlogu/WAL do brokeru; vyžaduje exactly-once zpracování a deduplikaci na konzumentech.
Idempotence, deduplikace a přesně-jednou
- Idempotentní klíče: uživatelský request-id ukládaný do DB pro detekci opakování; sloupec
unique(request_id). - Upsert/merge: atomické „vložit nebo aktualizovat“; eliminuje race conditions u opakovaných pokusů.
- Out-of-order tolerance: držte verze nebo timestampy s monotónní logikou, ukládejte pouze „novější“ stav.
Čas, hodiny a monotónnost
- Logické hodiny (Lamport), vektorové hodiny: porovnání kauzality v distribuovaném systému.
- Monotonicita v rámci session: session stickiness a tokeny konzistence (např. read-your-writes v cloudu).
- Wall-clock vs. event time: audit a SLA měřte opatrně; NTP drift může zkreslit „aktuálnost“.
Konzistence schématu a invariants
- Deklarativní omezení: cizí klíče, unikáty, check constraints; první linie obrany proti porušení invariantu.
- „Transactional DDL“: možnost migrovat schéma atomicky (PostgreSQL); u velkých tabulek nutno volit online techniky.
- Backfilling a dvojí zápis: postupné nasazení změn schématu (expand → migrate → contract), kompatibilní serializace (Avro/Protobuf s evolucí).
Výkonnostní dopady transakcí
- Delší transakce = delší držení zámků/verzí → větší konflikty, VACUUM/GC tlak. Preferujte jemnozrnné, krátké transakce.
- Indexy urychlují čtení, ale prodlužují zápisy; pečlivě zvažte sekundární indexy u write-heavy tabulek.
- Batchování: více logických akcí sjednoťte do menších atomických skupin pro snížení per-operation overheadu.
Transakce přes partition/shard
- Sharding-friendly klíče: minimalizujte cross-shard operace; při nutnosti použijte 2PC nebo sagas.
- Globální sekvence: unikátní ID generujte pomocí snowflake schémat nebo sekvencí s přidělováním bloků; vyhněte se hot-spotům.
Testování konzistence a transakcí
- Jehlové testy (nemesis): chaos engineering – zabíjení uzlů, síťové split-brain, clock skew, zpožďování paketů.
- Linearisability testing: kontrola, zda historie operací odpovídá sériovému plánu; nástroje pro generování konkurenčních scénářů.
- Bankovní test: sumy zůstatků se nesmí měnit; klasická integritní zkouška při paralelizaci.
- Fuzzy a property-based testy: vlastnosti (invarianty) definujte a nechte generátor vstupů hledat porušení.
Observabilita a provoz
- Tranzakční metriky: doby commit/rollback, počet retry, konfliktů, deadlocků, latence WAL/replic.
- Logování s korelací: trace-id a span-id pro sledování cest napříč službami; auditní log transakcí a změn schématu.
- Alarmy: nárůst serialization failures, lock timeout, replikace zaostává, růst bloat.
Praktické návrhové vzory
- Optimistická konkurence: sloupec
versiona kontrola vUPDATE ... WHERE id=? AND version=?; při selhání klient obnoví data a opakuje. - Přísné pořadí operací: definujte kanonické pořadí zápisů pro zabránění deadlockům (např. vždy zamykat v pořadí Account.id vzestupně).
- Idempotentní endpointy: přijímejte
Idempotency-Key, ukládejte stav zpracování a vracejte stejnou odpověď při opakování. - 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.
Checklist pro výběr konzistence a transakční strategie
- Jaké invarianty musejí držet vždy (peníze, zásoby) a jaké mohou být eventual?
- Jaké jsou požadavky na latenci a dostupnost v případě rozdělení sítě?
- Je doména vhodná pro lokální ACID + outbox, nebo vyžaduje sagu?
- Jaké izolační úrovně podporuje zvolená DB a s jakými náklady?
- Má aplikace idempotentní retry a detekci duplicit?
- Jak budeme testovat konflikty, deadlocky a failure módy (chaos/jehlové testy)?
Příklady (ilustrativní bez úplného kódu)
- Optimistický update 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); relay čte nové outbox záznamy a publikuje do brokeru s deduplikací. - Kompenzace v ságách: při neúspěchu kroku „shipment“ volat
cancelPaymentareleaseInventory.
Časté chyby a jak se jim vyhnout
- Spojení dlouhých transakcí s interakcí uživatele (čekání na UI) → drží zámky; používejte krátké atomické kroky.
- Spoléhání na „serializable“ bez porozumění chování konkrétní DB → neočekávané serialization failures; zaveďte retry a idempotenci.
- Ignorování integrity na úrovni DB (FK, check) a náhrada pouze aplikační logikou → závody a nekonzistence.
- „Distribuované transakce jako default“ → vysoká složitost; preferujte lokální ACID + messaging.
Shrnutí
Transakce a konzistence dat jsou kompromisem mezi bezpečností invariantu, latencí a škálovatelností. V monolitech a relačních DB využijte 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 explicitně definovanými invarianty, měřením (latence, konflikty, retry) a důsledným testováním extrémních stavů. Takový přístup minimalizuje riziko ztráty konzistence, udrží výkon a zjednoduší provoz i audit.