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

Migrări de bază de date fără downtime: Pattern-ul în 4 pași pe tabele mari

De Ioan Manole, 11 aug. 2026 · 2 vizualizări · 2 like-uri

Postat acum 6 zile
sql
-- Pasul 1: Adăugare coloană rapidă (instant în Postgres 11+)
ALTER TABLE users ADD COLUMN full_name_new VARCHAR(255);

-- Pasul 2 (Exemplu query folosit de worker-ul de backfill în batch-uri)
UPDATE users 
SET full_name_new = CONCAT(first_name, ' ', last_name)
WHERE id IN (
    SELECT id FROM users 
    WHERE full_name_new IS NULL 
    LIMIT 2000
);

-- Pasul 4: Validare constraint fără lock masiv pe scrieri
ALTER TABLE users 
ADD CONSTRAINT check_full_name_not_null 
CHECK (full_name_new IS NOT NULL) NOT VALID;

ALTER TABLE users VALIDATE CONSTRAINT check_full_name_not_null;

Am văzut prea multe incidente la 2 noaptea declanșate de un aparent inofensiv ALTER TABLE. Dacă ai un tabel cu peste 30-40 de milioane de rânduri în Postgres, o schimbare de schemă executată direct îți blochează scrierile și îți umple Slack-ul de alerte. Astăzi vorbim despre pattern-ul Expand/Contract — singura metodă curată prin care faci migrări fără să pici producția.

Problema cu ALTER TABLE tradițional

Când dai ALTER TABLE users ADD COLUMN phone VARCHAR NOT NULL DEFAULT 'N/A', baza de date încearcă să fie utilă. În spate, rescrie fiecare pagină de pe disc sau pune un lock exclusiv (AccessExclusiveLock) până termină de procesat toate rândurile. La 100.000 de rânduri nu simți nimic. La 45 de milioane de rânduri, asta înseamnă 10-15 minute în care aplicația ta dă 500-uri pe bandă rulantă.

Am pățit asta acum vreo doi ani la un e-commerce mare. Am blocat tabela de comenzi timp de 12 minute la prânz pentru o amărâtă de redenumire de coloană. De atunci, am interzis orice migrare distructivă directă.

Soluția: Pattern-ul în 4 pași (Expand/Contract)

Ca să eviți lock-urile lungi și să păstrezi compatibilitatea cu codul existent în timpul deploy-ului, spargi migrarea în patru pași independenți, derulați pe parcursul câtorva zile.

1. Adaugi coloana nouă (Nullable)

Primul pas este de tip "Expand". Adaugi coloana nouă, dar o lași obligatoriu NULLABLE și fără valori default grele. Postgres va adăuga coloana aproape instant în metadate (O(1)), fără să resfere paginile de pe disc.

2. Dual-Write și Backfill

Din acest moment, modifici codul din aplicație astfel încât să scrie în ambele coloane (cea veche și cea nouă), dar să citească tot din cea veche.

După ce noul cod e în producție, pornești un script de fundal (un job sau un cron) care ia rândurile vechi în batch-uri mici — de exemplu, 2.000 de rânduri per tranzacție — și populează coloana nouă. Pui o mică pauză (pg_sleep) între batch-uri ca să nu dai spike în IOPS-ul din AWS/GCP.

3. Schimbi citirea (The Flip)

Când scriptul de backfill a ajuns la 100% și nu mai ai nicio valoare NULL pe rândurile vechi, faci un nou deploy. De data asta, schimbi aplicația să citească din coloana nouă. Păstrezi scrierea dublă pentru siguranță. Dacă apare vreo problemă neprevăzută, poți face rollback la cod în 5 secunde fără să pierzi date.

4. Curățenia (Contract)

După ce aplicația rulează stabil de 24-48 de ore citind din noua coloană, elimini dual-write-ul din cod. Apoi dai drop la coloana veche din baza de date. Dacă aveai nevoie de restricție NOT NULL, o pui folosind NOT VALID și apoi faci VALIDATE CONSTRAINT separat, ca să nu blochezi tabela.

Ce pierzi când aplici strategia asta?

Nu există magie fără trade-off. În loc de o singură migrare și un singur PR de 5 minute, procesul ăsta te obligă la 3-4 PR-uri separate și minim 24-48 de ore de așteptare. Scrierea dublă adaugă o latență insemnificativă la INSERT/UPDATE, iar logica din ORM devine temporar mai stufoasă.

Dar din punctul meu de vedere, să treci de la risc de downtime de 15 minute la zero secunde de downtime merită cu vârf și îndesat cele 3 PR-uri în plus.

Voi ce soluții folosiți pentru migrările pe tabele mari: aveți totul automatizat prin pipeline-uri sau încă vă asumați ferestre de mentenanță noaptea?

Răspunsuri 0

Se încarcă răspunsurile…

Loghează-te pentru a răspunde

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