Hvis flere noder skriver de samme dataene uten styring, får du feil. I praksis handler dette om tre valg: hvordan data deles, hvordan transaksjoner samordnes og hvor strengt systemet skal beskytte korrekthet.
Jeg ville kokt hele temaet ned til dette:
- Låsing / 2PL passer når konflikter skjer ofte, men gir mer venting.
- MVCC passer når mange leser samtidig, fordi lesing og skriving oftere kan gå side om side.
- OCC passer når konflikter er sjeldne, men kan gi mange aborter under trykk.
- 2PC brukes når en transaksjon treffer flere noder, men hver ekstra nettverksrunde koster tid.
- Replikering gjør systemet mer feilutsatt: to noder kan midlertidig være uenige.
- Isolasjonsnivå styrer hvor mange feil du tillater.
READ COMMITTEDslipper gjennom mer.SERIALIZABLEstopper mer, men koster mer. - Partisjoner, deadlock og write skew er ikke teori. Det er typiske feilbilder i drift.
- Overvåking bør følge låseventing, deadlock, abortrate, gjenforsøk og replikeringslag.
Med andre ord: du får ikke både lav ventetid, høy tilgjengelighet og streng global konsistens uten kostnad. Derfor må du velge mekanisme etter dataenes risiko, ikke bare etter fart.
Noen tall og fakta som setter rammen:
- En distribuert commit med 2 faser betyr minst to koordinerte steg mellom noder.
- Ved multi-leader kan 2 samtidige skrivinger på ulike replikaer gi konflikt etterpå.
- Under høy konflikt kan OCC kaste bort mye arbeid fordi transaksjoner feiler helt på slutten.
- Med MVCC lagres flere versjoner av samme rad, som gir bedre leseflyt men mer lagringsbruk.
| Valg | Når jeg ville brukt det | Typisk kostnad |
|---|---|---|
| Låsing / 2PL | Når data ofte kolliderer | Venting, deadlock, mer nettverk |
| MVCC | Når lesing dominerer | Flere versjoner, mer opprydding |
| OCC | Når konflikter er få | Aborter og gjenforsøk |
| Primær-backup | Når én skriverute er grei | Primæren kan bli flaskehals |
| Multi-leader | Når flere steder må ta imot skriv | Konflikthåndtering blir tyngre |
| SERIALIZABLE | Når korrekthet veier mest | Lavere throughput, mer koordinering |
Kort sagt: Jeg ville valgt strengere kontroll for penger, saldo og bokføring, og svakere modeller bare der litt etterslep faktisk er greit.
Hvordan distribuerte databaser koordinerer samtidige operasjoner
Valgene rundt koordinering, replisering og konsistens avgjør hvor godt samtidige transaksjoner faktisk flyter i praksis. Det er her mye av jobben skjer i en distribuert database.
Sharding, replikering og distribuerte transaksjoner
Når en transaksjon treffer flere shards, må systemet samordne den på tvers av noder. Det skjer ofte med to-fase-commit (2PC): først går nodene inn i en prepare-fase, og deretter blir transaksjonen enten bekreftet eller avbrutt globalt.
Problemet med 2PC er ganske rett fram: modellen er sårbar for nettverksforsinkelse. Over et WAN (Wide Area Network) blir global koordinering tregere og dyrere, siden hver runde mellom noder koster tid.
Replikeringsmodellen spiller også en stor rolle. Med primær-backup (single-leader) håndterer én node skriveoperasjonene. Det gjør koordineringen enklere, men kan også bli en flaskehals. Med multi-leder (aktiv-aktiv) kan alle replikaer ta imot skriveoperasjoner. Det gir bedre tilgjengelighet og lavere latens, men krever mer samordning for å unngå konflikter og anomalier som write skew.
Kort sagt: replikeringsmodellen setter grenser for hvor mye samtidighet systemet tåler før ting begynner å kollidere.
Konsistensmodeller og CAP-avveininger
Distribuerte systemer kan ikke samtidig garantere sterk konsistens, høy tilgjengelighet og toleranse mot nettverkspartisjoner. Det er kjernen i CAP-teoremet. I praksis betyr det at man må veie streng korrekthet opp mot høy tilgjengelighet.
Det er denne avveiningen som skiller sterk, kausal og eventual konsistens.
Sterk konsistens krever synkron koordinering, ofte via konsensusprotokoller som Paxos eller Raft. Eventual konsistens lar noder jobbe lokalt og synkronisere asynkront. Det gir høyere tilgjengelighet, men kan føre til at ulike noder midlertidig viser ulike data. Kausal konsistens ligger et sted imellom: operasjoner som henger logisk sammen, vises i riktig rekkefølge, mens urelaterte operasjoner ikke trenger en global orden.
Når replikaer kan avvike fra hverandre, blir det sentrale spørsmålet hvor mye konsistens systemet vil prioritere over latens. Det er her arkitekturvalgene får direkte følger for hvordan samtidige operasjoner oppfører seg.
Hvordan reelle systemer håndterer samtidighet og replikering
I praksis ser man ulike valg i ulike databaser. PostgreSQL og MySQL bruker ofte primær-backup for enklere konsistens. MongoDB støtter konfigurerbare lese- og skrivekrav, slik at utviklere kan velge sterkere eller svakere konsistens per operasjon. Apache Cassandra prioriterer tilgjengelighet og eventual konsistens, og passer godt der litt utdaterte data er greit. Google Spanner sikter mot global serialiserbarhet på tvers av datasentre.
| System | Typisk tilnærming | Konsekvens for samtidighet |
|---|---|---|
| PostgreSQL / MySQL | Primær-backup | Én primærnode håndterer skriveoperasjonene |
| MongoDB | Konfigurerbare lese- og skrivekrav | Sterkere eller svakere konsistens per operasjon |
| Apache Cassandra | Eventual konsistens | Høy tilgjengelighet, men risiko for utdaterte data |
| Google Spanner | Globalt koordinert konsistens | Global serialiserbarhet |
sbb-itb-84d7fbf
Kjernemekanismer for samtidighetskontroll
Samtidighetskontroll i distribuerte databaser: Mekanismer sammenlignet
Arkitekturvalgene setter rammene. Men det er kontrollmekanismene som avgjør hva som faktisk skjer når to transaksjoner treffer de samme dataene samtidig. I distribuerte databaser må dette fungere på tvers av både shards og replikaer.
Låsing og to-fase-låsing
Låsing er den mest klassiske modellen. Før en transaksjon kan lese eller skrive data, må den ta enten en delt lås (S) eller en eksklusiv lås (X). En delt lås gjør at flere transaksjoner kan lese samtidig, men den stopper skriving. En eksklusiv lås stenger ute både lesere og skrivere.
To-fase-låsing (2PL) deler prosessen i to faser: en voksende fase der transaksjonen henter inn låser, og en krympende fase der den slipper dem. Når den først har begynt å slippe låser, kan den ikke ta nye. Strikt 2PL holder alle låser helt til transaksjonen er ferdig. Det hindrer kaskader av rollback.
I et distribuert oppsett blir låsing fort dyrt. En fullt distribuert låsemanager må snakke med andre noder for hver eneste låseoperasjon. Det gir mer latens og langt høyere koordineringskostnader.
Tidsstempler, MVCC og optimistisk validering
Tidsstempelbasert ordning gir hver transaksjon et tidsstempel fra systemklokken eller en logisk teller. Transaksjoner med lavere tidsstempel går først. Fordelen er enkel å se: det oppstår ikke vranglås (deadlock). Baksiden er at transaksjoner kan bli avbrutt og startet på nytt flere ganger, noe som kan gi starvation.
MVCC (Multiversion Concurrency Control) går en annen vei. Hver skriveoperasjon lager en ny versjon av dataene. Lesere får da se den versjonen som var gyldig da transaksjonen startet, uten å bli stoppet av skriv som fortsatt pågår. Det er nettopp derfor MVCC passer så godt i systemer med mye lesing. Snapshot Isolation er den vanligste formen for MVCC i kommersielle databaser, men den gir ikke alltid full serialiserbarhet i alle kanttilfeller.
Optimistisk samtidighetskontroll (OCC) bygger på en ganske enkel tanke: la transaksjoner jobbe på lokale kopier uten låsing underveis. Først når de skal committe, sjekkes det om det finnes konflikter. Hvis valideringen feiler, blir transaksjonen rullet tilbake og startet på nytt. Dette fungerer godt når konflikter er sjeldne. Under høy last kan bildet snu fort, fordi abortfrekvensen stiger og mye arbeid går tapt.
Låsing, MVCC og OCC sammenlignet
| Mekanisme | Passer best til | Styrker | Svakheter | Distribuerte avveininger |
|---|---|---|---|---|
| Låsing / 2PL | Høy konflikttetthet | Garanterer serialiserbarhet; hindrer tapte oppdateringer | Risiko for deadlock; høy ventetid | Høy koordinering; hver lås krever nettverkstrafikk |
| MVCC | Lesetunge arbeidsbelastninger | Lesere blokkerer ikke skrivere; høy gjennomstrømning | Lagringskostnad for flere versjoner | Krever versjonskontroll og konsistent versjonsrekkefølge |
| OCC | Lav konflikttetthet | Ingen låsekostnader under kjøring; ingen vranglås | Høy abortfrekvens under belastning; bortkastet arbeid | Validering kan bli flaskehals |
Strengt 2PL brukes fortsatt i systemer der global serialiserbarhet veier tyngst.
Neste del går inn i feilmodusene som dukker opp når disse mekanismene møter deadlock, replikeringskonflikter og partisjoner.
Vanlige problemer og hvordan systemer håndterer dem
Når slike mekanismer møter faktisk last, dukker det som regel opp to typer feil: anomalier og replikeringskonflikter.
Isolasjonsanomalier og vranglås
Lavere isolasjon gir fortsatt rom for feil som dirty read og non-repeatable read.
Skriv-skjevhet skjer når to transaksjoner hver for seg ser gyldige ut, men sammen bryter en regel. Et vanlig grep er å bruke SELECT FOR UPDATE for å låse de radene som blir berørt, slik at risikoen for write skew blir mindre.
Vranglås oppstår når to eller flere transaksjoner blir stående og vente på hverandre i en sirkel. Det er nok krevende i ett system, men i distribuerte oppsett blir det enda mer knotete fordi ventekjeden kan gå på tvers av flere noder. To kjente måter å forebygge dette på er Wait-Die og Wound-Wait. I Wait-Die får en eldre transaksjon vente, mens en yngre blir avbrutt og startet på nytt. I Wound-Wait er det den eldre transaksjonen som presser den yngre til å restarte.
Replikeringskonflikter og partisjonsrelaterte feil
Når to klienter skriver til ulike replikaer samtidig, kan begge skrivingene lykkes lokalt. Problemet kommer etterpå: systemet kan ende opp med to ulike tilstander.
Den enkleste løsningen er last-write-wins (LWW). Da vinner den siste skrivingen ut fra tidsstempel. Det er lett å sette opp, men prisen er at nyere data kan bli overskrevet uten varsel.
Mer avanserte valg er versjonsvektorer, som holder styr på hvilke oppdateringer hver replika har sett, og CRDT-er (Conflict-free Replicated Data Types), altså datastrukturer som er laget for å kunne flettes automatisk uten konflikt. I shardede oppsett kan én treg node også sinke hele oppslaget hvis klienten må innom mange shards.
Nettverkspartisjoner gjør alt dette mer krevende. Når noder ikke lenger kan samordne seg, må systemet i praksis velge: enten avvise skrivinger der og da, eller ta dem imot og rydde opp i konfliktene senere. Her er idempotente gjenforsøk med eksponentiell backoff og tilfeldig forsinkelse (jitter) et viktig mønster, særlig i systemer som bruker OCC, der transaksjoner kan feile ved commit.
Sammenligning av konflikt- og vranglåshåndtering
| Tilnærming | Korrekthet | Kompleksitet | Passer best til |
|---|---|---|---|
| Vranglåsdeteksjon | Høy | Høy | Systemer med høy konflikttetthet der presis oppløsning er nødvendig |
| Wait-Die / Wound-Wait | Høy | Middels | Distribuerte systemer med tidsstempelbasert prioritering av transaksjoner |
| Tidsavbrudd (timeout) | Middels | Lav | Enkle distribuerte miljøer; avbryter ofte også legitime, langkjørende transaksjoner |
| Last-write-wins (LWW) | Lav | Lav | NoSQL-databaser der eventual konsistens er akseptabel |
| Egendefinert merge-logikk | Høy | Høy | Domener med komplekse forretningsregler for konfliktoppløsning |
| CRDT-er | Høy | Middels | Systemer der automatisk sammenslåing uten konflikter er et krav |
Det riktige valget styres i praksis av tre ting: isolasjonsnivå, replikeringsmodell og hvor godt systemet kan observeres i drift. Hvis du ikke ser hva som skjer, er det også langt vanskeligere å forstå hvorfor konfliktene oppstår.
Konfigurasjon, overvåking og konklusjon
Isolasjonsinnstillinger og observabilitet i vanlige databaser
Valg av isolasjonsnivå avgjør hvor hardt disse mekanismene må jobbe i praksis. READ COMMITTED er ofte standard. Det gir mindre blokkering, men åpner også for non-repeatable reads og phantom reads. Bruk SERIALIZABLE når korrekthet veier tyngre enn ytelse.
Det holder ikke å velge et nivå og håpe på det beste. Du må følge med på låseventekøer, blokkerte sesjoner, vranglåser, avbrutte transaksjoner og replikeringsforsinkelse. Blokkerte sesjoner peker gjerne mot låseproblemer. Mange gjenforsøk peker ofte mot konflikt i OCC eller støy fra replikering. Ser du mange avbrutte eller gjenprøvde transaksjoner, er det et tydelig tegn på konflikt.
For kritiske oppslag, som gjeldende saldo, bør lesingen gå mot primærskriveren eller bruke sesjonsbasert konsistens. Ellers kan du ende opp med data som ser riktige ut et øyeblikk, men som ligger bak det som faktisk er skrevet.
Når mekanismen er valgt, blir spørsmålet fort ganske jordnært: Ser du konfliktene tidlig nok, før de blir til feil i produksjon?
Designvalg for norske virksomheter
Når mønsteret er kjent, må databasen settes opp etter risikoen i dataene, ikke omvendt.
For norske virksomheter er målet sjelden maks gjennomstrømming for enhver pris. Det handler oftere om å bevare saldo, integritet og sporbarhet. SERIALIZABLE er det sikreste valget, men mange team går for READ COMMITTED kombinert med deterministisk radlåsing. Da låses rader i fast rekkefølge, noe som kan hindre vranglås og write skew uten hele kostnaden i ytelse.
Det er også smart å skille mellom ulike typer flyt:
- Intern bokføring bør være strengt konsistent.
- Eksterne betalinger bør håndteres med reserver-først og bekreft-etterpå.
Tripletex bruker company_id som shardingnøkkel for å holde flest mulig transaksjoner innen én shard. Det er et godt eksempel på at designvalg i databasen ofte følger forretningsgrensene, ikke bare tekniske ønsker.
Konklusjon: beslutningene som betyr mest
Overvåking viser ikke bare at noe går galt. Den viser også hvilken kontrollmodell som faktisk passer lasten. Samtidighetskontroll i distribuerte databaser handler om å beskytte korrekthet under last, replikeringsforsinkelse og partisjonssvikt. Låsing passer når konflikter skjer ofte, MVCC passer ved blandede lese- og skrivemønstre, og OCC passer der konflikter er sjeldne. Valget styres av konfliktnivå, konsistenskrav og hvor godt du kan observere det som skjer.
FAQs
Når bør jeg velge MVCC fremfor 2PL?
Velg MVCC fremfor 2PL når du trenger høy ytelse i systemer med mange samtidige brukere. Med MVCC kan lesinger skje uten å blokkere skriving, fordi databasen lagrer flere versjoner av de samme dataene. Det gir mindre venting og jevnere flyt når mange er inne i systemet samtidig.
2PL bygger derimot på låser. Det betyr ofte at transaksjoner må stå i kø og vente på hverandre, særlig når både lesing og skriving skjer tett på de samme radene. I praksis kan det bremse tempoet ganske mye.
2PL passer best når streng serialiserbarhet er et hardt krav. MVCC er som regel et bedre valg når målet er bedre flyt og høy tilgjengelighet i moderne, skalerbare databasesystemer.
Hva er forskjellen mellom 2PC og 2PL?
2PL er en metode for samtidighetskontroll. Den bestemmer når en transaksjon kan ta og slippe låser, slik at data blir behandlet på en konsistent måte og uten konflikter.
2PC er en protokoll for distribuerte systemer. Den samordner om en transaksjon skal fullføres eller avbrytes på tvers av flere noder. Kort sagt: 2PL styrer låsing, mens 2PC styrer commit/rollback.
Hvilket isolasjonsnivå passer for systemet mitt?
Det handler om balansen mellom ytelse og konsistens. Serialiserbarhet gir de sterkeste garantiene, fordi transaksjoner oppfører seg som om de kjører alene. Ulempen er at dette kan gi lavere ytelse i distribuerte systemer.
Snapshot Isolation er ofte et kompromiss som gir høy ytelse, men uten full serialiserbarhet. Hvis applikasjonen krever streng dataintegritet og ikke tåler konfliktrisiko, bør du velge serialiserbar isolasjon.