Renommer une colonne dans PostgreSQL ne met pas à jour le programme qui lit ses changements. Le connecteur peut continuer à émettre des événements alors que le consommateur cherche encore l’ancien nom. Une évolution de schéma CDC se prépare donc des deux côtés : dans la base et dans les lecteurs du flux.
Prenons montant_centimes, renommé en total_centimes, sans changer l’unité ni la définition métier. Le problème paraît limité à un nom. Il touche pourtant les nouvelles écritures, l’état déjà reconstruit chez le consommateur et les événements anciens qu’il peut devoir relire.
L’atelier téléchargeable oppose un renommage direct à une transition avec deux colonnes sur des commandes fictives. Il utilise PostgreSQL, Debezium Server et Redis Streams, sans Kafka ni Schema Registry. Les captures permettent de distinguer ce qui s’est produit dans la chaîne locale et les contrôles à ajouter dans votre environnement.

Trois objets évoluent à des moments différents
Le schéma SQL décrit les colonnes de la source. L’événement de changement décrit une ligne et son contexte. Le consommateur transforme cet événement en une table ou une autre vue métier. C’est cette dernière étape qui doit préserver le montant attendu.
La documentation du connecteur PostgreSQL précise qu’il ne transmet pas les changements DDL comme des événements dédiés aux consommateurs. Une modification de structure peut ensuite se refléter dans le schéma d’un événement de ligne. Recevoir le nouveau nom dans un événement ne migre pas le code du lecteur ni les anciennes lignes de sa projection. Connecteur PostgreSQL Debezium 3.7.
PostgreSQL distingue aussi la réplication des données de celle du schéma : son mécanisme de réplication logique ne réplique pas les commandes DDL. Une cible PostgreSQL abonnée peut demander sa propre migration. Notre atelier utilise une projection Python sur Redis, pas une subscription PostgreSQL ; les deux chaînes ont leurs propres comportements. Restrictions PostgreSQL 16.
Avant le changement, listez les applications qui écrivent la colonne, les consommateurs qui la lisent, les traitements différés et les procédures de restauration. Un ancien UPDATE montant_centimes peut casser directement dans la base après le renommage. Le test d’un lecteur CDC ne couvre pas à lui seul ces écritures applicatives.
Le renommage direct expose la dépendance du lecteur
Le premier chemin garde un consommateur qui lit explicitement after["montant_centimes"]. Après le renommage SQL, une vraie modification de ligne déclenche un nouvel événement. Le lecteur historique ne retrouve plus la clé attendue.
Ce défaut dépend de ce consommateur. Une application disposant déjà d’un adaptateur de versions pourrait traiter le nouveau nom. Le test sert à exposer une dépendance concrète, sans conclure que tout renommage échoue dans toutes les chaînes.
La seconde branche conserve d’abord les deux noms. Le traitement de lecture peut continuer à alimenter un montant normalisé, quel que soit le nom présent dans l’événement. La référence de la recette garde les étapes et les résultats observés.
Ajouter, remplir, basculer, retirer
1. Ajouter le nouveau champ sans enlever l’ancien
Ajoutez total_centimes nullable. L’application continue à trouver montant_centimes. Préparez les lecteurs à trois formes : ancien champ seul, coexistence des deux, nouveau champ seul. Le type et l’unité restent identiques dans cet atelier ; une conversion de devise ou un nouveau calcul serait une autre migration.
L’ajout de colonne ne remplit pas automatiquement l’état existant de votre consommateur. Même une valeur par défaut visible dans une requête SQL ne signifie pas que chaque ancienne ligne a produit un événement de mise à jour. PostgreSQL peut appliquer certains ajouts avec défaut sans réécrire les lignes physiques. Modification d’une table.
2. Faire écrire les deux champs avant de compléter l’historique
Déployez la double écriture sur les producteurs concernés, dans la même opération métier. Pour une nouvelle vente, les deux colonnes doivent porter le même nombre de centimes. Ensuite, complétez les lignes anciennes encore sans nouvelle valeur :
UPDATE commandes_progressives
SET total_centimes = montant_centimes
WHERE total_centimes IS NULL;
Ce backfill est une modification des lignes. Dans la chaîne de l’atelier, Debezium peut alors la transmettre au consommateur. À l’inverse, rejouer un vieux message qui ne contient que l’ancien champ ne lui ajoute pas une colonne. C’est votre règle de correspondance qui peut convertir ce message en montant normalisé.
Sur une table réelle, découpez et surveillez ce travail : lignes restantes, verrous, volume de journal et retard jusqu’à la cible. Le petit UPDATE de la recette ne démontre pas qu’une table volumineuse peut être reprise sans perturbation. N’effectuez pas de DDL concurrent à un snapshot incrémental PostgreSQL Debezium : cette combinaison n’est pas prise en charge dans la documentation 3.7. Snapshots incrémentaux.
3. Vérifier les valeurs pendant la coexistence
Le lecteur de transition choisit la nouvelle valeur lorsqu’elle est présente et non nulle. Il conserve zéro comme un montant valide. Si les deux champs portent des valeurs non nulles différentes, il signale le conflit au lieu de choisir silencieusement l’un des deux. La projection garde alors sa dernière valeur acceptée et une alerte : elle ne doit pas être présentée comme à jour tant que le conflit reste ouvert.
Une valeur absente et un montant nul ne sont pas interchangeables. Évitez un choix fondé sur la seule vérité booléenne, tel que nouveau or ancien : zéro est faux dans ce test. Le lecteur doit examiner la présence et la nullité, puis contrôler la cohérence des deux valeurs.
Comparez les identifiants et les montants de la projection à la source et à une référence de scénario indépendante. Un total seul peut masquer deux erreurs qui se compensent. Une file qui se vide ou un connecteur disponible ne répond pas à cette question métier.
4. Retirer l’ancien champ quand ses dépendances sont traitées
Avant la suppression, vérifiez que les producteurs n’écrivent plus l’ancien nom et que les lecteurs savent traiter les nouveaux événements. Le rattrapage doit être fini jusqu’à un point de contrôle identifié. Réexécutez aussi les jobs différés et les procédures de restauration concernés.
Le code qui accepte les anciens événements peut devoir rester après le retrait SQL. La rétention du flux, les sauvegardes et les reconstructions de cible déterminent pendant combien de temps ces événements restent utiles. Une période d’observation sans erreur ne prouve pas qu’ils ne seront jamais relus.
Un retour à l’ancienne version de l’application ne recrée pas une colonne supprimée. Préparez le retour arrière avant le retrait : schéma, remise en cohérence des valeurs, versions de lecteurs et position de reprise. La recette n’en fait pas une procédure de restauration de production.
Reproduire le cas dans un environnement isolé
L’archive contient Compose, la configuration Debezium, les tables fictives, le script Python et les captures de référence. Sauvegardez les rapports livrés avant un nouvel essai : le script les remplace. Prévoyez Docker avec Compose et Python 3.10 ou plus. Aucun compte cloud ni bibliothèque Python supplémentaire n’est nécessaire. Utilisez un dossier neuf.
unzip recette-schema.zip -d recette-schema
cd recette-schema/evolution-schema-cdc
docker compose up -d
python3 recette.py
La double écriture du laboratoire est réalisée par deux affectations dans une même instruction SQL. Le déploiement de plusieurs versions d’application et leurs écritures concurrentes restent à tester.
Le projet Compose est dédié à cet atelier et n’expose pas de port sur l’hôte. Il utilise des volumes propres et un compte de laboratoire. Ce choix isole les données fictives ; il ne constitue pas une configuration de production. Le script exige un état initial neuf.
L’exécution du 8 novembre 2026 utilisait Debezium Server 3.7.0.Final, PostgreSQL 16.15, Redis 7.2.16 et Python 3.14.7, sur ARM64. Ces versions ont été reproduites ; aucune comparaison de versions n’est revendiquée. Les tags PostgreSQL et Redis peuvent évoluer, les identifiants d’images observés figurent dans l’archive.
| Contrôle |
Observation locale |
| Renommage direct suivi d’une vraie modification de statut |
KeyError('montant_centimes') dans le lecteur historique |
| Nouveau champ nul avant backfill |
Le lecteur de transition reprend l’ancien montant ; le lecteur « nouveau champ seulement » refuse la valeur nulle |
| Nouveau montant égal à zéro après retrait de l’ancien |
Le lecteur de transition conserve 0 ; le choix booléen renvoie null |
| Deux montants de 11 000 et 12 000 pour le même identifiant |
SchemaConflict, puis correction capturée par CDC |
| État final progressif |
Identifiants 1, 2 et 3 : 11 000, 0 et 30 000 centimes, soit 410 EUR fictifs |
| Source, projection et référence indépendante |
Identiques par identifiant, montant et statut après correction |
La branche directe a produit trois événements de snapshot et une mise à jour ; la branche progressive, trois snapshots et huit mises à jour. Ces événements ne sont pas autant de commandes : la projection remplace l’état d’une même clé. Aucun nouvel événement de ligne n’a été observé après chacun des trois DDL seuls dans des fenêtres d’environ deux secondes. Ces fenêtres sont des observations locales, pas une garantie de délai.
Le conflit reste présent dans les captures après la correction de la source. Un rejeu strict s’arrête encore dessus. Le lecteur de démonstration peut le mettre en quarantaine avec une alerte conservée, puis appliquer le correctif ultérieur. Ce choix exige en production une politique durable de suivi des erreurs et de reprise. La recette ne l’implémente pas comme un service exploitable.
Vous pouvez examiner ce comportement sans Docker en rejouant les captures livrées :
python3 recette.py --replay-only
Cette commande vérifie aussi une contre-épreuve locale où deux montants sont permutés : le total reste identique, mais la comparaison exacte rejette la projection. Cette permutation est construite pour le contrôle ; elle ne provient pas d’un événement émis par Debezium. Rapport complet · Événements capturés.
Le rejeu des anciens événements est effectué localement à partir des captures. Il vérifie le comportement du lecteur, sans provoquer une panne du connecteur ni démontrer une garantie de livraison. La projection Python ne constitue pas un consommateur durable.
En cas d’échec, conservez le rapport et consultez docker compose logs debezium. Les délais d’attente du script sont des bornes de recette, pas des promesses de latence. Après sauvegarde des résultats, docker compose down --volumes supprime uniquement les données du projet de laboratoire nommé dans son fichier Compose. Ne lancez pas ce nettoyage sur un autre projet.
Un registre de schémas ne remplace pas ce contrôle
Si votre chaîne utilise Confluent Schema Registry, vérifiez le sens de compatibilité. BACKWARD porte sur un nouveau lecteur face à des données écrites avec un ancien schéma. Il ne garantit pas qu’un ancien lecteur accepte les nouvelles données. La variante transitive étend la comparaison aux versions antérieures ; les règles dépendent du format et des définitions des champs. Un contrôle de compatibilité ne prouve pas que le programme calcule encore le montant métier attendu. Aucun registre n’est testé ici. Compatibilité Confluent.
La décision de retrait tient donc dans des preuves distinctes : champs acceptés par les lecteurs, valeurs conservées dans la projection et capacité à relire l’historique encore nécessaire. Le guide de recette CDC couvre les suppressions et la reprise du connecteur ; le contrat de données aide à formaliser les dépendances de cette migration.
Debezium est distribué sous licence Apache 2.0. L’atelier ne souscrit aucun service managé. En production, chiffrez le stockage du flux, le backfill, la supervision et le temps de coexistence des deux formats. Les montants fictifs de la recette ne mesurent aucun gain financier. Dépôt Debezium et licence.