Hvorfor kan du trenge semi-synkron replikering?

Hei alle sammen. Vladislav Rodin tar kontakt. Jeg underviser for tiden kurs om programvarearkitektur og høystress programvarearkitektur ved OTUS. I påvente av oppstart av ny kursflyt "Høybelastningsarkitekt" Jeg bestemte meg for å skrive et kort stykke originalt materiale som jeg vil dele med deg.

Hvorfor kan du trenge semi-synkron replikering?

Innledning

På grunn av det faktum at HDD-en bare kan utføre rundt 400-700 operasjoner per sekund (som er uforlignelig med typiske rps for et høybelastningssystem), er den klassiske diskdatabasen flaskehalsen i arkitekturen. Derfor er det nødvendig å være spesielt oppmerksom på skaleringsmønstrene til denne lagringen.

For øyeblikket er det 2 databaseskaleringsmønstre: replikering og sharding. Sharding lar deg skalere skriveoperasjonen, og som et resultat redusere rps per skriving per server i klyngen din. Replikering lar deg gjøre det samme, men med leseoperasjoner. Det er dette mønsteret denne artikkelen er viet til.

replikering

Hvis du ser på replikering på et veldig høyt nivå, er det en enkel ting: du hadde én server, det var data på den, og så kunne ikke denne serveren lenger takle belastningen med å lese disse dataene. Du legger til et par servere til, synkroniserer data på tvers av alle servere, og brukeren kan lese fra hvilken som helst server i klyngen din.

Til tross for dens tilsynelatende enkelhet, er det flere alternativer for å klassifisere ulike implementeringer av denne ordningen:

  • Etter roller i klyngen (master-master eller master-slave)
  • Etter objekter som er sendt (radbasert, setningsbasert eller blandet)
  • I henhold til nodesynkroniseringsmekanismen

I dag skal vi behandle punkt 3.

Hvordan oppstår en transaksjonsforpliktelse?

Dette emnet er ikke direkte relatert til replikering; en egen artikkel kan skrives om det, men siden videre lesing er ubrukelig uten å forstå transaksjonsforpliktelsesmekanismen, la meg minne deg på de mest grunnleggende tingene. En transaksjonsforpliktelse skjer i 3 stadier:

  1. Logger en transaksjon til databaseloggen.
  2. Bruke en transaksjon i en databasemotor.
  3. Returnerer bekreftelse til kunden på at transaksjonen ble gjennomført.

I forskjellige databaser kan denne algoritmen ha nyanser: for eksempel i InnoDB-motoren til MySQL-databasen er det 2 logger: en for replikering (binær logg), og den andre for å opprettholde ACID (angre/gjør om logg), mens den er i PostgreSQL det er én logg som utfører begge funksjonene (skriv frem logg = WAL). Men det som er presentert ovenfor er nettopp det generelle konseptet, som gjør at slike nyanser ikke kan tas i betraktning.

Synkron (synkron) replikering

La oss legge til logikk for å replikere de mottatte endringene i transaksjons-forpliktelsesalgoritmen:

  1. Logger en transaksjon til databaseloggen.
  2. Bruke en transaksjon i en databasemotor.
  3. Sender data til alle replikaer.
  4. Mottar bekreftelse fra alle replikaer på at en transaksjon er fullført på dem.
  5. Returnerer bekreftelse til kunden på at transaksjonen ble gjennomført.

Med denne tilnærmingen får vi en rekke ulemper:

  • klienten venter på at endringene skal brukes på alle replikaer.
  • ettersom antall noder i klyngen øker, reduserer vi sannsynligheten for at skriveoperasjonen vil lykkes.

Hvis alt er mer eller mindre klart med 1. punkt, så er årsakene til 2. punkt verdt å forklare. Hvis vi under synkron replikering ikke mottar svar fra minst én node, ruller vi tilbake transaksjonen. Ved å øke antall noder i klyngen øker du dermed sannsynligheten for at en skriveoperasjon mislykkes.

