eduardweb.
Database & PrismaAvansat#postgresql#migrations#backend#databases

Cum faci migrări de baze de date cu zero downtime. Pattern-ul în 4 pași pe care l-am învățat pe pielea mea

De Ștefan Iliescu, 16 iun. 2026 · 19 vizualizări · 2 like-uri

Postat 16 iun. 2026
typescript
// Pasul 1 & 2: Dual Write în Repository
async function updateUserPhone(userId: string, newPhone: string) {
  // Scriem în ambele coloane pentru a asigura compatibilitatea în faza de tranziție
  await db.query(
    `UPDATE users 
     SET phone = $1, 
         phone_number = $1 
     WHERE id = $2`,
    [newPhone, userId]
  );
}

Am fost în punctul ăla în care un simplu ALTER TABLE a blocat toată aplicația timp de 15 minute pentru că tabela avea peste 20 de milioane de rânduri. Clienții urlau pe Slack, managerul stătea în spatele meu transpirat, iar eu căutam disperat ID-ul procesului ca să dau kill la query. Atunci am înțeles că migrările clasice în care oprești aplicația, rulezi migrarea și o pornești înapoi sunt o rețetă sigură pentru dezastru la scară mare.

Dacă ai trecut de faza de început și lucrezi la un sistem unde fiecare minut de downtime înseamnă bani pierduți, trebuie să înveți să schimbi schema bazei de date "la cald". Soluția standard din industrie se bazează pe patru pași simpli, dar care cer răbdare: Add, Backfill, Flip, Remove.

De ce crapă abordarea clasică?

Când rulezi un ALTER TABLE table_name RENAME COLUMN old_col TO new_col, baza de date (fie că e Postgres sau MySQL) trebuie să obțină un lock exclusiv pe tabelă (Access Exclusive Lock). În timp ce se execută comanda, nicio altă interogare de citire sau scriere nu se poate rula. Dacă tabela e uriașă, aplicația va da timeout-uri pe bandă rulantă.

Pentru a evita asta, împărțim procesul în patru etape distincte, rulate pe parcursul mai multor deployment-uri.

Cei 4 pași ai migrației sigure

Să luăm ca exemplu cazul în care vrem să redenumim coloana phone în phone_number și să-i schimbăm formatul.

1. Add (Adăugarea)

În primul pas, adaugi noua coloană în baza de date ca fiind opțională (nullable).

Atenție: Nu ștergi și nu modifici coloana veche încă. În același timp, modifici codul aplicației să facă dual-write. Adică, de fiecare dată când salvezi un user, scrii numărul de telefon atât în phone, cât și în phone_number. Aplicația citește în continuare doar din phone.

2. Backfill (Sincronizarea datelor vechi)

Acum ai date noi care se salvează în ambele locuri, dar înregistrările vechi au noua coloană goală. Trebuie să le migrezi.

Scrii un script care rulează în background și copiază datele din phone în phone_number pentru înregistrările vechi.

Pont din experiență: Fă asta în batch-uri mici (de exemplu, câte 1000 de rânduri o dată) cu o scurtă pauză între ele. La un proiect cu peste 12 milioane de rânduri, am rulat un astfel de script timp de 6 ore. A durat mult? Da. A simțit vreun user vreo încetinire? Absolut deloc.

3. Flip (Comutarea)

După ce scriptul de backfill s-a terminat și ești sigur că toate rândurile au datele sincronizate, modifici codul aplicației.

Acum, aplicația va citi exclusiv din coloana nouă (phone_number) și va scrie tot în ea. Oprești dual-write-ul. În acest moment, coloana veche phone devine redundantă, dar o lași acolo ca plasă de siguranță în caz că trebuie să dai rollback rapid.

4. Remove (Curățenia)

După câteva zile în care ai monitorizat logurile și totul e stabil, rulezi ultima migrare de bază de date: ștergi coloana veche phone.

Compromisul de care nu-ți spune nimeni

Sună ideal, nu? Totuși, există un trade-off destul de mare.

Această metodă cere o disciplină de fier și multă birocrație în cod. În loc de o singură migrare rapidă, ai nevoie de cel puțin 3 deployment-uri de cod diferite și de un script de migrare de date. Procesul de development devine mai lent, iar testarea locală e mai enervantă pentru că trebuie să simulezi stările intermediare.

Pentru un proiect mic sau aflat la început, unde un downtime de 2 minute la ora 3 dimineața nu deranjează pe nimeni, pattern-ul ăsta e overkill total. Dar când ai mii de useri activi pe minut, devine singura opțiune viabilă.

Voi cum gestionați migrările astea dureroase? Ați încercat să folosiți views pentru a masca schimbările sau mergeți tot pe varianta clasică de dual-write în cod?

Răspunsuri 0

Se încarcă răspunsurile…

Loghează-te pentru a răspunde

Doar membrii comunității pot lăsa comentarii.