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

Cum facem migrări de DB fără downtime: Pattern-ul în 4 pași aplicat pe 14M de rânduri

De Ștefan Iliescu, 21 mai 2026 · 6 vizualizări · 2 like-uri

Postat 21 mai 2026
sql
-- Script de backfill rulat repetat în background pentru a evita table lock-ul
UPDATE users
SET phone_normalized = REGEXP_REPLACE(phone_number, '[^0-9+]', '', 'g')
WHERE id IN (
    SELECT id FROM users 
    WHERE phone_normalized IS NULL 
    AND phone_number IS NOT NULL
    LIMIT 5000
);

Am trecut acum un an prin coșmarul de a migra o tabelă de facturare cu peste 14 milioane de rânduri într-o bază de date PostgreSQL, în timp ce aveam trafic constant. Dacă faci un simplu ALTER TABLE ADD COLUMN cu o valoare default complexă sau un RENAME COLUMN, blochezi tabela și îți pui aplicația în cap instant. Soluția pe care am aplicat-o și care ne-a salvat de la un downtime de 30 de minute se bazează pe un pattern clasic în 4 pași.

Pasul 1 și 2: Adăugarea coloanei și scrierea duală

În loc să modifici direct coloana existentă, creezi una nouă, dar o lași nullable la început. Să zicem că vrem să migrăm de la phone_number (care e un string dezordonat introdus de useri) la phone_normalized (un format curățat).

Prima migrare de DB doar adaugă phone_normalized ca nullable. Imediat după, în codul aplicației, începi să scrii în ambele coloane (dual write). Orice user nou sau update va scrie în ambele locuri. Totuși, aplicația citește în continuare din coloana veche. În faza asta, sistemul funcționează normal, iar noile înregistrări sunt deja aliniate cu noul format.

Pasul 3: Backfill-ul (Aici se decide succesul)

Acum ai date noi scrise în ambele coloane, dar ce facem cu cele 14 milioane de rânduri vechi? Aici am făcut marea greșeală la începutul carierei: am rulat un UPDATE masiv pe toată tabela. Rezultatul? Lock total pe tabelă, CPU la 100% și alerte de timeout pe Slack.

Trade-off-ul corect este backfill-ul în batch-uri mici. Rulezi un script în background (printr-un cron sau un worker dedicat) care ia câte 5.000 de rânduri, le procesează și le salvează. Durează mai mult — la noi a durat cam 4 ore pe producție —, dar baza de date nici nu simte efortul. Tranzacțiile clienților merg mai departe fără nicio milisecundă de lag.

Pasul 4: Flip-ul și curățenia de primăvară

Când scriptul de backfill s-a terminat și toate rândurile au noua valoare populată, e timpul pentru "flip". Schimbi codul aplicației să citească exclusiv din phone_normalized. Lasă sistemul să ruleze așa vreo 3 zile. E plasa ta de siguranță în caz că ceva a scăpat la teste. Dacă apare vreo problemă neprevăzută, poți face rollback instant în cod, fără să atingi baza de date.

După ce ești 100% sigur că totul e stabil, elimini scrierea duală din cod. La final, poți șterge coloana veche phone_number printr-un simplu drop (recomandat la o oră cu trafic mai scăzut, doar ca măsură suplimentară de precauție).

Trade-off-uri și concluzie

Metoda asta are un cost evident: scrii de trei ori mai mult cod și trebuie să fii extrem de disciplinat. Ai nevoie de cel puțin 3 deploy-uri separate de cod ca să faci tot procesul în siguranță. Dar merită? Absolut. Am redus downtime-ul de la un potențial dezastru de jumătate de oră la exact zero secunde.

Voi cum gestionați migrările astea grele pe tabele mari? Mergeți pe pattern-ul ăsta în pași sau încă mai riscați cu un maintenance window duminica la 3 dimineața?

Răspunsuri 0

Se încarcă răspunsurile…

Loghează-te pentru a răspunde

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