Modulul 4 se deschide aici. Modulul 3 a fost despre ce bază de date să alegi. Modulul 4 e despre ce se întâmplă când o singură instanță a acelei baze de date nu mai este de ajuns: când un singur server nu poate ține datele, când un singur server nu poate servi load-ul, când un singur server care cade înseamnă că toată aplicația se prăbușește. Pattern-urile sunt aceleași indiferent dacă store-ul tău e Postgres, MongoDB, Cassandra sau DynamoDB. Sunt, de asemenea, vechi. Literatura de sisteme distribuite lucrează la ele din anii 1980, iar trade-off-urile pe care le fac apar în fiecare data store modern, uneori prin configurație, alteori cusute direct în design.
Această primă lecție e despre replication: păstrarea a mai mult de o copie a datelor tale pe mai mult de o mașină. Lecția următoare e despre consecințe. Cea de după e despre partitioning, care e axa ortogonală. Împreună acoperă cea mai mare parte din ce trebuie să știi despre scalarea unei baze de date pe orizontală.
De ce să replici, în primul rând
Patru motive, aproximativ în ordinea în care echipele le descoperă.
Durabilitate. Un singur server poate cădea în multe feluri: discul moare, kernel panic, datacenter-ul rămâne fără curent, cineva se împiedică de un cablu. Dacă unica ta copie a datelor trăiește pe acea mașină, eșecul e un eveniment de pierdere de date. Dacă datele sunt replicate pe o a doua mașină într-un domeniu de eșec separat, supraviețuiești primului eșec cu datele intacte. Acesta este motivul original pentru care există replication.
Latency. Un utilizator din Singapore care citește dintr-o bază de date din Londra plătește în jur de o sută șaptezeci de milisecunde de round-trip pe query. Dacă datele sunt replicate într-o regiune din Singapore, aceeași citire durează două milisecunde. Read replicas în regiuni geografic diverse schimbă o parte de consistency pentru câștiguri foarte mari de latency pe workload-uri intensive de citire.
Read throughput. Un primary singur poate servi doar atâtea citiri pe secundă înainte să-i satureze CPU-ul, IO-ul sau rețeaua. Adăugarea de read replicas răspândește load-ul de citire pe mai multe mașini. Scrierile încă trebuie să treacă prin primary, dar pe majoritatea workload-urilor citirile sunt de zece până la o sută de ori mai multe decât scrierile, iar read replicas absorb cea mai mare parte din presiune.
Siguranță la upgrade și mentenanță. Când primary-ul are nevoie de un upgrade major, un patch de OS sau hardware nou, nu vrei să iei un outage de mai multe ore. Cu replication poți promova un follower ca primary, faci treaba pe vechiul primary și te întorci. Aplicația vede o fereastră scurtă de failover în loc de un outage lung.
Trei familii de strategie de replication acoperă în esență fiecare design în producție. Diferă pe o singură axă: unde sunt acceptate scrierile.
Leader/follower
Un nod e leader-ul. Acceptă fiecare scriere. Aplică scrierea în propriul storage, apoi trimite schimbarea fiecărui follower. Follower-ii aplică schimbările în aceeași ordine. Citirile pot merge la orice replica suficient de actualizată pentru nevoile aplicației.
Acesta e pattern-ul standard în Postgres, MySQL, replica sets de MongoDB, Redis, SQL Server, Oracle și majoritatea bazelor de date relaționale. Numele variază: leader și follower, primary și secondary, master și slave (terminologia mai veche, acum de obicei înlocuită). Forma este aceeași.
Două butoane de configurație importante.
Synchronous versus asynchronous. Replication synchronous înseamnă că leader-ul așteaptă ca cel puțin N follower-i să confirme că au aplicat scrierea înainte ca scrierea să fie acknowledged la client. Asynchronous înseamnă că leader-ul confirmă imediat ce propriul storage are scrierea și o trimite follower-ilor în background. Synchronous oferă garanții mai puternice de durabilitate: dacă leader-ul moare în clipa de după acknowledge, scrierea e deja pe un follower. Asynchronous oferă latency mai mic la scriere, dar acceptă o mică fereastră în care o scriere acknowledged poate fi pierdută la căderea leader-ului. Postgres îi spune synchronous_commit și te lasă să-l configurezi per tranzacție. Majoritatea deployment-urilor de producție rulează un mix: synchronous către un standby apropiat pentru durabilitate, asynchronous către replicile mai îndepărtate pentru latency.
Failover. Când leader-ul moare, cineva trebuie să promoveze un follower să fie noul leader. Asta e mai greu decât pare. Noul leader trebuie să fie cel mai actualizat follower, altfel scrieri care fuseseră acknowledged sunt pierdute. Clienții trebuie să fie informați să trimită scrierile la noul leader. Vechiul leader, când revine, trebuie reconciliat cu noua stare. Dacă două noduri cred amândouă că sunt leader în același timp (un split-brain), poți obține scrieri conflictuale pe care baza de date n-are cum să le merge. Sistemele de producție folosesc unelte de coordonare ca Patroni, Orchestrator sau mecanisme built-in (alegerile MongoDB, Redis Sentinel) ca să facă failover-ul automat și sigur. A face asta de mână la 3 dimineața e tipul de poveste operațională care ajunge în postmortem-uri.
Pattern-ul leader/follower se potrivește bine cu modelul mental relațional: scrierile sunt liniarizate printr-un singur nod, tranzacțiile au o ordine clară, povestea de consistency e simplă. Costul e că scrierile nu scalează dincolo de ce poate gestiona singurul leader și că problema de failover e mereu prezentă.
Multi-leader
Două sau mai multe noduri acceptă scrieri. Fiecare leader își replică scrierile către ceilalți leaders. Citirile pot merge la orice leader.
Cazurile de utilizare motivante sunt cele pe care leader/follower le tratează stângaci.
Multi-region active-active. Ai utilizatori în trei regiuni. Cu leader/follower, doar o regiune are o bază de date scriibilă; utilizatorii din celelalte două regiuni plătesc latency cross-region pe fiecare scriere. Cu multi-leader, fiecare regiune are un leader, utilizatorii scriu pe cel local, iar leader-ii fac sync între ei în background. Latency-ul de scriere rămâne mic peste tot.
Multi-datacenter on-prem. Același argument, aplicat la deployment-uri enterprise cu două sau mai multe datacenters care trebuie să funcționeze independent dacă link-ul dintre ele cade.
Aplicații offline-first. Aplicații mobile, anumiți editori colaborativi, tipul de sistem unde fiecare device e efectiv un leader pentru propriile scrieri și face sync cu un sistem central sau cu peers la următoarea conectivitate. CouchDB și sisteme similare au fost proiectate în jurul acestui pattern.
Problema dificilă cu multi-leader e conflict resolution. Doi leaders pot accepta scrieri conflictuale în fereastra de dinainte să fi sincronizat. Utilizatorul A din Londra actualizează cantitatea comenzii la 5; utilizatorul B din Frankfurt actualizează cantitatea aceleiași comenzi la 7; ambele scrieri sunt acceptate; când leader-ii fac sync, există acum două versiuni valide ale aceluiași record și sistemul trebuie să decidă care e corectă. Trei familii de rezolvare.
Last-write-wins. Fiecare scriere are un timestamp; cea mai târzie câștigă. Simplu, dar depinde de ceasuri sincronizate (problema lecției 13 deghizată) și aruncă tăcut date: actualizarea unui utilizator e pierdută fără avertisment. Acceptabil pentru unele workload-uri (ultima locație cunoscută a unui utilizator, un câmp „last seen at”), inacceptabil pentru cele mai multe.
CRDTs (Conflict-free Replicated Data Types). Structuri matematice proiectate astfel încât orice secvență de operații de la orice pereche de replici să se merge la aceeași stare finală, indiferent de ordine. Counters, sets, ordered lists, documente JSON-like au toate formulări CRDT. Folosite în Riak, Redis CRDB, Automerge, Yjs. Puternice, dar îți constrâng modelul de date: trebuie să-ți exprimi starea ca operații CRDT, nu ca actualizări arbitrare.
Rezolvare manuală sau la nivel de aplicație. Sistemul ridică conflictele la aplicație sau la utilizator, iar ei aleg. Conflictele de merge ale Git sunt exemplul canonic. Util pentru cazurile în care aplicația are contextul să facă o alegere sensibilă, dar scump în complexitate operațională.
Unelte în producție: Postgres BDR (bidirectional replication), MySQL Group Replication, Couchbase, Riak, anumite topologii CockroachDB. Multi-leader e real, funcționează, dar povestea de conflict e cea pentru care semnezi, iar maturitatea operațională cerută e semnificativ mai mare decât la leader/follower.
Leaderless
Nu există leader. Orice nod acceptă orice scriere. Clientul (sau un coordinator în numele clientului) trimite scrierea la N replici; citirile interoghează N replici; un quorum decide care e valoarea. Acesta e designul în stil Dynamo din lucrarea Amazon din 2007 și e baza pentru Cassandra, ScyllaDB, DynamoDB, Riak și majoritatea key-value stores distribuite la scară mare.
Mecanica se bazează pe trei numere: N (numărul de replici la care merge o scriere), W (numărul care trebuie să confirme pentru ca o scriere să fie considerată reușită) și R (numărul care trebuie să răspundă pentru ca o citire să fie considerată reușită). Aplicația alege W și R per request, balansând latency cu consistency.
Regula faimoasă este W + R > N. Dacă numărul de replici la care ai scris plus numărul de replici de la care ai citit e mai mare decât numărul total de replici, atunci orice citire e garantată să se suprapună cu cea mai recentă scriere și poți să-ți citești propriile scrieri consistent. Cu N=3, W=2 și R=2 satisface asta și e default-ul comun. W=1 și R=1 (eventual consistency) e mai rapid, dar permite citiri stale. W=3 și R=1 face scrierile mai lente, dar citirile mai ieftine.
Când replicile nu sunt de acord, sistemul are nevoie de o cale să convergă. Două mecanisme.
Read repair. Când o citire returnează valori inconsistente de la replici diferite, coordinator-ul o alege pe cea mai recentă (după version vector sau timestamp), o întoarce la client și împinge valoarea corectată înapoi la replicile care erau stale. Repararea se întâmplă pe path-ul de citire, oportunist.
Anti-entropy. Un proces de background compară replicile (de obicei folosind Merkle trees ca să fie comparația ieftină) și sincronizează orice diferențe găsite. Asta prinde valorile pe care nicio citire nu s-a întâmplat să le atingă.
Tabelul de trade-off pentru leaderless: scrierile reușesc în multe moduri de eșec care ar bloca un sistem bazat pe leader, pentru că atâta timp cât oricare W replici sunt accesibile, scrierea trece. Nu există failover, pentru că nu există leader de la care să faci failover. Costul e că modelul de consistency e eventual prin default și aplicația trebuie să se gândească atent la W, R și conflict resolution.
Cele trei pattern-uri unul lângă altul
flowchart LR
subgraph LF[Leader/follower]
L1[Leader] --> F1[Follower]
L1 --> F2[Follower]
L1 --> F3[Follower]
end
subgraph ML[Multi-leader]
M1[Leader A] <--> M2[Leader B]
M2 <--> M3[Leader C]
M1 <--> M3
end
subgraph LL[Leaderless]
N1[Node] <--> N2[Node]
N2 <--> N3[Node]
N1 <--> N3
N3 <--> N4[Node]
N1 <--> N4
N2 <--> N4
end
Cele trei familii fac alegeri diferite pe patru axe pe care merită să le ții în cap.
| Axă | Leader/follower | Multi-leader | Leaderless |
|---|---|---|---|
| Scrieri în timpul căderii leader-ului | Blocate până la failover | Continuă pe ceilalți leaders | Continuă, niciun leader de eșuat |
| Consistency implicit | Strong pe leader, lagged pe followers | Eventual (conflicte posibile) | Eventual, ajustabil prin W și R |
| Răspândire geografică | O singură regiune scriibilă | Toate regiunile scriibile | Toate nodurile scriibile |
| Complexitatea conflict-resolution | Mică (fără scrieri concurente) | Mare (CRDTs sau logică de aplicație) | Medie (versionare și quorum) |
Un ghid pragmatic: leader/follower e default-ul pentru majoritatea aplicațiilor, iar majoritatea echipelor ar trebui să se oprească din citit aici. Multi-leader își câștigă complexitatea când latency-ul de scriere geografic sau active-active autentic e o cerință strictă. Leaderless are sens pentru scară foarte mare, cerințe foarte mari de availability și workload-uri key-value unde povestea de consistency poate fi proiectată în jurul quorum-urilor.
Ce despachetează lecțiile următoare
Lecția 26 ia cel mai comun pattern de producție, asynchronous leader/follower, și se uită la cum se simt de fapt trade-off-urile lui pentru un utilizator. Sintagma e „replication lag”, iar consecința e bug-ul utilizator-a-văzut-date-stale pe care fiecare echipă îl întâlnește și fiecare echipă îl gestionează ușor diferit.
Lecția 27 se întoarce către axa ortogonală: partitioning. Acolo unde replication păstrează mai mult de o copie a acelorași date, partitioning împarte datele între noduri astfel încât fiecare nod să țină doar o bucată. Majoritatea deployment-urilor reale de producție fac ambele simultan, iar alegerile se compun.
Citări și lecturi suplimentare
- Martin Kleppmann, Designing Data-Intensive Applications (O’Reilly, 2017), Capitolul 5. Tratamentul de referință al pattern-urilor de replication; tot ce e în această lecție e o compresie a acelui capitol.
- Giuseppe DeCandia et al., “Dynamo: Amazon’s Highly Available Key-value Store”, SOSP 2007,
https://www.allthingsdistributed.com/files/amazon-dynamo-sosp2007.pdf(consultat 2026-05-01). Lucrarea care a definit designul leaderless cu quorum. - Documentația Postgres, “High Availability, Load Balancing, and Replication”,
https://www.postgresql.org/docs/current/high-availability.html(consultat 2026-05-01). Referința pentru streaming replication, synchronous commit și pattern-uri de failover. - AWS DynamoDB Developer Guide, “Best Practices for Designing and Architecting with DynamoDB”,
https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/best-practices.html(consultat 2026-05-01). Designul leaderless în producție. - Documentația MongoDB, “Replication”,
https://www.mongodb.com/docs/manual/replication/(consultat 2026-05-01). Replica sets ca implementare de leader/follower cu alegere automată.