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

Migrări de baze de date fără downtime: pattern-ul în 4 pași pe care îl folosesc la scale

De Mihai Popescu, 5 iun. 2026 · 1 vizualizări · 3 like-uri

Postat acum 4 zile
typescript
async function updateUserProfile(userId: string, newData: any) {
  // Pasul 1 & 2: Scriem în ambele coloane (dual-write)
  const query = `
    UPDATE users 
    SET 
      old_bio = $1, 
      new_bio_json = $2 
    WHERE id = $3
  `;
  
  // new_bio_json primește structura nouă, old_bio rămâne compatibil cu codul vechi
  await db.query(query, [newData.legacyBioText, JSON.stringify(newData.richBio), userId]);
}

Să faci un ALTER TABLE pe o bază de date cu milioane de rânduri direct în producție este rețeta perfectă pentru un infarct duminică seara. Am pățit-o acum vreo 6 ani pe un Postgres de producție și de atunci nu mai ating structura tabelelor mari fără să aplic pattern-ul ăsta în 4 pași.

Când tabela are dimensiuni considerabile, orice modificare de schemă riscă să pună un lock exclusiv. La un proiect cu peste 8 milioane de useri activi, asta înseamnă că request-urile se adună în coadă, conexiunile la DB se epuizează rapid și aplicația moare. Soluția pe care o folosesc mereu este pattern-ul Expand and Contract (sau mai simplu: Add, Backfill, Flip, Remove).

Pasul 1: Add (Adaugă coloana nouă și scrie în ambele)

Primul pas este să adăugăm coloana nouă în baza de date, dar obligatoriu ca fiind NULLABLE (sau cu o valoare default safe). Nu punem constrângeri de tip NOT NULL în această fază.

În codul aplicației, facem deployment la o versiune care scrie în ambele coloane (cea veche și cea nouă), dar citește în continuare exclusiv din cea veche. Acest mecanism se numește dual-writing.

Trade-off sincer: Performanța scrierilor va scădea insesizabil pentru că baza de date trebuie să proceseze două câmpuri în loc de unul, dar riscul de downtime este zero.

Pasul 2: Backfill (Popularea datelor vechi)

Acum avem coloana nouă, dar ea este goală pentru toate înregistrările create înainte de Pasul 1. Trebuie să copiem datele din coloana veche în cea nouă.

Niciodată să nu dai un simplu UPDATE table SET new_col = old_col. Vei bloca tabela complet. În schimb, scrie un script de migrare (un worker background sau un cron) care face update în batch-uri mici, de exemplu câte 500 sau 1000 de rânduri o dată, ordonate după ID.

La un proiect trecut, am avut de migrat în jur de 12 milioane de rânduri. Am rulat scriptul cu un sleep de 100ms între batch-uri. A durat cam 5 ore, dar CPU-ul bazei de date nu a trecut de 15% și userii nu au simțit absolut nimic.

Pasul 3: Flip (Schimbă citirea și dezactivează dual-write)

După ce scriptul de backfill s-a terminat și toate rândurile sunt sincronizate, facem al treilea deployment.

De data asta modificăm aplicația să citească doar din coloana nouă. Totuși, e bine să păstrezi scrierea în ambele coloane încă 24-48 de ore. De ce? Dacă descoperi un bug critic în producție legat de noul format, poți face rollback instant la codul vechi fără să pierzi datele salvate în acest interval.

Pasul 4: Remove (Curățenia de primăvară)

După câteva zile în care totul e stabil și suntem siguri că noua logică funcționează perfect, eliminăm complet referințele către coloana veche din codul aplicației.

La final, rulăm o migrare care șterge coloana veche (ALTER TABLE DROP COLUMN). Unele motoare de DB tot vor pune un lock scurt aici, dar fiind vorba doar de eliminarea unei coloane nefolosite, operațiunea este extrem de rapidă.

Concluzie

Metoda asta e plictisitoare și necesită cel puțin 3-4 deployment-uri separate în loc de unul singur. Pentru un startup la început de drum e o pierdere totală de timp. Dar când ai zeci de mii de utilizatori concurenți și downtime-ul te costă reputație și bani, este singura metodă matură de lucru.

Voi cum gestionați migrările astea grele? Ați încercat instrumente automate de tip gh-ost sau preferați să scrieți manual logica în aplicație?

Răspunsuri 0

Se încarcă răspunsurile…

Loghează-te pentru a răspunde

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