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

Migrări de baze de date fără downtime: Cum aplic pattern-ul în 4 pași în producție

De Ioan Manole, 11 iul. 2026 · 12 vizualizări · 3 like-uri

Postat 11 iul. 2026
sql
-- Exemplu de script de backfill în batch-uri pentru Postgres
-- Evităm blocarea tabelului prin actualizarea a câte 5000 de rânduri o dată

DO $$
DECLARE
  rows_updated INT := 1;
BEGIN
  LOOP
    UPDATE users 
    SET new_phone_number = old_phone 
    WHERE id IN (
        SELECT id 
        FROM users 
        WHERE new_phone_number IS NULL 
          AND old_phone IS NOT NULL
        LIMIT 5000
    );
    
    GET DIAGNOSTICS rows_updated = ROW_COUNT;
    COMMIT; -- Eliberăm lock-urile după fiecare batch
    
    EXIT WHEN rows_updated = 0;
    PERFORM pg_sleep(0.1); -- Lăsăm DB-ul să respire 100ms
  END LOOP;
END $$;

Am trecut cu toții prin asta: dai un deploy seara, rulează o migrare aparent inofensivă și te trezești cu lock-uri pe bază și timeout-uri în producție. Am pățit-o acum vreo 6 ani pe un tabel de tranzacții cu 14 milioane de rânduri, unde un simplu ALTER TABLE ADD COLUMN cu valoare default a blocat complet scrierile timp de 8 minute. De atunci, am trecut exclusiv pe pattern-ul de migrare în 4 pași (Expand and Contract).

Dacă ai o bază de date de producție care nu își permite niciun minut de downtime, nu poți pur și simplu să redenumești o coloană sau să schimbi un tip de date direct. Trebuie să rulezi aplicația în modul "hibrid", unde atât codul vechi, cât și cel nou pot funcționa în paralel.

Cei 4 pași ai migrației sigure

Principiul de bază e simplu: separi modificarea bazei de date de modificarea codului din aplicație. Totul se întâmplă în patru etape distincte.

1. Add (Expand)

Adaugi noua coloană în baza de date, dar o lași complet opțională (NULLABLE) și fără constrângeri dure (cum ar fi FOREIGN KEY sau UNIQUE care ar putea bloca scrierile din codul vechi). În acest moment, aplicația ta habar nu are de noua coloană și scrie în continuare în cea veche.

2. Backfill & Dual Write

Aici e munca grea. Faci deploy la o versiune de cod care scrie în ambele coloane (cea veche și cea nouă), dar citește în continuare din cea veche. În paralel, rulezi un script de backfill pentru a copia datele din coloana veche în cea nouă pentru înregistrările existente.

Greșeala clasică pe care am văzut-o des e să rulezi un singur UPDATE uriaș. Asta va bloca tabela instant. Trebuie să faci update în batch-uri mici, de exemplu câte 1000 sau 5000 de rânduri, cu o mică pauză între ele (sleep de 50-100ms) ca să lași baza de date să respire.

3. Flip

După ce backfill-ul s-a terminat și ești sigur că datele sunt identice, faci deploy la o nouă versiune de cod. Acum, aplicația citește și scrie exclusiv din/în coloana nouă. Codul nu mai atinge deloc coloana veche, deși ea încă există fizic în baza de date.

4. Remove (Contract)

Aștepți câteva zile. Te uiți pe loguri, verifici APM-ul și te asiguri că totul e stabil. Abia acum rulezi migrarea SQL care șterge coloana veche sau elimină codul redundant de fallback din aplicație.

Trade-off-ul sincer: Costul siguranței

Să fim sinceri: pattern-ul ăsta e enervant de lung. În loc de o singură migrare rapidă și un deploy de 5 minute, ajungi să faci cel puțin 3-4 deploy-uri separate și să scrii cod de compatibilitate pe care oricum îl vei șterge peste o săptămână. În plus, codul tău temporar va fi destul de urât.

Pentru un site mic sau un MVP cu 100 de utilizatori activi, e complet overkill. Acolo e mai simplu să ai 10 secunde de downtime la 3 dimineața. Dar când lucrezi la un sistem cu trafic constant, unde fiecare secundă de downtime înseamnă clienți pierduți și alerte de PagerDuty, cele 4 etape sunt singura metodă prin care poți dormi liniștit noaptea.

Cum gestionați voi migrările mari de scheme în producție? Ați avut incidente memorabile din cauza unui lock pe tabelă?

Răspunsuri 0

Se încarcă răspunsurile…

Loghează-te pentru a răspunde

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