← Tutti gli articoli

Postgres zero-downtime migration: i pattern che usiamo davvero

15 January 20252 min di lettura

Aggiungere colonne, rinominare, cambiare tipo: tutto senza interrompere il servizio. Le quattro tecniche che applichiamo.

Per progetti SaaS attivi 24/7, "fermiamo 10 minuti per fare la migration" non è un'opzione. Vediamo le quattro tecniche che usiamo davvero per cambiare schema senza fermare il servizio.

1. Expand-contract

Il pattern più importante. Per ogni cambio di schema:

  1. Expand: aggiungi la nuova colonna/tabella senza toccare la vecchia.
  2. Deploya il codice che scrive in entrambe.
  3. Backfill dei dati storici.
  4. Deploya il codice che legge dalla nuova.
  5. Contract: rimuovi la vecchia.

Sono 5 deploy. Sono anche zero downtime.

2. Lock-aware ALTER TABLE

In Postgres alcuni ALTER bloccano l'intera tabella. Le regole d'oro:

  • ADD COLUMN ... NOT NULL con DEFAULT su Postgres < 11: lock pesante. Su Postgres ≥ 11: lock leggero.
  • ALTER COLUMN TYPE: spesso richiede una full table rewrite. Da evitare in produzione, meglio fare expand-contract.
  • CREATE INDEX: usa sempre CONCURRENTLY.
  • ALTER TABLE con timeout: imposta statement_timeout e lock_timeout per non bloccare il sistema se il lock non arriva subito.

3. Backfill a batch

Mai un singolo UPDATE ... WHERE ... su una tabella grande. Serve uno script Node che processa 1.000-10.000 righe per volta, con pause:

while (rows = await fetchBatch()) {
  await updateBatch(rows);
  await sleep(200); // respirare
}

4. Feature flag

Il deploy del codice nuovo va dietro feature flag. Se il backfill non è completo o c'è un bug, lo si disattiva senza rollback Git.

Quando NON usare zero-downtime

Per progetti interni con utenti aziendali a orari precisi, una manutenzione notturna di 5 minuti costa molto meno tempo che il setup completo zero-downtime. Bisogna sapere quando ne vale la pena.