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

Migrări DB fără downtime pe tabele mari: cum aplic pattern-ul pe 4 pași

De Delia Petre, 22 aug. 2026 · 19 vizualizări · 2 like-uri

Postat 22 aug. 2026
sql
-- Pasul 2: Exemplu de backfill sigur în batch-uri (Postgres)
DO $$
DECLARE
    batch_size INT := 5000;
    rows_updated INT := 1;
BEGIN
    WHILE rows_updated > 0 LOOP
        WITH rows_to_update AS (
            SELECT id 
            FROM users 
            WHERE address_json IS NULL AND address IS NOT NULL
            LIMIT batch_size
            FOR UPDATE SKIP LOCKED
        )
        UPDATE users u
        SET address_json = json_build_object('raw', u.address)
        FROM rows_to_update r
        WHERE u.id = r.id;

        GET DIAGNOSTICS rows_updated = ROW_COUNT;
        COMMIT;
        PERFORM pg_sleep(0.05); -- pauză pentru a elibera I/O
    END LOOP;
END $$;

Un ALTER TABLE dat neglijent pe o tabelă cu trafic mare e cel mai rapid mod de a genera 504 în cascadă pe tot clusterul. Am învățat asta prin 2018 când am blocat un Postgres cu vreo 14 milioane de rânduri timp de 9 minute pentru un amărât de rename de coloană. De atunci, orice modificare structurală pe tabele critice trece obligatoriu prin pattern-ul pe 4 pași: Add, Backfill, Flip, Remove (cunoscut și ca Expand/Contract).

Problema cu abordarea naivă

Când rulezi un ALTER TABLE users RENAME COLUMN legacy_id TO uuid sau adaugi o coloană NOT NULL fără un default safe în versiuni vechi de DB, motorul are nevoie de un lock exclusiv (ACCESS EXCLUSIVE în Postgres).

Dacă ai tranzacții lungi active, comanda de alter intră în coadă și așteaptă. Problema e că toate query-urile venite după ea vor fi blocate în spatele ei, umplând pool-ul de conexiuni în câteva secunde.

Cei 4 pași în practică

Să presupunem că avem coloana address (text simplu) și vrem să migrăm la address_json (JSONB) fără nicio secundă de mentenanță.

Pasul 1: Add (Expand)

Adaugi coloana nouă ca NULLABLE. În Postgres 11+, adăugarea unei coloane fără NOT NULL sau cu o valoare default constantă este o operație instantă pe metadata, fără scanare de tabelă.

În aceeași etapă (sau un deploy imediat următor), modifici codul de backend să facă Dual Write: orice insert sau update nou scrie atât în address, cât și în address_json.

Pasul 2: Backfill (Umplerea datelor vechi)

Datele noi sunt scrise corect, dar datele vechi au address_json null. Aici intervine migrarea asincronă. Niciodată nu faci un UPDATE users SET address_json = ... global, pentru că blochezi tot.

Rulezi un script/worker extern care procesează datele în batch-uri mici (de exemplu câte 2.000 - 5.000 de ID-uri), cu o mică pauză (sleep 50ms) între ele. Durează mai mult, dar baza de date nici nu simte încărcarea pe I/O.

Pasul 3: Flip (Comutarea citirii)

Verifici că nu mai există rânduri unde address e populat dar address_json e null. Când datele sunt 100% sincronizate, dai deploy la versiunea de backend care citește doar din address_json.

În pasul ăsta e recomandat să păstrezi dual-write-ul activ pentru încă 24-48h. Dacă apare vreun bug critic în formatul JSON, poți da rollback instant la citirea din coloana veche fără pierderi de date.

Pasul 4: Remove (Contract)

După ce codul e stabil și monitorizarea e curată:

  1. Oprești scrierea pe coloana veche.
  2. Dai drop la coloana veche (ALTER TABLE users DROP COLUMN address).
  3. Adaugi constrângeri suplimentare pe noua coloană (NOT NULL), preferabil folosind NOT VALID urmat de VALIDATE CONSTRAINT ca să eviți table locks.

Trade-off-uri reale

Nimic nu e gratis. În loc de o migrare de 30 de secunde ai un proces care se întinde pe 2-3 zile și minim două PR-uri separate. Ai cod duplicat temporar în modele și consumi mai mult disk space pe durata tranziției.

Pentru un proiect mic cu 10k rânduri, e overkill total — oprești traficul 2 secunde noaptea și ai rezolvat. Dar la zeci de milioane de înregistrări și SLA de 99.99%, e singura metodă prin care poți dormi liniștit în timpul unui deploy.

Voi cum gestionați backfill-ul: scripturi de migrare în codul aplicației, un tool dedicat (gen gh-ost / pg-roll) sau simple worker jobs?

Răspunsuri 0

Se încarcă răspunsurile…

Loghează-te pentru a răspunde

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