Ciao, amico!
Oggi parliamo di cosa sono le migrazioni del database e perché sono così importanti. Nel mondo di oggi, non sorprende che qualsiasi cambiamento a un database debba essere fatto con attenzione e secondo un processo specifico. Idealmente, questi passaggi dovrebbero essere integrati nella nostra pipeline CI/CD affinché tutto funzioni automaticamente.
Ecco la nostra agenda:
- Qual è il problema?
- Come lo risolviamo?
- Un esempio semplice
- Un esempio più complesso
- Raccomandazioni
- Risultati
- Conclusione
Qual è il problema?
Se il tuo team non ha mai affrontato le migrazioni del database e non sei del tutto sicuro del perché siano necessarie, cerchiamo di chiarirlo. Se conosci già le basi, sentiti libero di saltare.
Principale sfida
Quando apportiamo cambiamenti “programmati” e “graduali” al database, dobbiamo mantenere la disponibilità del servizio e soddisfare i requisiti SLA (affinché gli utenti non subiscano interruzioni o ritardi). Immagina di voler cambiare un tipo di colonna in una tabella con 5 milioni di utenti. Se lo fai “in modo diretto” (ad esempio, eseguendo semplicemente ALTER TABLE
senza preparazione), la tabella potrebbe rimanere bloccata per un tempo significativo — e i tuoi utenti rimarrebbero senza servizio.
Per evitare tali mal di testa, segui due regole:
- Applica le migrazioni in un modo che non blocchi la tabella (o almeno minimizzi i blocchi).
- Se hai bisogno di cambiare un tipo di colonna, è spesso più facile creare prima una nuova colonna con il tipo corretto e poi eliminare quella vecchia.
Un altro problema: Controllo delle versioni e Ripristini
A volte è necessario ripristinare una migrazione.
Farlo manualmente — accedendo al database di produzione e modificando i dati — non è solo rischioso ma anche probabilmente impossibile se non hai accesso diretto. È qui che gli strumenti di migrazione dedicati risultano utili. Ti permettono di applicare le modifiche in modo pulito e di ripristinarle se necessario.
Come lo risolviamo? Usa gli strumenti giusti
Ogni linguaggio ed ecosistema ha i propri strumenti di migrazione:
- Per Java, Liquibase o Flyway sono comuni.
- Per Go, una scelta popolare è goose (quello che esamineremo qui).
- E così via.
Goose: Cos’è e perché è utile
Goose è un’utilità leggera per Go che ti aiuta a gestire le migrazioni automaticamente. Offre:
- Semplicità. Dipendenze minime e una struttura di file trasparente per le migrazioni.
- Versatilità. Supporta vari driver DB (PostgreSQL, MySQL, SQLite, ecc.).
- Flessibilità. Scrivi migrazioni in codice SQL o Go.
Installare Goose
go install github.com/pressly/goose/v3/cmd/goose@latest
Come Funziona: Struttura di Migrazione
Per impostazione predefinita, Goose cerca i file di migrazione in db/migrations
. Ogni migrazione segue questo formato:
NNN_migration_name.(sql|go)
NNN
è il numero di migrazione (ad es.,001
,002
, ecc.).- Dopo di che, puoi avere un qualsiasi nome descrittivo, ad esempio
init_schema
. - L’estensione può essere
.sql
o.go
.
Esempio di una Migrazione SQL
File: 001_init_schema.sql
:
-- +goose Up
CREATE TABLE users (
id SERIAL PRIMARY KEY,
username VARCHAR(255) NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT now()
);
-- +goose Down
DROP TABLE users;
Il nostro Primo Esempio
Cambiare il Tipo di Colonna (Stringa → Intero)
Supponiamo di avere una tabella users
con una colonna age
di tipo VARCHAR(255)
. Ora vogliamo cambiarlo in INTEGER
. Ecco come potrebbe apparire la migrazione (file 005_change_column_type.sql
):
-- +goose Up
ALTER TABLE users ALTER COLUMN age TYPE INTEGER USING (age::INTEGER);
-- +goose Down
ALTER TABLE users ALTER COLUMN age TYPE VARCHAR(255) USING (age::TEXT);
Cosa sta succedendo qui:
-
Migrazione Up
- Cambiamo la colonna
age
inINTEGER
. La clausolaUSING (age::INTEGER)
dice a PostgreSQL come convertire i dati esistenti al nuovo tipo. - Nota che questa migrazione fallirà se ci sono dati in
age
che non sono numerici. In tal caso, avrai bisogno di una strategia più complessa (vedi sotto).
- Cambiamo la colonna
-
Rollback della migrazione
- Se torniamo indietro, riportiamo
età
aVARCHAR(255)
. - Utilizziamo di nuovo
USING (età::TEXT)
per convertire daINTEGER
a testo.
- Se torniamo indietro, riportiamo
Casi Secondari e Complessi: Migrazioni a più passaggi
Se la colonna età
potrebbe contenere dati disordinati (non solo numeri), è più sicuro fare questo in diversi passaggi:
- Aggiungi una nuova colonna (
età_int
) di tipoINTEGER
. - Copia i dati validi nella nuova colonna, gestendo o rimuovendo le voci non valide.
- Elimina la vecchia colonna.
-- +goose Up
-- Step 1: Add a new column
ALTER TABLE users ADD COLUMN age_int INTEGER;
-- Step 2: Try to move data over
UPDATE users
SET age_int = CASE
WHEN age ~ '^[0-9]+$' THEN age::INTEGER
ELSE NULL
END;
-- (optional) remove rows where data couldn’t be converted
-- DELETE FROM users WHERE age_int IS NULL;
-- Step 3: Drop the old column
ALTER TABLE users DROP COLUMN age;
-- +goose Down
-- Step 1: Recreate the old column
ALTER TABLE users ADD COLUMN age VARCHAR(255);
-- Step 2: Copy data back
UPDATE users
SET age = age_int::TEXT;
-- Step 3: Drop the new column
ALTER TABLE users DROP COLUMN age_int;
Per consentire un corretto rollback, la sezione Down
riflette semplicemente le azioni in ordine inverso.
L’Automazione è Fondamentale
Per risparmiare tempo, è davvero conveniente aggiungere i comandi di migrazione a un Makefile (o a qualsiasi altro sistema di build). Di seguito è riportato un esempio di Makefile con i principali comandi di Goose per PostgreSQL.
Supponiamo:
- Il DSN per il database è
postgres://utente:password@localhost:5432/nomedb?sslmode=disable
. - I file di migrazione si trovano in
db/migrations
.
# File: Makefile
DB_DSN = "postgres://user:password@localhost:5432/dbname?sslmode=disable"
MIGRATIONS_DIR = db/migrations
# Install Goose (run once)
install-goose:
go install github.com/pressly/goose/v3/cmd/goose@latest
# Create a new SQL migration file
new-migration:
ifndef NAME
$(error Usage: make new-migration NAME=your_migration_name)
endif
goose -dir $(MIGRATIONS_DIR) create $(NAME) sql
# Apply all pending migrations
migrate-up:
goose -dir $(MIGRATIONS_DIR) postgres $(DB_DSN) up
# Roll back the last migration
migrate-down:
goose -dir $(MIGRATIONS_DIR) postgres $(DB_DSN) down
# Roll back all migrations (be careful in production!)
migrate-reset:
goose -dir $(MIGRATIONS_DIR) postgres $(DB_DSN) reset
# Check migration status
migrate-status:
goose -dir $(MIGRATIONS_DIR) postgres $(DB_DSN) status
Come usarlo?
1. Crea una nuova migrazione (file SQL). Questo genera un file db/migrations/002_add_orders_table.sql
.
make new-migration NAME=add_orders_table
2. Applica tutte le migrazioni. Goose creerà una tabella schema_migrations
nel tuo database (se non esiste già) e applicherà eventuali nuove migrazioni in ordine crescente.
make migrate-up
3. Ritorna all’ultima migrazione. Basta eseguire il rollback dell’ultima migrazione.
make migrate-down
4. Ritorna a tutte le migrazioni (usa cautela in produzione). Reset completo.
make migrate-reset
5. Controlla lo stato delle migrazioni.
make migrate-status
Esempio di output:
$ goose status
$ Applied At Migration
$ =======================================
$ Sun Jan 6 11:25:03 2013 -- 001_basics.sql
$ Sun Jan 6 11:25:03 2013 -- 002_next.sql
$ Pending -- 003_and_again.go
Riepilogo
Utilizzando gli strumenti di migrazione e un Makefile, possiamo:
- Limitare l’accesso diretto al database di produzione, apportando modifiche solo tramite migrazioni.
- Tracciare facilmente le versioni del database e tornare indietro in caso di problemi.
- Mantenere un’unica, coerente storia delle modifiche al database.
- Eseguire migrazioni “fluide” che non interromperanno un ambiente di produzione in esecuzione nel mondo dei microservizi.
- Ottenere una validazione extra: ogni modifica passerà attraverso un processo di revisione del codice (supponendo che tu abbia quelle impostazioni attive).
Un altro vantaggio è che è facile integrare tutti questi comandi nel tuo flusso di lavoro CI/CD. E ricorda — la sicurezza prima di tutto.
Ad esempio:
jobs
migrate
runs-on ubuntu-latest
steps
name Install Goose
run
make install-goose
name Run database migrations
env
DB_DSN $ secrets.DATABASE_URL
run
make migrate-up
Conclusione e consigli
Le idee principali sono così semplici:
- Mantieni le tue migrazioni piccole e frequenti. Sono più facili da rivedere, testare e annullare se necessario.
- Usa lo stesso strumento in tutti gli ambienti in modo che sviluppo, staging e produzione siano sincronizzati.
- Integra le migrazioni nel CI/CD così da non dipendere da una sola persona che le esegue manualmente.
In questo modo, avrai un processo affidabile e controllato per cambiare la struttura del tuo database – uno che non interrompe la produzione e ti consente di rispondere rapidamente se qualcosa va storto.
Buona fortuna con le tue migrazioni!
Grazie per aver letto!
Source:
https://dzone.com/articles/goose-as-crucial-tool-for-your-service