De ce ar putea fi necesară replicarea semi-sincronă?

Salut tuturor. Sunt Vladislav Rodin. În prezent, predau cursuri pe portalul OTUS, dedicate arhitecturii software și arhitecturii software-ului supus unor încărcări mari. Înainte de începerea unui nou curs „Arhitect pentru sarcini înalte” am decis să scriu un material scurt de autor pe care doresc să-l împărtășesc cu voi.

De ce ar putea fi necesară replicarea semi-sincronă?

Introducere

Din cauza faptului că pe un HDD pot fi efectuate doar aproximativ 400-700 de operațiuni pe secundă (ceea ce este incomparabil cu rps-urile tipice care vin pe un sistem supus unor mari încărcări), baza de date clasică pe disc reprezintă un gât de sticlă în arhitectură. De aceea, este necesar să acordăm o atenție specială modelelor de scalare a acestui depozit.

În prezent, există două modele de scalare a bazei de date: replicarea și shardarea. Shardarea permite scalarea operațiunii de scriere și, prin urmare, reduce rps-urile de scriere pentru un server din clusters-ul vostru. Replicarea permite să se facă același lucru, dar cu operațiile de citire. Această articole se concentrează asupra acestui model.

Replicare

Dacă ne uităm la replicare dintr-o perspectivă de înalt nivel, este un lucru simplu: aveți un server, pe care erau stocate datele, iar apoi acest server nu mai face față încărcăturii de citire a acestor date. Adăugați încă câteva servere, sincronizați datele pe toate serverele, iar utilizatorul poate citi de pe orice server din cluster-ul vostru.

În ciuda simplificării aparente, există mai multe variante de clasificare a diferitelor implementări ale acestei scheme:

  • După rolurile în cluster (master-master sau master-slave)
  • După obiectele transmise (bazate pe rânduri, bazate pe instrucțiuni sau mixte)
  • După mecanismul de sincronizare a nodurilor

Astăzi ne vom concentra exact pe cel de-al 3-lea punct.

Cum se desfășoară comiterea unei tranzacții

Această temă nu este direct legată de replicare, ar putea fi scris un articol separat pe această temă, totuși, deoarece fără o înțelegere a mecanismului de comitere a tranzacției, lectura ulterioară devine inutilă, îmi permit să amintesc cele mai esențiale aspecte. Comiterea tranzacției se desfășoară în 3 etape:

  1. Înregistrarea tranzacției în jurnalul bazei de date.
  2. Aplicarea tranzacției în motorul bazei de date.
  3. Returnarea unei confirmări clientului cu privire la aplicarea cu succes a tranzacției.

În diverse baze de date, acest algoritm poate prezenta nuanțe: de exemplu, în motorul InnoDB al bazei MySQL există 2 jurnale: unul pentru replicare (binary log) și altul pentru menținerea ACID (undo/red log), în timp ce în PostgreSQL există un singur jurnal care îndeplinește ambele funcții (write ahead log = WAL). Dar ceea ce este prezentat mai sus este conceptul general, care permite neglijarea acestor nuanțe.

Replicare sincronă (sync)

Să adăugăm în algoritmul de comitere a tranzacției logica de replicare a modificărilor primite:

  1. Înregistrarea tranzacției în jurnalul bazei de date.
  2. Aplicarea tranzacției în motorul bazei de date.
  3. Trimiterea datelor către toate replicile.
  4. Primirea confirmării de la toate replicile despre executarea tranzacției asupra acestora.
  5. Returnarea unei confirmări clientului cu privire la aplicarea cu succes a tranzacției.

Prin această abordare, obținem o serie de dezavantaje:

  • clientul așteaptă aplicarea modificărilor pe toate replicile.
  • odată cu creșterea numărului de noduri din cluster, scădem probabilitatea ca operația de scriere să aibă succes.

Dacă la primul punct lucrurile sunt mai mult sau mai puțin clare, motivele pentru al doilea punct merită explicate. Dacă în cazul replicării sincrone nu obținem un răspuns de la măcar un nod, inversăm tranzacția. Prin urmare, crescând numărul de noduri din cluster, creșteți probabilitatea ca operația de scriere să eșueze.

