🔝 Retour au Sommaire
Les migrations de base de données (database migrations) sont un système de gestion des modifications du schéma de votre base de données PostgreSQL au fil du temps. Pensez-y comme un système de contrôle de version (Git) pour votre base de données.
Imaginez que votre base de données PostgreSQL soit un immeuble en construction :
- Sans migrations : Vous modifiez l'immeuble au hasard, personne ne sait ce qui a été fait, quand, ni par qui
- Avec migrations : Chaque modification est documentée dans un carnet (migration file), numérotée, datée, et peut être rejouée ou annulée
Les migrations permettent de tracer l'évolution de votre schéma de base de données, de la synchroniser entre plusieurs environnements (développement, test, production), et de collaborer en équipe sans chaos.
Vous développez une application avec PostgreSQL :
Jour 1 - Développeur A :
CREATE TABLE users (
id SERIAL PRIMARY KEY,
name VARCHAR(100)
);Jour 5 - Développeur B :
-- Ajoute une colonne email
ALTER TABLE users ADD COLUMN email VARCHAR(255);Jour 10 - Développeur C :
-- Renomme la colonne name en full_name
ALTER TABLE users RENAME COLUMN name TO full_name;Base de données locale du Développeur A :
users(id, name, email)
Base de données locale du Développeur B :
users(id, full_name, email)
Base de données de production :
users(id, name) ← Pas à jour !
→ Le code de l'application ne fonctionne pas partout
Si vous perdez votre base de données de production, comment la recréer dans le bon état ? Vous avez lancé des dizaines de requêtes SQL au fil des mois, impossible de se souvenir de toutes.
Quand plusieurs développeurs modifient le schéma en parallèle, les conflits sont inévitables et difficiles à résoudre.
Lors du déploiement d'une nouvelle version de l'application, vous devez vous souvenir manuellement des modifications SQL à appliquer sur la production. Oubliez une modification et l'application plante.
Si une mise à jour de production échoue, comment revenir en arrière ? Sans historique structuré, c'est très risqué.
Les migrations transforment les modifications de schéma en fichiers de migration versionnés :
Historique des migrations :
├── 001_create_users_table.sql
├── 002_add_email_to_users.sql
├── 003_rename_name_to_full_name.sql
├── 004_create_orders_table.sql
└── 005_add_index_on_email.sql
Chaque fichier :
✅ Est numéroté (ordre d'exécution)
✅ Décrit une modification atomique
✅ Est versionné dans Git
✅ Peut être rejoué sur n'importe quelle base
1. Développement local :
Développeur crée → 006_add_phone_to_users.sql
Exécute la migration localement
Commit le fichier dans Git
2. Autres développeurs :
Pull le code depuis Git
Exécutent automatiquement la nouvelle migration
Leurs bases locales sont synchronisées
3. Tests / Staging :
Déploiement automatique
Les migrations s'appliquent automatiquement
Base de test identique au développement
4. Production :
Déploiement
Les migrations s'appliquent automatiquement
Traçabilité complète de ce qui a été appliqué
Les outils de migration créent une table de suivi dans PostgreSQL :
-- Table créée automatiquement par l'outil de migration
CREATE TABLE schema_migrations (
version VARCHAR(255) PRIMARY KEY,
description TEXT,
applied_at TIMESTAMP DEFAULT NOW(),
execution_time_ms INTEGER,
success BOOLEAN
);Exemple de contenu :
version | description | applied_at | success
--------|--------------------------|---------------------|--------
001 | create_users_table | 2025-01-15 10:00:00 | true
002 | add_email_to_users | 2025-01-20 14:30:00 | true
003 | rename_name_to_full_name | 2025-01-25 09:15:00 | true
004 | create_orders_table | 2025-02-01 11:45:00 | true
Grâce à cette table, l'outil sait :
- Quelles migrations ont été appliquées
- Quelles migrations restent à appliquer
- Dans quel ordre les appliquer
Il existe de nombreux outils de migration, mais trois sont particulièrement populaires dans l'écosystème PostgreSQL :
| Critère | Flyway | Liquibase | Alembic |
|---|---|---|---|
| Langage principal | Java | Java | Python |
| Format migrations | SQL (+ Java) | SQL, XML, JSON, YAML | Python (+ SQL) |
| Courbe apprentissage | Facile | Moyenne | Moyenne |
| Écosystème | JVM (Java, Kotlin) | JVM + autres | Python (Django, Flask) |
| Open Source | ✅ Oui (+ version Pro) | ✅ Oui (+ version Pro) | ✅ Oui |
| Type de migrations | Versionnées, Repeatable | Versionnées, ChangeSet | Versionnées (Alembic) |
| Rollback | 🔶 Limité (version gratuite) | ✅ Complet | ✅ Complet |
| CI/CD | ✅ Excellent | ✅ Excellent | ✅ Excellent |
| PostgreSQL | ✅ Support complet | ✅ Support complet | ✅ Support complet |
Flyway est conçu pour être simple et direct : vous écrivez du SQL pur, vous numérotez vos fichiers, Flyway les exécute dans l'ordre. Pas de format propriétaire, pas de complexité inutile.
Slogan officieux : "Migrations made easy"
Développé en Java, Flyway est particulièrement populaire dans l'écosystème Java/Spring Boot, mais fonctionne avec n'importe quelle stack via sa CLI (Command Line Interface).
Flyway utilise une convention de nommage stricte :
Format : V{version}__{description}.sql
Exemples :
V1__create_users_table.sql
V2__add_email_to_users.sql
V3__create_orders_table.sql
V4__add_index_on_email.sql
V2.1__add_phone_to_users.sql (version intermédiaire)
Règles :
- Commence par
V(Versioned migration) - Suivi d'un numéro de version (1, 2, 3, ou 1.1, 1.2, etc.)
- Deux underscores
__ - Description lisible (snake_case)
- Extension
.sql
Fichier : V1__create_users_table.sql
-- Flyway migration: Create users table
CREATE TABLE users (
id SERIAL PRIMARY KEY,
email VARCHAR(255) UNIQUE NOT NULL,
full_name VARCHAR(255) NOT NULL,
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- Créer un index pour optimiser les recherches
CREATE INDEX idx_users_email ON users(email);Fichier : V2__add_phone_to_users.sql
-- Flyway migration: Add phone number to users
ALTER TABLE users
ADD COLUMN phone VARCHAR(20);
-- Commentaire pour documentation
COMMENT ON COLUMN users.phone IS 'User phone number (optional)';flyway migrate
→ Applique toutes les migrations en attente
→ Exécute les fichiers V1, V2, V3... dans l'ordre
→ Met à jour la table flyway_schema_history
flyway info
→ Affiche l'état de toutes les migrations
→ Lesquelles sont appliquées, lesquelles sont en attente
flyway validate
→ Vérifie que les migrations appliquées n'ont pas été modifiées
→ Détecte les incohérences
flyway clean
→ ⚠️ Supprime TOUTES les tables (base vide)
→ Uniquement pour développement !
flyway repair
→ Corrige la table flyway_schema_history
→ Utile en cas de migration échouée
Flyway crée automatiquement la table flyway_schema_history :
CREATE TABLE flyway_schema_history (
installed_rank INT NOT NULL,
version VARCHAR(50),
description VARCHAR(200) NOT NULL,
type VARCHAR(20) NOT NULL,
script VARCHAR(1000) NOT NULL,
checksum INT,
installed_by VARCHAR(100) NOT NULL,
installed_on TIMESTAMP NOT NULL DEFAULT NOW(),
execution_time INT NOT NULL,
success BOOLEAN NOT NULL
);Cette table enregistre chaque migration exécutée avec son checksum (empreinte) pour détecter toute modification ultérieure.
Flyway propose aussi des migrations répétables qui s'exécutent à chaque fois qu'elles changent :
Format : R__{description}.sql
Exemples :
R__create_view_active_users.sql
R__create_function_calculate_total.sql
Cas d'usage :
- Vues (VIEW)
- Fonctions (FUNCTION)
- Procédures stockées
- Triggers
→ Ré-exécutées automatiquement si le fichier change
Exemple : R__create_view_active_users.sql
-- Repeatable migration: Vue des utilisateurs actifs
CREATE OR REPLACE VIEW active_users AS
SELECT id, email, full_name
FROM users
WHERE deleted_at IS NULL
ORDER BY created_at DESC; - ✅ Simplicité : SQL pur, pas de syntaxe propriétaire à apprendre
- ✅ Convention de nommage claire : On comprend immédiatement l'ordre et le contenu
- ✅ Rapide à mettre en place : Configuration minimale
- ✅ CLI puissante : Utilisable dans n'importe quel projet (Java, Node, Python, Go...)
- ✅ Intégration CI/CD : Facile à intégrer dans les pipelines
- ✅ Validation automatique : Détecte les modifications de migrations appliquées
- ❌ Rollback limité : Version gratuite ne supporte pas les rollbacks automatiques (version Pro payante requise)
- ❌ Pas de génération automatique : Vous devez écrire le SQL manuellement
- ❌ Migrations bidirectionnelles difficiles : Pas de concept "UP/DOWN" natif
- ❌ Version Pro payante : Fonctionnalités avancées (rollback, undo, dry-run) nécessitent une licence
- Applications Java/Spring Boot (intégration native)
- Projets préférant SQL pur sans abstraction
- Équipes voulant un outil simple et direct
- CI/CD automatisés nécessitant peu de configuration
Liquibase se veut flexible et agnostique : vous pouvez écrire vos migrations en SQL, XML, JSON ou YAML. Il offre aussi des capacités de rollback avancées et de génération automatique de migrations.
Slogan officieux : "Source control for your database"
Développé en Java comme Flyway, mais avec une approche plus abstraite et orientée métadonnées.
Liquibase utilise des ChangeSets (ensembles de modifications) organisés dans un fichier principal :
Structure typique :
├── db/
│ ├── changelog/
│ │ ├── db.changelog-master.xml (ou .yaml/.json)
│ │ ├── changes/
│ │ │ ├── 001-create-users-table.sql
│ │ │ ├── 002-add-email-to-users.sql
│ │ │ └── 003-create-orders-table.sql
Fichier : db.changelog-master.xml
<?xml version="1.0" encoding="UTF-8"?>
<databaseChangeLog
xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog
http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-latest.xsd">
<changeSet id="1" author="john">
<createTable tableName="users">
<column name="id" type="SERIAL">
<constraints primaryKey="true"/>
</column>
<column name="email" type="VARCHAR(255)">
<constraints nullable="false" unique="true"/>
</column>
<column name="full_name" type="VARCHAR(255)">
<constraints nullable="false"/>
</column>
</createTable>
</changeSet>
<changeSet id="2" author="jane">
<addColumn tableName="users">
<column name="phone" type="VARCHAR(20)"/>
</addColumn>
</changeSet>
</databaseChangeLog>Fichier : db.changelog-master.yaml
databaseChangeLog:
- changeSet:
id: 1
author: john
changes:
- createTable:
tableName: users
columns:
- column:
name: id
type: SERIAL
constraints:
primaryKey: true
- column:
name: email
type: VARCHAR(255)
constraints:
nullable: false
unique: true
- column:
name: full_name
type: VARCHAR(255)
constraints:
nullable: false
- changeSet:
id: 2
author: jane
changes:
- addColumn:
tableName: users
columns:
- column:
name: phone
type: VARCHAR(20)Fichier : db.changelog-master.json
{
"databaseChangeLog": [
{
"changeSet": {
"id": "1",
"author": "john",
"changes": [
{
"createTable": {
"tableName": "users",
"columns": [
{
"column": {
"name": "id",
"type": "SERIAL",
"constraints": {
"primaryKey": true
}
}
}
]
}
}
]
}
}
]
}Liquibase peut aussi utiliser du SQL pur avec des annotations spéciales :
Fichier : 001-create-users-table.sql
--liquibase formatted sql
--changeset john:1
CREATE TABLE users (
id SERIAL PRIMARY KEY,
email VARCHAR(255) UNIQUE NOT NULL,
full_name VARCHAR(255) NOT NULL
);
--rollback DROP TABLE users;
--changeset jane:2
ALTER TABLE users ADD COLUMN phone VARCHAR(20);
--rollback ALTER TABLE users DROP COLUMN phone;Note importante : La directive --rollback permet de définir comment annuler la migration !
Un ChangeSet est une unité atomique de modification :
ChangeSet :
- id: Identifiant unique (ex: "1", "create-users", "2024-01-15-001")
- author: Qui a créé ce changement
- changes: Liste des modifications SQL/DDL
- rollback: (optionnel) Comment annuler ce changement
- preconditions: (optionnel) Conditions pour exécuter
- context: (optionnel) Environnements cibles (dev, prod, etc.)
liquibase update
→ Applique tous les changeSets en attente
→ Équivalent de flyway migrate
liquibase rollback <tag>
→ Annule les changeSets jusqu'à un tag donné
→ ⭐ Fonctionnalité clé de Liquibase
liquibase rollbackCount <number>
→ Annule les N derniers changeSets
liquibase status
→ Affiche les changeSets en attente
→ Équivalent de flyway info
liquibase validate
→ Vérifie la cohérence des changeSets
liquibase generateChangeLog
→ 🚀 Génère automatiquement un changelog depuis une base existante
→ Très utile pour adopter Liquibase sur un projet existant
liquibase diff
→ Compare deux bases et génère les différences
→ Utile pour synchroniser dev ↔ prod
liquibase tag <tagName>
→ Crée un point de restauration nommé
Liquibase crée deux tables :
1. databasechangelog (historique des changeSets appliqués)
CREATE TABLE databasechangelog (
id VARCHAR(255) NOT NULL,
author VARCHAR(255) NOT NULL,
filename VARCHAR(255) NOT NULL,
dateexecuted TIMESTAMP NOT NULL,
orderexecuted INT NOT NULL,
exectype VARCHAR(10) NOT NULL,
md5sum VARCHAR(35),
description VARCHAR(255),
comments VARCHAR(255),
tag VARCHAR(255),
liquibase VARCHAR(20),
contexts VARCHAR(255),
labels VARCHAR(255),
deployment_id VARCHAR(10)
);2. databasechangeloglock (verrou pour éviter les exécutions concurrentes)
CREATE TABLE databasechangeloglock (
id INT NOT NULL PRIMARY KEY,
locked BOOLEAN NOT NULL,
lockgranted TIMESTAMP,
lockedby VARCHAR(255)
);- ✅ Rollback intégré : Annuler des migrations facilement (même en version gratuite)
- ✅ Multi-format : SQL, XML, YAML, JSON au choix
- ✅ Génération automatique :
generateChangeLogpour bases existantes - ✅ Diff entre bases : Détecter automatiquement les différences
- ✅ Préconditions : Exécuter un changeSet seulement si une condition est remplie
- ✅ Contextes : Appliquer certains changeSets seulement en dev, prod, etc.
- ✅ Agnostique : Support étendu de nombreuses bases de données
- ❌ Courbe d'apprentissage : Plus complexe que Flyway (syntaxe XML/YAML à apprendre)
- ❌ Verbosité : Les fichiers XML/YAML peuvent être longs
- ❌ Configuration : Plus de paramètres à configurer
- ❌ Abstraction : L'abstraction XML/YAML peut éloigner du SQL réel
- ❌ Debugging : Erreurs parfois difficiles à comprendre (surtout en XML)
- Projets nécessitant des rollbacks fréquents
- Migrations de bases de données existantes (generateChangeLog)
- Déploiements multi-environnements complexes (dev, test, staging, prod)
- Équipes préférant une abstraction au-dessus du SQL
- Besoins de synchronisation entre bases (diff)
Alembic est l'outil de migration standard de l'écosystème Python, particulièrement utilisé avec SQLAlchemy (l'ORM Python le plus populaire). Il combine flexibilité du Python avec SQL pur.
Slogan officieux : "A database migration tool for SQLAlchemy"
Développé par le créateur de SQLAlchemy (Mike Bayer), Alembic est le choix naturel pour les applications Python (Django, Flask, FastAPI).
Note : Django a son propre système de migrations intégré, mais Alembic peut être utilisé pour des besoins avancés.
Structure typique :
project/
├── alembic/
│ ├── versions/
│ │ ├── 001_create_users_table.py
│ │ ├── 002_add_email_to_users.py
│ │ └── 003_create_orders_table.py
│ ├── env.py (configuration Alembic)
│ └── script.py.mako (template de migration)
├── alembic.ini (configuration principale)
└── models.py (vos modèles SQLAlchemy)
Alembic utilise des fichiers Python avec deux fonctions : upgrade() et downgrade().
Fichier : 001_create_users_table.py
"""Create users table
Revision ID: 001
Revises:
Create Date: 2025-01-15 10:00:00
"""
from alembic import op
import sqlalchemy as sa
# Identifiants de révision
revision = '001'
down_revision = None
branch_labels = None
depends_on = None
def upgrade():
"""Applique la migration (UP)"""
op.create_table(
'users',
sa.Column('id', sa.Integer(), primary_key=True),
sa.Column('email', sa.String(255), unique=True, nullable=False),
sa.Column('full_name', sa.String(255), nullable=False),
sa.Column('created_at', sa.TIMESTAMP(timezone=True),
server_default=sa.text('NOW()'))
)
# Créer un index
op.create_index('idx_users_email', 'users', ['email'])
def downgrade():
"""Annule la migration (DOWN)"""
op.drop_index('idx_users_email', 'users')
op.drop_table('users')Fichier : 002_add_phone_to_users.py
"""Add phone to users
Revision ID: 002
Revises: 001
Create Date: 2025-01-20 14:30:00
"""
from alembic import op
import sqlalchemy as sa
revision = '002'
down_revision = '001' # Dépend de la migration 001
def upgrade():
"""Ajoute la colonne phone"""
op.add_column('users',
sa.Column('phone', sa.String(20), nullable=True))
def downgrade():
"""Supprime la colonne phone"""
op.drop_column('users', 'phone')Vous pouvez aussi écrire du SQL pur avec op.execute() :
def upgrade():
"""Migration avec SQL brut"""
op.execute("""
CREATE TABLE orders (
id SERIAL PRIMARY KEY,
user_id INTEGER REFERENCES users(id),
total NUMERIC(10, 2) NOT NULL,
created_at TIMESTAMPTZ DEFAULT NOW()
)
""")
# Index avec SQL pur
op.execute("""
CREATE INDEX idx_orders_user_id ON orders(user_id)
""")
def downgrade():
"""Rollback avec SQL brut"""
op.execute("DROP TABLE orders")alembic init alembic
→ Initialise Alembic dans le projet
→ Crée la structure de dossiers
alembic revision -m "Create users table"
→ Crée un nouveau fichier de migration vide
→ Vous devez écrire upgrade() et downgrade()
alembic revision --autogenerate -m "Add phone column"
→ 🚀 Génère automatiquement la migration en comparant les modèles SQLAlchemy
→ Très puissant pour éviter d'écrire le SQL manuellement
alembic upgrade head
→ Applique toutes les migrations en attente
→ Équivalent de flyway migrate / liquibase update
alembic downgrade -1
→ Annule la dernière migration
→ Exécute la fonction downgrade()
alembic downgrade <revision>
→ Annule jusqu'à une révision donnée
alembic current
→ Affiche la révision actuelle de la base
alembic history
→ Affiche l'historique de toutes les migrations
alembic show <revision>
→ Affiche les détails d'une migration spécifique
alembic stamp head
→ Marque la base comme étant à jour sans exécuter les migrations
→ Utile lors de l'adoption d'Alembic sur une base existante
La killer feature d'Alembic : comparer vos modèles SQLAlchemy avec la base actuelle et générer automatiquement les migrations.
Exemple de workflow :
1. Vous modifiez votre modèle SQLAlchemy :
# models.py
class User(Base):
__tablename__ = 'users'
id = Column(Integer, primary_key=True)
email = Column(String(255), unique=True, nullable=False)
full_name = Column(String(255), nullable=False)
phone = Column(String(20)) # ← Nouvelle colonne ajoutée
created_at = Column(TIMESTAMP(timezone=True), server_default=func.now())2. Vous lancez la génération automatique :
alembic revision --autogenerate -m "Add phone to users"3. Alembic génère automatiquement :
def upgrade():
op.add_column('users', sa.Column('phone', sa.String(20), nullable=True))
def downgrade():
op.drop_column('users', 'phone')4. Vous vérifiez et appliquez :
alembic upgrade headAlembic crée la table alembic_version :
CREATE TABLE alembic_version (
version_num VARCHAR(32) NOT NULL PRIMARY KEY
);Contrairement à Flyway/Liquibase, cette table ne stocke que la version actuelle, pas l'historique complet. L'historique est dans les fichiers de migration.
- ✅ Intégration SQLAlchemy : Génération automatique depuis les modèles ORM
- ✅ Rollback natif : Fonctions upgrade()/downgrade() obligatoires
- ✅ Flexibilité Python : Logique complexe possible dans les migrations (loops, conditions, etc.)
- ✅ SQL brut supporté : Pas obligé d'utiliser l'abstraction si vous préférez SQL pur
- ✅ Branching : Support de branches de migrations parallèles (avancé)
- ✅ Écosystème Python : Intégration naturelle avec Flask, FastAPI, Django (via extension)
- ❌ Python uniquement : Pas utilisable dans des projets non-Python
- ❌ Courbe d'apprentissage : API Alembic + SQLAlchemy à apprendre
- ❌ Autogenerate imparfait : Ne détecte pas tout, nécessite vérification manuelle
- ❌ Moins populaire hors Python : Flyway et Liquibase ont des communautés plus larges
- ❌ Configuration initiale : Plus complexe que Flyway
- Applications Python (Flask, FastAPI, Django)
- Projets utilisant SQLAlchemy comme ORM
- Besoin de génération automatique de migrations
- Migrations nécessitant de la logique Python complexe
- Équipes Python confortables avec l'écosystème
Toutes les migrations ont une direction "vers l'avant" (upgrade, migrate, up) qui applique les modifications.
État initial :
Table users (id, name)
Migration 001 (UP) :
ALTER TABLE users ADD COLUMN email VARCHAR(255)
État final :
Table users (id, name, email)
La capacité d'annuler une migration pour revenir à l'état précédent.
État actuel :
Table users (id, name, email)
Migration 001 (DOWN) :
ALTER TABLE users DROP COLUMN email
État final :
Table users (id, name)
Comparaison :
- Flyway (gratuit) : ❌ Pas de rollback automatique (version Pro payante)
- Liquibase : ✅ Rollback complet (même version gratuite)
- Alembic : ✅ Rollback via downgrade()
Une migration doit être idempotente : si elle est exécutée plusieurs fois, elle ne doit pas causer d'erreur.
Exemple non-idempotent :
-- ❌ Erreur si la table existe déjà
CREATE TABLE users (id SERIAL PRIMARY KEY);Exemple idempotent :
-- ✅ Ne fait rien si la table existe
CREATE TABLE IF NOT EXISTS users (id SERIAL PRIMARY KEY);Les migrations doivent s'exécuter dans une transaction PostgreSQL pour garantir l'atomicité.
BEGIN;
-- Migration 001
CREATE TABLE users (...);
CREATE INDEX idx_users_email ON users(email);
-- Si une erreur survient ici, tout est annulé
COMMIT;
Si une partie de la migration échoue, PostgreSQL annule tout (ROLLBACK automatique).
Les outils calculent une empreinte (hash) de chaque migration pour détecter toute modification après application.
Migration 001_create_users.sql appliquée :
Checksum: a3f5b2c...
Quelqu'un modifie 001_create_users.sql :
Nouveau checksum: e7d9a1f...
Outil détecte : ⚠️ ERREUR - Migration modifiée après application !
Règle d'or : Ne JAMAIS modifier une migration déjà appliquée en production. Créez une nouvelle migration.
Pour adopter un outil de migration sur une base existante, il faut définir un point de départ (baseline).
Base de données existante avec 20 tables :
Option 1 - Baseline :
alembic stamp head
→ Marque la base comme étant à la version actuelle
→ Les futures migrations s'appliquent à partir de là
Option 2 - Générer le changelog initial :
liquibase generateChangeLog
→ Crée une migration représentant l'état actuel
→ Cette migration devient la v1
Utilisez des noms de migration descriptifs et datés :
✅ Bon :
V001__create_users_table.sql
V002__add_email_index_to_users.sql
2025_01_15_001_create_users_table.py
❌ Mauvais :
migration1.sql
fix.sql
update.sql
Chaque migration doit faire une seule chose logique :
✅ Bon :
001_create_users_table.sql
002_create_orders_table.sql
003_add_foreign_key_orders_to_users.sql
❌ Mauvais :
001_create_all_tables_and_indexes_and_data.sql
→ Difficile à déboguer si ça échoue
Si vous implémentez un rollback (Liquibase, Alembic), testez-le avant de déployer :
# Appliquer la migration
alembic upgrade +1
# Tester le rollback
alembic downgrade -1
# Ré-appliquer
alembic upgrade +1Séparez les migrations qui modifient le schéma de celles qui modifient les données :
Migration de schéma :
-- 010_add_status_to_orders.sql
ALTER TABLE orders ADD COLUMN status VARCHAR(20) DEFAULT 'pending';Migration de données :
-- 011_populate_order_status.sql
UPDATE orders SET status = 'completed' WHERE completed_at IS NOT NULL;
UPDATE orders SET status = 'cancelled' WHERE cancelled_at IS NOT NULL; Pourquoi séparer ? Les migrations de données peuvent être lentes sur de grandes tables et nécessitent une stratégie différente (batch processing, zero-downtime, etc.).
Certaines migrations sont destructives et ne peuvent pas être annulées sans perte de données :
-- ⚠️ Migration destructive
ALTER TABLE users DROP COLUMN phone;
-- Rollback : Impossible de récupérer les données supprimées !Stratégie safe :
Migration 1 : Ajouter nouvelle colonne phone_number
Migration 2 : Copier les données de phone vers phone_number
Migration 3 : Déployer le code utilisant phone_number
Migration 4 : (Plus tard) Supprimer l'ancienne colonne phone
Les fichiers de migration doivent être versionnés dans votre dépôt Git :
Git repository :
├── src/
├── tests/
├── migrations/ ← Versionné dans Git
│ ├── V001__create_users.sql
│ ├── V002__add_email_index.sql
└── README.md
→ Toute l'équipe partage les mêmes migrations
→ Historique complet dans Git
Règle absolue : Une fois qu'une migration est appliquée en production, elle est immuable.
❌ Jamais ça :
Modifier V001__create_users.sql après l'avoir déployée
✅ Faire ça :
Créer V005__fix_users_table.sql pour corriger
Votre stratégie de migration doit fonctionner sur tous les environnements :
Développement local :
→ Migrations appliquées au fur et à mesure
→ Base peut être détruite/recréée facilement
Staging / Test :
→ Copie de production (anonymisée)
→ Migrations testées avant production
Production :
→ Migrations appliquées avec prudence
→ Backup avant migration
→ Plan de rollback prêt
Intégrez les migrations dans votre pipeline de déploiement :
Pipeline CI/CD :
1. Tests unitaires
2. Build de l'application
3. ✅ Vérifier les migrations (validate)
4. Déployer l'application
5. ✅ Appliquer les migrations (migrate)
6. Tests d'intégration
7. Déploiement en production
Ajoutez des commentaires dans vos migrations pour expliquer pourquoi :
-- Migration: Add customer_tier column for loyalty program
-- Context: Marketing team requested tiering system for customers
-- Ticket: JIRA-1234
-- Author: John Doe
-- Date: 2025-01-15
ALTER TABLE customers
ADD COLUMN customer_tier VARCHAR(20) DEFAULT 'bronze'
CHECK (customer_tier IN ('bronze', 'silver', 'gold', 'platinum'));
COMMENT ON COLUMN customers.customer_tier IS
'Customer loyalty tier: bronze (default), silver, gold, platinum.
Based on total purchase amount in last 12 months.';Problème : Vous devez renommer une colonne sans arrêter l'application.
Stratégie en plusieurs étapes :
Étape 1 (Migration 010) : Ajouter nouvelle colonne
ALTER TABLE users ADD COLUMN full_name VARCHAR(255);
Étape 2 (Migration 011) : Copier les données
UPDATE users SET full_name = name WHERE full_name IS NULL;
Étape 3 (Déploiement code v2) :
Application lit/écrit dans les deux colonnes (name et full_name)
Étape 4 (Migration 012) : Rendre full_name NOT NULL
ALTER TABLE users ALTER COLUMN full_name SET NOT NULL;
Étape 5 (Déploiement code v3) :
Application utilise uniquement full_name
Étape 6 (Migration 013 - Plus tard) : Supprimer ancienne colonne
ALTER TABLE users DROP COLUMN name;
Avantage : Aucune interruption de service.
Problème : Vous devez ajouter une colonne NOT NULL à une table de 100 millions de lignes.
Mauvaise approche :
-- ❌ Bloque la table pendant des heures
ALTER TABLE orders ADD COLUMN processed BOOLEAN NOT NULL DEFAULT false;Bonne approche :
-- Migration 020: Ajouter colonne nullable
ALTER TABLE orders ADD COLUMN processed BOOLEAN;
-- Migration 021: Remplir par batch (en plusieurs petits UPDATE)
-- Peut être fait via un script Python/Job séparé
UPDATE orders SET processed = false
WHERE id BETWEEN 1 AND 1000000 AND processed IS NULL;
-- Répéter pour tous les ranges...
-- Migration 022: Rendre NOT NULL une fois rempli
ALTER TABLE orders ALTER COLUMN processed SET DEFAULT false;
ALTER TABLE orders ALTER COLUMN processed SET NOT NULL; Problème : Deux développeurs créent des migrations en parallèle.
Développeur A crée :
V010__add_phone_to_users.sql
Développeur B crée (en même temps) :
V010__add_address_to_users.sql ← Conflit !
Solution avec Liquibase :
# Utiliser des IDs uniques (timestamp + description)
changeSet:
id: 2025-01-15-10h30-add-phone
author: dev-a
changeSet:
id: 2025-01-15-11h00-add-address
author: dev-bSolution avec Alembic :
# Alembic génère des IDs uniques automatiquement
alembic revision -m "Add phone"
# → Génère : 0a3f2b1c_add_phone.py
alembic revision -m "Add address"
# → Génère : 7d9e4f5a_add_address.py
# Fusion des branches :
alembic merge heads -m "Merge phone and address"Problème : Vous voulez annuler seulement certaines migrations, pas toutes.
Avec Liquibase (tags) :
# Créer un tag avant déploiement risqué
liquibase tag "before-major-refactor"
# Déployer plusieurs migrations
liquibase update
# Si problème, revenir au tag
liquibase rollback "before-major-refactor"Avec Alembic :
# Descendre à une révision spécifique
alembic downgrade a3f2b1c
# Ou annuler les 3 dernières migrations
alembic downgrade -3- ✅ Vous voulez la simplicité avant tout
- ✅ Vous écrivez du SQL pur et ne voulez pas d'abstraction
- ✅ Vous êtes dans l'écosystème Java/Spring Boot
- ✅ Vous n'avez pas besoin de rollbacks fréquents
- ✅ Vous voulez une intégration CI/CD facile
- ✅ Vous avez un budget limité (version gratuite suffit)
- ✅ Vous avez besoin de rollbacks réguliers
- ✅ Vous voulez générer automatiquement des migrations depuis une base existante
- ✅ Vous gérez des environnements multiples complexes (dev/test/staging/prod)
- ✅ Vous voulez comparer et synchroniser plusieurs bases (diff)
- ✅ Vous préférez XML/YAML au SQL pur
- ✅ Vous avez besoin de préconditions et contextes avancés
- ✅ Vous développez en Python (Flask, FastAPI, SQLAlchemy)
- ✅ Vous utilisez un ORM et voulez générer les migrations automatiquement
- ✅ Vous voulez écrire de la logique Python dans les migrations
- ✅ Vous avez besoin de rollbacks avec une approche up/down claire
- ✅ Votre équipe est confortable avec Python
- ✅ Vous voulez du SQL brut avec la flexibilité Python
| Critère | Flyway | Liquibase | Alembic |
|---|---|---|---|
| Simplicité | ⭐⭐⭐ | ⭐⭐ | ⭐⭐ |
| Rollback automatique | ❌ (Pro) | ✅ | ✅ |
| Génération automatique | ❌ | ✅ | ✅ |
| SQL pur | ✅ | ✅ | ✅ |
| Multi-langage | ✅ | ✅ | ❌ (Python) |
| Courbe d'apprentissage | Facile | Moyenne | Moyenne |
| Communauté | Large | Large | Python |
| Open Source gratuit complet | 🔶 | ✅ | ✅ |
Les migrations de base de données sont essentielles pour maintenir une base PostgreSQL évolutive, traçable et synchronisée entre environnements. Les trois outils présentés (Flyway, Liquibase, Alembic) offrent des approches différentes mais convergent vers le même objectif : gérer le schéma comme du code.
- Les migrations versionnent votre schéma comme Git versionne votre code
- Chaque outil a ses forces : simplicité (Flyway), flexibilité (Liquibase), intégration Python (Alembic)
- Les bonnes pratiques sont universelles : atomicité, idempotence, tests, documentation
- Ne jamais modifier une migration appliquée : créez une nouvelle migration
- Automatisez dans votre CI/CD pour éviter les erreurs humaines
- Testez les rollbacks même si vous ne les utilisez jamais en production
Après avoir maîtrisé les migrations, explorez :
- 20.4.3. Schema versioning : Stratégies de versionnement avancées
- 20.4.4. Zero-downtime deployments : Déployer sans interruption
- 19.3. Migrations majeures : Migrer de PostgreSQL 17 vers 18
Les migrations sont la fondation d'un projet PostgreSQL professionnel. Investir du temps dans leur mise en place vous épargnera d'innombrables heures de debugging et de conflits en production.
Ressources complémentaires :
- Documentation Flyway : https://flywaydb.org/documentation/
- Documentation Liquibase : https://docs.liquibase.com/
- Documentation Alembic : https://alembic.sqlalchemy.org/
- Article Martin Fowler : "Evolutionary Database Design"