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

Migrări de baze de date fără downtime: Pattern-ul în 4 pași de care ai nevoie la scale

De Radu Grigore, 13 iun. 2026 · 19 vizualizări · 2 like-uri

Postat 13 iun. 2026
sql
-- Pasul 2: Exemplu de query de backfill rulat într-un loop în script
-- Actualizăm doar câte 5000 de rânduri o dată ca să evităm lock-ul pe tabelă

UPDATE transactions
SET new_currency = old_currency
WHERE id IN (
  SELECT id FROM transactions
  WHERE new_currency IS NULL
  LIMIT 5000
);

Salutare tuturor. Am trecut acum ceva timp prin faza în care trebuia să modificăm o tabelă de tranzacții cu vreo 18 milioane de înregistrări, pe un PostgreSQL care gâfâia deja în orele de vârf. Dacă vii din lumea proiectelor mici, tentația e să dai un ALTER TABLE ADD COLUMN direct în producție, duminica la 3 dimineața. Dar când ai trafic constant și business-ul pierde bani la fiecare secundă de downtime, tactica asta te trimite direct la raport.

Pentru a evita blocarea tabelelor mari, eu folosesc de ani de zile pattern-ul Expand and Contract (cunoscut și ca Parallel Change). Sună pompos, dar în esență înseamnă că nu modifici niciodată o structură existentă direct, ci o faci în patru pași simpli și siguri.

Pasul 1: Expand (Scrierea dublă)

Primul pas este să adaugi noua coloană în baza de date, dar o lași complet nullable și fără constrângeri stricte. Imediat după, modifici codul aplicației să facă scriere dublă.

Mai exact: aplicația scrie datele noi atât în coloana veche, cât și în cea nouă. Totuși, la citire, aplicația folosește în continuare exclusiv coloana veche. În felul acesta, baza de date nu suferă nicio blocare majoră, iar codul este backward-compatible.

Pasul 2: Backfill (Migrarea istorică)

Acum ai datele noi salvate în ambele locuri, dar rândurile vechi au încă valoarea NULL pe coloana nouă. Aici intervine migrarea de background.

Am văzut mulți juniori care dau un singur query uriaș de update. Este rețeta perfectă pentru dezastru; o să blochezi tabela ore în șir din cauza lock-urilor de tranzacție. Soluția testată de mine este să scrii un script simplu care face update în batch-uri mici. Eu folosesc de obicei batch-uri de 5.000 sau 10.000 de rânduri, cu un mic sleep de 50-100ms între ele ca să las baza de date să respire și să servească request-urile legitime ale userilor.

Pasul 3: Flip (Comutarea citirilor)

După ce scriptul de backfill s-a terminat (verifici simplu cu un query de tip COUNT unde coloana nouă este NULL), poți face trecerea.

Modifici codul aplicației astfel încât să citească exclusiv din coloana nouă. În acest punct, poți păstra încă scrierea dublă pentru câteva zile. De ce? În caz că descoperi un bug neprevăzut în producție, poți face rollback instant la codul vechi fără să pierzi datele salvate în acest interval.

Pasul 4: Contract (Curățenia)

Odată ce noul cod e stabil în producție și totul funcționează brici de câteva zile, vine timpul să facem curat.

  1. Modifici aplicația să nu mai scrie deloc în coloana veche.
  2. Adaugi constrângerile de tip NOT NULL pe noua coloană (dacă e cazul), dar atenție cum faci asta în Postgres (folosește NOT VALID și apoi VALIDATE CONSTRAINT ca să eviți un table scan blocant).
  3. Dai drop la coloana veche.

Trade-off-ul sincer

Marele dezavantaj? Scrii de trei ori mai mult cod și ai nevoie de cel puțin 3-4 deployment-uri separate în loc de unul singur. Procesul durează mai mult și cere disciplină. Dar pentru mine, să pot dormi liniștit noaptea în timp ce baza de date rulează migrări la prânz fără ca userii să simtă ceva, merită fiecare linie de cod în plus.

Voi cum gestionați migrările astea sensibile? Ați folosit vreodată tool-uri automate gen gh-ost sau pt-online-schema-change, sau mergeți tot pe varianta manuală din aplicație?

Răspunsuri 0

Se încarcă răspunsurile…

Loghează-te pentru a răspunde

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