Bonjour, mon pote !
Aujourd’hui, parlons de ce que sont les migrations de base de données et pourquoi elles sont si importantes. Dans le monde d’aujourd’hui, il n’est pas surprenant que tout changement apporté à une base de données doive être effectué avec soin et selon un processus spécifique. Idéalement, ces étapes seraient intégrées dans notre pipeline CI/CD afin que tout fonctionne automatiquement.
Voici notre agenda :
- Quel est le problème ?
- Comment le résoudre ?
- Un exemple simple
- Un exemple plus complexe
- Recommandations
- Résultats
- Conclusion
Quel est le problème ?
Si votre équipe n’a jamais traité de migrations de base de données et que vous n’êtes pas tout à fait sûr de leur nécessité, clarifions cela. Si vous connaissez déjà les bases, n’hésitez pas à passer à la suite.
Défi principal
Lorsque nous apportons des changements « planifiés » et « fluides » à la base de données, nous devons maintenir la disponibilité du service et respecter les exigences SLA (afin que les utilisateurs ne subissent pas de temps d’arrêt ou de latence). Imaginez que vous souhaitiez changer le type d’une colonne dans une table contenant 5 millions d’utilisateurs. Si vous le faites « de front » (par exemple, en exécutant simplement ALTER TABLE
sans préparation), la table pourrait être verrouillée pendant un temps significatif — et vos utilisateurs seraient privés de service.
Pour éviter de tels maux de tête, suivez deux règles :
- Appliquez les migrations d’une manière qui ne verrouille pas la table (ou au moins minimise les verrouillages).
- Si vous avez besoin de changer le type d’une colonne, il est souvent plus facile de créer d’abord une nouvelle colonne avec le bon type, puis de supprimer l’ancienne par la suite.
Autre problème : contrôle de version et retours en arrière
Parfois, vous devez revenir en arrière sur une migration.
Le faire manuellement — en accédant à la base de données de production et en manipulant les données — est non seulement risqué, mais aussi probablement impossible si vous n’avez pas un accès direct. C’est là que les outils de migration dédiés sont utiles. Ils vous permettent d’appliquer proprement des changements et de les annuler si nécessaire.
Comment résoudre ce problème ? Utilisez les bons outils
Chaque langage et écosystème a ses propres outils de migration :
- Pour Java, Liquibase ou Flyway sont courants.
- Pour Go, un choix populaire est goose (celui que nous examinerons ici).
- Et ainsi de suite.
Goose : ce que c’est et pourquoi c’est utile
Goose est un utilitaire léger en Go qui vous aide à gérer les migrations automatiquement. Il offre :
- Simplicité. Des dépendances minimales et une structure de fichiers transparente pour les migrations.
- Polyvalence. Prise en charge de divers pilotes de base de données (PostgreSQL, MySQL, SQLite, etc.).
- Flexibilité. Écrivez des migrations en SQL ou en code Go.
Installation de Goose
go install github.com/pressly/goose/v3/cmd/goose@latest
Comment ça fonctionne : Structure de migration
Par défaut, Goose recherche les fichiers de migration dans db/migrations
. Chaque migration suit ce format :
NNN_migration_name.(sql|go)
NNN
est le numéro de migration (par exemple,001
,002
, etc.).- Ensuite, vous pouvez avoir un nom descriptif, par exemple
init_schema
. - L’extension peut être
.sql
ou.go
.
Exemple d’une migration SQL
Fichier : 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;
Notre premier exemple
Changement de type de colonne (String → Int)
Supposons que nous avons une table users
avec une colonne age
de type VARCHAR(255)
. Maintenant, nous voulons la modifier en INTEGER
. Voici à quoi ressemblerait la migration (fichier 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);
Ce qui se passe ici :
-
Migration vers le haut
- Nous changeons la colonne
age
enINTEGER
. La clauseUSING (age::INTEGER)
indique à PostgreSQL comment convertir les données existantes en nouveau type. - Notez que cette migration échouera s’il y a des données dans
age
qui ne sont pas numériques. Dans ce cas, vous aurez besoin d’une stratégie plus complexe (voir ci-dessous).
- Nous changeons la colonne
-
Migration descendante
- Si nous revenons en arrière, nous ramenons
age
àVARCHAR(255)
. - Nous utilisons à nouveau
USING (age::TEXT)
pour convertir deINTEGER
à texte.
- Si nous revenons en arrière, nous ramenons
Les cas deux et complexes : migrations multi-étapes
Si la colonne age
pourrait contenir des données désordonnées (pas seulement des nombres), il est plus sûr de le faire en plusieurs étapes :
- Ajouter une nouvelle colonne (
age_int
) de typeINTEGER
. - Copier les données valides dans la nouvelle colonne, en traitant ou en supprimant les entrées invalides.
- Supprimer l’ancienne colonne.
-- +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;
Pour permettre un retour en arrière correct, la section Down
reflète simplement les actions à l’envers.
L’automatisation est essentielle
Pour gagner du temps, il est vraiment pratique d’ajouter des commandes de migration à un Makefile (ou à tout autre système de construction). Voici un exemple de Makefile avec les principales commandes Goose pour PostgreSQL.
Supposons :
- Le DSN pour la base de données est
postgres://user:password@localhost:5432/dbname?sslmode=disable
. - Les fichiers de migration se trouvent dans
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
Comment l’utiliser?
1. Créez une nouvelle migration (fichier SQL). Cela génère un fichier db/migrations/002_add_orders_table.sql
.
make new-migration NAME=add_orders_table
2. Appliquez toutes les migrations. Goose créera une table schema_migrations
dans votre base de données (si elle n’existe pas déjà) et appliquera toutes les nouvelles migrations par ordre croissant.
make migrate-up
3. Annulez la dernière migration. Annulez simplement la dernière.
make migrate-down
4. Annulez toutes les migrations (utilisez avec prudence en production). Réinitialisation complète.
make migrate-reset
5. Vérifiez l’état des migrations.
make migrate-status
Exemple de sortie:
$ 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
Résumé
En utilisant des outils de migration et un Makefile, nous pouvons:
- Restreindre l’accès direct à la base de données de production, en apportant des modifications uniquement par le biais de migrations.
- Suivre facilement les versions de la base de données et les annuler en cas de problème.
- Maintenir un historique unique et cohérent des changements de base de données.
- Réaliser des migrations « fluides » qui ne perturberont pas un environnement de production en cours d’exécution dans un monde de microservices.
- Obtenir une validation supplémentaire – chaque changement passera par un processus de PR et de revue de code (en supposant que vous avez ces paramètres en place).
Un autre avantage est qu’il est facile d’intégrer toutes ces commandes dans votre pipeline CI/CD. Et n’oubliez pas – la sécurité avant tout.
Par exemple:
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
Conclusion et Conseils
Les idées principales sont si simples :
- Gardez vos migrations petites et fréquentes. Elles sont plus faciles à réviser, tester et revenir en arrière si nécessaire.
- Utilisez le même outil dans tous les environnements afin que le développement, la mise en scène et la production soient synchronisés.
- Intégrez les migrations dans CI/CD pour ne pas dépendre de la personne qui les exécute manuellement.
Ainsi, vous disposerez d’un processus fiable et contrôlé pour modifier la structure de votre base de données — un processus qui ne casse pas la production et vous permet de réagir rapidement en cas de problème.
Bonne chance avec vos migrations!
Merci de votre lecture!
Source:
https://dzone.com/articles/goose-as-crucial-tool-for-your-service