Kan vi vente på bekreftelse fra bare en viss prosentandel av noder, for eksempel fra 51 % (quorum)? Ja, det kan vi, men i den klassiske versjonen kreves bekreftelse fra alle noder, fordi det er slik vi kan sikre fullstendig datakonsistens i klyngen, noe som er en utvilsom fordel med denne typen replikering.

Asynkron (asynkron) replikering

La oss endre den forrige algoritmen. Vi vil sende data til replikaene "en gang senere", og "en gang senere" vil endringene bli brukt på replikkene:

  1. Logger en transaksjon til databaseloggen.
  2. Bruke en transaksjon i en databasemotor.
  3. Returnerer bekreftelse til kunden på at transaksjonen ble gjennomført.
  4. Sende data til replikaer og bruke endringer på dem.

Denne tilnærmingen fører til at klyngen fungerer raskt, fordi vi ikke lar klienten vente på at dataene skal nå replikaene og til og med bli forpliktet.

Men betingelsen om å dumpe data på replikaer "en gang senere" kan føre til tap av en transaksjon, og til tap av en transaksjon bekreftet av brukeren, fordi hvis dataene ikke hadde tid til å replikeres, en bekreftelse til klienten om suksessen til operasjonen ble sendt, og noden som endringene kom til krasjet HDD, mister vi transaksjonen, noe som kan føre til svært ubehagelige konsekvenser.

Semisynk replikering

Til slutt kommer vi til semi-synkron replikering. Denne typen replikering er ikke veldig kjent eller veldig vanlig, men den er av betydelig interesse fordi den kan kombinere fordelene med både synkron og asynkron replikering.

La oss prøve å kombinere de 2 tidligere tilnærmingene. Vi vil ikke beholde klienten lenge, men vi vil kreve at dataene replikeres:

  1. Logger en transaksjon til databaseloggen.
  2. Bruke en transaksjon i en databasemotor.
  3. Sender data til replikaer.
  4. Mottar bekreftelse fra replikaen på at endringene er mottatt (de vil bli brukt "en gang senere").
  5. Returnerer bekreftelse til kunden på at transaksjonen ble gjennomført.

Vær oppmerksom på at med denne algoritmen oppstår transaksjonstap bare hvis både noden som mottar endringene og replika-noden mislykkes. Sannsynligheten for en slik feil anses som lav, og disse risikoene aksepteres.

Men med denne tilnærmingen er det en mulig risiko for fantomlesninger. La oss forestille oss følgende scenario: i trinn 4 mottok vi ingen bekreftelse fra noen replika. Vi må tilbakestille denne transaksjonen og ikke returnere en bekreftelse til kunden. Siden dataene ble brukt i trinn 2, er det et tidsgap mellom slutten av trinn 2 og tilbakeføringen av transaksjonen, hvor parallelle transaksjoner kan se endringer som ikke skal være i databasen.

Tapsfri semisync replikering

Hvis du tenker litt, kan du bare reversere trinnene til algoritmen og fikse problemet med fantomlesninger i dette scenariet:

  1. Logger en transaksjon til databaseloggen.
  2. Sender replikadata.
  3. Mottar bekreftelse fra replikaen på at endringene er mottatt (de vil bli brukt "en gang senere").
  4. Bruke en transaksjon i en databasemotor.
  5. Returnerer bekreftelse til kunden på at transaksjonen ble gjennomført.

Nå forplikter vi endringer bare hvis de har blitt replikert.

Utgang

Som alltid er det ingen ideelle løsninger; det er et sett med løsninger, som hver har sine egne fordeler og ulemper og er egnet for å løse ulike klasser av problemer. Dette er helt sant for å velge en mekanisme for å synkronisere data i en replikert database. Settet med fordeler som semi-synkron replikering har er tilstrekkelig solid og interessant til at det kan anses som verdt oppmerksomhet, til tross for dets lave utbredelse.

Det er alt. Ser deg på kurs!

Kilde: www.habr.com

Legg til en kommentar