🎯 OBJECTIF
Comprendre comment :
🧠 MODÈLE MENTAL
Le replication slot est la bombe à retardement n°1 d'un déploiement Debezium. Il retient du WAL côté Postgres tant qu'il n'a pas confirmé sa lecture. Si Debezium ne confirme plus — parce qu'il est mort, parce que ses tables capturées sont inactives, ou parce qu'il a été supprimé sans cleanup — le disque Postgres se remplit. Sans monitoring, ce remplissage est invisible jusqu'à l'outage.
Le slot lag se mesure en SQL, s'alerte via Grafana, et se résout selon le mode de défaillance. Le heartbeat ne résout qu'un seul des quatre modes. Confondre les modes conduit à appliquer le mauvais remède.
Prérequis : kafka-connect-2-debezium-cdc (WAL, LSN, slot, publication).
Le lag implique 3 positions. Deux viennent du slot, la troisième est globale à l'instance.
| LSN | Signification | Qui le met à jour | Où stocké |
|---|---|---|---|
pg_current_wal_lsn() |
Où Postgres en est dans l'écriture du WAL maintenant. Avance en permanence. | Postgres (writes) | Fonction globale — pas dans le slot |
restart_lsn |
Position à partir de laquelle Postgres garantit de pouvoir redémarrer le slot. WAL avant ce LSN = recyclable. | Postgres (quand le consommateur progresse) | Colonne de pg_replication_slots |
confirmed_flush_lsn |
Position que Debezium a explicitement confirmé avoir flushée vers Kafka. | Debezium via StandbyStatusUpdate |
Colonne de pg_replication_slots |
Ordre garanti : confirmed_flush_lsn ≤ restart_lsn ≤ pg_current_wal_lsn().
lag_bytes = pg_current_wal_lsn() - confirmed_flush_lsnsqlPoint le plus piégeux : le WAL Postgres est global à toute l'instance, pas partitionné par table ni par publication. Toutes les écritures de toutes les tables de toutes les bases vont dans le même WAL.
flowchart TB
T1["stock.stock\n(dans la publication)\n10 writes/jour"]
T2["audit.log\n(PAS dans la publication)\n500 000 writes/jour"]
T3["metrics.raw\n(PAS dans la publication)\n2 000 000 writes/jour"]
WAL["pg_wal/ — UN SEUL WAL GLOBAL\nevents séquentiels par LSN"]
T1 --> WAL
T2 --> WAL
T3 --> WAL
WAL --> SLOT["Slot Debezium\n📍 confirmed_flush_lsn"]
SLOT -->|"publie uniquement\nstock.stock"| KAFKA["Kafka"]mermaidSi la publication ne capture que stock.stock (10 writes/jour) mais que le reste de la base écrit 50 GB de WAL/jour, Debezium ne confirme des LSN que quand il lit un event de stock.stock. Entre deux events, le slot retient obligatoirement les 50 GB des autres tables — Postgres ne peut pas recycler au-delà du dernier LSN confirmé.
Le slot est active=false. confirmed_flush_lsn est gelé. Lag = croissance linéaire proportionnelle au débit d'écriture de la base. Diagnostic immédiat : active=false depuis > 5 min.
Debezium tourne mais lit plus lentement que Postgres n'écrit. active=true, lag qui grossit en permanence. Diagnostic : tendance croissante sur courbe de lag.
Debezium tourne correctement, mais les tables de la publication sont peu actives par rapport au volume d'écriture global de l'instance. Debezium ne confirme que lorsqu'il traite un event de la publication — entre deux events, tout le WAL des autres tables est retenu. C'est le seul cas où le heartbeat aide.
Quelqu'un a détruit le cluster Connect sans drop le slot. Slot active=false, éternel. Personne ne le voit jusqu'à ce que le disque explose. Aucun monitoring applicatif ne l'attrape — seulement le monitoring SQL du lag.
| Mode | Heartbeat efficace ? | Raison |
|---|---|---|
| Debezium mort | ❌ | Rien ne lit le heartbeat |
| Sous-dimensionné | ❌ | Il lit déjà au max, le problème vient du débit |
| Tables inactives | ✅ | C'est exactement le cas pour lequel il est conçu |
| Kafka down | ❌ | Debezium ne peut pas confirmer même s'il lit |
| Slot orphelin | ❌ | Pas de Debezium qui tourne |
heartbeat.action.query exécute un UPDATE dans une table capturée toutes les N secondes.
Mécanique :
confirmed_flush_lsn avance au présent.Pattern recommandé — table dédiée :
CREATE TABLE public.debezium_heartbeat (
id INT PRIMARY KEY DEFAULT 1,
ts TIMESTAMPTZ NOT NULL DEFAULT now(),
CONSTRAINT single_row CHECK (id = 1)
);
INSERT INTO public.debezium_heartbeat VALUES (1, now());
-- Ajouter à la publication
ALTER PUBLICATION sie_debezium_pub ADD TABLE public.debezium_heartbeat;sql"heartbeat.interval.ms": "10000",
"heartbeat.action.query": "UPDATE public.debezium_heartbeat SET ts = now() WHERE id = 1"jsonUPDATE en place, pas d'accumulation de lignes. Un seul heartbeat suffit même si la publication couvre plusieurs tables métier.
🚨 Le heartbeat n'est PAS une solution universelle
Il résout uniquement le scénario "Debezium tourne, publie correctement, mais n'a rien à lire dans la publication". Pour tous les autres modes : alertes dédiées (active=false, métriques Kafka producer, task status via /connectors/{name}/status). Le monitoring du lag reste la sécurité de dernier recours.
SELECT
slot_name,
active,
active_pid,
confirmed_flush_lsn,
pg_current_wal_lsn() AS current_lsn,
pg_wal_lsn_diff(pg_current_wal_lsn(), confirmed_flush_lsn) AS lag_bytes,
pg_size_pretty(
pg_wal_lsn_diff(pg_current_wal_lsn(), confirmed_flush_lsn)
) AS lag_human
FROM pg_replication_slots
WHERE slot_type = 'logical';sqlSeuils d'alerte :
active = false depuis 5 min → critiquelag_human > 1 GB → warninglag_human > 10 GB → critiqueGrafana via postgres_exporter :
pg_replication_slot_lag_bytes{slot_name="sie_debezium_slot"}pg_replication_slot_active == 0 pendant 5 min → critique🚨 Timeline d'un outage sans monitoring
Vendredi 18h15 — Cluster Connect crashe → confirmed_flush_lsn figé
Samedi 03h00 — 5 GB de lag → pas d'alerte (seuil absent ou trop haut)
Dimanche 09h00 — pg_wal/ à 95% du disque → alerte disque (trop tard)
Dimanche 10h00 — disque 100% → Postgres refuse toutes les écritures → outage total
-- Vérifier qu'il est inactif
SELECT slot_name, active, active_pid
FROM pg_replication_slots WHERE slot_name = 'sie_debezium_slot';
-- Drop (irréversible — Postgres recycle dès que possible)
SELECT pg_drop_replication_slot('sie_debezium_slot');sql# 1. Arrêter le connector
PUT /connectors/sie-stock-source/stop
# 2. Supprimer les offsets côté Connect (connect-offsets)
DELETE /connectors/sie-stock-source/offsets
# 3. Supprimer le connector
DELETE /connectors/sie-stock-source
# 4. Drop le slot côté Postgres
# psql: SELECT pg_drop_replication_slot('sie_debezium_slot');
# 5. Recréer avec snapshot.mode=initial
POST /connectorsbash🚨 Supprimer un connector ne reset PAS les offsets
L'entrée dans connect-offsets (compacted) survit au DELETE /connectors/.... Pour repartir de zéro, il faut DELETE /connectors/{name}/offsets en plus. Et côté Postgres : pg_drop_replication_slot(...). Sans ça, le nouveau connector tentera de reprendre à un LSN potentiellement déjà recyclé → échec au démarrage.
-- Drop immédiat pour libérer le WAL
SELECT pg_drop_replication_slot('sie_debezium_slot');sqlDebezium au prochain démarrage ne trouvera plus son slot. Options : snapshot.mode=initial (resynchronisation complète, long) ou snapshot.mode=never (repart du LSN courant, trou dans l'historique). Choix selon tolérance à la perte d'events.
⚡ TL;DR — chaque concept en une ligne
Slot lag
✓ pg_current_wal_lsn() - confirmed_flush_lsn — volume de WAL que Postgres retient à cause du slot.
⚠ Croissance linéaire sans alerte = outage disque en quelques heures sur une base active.
WAL global vs tables capturées ✓ Debezium peut tirer son curseur vers l'avant via le heartbeat même si les tables métier sont inactives. ⚠ Le WAL de toutes les autres tables de l'instance est retenu jusqu'à la prochaine confirmation — ratio 0.1% écrit / 99.9% WAL retenu = catastrophe.
4 modes de défaillance ✓ Le heartbeat résout uniquement le mode 3 (tables inactives). Les 3 autres nécessitent une action humaine. ⚠ Un slot orphelin est le plus dangereux — aucune alerte applicative, seulement le monitoring SQL du lag.
Reset propre
✓ DELETE /connectors/{name}/offsets + pg_drop_replication_slot(...) — les deux systèmes ensemble.
⚠ Supprimer le connector sans vider les offsets → désalignement garanti au redémarrage.
🎓 À retenir
active=false et lag_bytes, le slot orphelin ou mort provoque un outage DB sans préavis visible. C'est la première alerte à mettre en place sur tout déploiement Debezium.confirmed_flush_lsn. Un seul heartbeat tire ce curseur vers l'avant pour toutes les tables.connect-offsets