Hallo, maat!
Vandaag gaan we het hebben over wat database-migraties zijn en waarom ze zo belangrijk zijn. In de wereld van vandaag is het geen verrassing dat eventuele veranderingen aan een database zorgvuldig moeten worden uitgevoerd en volgens een specifiek proces. Idealiter zouden deze stappen geïntegreerd moeten zijn in onze CI/CD-pijplijn zodat alles automatisch verloopt.
Dit is onze agenda:
- Wat is het probleem?
- Hoe lossen we het op?
- Een eenvoudig voorbeeld
- Een complexer voorbeeld
- Aanbevelingen
- Resultaten
- Conclusie
Wat is het Probleem?
Als jouw team nog nooit te maken heeft gehad met database-migraties en je niet helemaal zeker weet waarom ze nodig zijn, laten we dat dan uitzoeken. Als je de basis al kent, kun je gerust verdergaan.
Belangrijkste Uitdaging
Wanneer we “geplande” en “soepele” veranderingen aan de database aanbrengen, moeten we de beschikbaarheid van de service behouden en voldoen aan SLA-vereisten (zodat gebruikers geen downtime of vertraging ondervinden). Stel je voor dat je het type van een kolom in een tabel met 5 miljoen gebruikers wilt wijzigen. Als je dit “frontaal” aanpakt (bijvoorbeeld gewoon ALTER TABLE
uitvoert zonder voorbereiding), kan de tabel voor een aanzienlijke tijd worden vergrendeld – en je gebruikers zouden zonder service achterblijven.
Om dergelijke hoofdpijn te voorkomen, volg twee regels:
- Voer migraties uit op een manier die de tabel niet vergrendelt (of minimaliseert de vergrendeling ten minste).
- Als je een kolomtype moet wijzigen, is het vaak gemakkelijker om eerst een nieuwe kolom met het juiste type te maken en daarna de oude te verwijderen.
Een ander probleem: versiebeheer en terugdraaien
Soms moet je een migratie terugdraaien.
Dit handmatig doen — de productie-database in gaan en met gegevens rommelen — is niet alleen riskant, maar ook waarschijnlijk onmogelijk als je geen directe toegang hebt. Dan komen speciale migratietools goed van pas. Ze stellen je in staat om wijzigingen op een nette manier toe te passen en indien nodig terug te draaien.
Hoe lossen we het op? Gebruik de juiste tools
Elke taal en ecosysteem heeft zijn eigen migratietools:
- Voor Java zijn Liquibase of Flyway gebruikelijk.
- Voor Go is een populaire keuze goose (deze zullen we hier bekijken).
- Enzovoort.
Goose: wat het is en waarom het nuttig is
Goose is een lichte Go-hulpprogramma dat je helpt om migraties automatisch te beheren. Het biedt:
- Eenvoud. Minimale afhankelijkheden en een transparante bestandsstructuur voor migraties.
- Veelzijdigheid. Ondersteunt verschillende DB-drivers (PostgreSQL, MySQL, SQLite, enz.).
- Flexibiliteit. Schrijf migraties in SQL of Go-code.
Het installeren van Goose
go install github.com/pressly/goose/v3/cmd/goose@latest
Hoe Het Werkt: Migratiestructuur
Standaard zoekt Goose naar migratiebestanden in db/migraties
. Elke migratie volgt dit formaat:
NNN_migration_name.(sql|go)
NNN
is het migratienummer (bijv.001
,002
, enz.).- Daarna kun je een beschrijvende naam hebben, bijvoorbeeld
init_schema
. - De extensie kan
.sql
of.go
zijn.
Voorbeeld van een SQL-migratie
Bestand: 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;
Ons Eerste Voorbeeld
Een Kolomtype Wijzigen (String → Int)
Stel dat we een tabel gebruikers
hebben met een kolom leeftijd
van het type VARCHAR(255)
. Nu willen we dit wijzigen naar INTEGER
. Dit is hoe de migratie eruit zou kunnen zien (bestand 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);
Wat er hier gebeurt:
-
Up migratie
- We wijzigen de kolom
leeftijd
naarINTEGER
. De clausuleUSING (leeftijd::INTEGER)
vertelt PostgreSQL hoe bestaande gegevens naar het nieuwe type moeten worden geconverteerd. - Merk op dat deze migratie zal mislukken als er gegevens in
leeftijd
staan die niet numeriek zijn. In dat geval heb je een complexere strategie nodig (zie hieronder).
- We wijzigen de kolom
-
Teruggaande migratie
- Als we terugdraaien, stellen we
age
weer in opVARCHAR(255)
. - We gebruiken opnieuw
USING (age::TEXT)
om vanINTEGER
terug naar tekst te converteren.
- Als we terugdraaien, stellen we
De Tweede en Complexe Gevallen: Multi-Stap Migraties
Als de kolom age
rommelige gegevens kan bevatten (niet alleen nummers), is het veiliger om dit in verschillende stappen te doen:
- Voeg een nieuwe kolom toe (
age_int
) van het typeINTEGER
. - Kopieer geldige gegevens naar de nieuwe kolom, en behandel of verwijder ongeldige invoer.
- Verwijder de oude kolom.
-- +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;
Om een juiste terugrol mogelijk te maken, spiegelt de Terug
sectie gewoon de acties in omgekeerde volgorde.
Automatisering is de Sleutel
Om tijd te besparen, is het echt handig om migratiecommando’s toe te voegen aan een Makefile (of een ander build-systeem). Hieronder staat een voorbeeld Makefile met de belangrijkste Goose-commando’s voor PostgreSQL.
Laten we aannemen:
- De DSN voor de database is
postgres://user:password@localhost:5432/dbname?sslmode=disable
. - Migratiebestanden bevinden zich 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
Hoe te gebruiken?
1. Maak een nieuwe migratie (SQL-bestand). Dit genereert een bestand db/migrations/002_add_orders_table.sql
.
make new-migration NAME=add_orders_table
2. Pas alle migraties toe. Goose zal een schema_migrations
tabel in je database aanmaken (als deze nog niet bestaat) en nieuwe migraties in oplopende volgorde toepassen.
make migrate-up
3. Rol de laatste migratie terug. Rol gewoon de laatste terug.
make migrate-down
4. Rol alle migraties terug (wees voorzichtig in productie). Volledige reset.
make migrate-reset
5. Controleer de migratiestatus.
make migrate-status
Voorbeeld van uitvoer:
$ 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
Samenvatting
Door gebruik te maken van migratietools en een Makefile, kunnen we:
- Directe toegang tot de productie-database beperken, waardoor wijzigingen alleen via migraties worden aangebracht.
- Eenvoudig databaseversies bijhouden en ze terugdraaien als er iets misgaat.
- Een enkele, consistente geschiedenis van databasewijzigingen behouden.
- “Vlotte” migraties uitvoeren die een draaiende productieomgeving in een microserviceswereld niet zullen verstoren.
- Extra validatie verkrijgen — elke wijziging zal door een PR- en codebeoordelingsproces gaan (ervan uitgaande dat je die instellingen hebt ingevoerd).
Een ander voordeel is dat het gemakkelijk is om al deze commando’s in je CI/CD-pijplijn te integreren. En onthoud — veiligheid boven alles.
Bijvoorbeeld:
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
Conclusie en tips
De belangrijkste ideeën zijn zo simpel:
- Houd je migraties klein en frequent. Ze zijn gemakkelijker te beoordelen, te testen en indien nodig terug te draaien.
- Gebruik dezelfde tool in alle omgevingen zodat dev, stage en prod gesynchroniseerd zijn.
- Integreer migraties in CI/CD zodat je niet afhankelijk bent van één persoon die ze handmatig uitvoert.
Op deze manier heb je een betrouwbaar en gecontroleerd proces voor het wijzigen van je databasestructuur – een dat de productie niet verstoort en je in staat stelt snel te reageren als er iets misgaat.
Veel succes met je migraties!
Bedankt voor het lezen!
Source:
https://dzone.com/articles/goose-as-crucial-tool-for-your-service