Putem aștepta confirmarea de la o anumită parte din noduri, de exemplu, de la 51% (quorum)? Da, putem, totuși în varianta clasică este necesară confirmarea de la toate nodurile, deoarece astfel ne asigurăm o consistență completă a datelor în cluster, care este un avantaj indiscutabil al acestui tip de replicare.

Replicare asincronă (async)

Să modificăm algoritmul anterior. Datele către replici le vom trimite „cândva mai târziu”, și „cândva mai târziu” modificările vor fi aplicate pe replici:

  1. Înregistrarea tranzacției în jurnalul bazei de date.
  2. Aplicarea tranzacției în motorul bazei de date.
  3. Returnarea unei confirmări clientului cu privire la aplicarea cu succes a tranzacției.
  4. Trimiterea datelor către replici și aplicarea modificărilor de către acestea.

Această abordare duce la faptul că clusterul funcționează rapid, deoarece nu ținem clientul în așteptare până când datele ajung la replici și sunt și confirmate.

Dar condiția de a trimite date către replici „cândva mai târziu” poate conduce la pierderea tranzacției, și anume pierderea unei tranzacții confirmate către utilizator, deoarece dacă datele nu au fost repliate la timp, confirmarea trimisă clientului despre succesul operației, iar nodul pe care au venit modificările a avut o defecțiune HDD, pierdem tranzacția, ceea ce poate duce la consecințe foarte neplăcute.

Replicare semi-sincronă (semisync)

În sfârșit am ajuns la replicarea semi-sincronă. Acest tip de replicare nu este foarte cunoscut și nu este foarte răspândit, totuși reprezintă un mare interes, deoarece poate combina avantajele atât ale replicării sincronizate, cât și ale celei asincronizate.

Să încercăm să combinăm cele două abordări anterioare. Nu vom ține clientul mult timp, dar vom cere ca datele să fie replicate:

  1. Înregistrarea tranzacției în jurnalul bazei de date.
  2. Aplicarea tranzacției în motorul bazei de date.
  3. Trimiterea datelor către replici.
  4. Obținerea unei confirmări de la replică că modificările au fost primite (aplicate vor fi „cândva mai târziu”).
  5. Returnarea unei confirmări clientului cu privire la aplicarea cu succes a tranzacției.

Rețineți că, în cadrul acestui algoritm, pierderea unei tranzacții apare doar în cazul în care atât nodul care acceptă modificările, cât și nodul-replică se defectează. Probabilitatea unei astfel de defecțiuni este considerată mică, iar aceste riscuri sunt acceptate.

Dar cu această abordare există riscul citirilor fantomă. Să ne imaginăm următorul scenariu: în pasul 4 nu am primit confirmări de la nicio replică. Trebuie să anulăm această tranzacție, iar clientului nu îi returnăm o confirmare. Deoarece datele au fost aplicate în pasul 2, între finalizarea pasului 2 și anularea tranzacției apare o fereastră temporală, în care tranzacțiile paralele pot vedea acele modificări care nu ar trebui să existe în baza de date.

Replicare semi-sincronă fără pierdere

Dacă ne gândim puțin, putem schimba pur și simplu pașii algoritmului pentru a rezolva problema citirilor fantomă în acest scenariu:

  1. Înregistrarea tranzacției în jurnalul bazei de date.
  2. Trimiterea datelor către replică.
  3. Obținerea unei confirmări de la replică că modificările au fost primite (aplicate vor fi „cândva mai târziu”).
  4. Aplicarea tranzacției în motorul bazei de date.
  5. Returnarea unei confirmări clientului cu privire la aplicarea cu succes a tranzacției.

Acum ne angajăm să facem modificările doar dacă au fost replicate.

Ieșire

Ca întotdeauna, nu există soluții perfecte, ci un set de soluții, fiecare având propriile sale avantaje și dezavantaje și fiind potrivită pentru diferite tipuri de probleme. Acest lucru este adevărat și pentru alegerea mecanismului de sincronizare a datelor în baza de date replicată. Setul de avantaje pe care îl oferă replicarea semi-sincronă este destul de solid și interesant, astfel încât să merite atenția, în ciuda răspândirii sale reduse.

Asta e tot. Ne vedem la curs!

Sursa: habr.com

Cumpără un hosting fiabil pentru site-uri cu protecție DDoS, servere VPS VDS 🔥 Cumpără un hosting fiabil pentru site-uri cu protecție DDoS, servere VPS VDS | ProHoster