๐ŸŸฅ Niveau 3 : Avancรฉ

๐Ÿ“จ Distributed Messaging pour Data Engineers

Bienvenue dans ce module avancรฉ oรน tu vas maรฎtriser les systรจmes de messaging distribuรฉs. Tu apprendras les fonctionnalitรฉs avancรฉes de Kafka, les alternatives comme RabbitMQ et Pulsar, et le Change Data Capture avec Debezium โ€” des compรฉtences essentielles pour construire des architectures data temps rรฉel !


Prรฉrequis

Niveau Compรฉtence
โœ… Requis Avoir suivi le module 24_kafka_streaming (Kafka, Spark SSS, Watermarks)
โœ… Requis Maรฎtriser topics, partitions, offsets, consumer groups
โœ… Requis Connaรฎtre kafka-python et confluent-kafka (producers/consumers)
โœ… Requis Maรฎtriser Spark Structured Streaming (readStream, writeStream, foreachBatch)
โœ… Requis Connaissances en Docker et Kubernetes (M14-M16, M27)
โœ… Requis Bases de donnรฉes relationnelles et SQL (pour Debezium CDC)
๐Ÿ’ก Recommandรฉ Expรฉrience avec des pipelines streaming en production

๐ŸŽฏ Objectifs du module

ร€ la fin de ce module, tu seras capable de :

  • Configurer les fonctionnalitรฉs avancรฉes de Kafka (Quotas, Tiered Storage, Transactions)
  • Comprendre et choisir entre Kafka, RabbitMQ et Pulsar
  • Implรฉmenter le Change Data Capture complet avec Debezium
  • Concevoir des architectures de messaging robustes
  • Gรฉrer les patterns avancรฉs : exactly-once, dead letter queues, event sourcing

Rappel : Ce qu'on a vu en M24 vs Ce qu'on approfondit ici

Module M24 (Intermediate) Ce module M29 (Advanced)
Architecture Lambda vs Kappa โ€”
Topics, Partitions, Offsets, Consumer Groups Tiered Storage, Quotas, Monitoring
Producers / Consumers (kafka-python, confluent-kafka) Transactions Kafka, Exactly-Once (EOS)
Schema Registry basics (Avro) Schema Registry avancรฉ (compatibilitรฉ, รฉvolution)
Spark Structured Streaming complet โ€”
Windowing, Watermarks, foreachBatch โ€”
Faust (aperรงu) โ€”
Debezium (mentionnรฉ) Debezium CDC en profondeur
โ€” RabbitMQ (alternative queue-based)
โ€” Apache Pulsar (alternative multi-tenant)
โ€” Patterns : DLQ, Saga, Event Sourcing, CQRS

Schรฉma : ร‰cosystรจme Messaging

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚                    DISTRIBUTED MESSAGING LANDSCAPE                          โ”‚
โ”‚                                                                             โ”‚
โ”‚   โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”   โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”   โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”          โ”‚
โ”‚   โ”‚     KAFKA       โ”‚   โ”‚   RABBITMQ      โ”‚   โ”‚     PULSAR      โ”‚          โ”‚
โ”‚   โ”‚  Log-based      โ”‚   โ”‚  Queue-based    โ”‚   โ”‚  Multi-tenant   โ”‚          โ”‚
โ”‚   โ”‚  High throughputโ”‚   โ”‚  Flexible routingโ”‚  โ”‚  Geo-replicationโ”‚          โ”‚
โ”‚   โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜   โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜   โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜          โ”‚
โ”‚            โ”‚                    โ”‚                     โ”‚                     โ”‚
โ”‚            โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜                     โ”‚
โ”‚                                 โ”‚                                           โ”‚
โ”‚                                 โ–ผ                                           โ”‚
โ”‚   โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”‚
โ”‚   โ”‚                        DEBEZIUM (CDC)                                โ”‚  โ”‚
โ”‚   โ”‚   Capture changes from databases โ†’ Stream to messaging systems      โ”‚  โ”‚
โ”‚   โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

โ„น๏ธ Le savais-tu ?

Kafka traite plus de 7 trillions de messages par jour chez LinkedIn, son crรฉateur.

RabbitMQ a รฉtรฉ crรฉรฉ en 2007 et implรฉmente le protocole AMQP, un standard ouvert pour le messaging.

Apache Pulsar a รฉtรฉ dรฉveloppรฉ par Yahoo! pour gรฉrer leurs 100 milliards de messages quotidiens avec une architecture multi-tenant native.

Debezium (du latin "from the beginning") capture chaque changement depuis le dรฉbut du log de la base de donnรฉes โ€” c'est la base du Change Data Capture.

๐Ÿ“– Kafka at LinkedIn


1. Kafka Avancรฉ

Cette section couvre les fonctionnalitรฉs avancรฉes de Kafka pour la production ร  grande รฉchelle. Tu connais dรฉjร  les bases (M24), on passe directement aux sujets avancรฉs.

1.1 Quotas et Throttling

Les quotas permettent de limiter les ressources consommรฉes par les clients pour รฉviter qu'un client ne monopolise le cluster.

Types de quotas

Quota Description Unitรฉ
producer_byte_rate Dรฉbit max en รฉcriture bytes/sec
consumer_byte_rate Dรฉbit max en lecture bytes/sec
request_percentage % CPU du broker %
controller_mutation_rate Taux de mutations (create/delete) mutations/sec

Configurer les quotas

# Quota par user
kafka-configs.sh --bootstrap-server localhost:9092 \
  --alter --add-config 'producer_byte_rate=1048576,consumer_byte_rate=2097152' \
  --entity-type users --entity-name data-pipeline-user

# Quota par client-id
kafka-configs.sh --bootstrap-server localhost:9092 \
  --alter --add-config 'producer_byte_rate=5242880' \
  --entity-type clients --entity-name etl-producer

# Quota par user + client-id (plus spรฉcifique)
kafka-configs.sh --bootstrap-server localhost:9092 \
  --alter --add-config 'producer_byte_rate=10485760' \
  --entity-type users --entity-name spark-user \
  --entity-type clients --entity-name spark-producer

# Quota par dรฉfaut pour tous les users
kafka-configs.sh --bootstrap-server localhost:9092 \
  --alter --add-config 'producer_byte_rate=1048576' \
  --entity-type users --entity-default

# Voir les quotas configurรฉs
kafka-configs.sh --bootstrap-server localhost:9092 \
  --describe --entity-type users --entity-name data-pipeline-user

Quotas dans le code Python

from confluent_kafka import Producer

# Le client doit spรฉcifier son client.id pour รชtre identifiรฉ par les quotas
producer = Producer({
    'bootstrap.servers': 'localhost:9092',
    'client.id': 'etl-producer',  # Identifiant pour les quotas
    'acks': 'all',
})

# Si le quota est dรฉpassรฉ, Kafka throttle automatiquement le client
# Le producer recevra des dรฉlais dans les rรฉponses

1.2 Tiered Storage (KIP-405)

Le Tiered Storage permet de stocker les donnรฉes anciennes sur un stockage moins cher (S3, GCS, Azure Blob) tout en gardant les donnรฉes rรฉcentes sur disque local.

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚                         TIERED STORAGE                                      โ”‚
โ”‚                                                                             โ”‚
โ”‚   โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”‚
โ”‚   โ”‚                    LOCAL TIER (Hot Data)                             โ”‚  โ”‚
โ”‚   โ”‚   โ”Œโ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”                           โ”‚  โ”‚
โ”‚   โ”‚   โ”‚Seg 5โ”‚ โ”‚Seg 6โ”‚ โ”‚Seg 7โ”‚ โ”‚Seg 8โ”‚ โ”‚Seg 9โ”‚  โ† Donnรฉes rรฉcentes      โ”‚  โ”‚
โ”‚   โ”‚   โ””โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”˜    (SSD local, rapide)    โ”‚  โ”‚
โ”‚   โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ”‚
โ”‚                                    โ”‚                                        โ”‚
โ”‚                        Offload automatique                                  โ”‚
โ”‚                                    โ–ผ                                        โ”‚
โ”‚   โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”‚
โ”‚   โ”‚                   REMOTE TIER (Cold Data)                            โ”‚  โ”‚
โ”‚   โ”‚   โ”Œโ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”                                   โ”‚  โ”‚
โ”‚   โ”‚   โ”‚Seg 1โ”‚ โ”‚Seg 2โ”‚ โ”‚Seg 3โ”‚ โ”‚Seg 4โ”‚  โ† Donnรฉes anciennes             โ”‚  โ”‚
โ”‚   โ”‚   โ””โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”˜    (S3/GCS/Azure, รฉconomique)    โ”‚  โ”‚
โ”‚   โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Configuration Tiered Storage

# server.properties (broker)

# Activer le tiered storage
remote.log.storage.system.enable=true

# Plugin de stockage (exemple S3)
remote.log.storage.manager.class.name=org.apache.kafka.tiered.storage.s3.S3RemoteStorageManager
remote.log.storage.manager.class.path=/opt/kafka/plugins/tiered-storage-s3.jar

# Configuration S3
remote.log.storage.s3.bucket=my-kafka-tiered-storage
remote.log.storage.s3.region=eu-west-1

# Rรฉtention locale (donnรฉes chaudes)
local.retention.ms=86400000  # 1 jour en local

# Rรฉtention totale (incluant remote)
retention.ms=2592000000  # 30 jours au total
# Activer le tiered storage sur un topic existant
kafka-configs.sh --bootstrap-server localhost:9092 \
  --alter --entity-type topics --entity-name events \
  --add-config 'remote.storage.enable=true,local.retention.ms=86400000,retention.ms=2592000000'

Avantages du Tiered Storage

Avantage Description
Coรปt rรฉduit Stockage S3 ~10x moins cher que SSD
Rรฉtention illimitรฉe Garder des annรฉes de donnรฉes
Cluster plus petit Moins de disque local nรฉcessaire
Replay facilitรฉ Relire des donnรฉes anciennes pour reprocessing

1.3 Transactions et Exactly-Once Semantics (EOS)

En M24, tu as vu les garanties de livraison (at-most-once, at-least-once, exactly-once). Ici, on va implรฉmenter exactly-once avec les transactions Kafka.

Rappel des garanties

Niveau Description Risque
At-most-once Fire & forget Perte de messages
At-least-once Retry jusqu'ร  ACK Doublons possibles
Exactly-once Transactions + idempotence Aucun (mais plus complexe)

Producer Idempotent (pas de doublons)

L'idempotence garantit qu'un message n'est รฉcrit qu'une seule fois mรชme en cas de retry rรฉseau.

from confluent_kafka import Producer

# Producer idempotent โ€” PAS de duplicatas mรชme avec retries
producer = Producer({
    'bootstrap.servers': 'localhost:9092',
    'enable.idempotence': True,  # โ† Active l'idempotence
    'acks': 'all',               # Requis pour idempotence
    'retries': 2147483647,       # Retries infinis (best practice)
    'max.in.flight.requests.per.connection': 5,  # Max 5 avec idempotence
})

# Comment รงa marche ?
# 1. Le producer assigne un Producer ID (PID) et un Sequence Number ร  chaque message
# 2. Le broker dรฉtecte les doublons en comparant (PID, Sequence)
# 3. Si un retry envoie le mรชme message, le broker le reconnaรฎt et ignore le doublon

Transactions complรจtes (multi-topics atomique)

Les transactions permettent d'รฉcrire sur plusieurs topics/partitions de maniรจre atomique : tout rรฉussit ou tout รฉchoue.

from confluent_kafka import Producer, KafkaException

# Producer transactionnel
producer = Producer({
    'bootstrap.servers': 'localhost:9092',
    'transactional.id': 'etl-pipeline-001',  # ID unique et STABLE
    'enable.idempotence': True,              # Implicitement activรฉ
    'acks': 'all',
})

# Initialiser les transactions (une seule fois au dรฉmarrage)
producer.init_transactions()

try:
    # Dรฉmarrer une transaction
    producer.begin_transaction()
    
    # ร‰crire sur PLUSIEURS topics (atomique)
    producer.produce('orders-processed', key='order-1', value='{"status": "done"}')
    producer.produce('audit-log', key='order-1', value='{"action": "order_processed"}')
    producer.produce('metrics', key='counter', value='{"orders_processed": 1}')
    
    # Commit la transaction โ€” TOUT ou RIEN
    producer.commit_transaction()
    print("โœ… Transaction committed successfully")
    
except KafkaException as e:
    # Abort en cas d'erreur โ€” aucun message n'est visible
    producer.abort_transaction()
    print(f"โŒ Transaction aborted: {e}")

Pattern Read-Process-Write (Exactly-Once complet)

Le pattern le plus puissant : lire, traiter, รฉcrire, et commiter les offsets dans la mรชme transaction.

from confluent_kafka import Consumer, Producer, KafkaException

# Consumer avec isolation transactionnelle
consumer = Consumer({
    'bootstrap.servers': 'localhost:9092',
    'group.id': 'etl-group',
    'isolation.level': 'read_committed',  # โ† Ne lit que les messages committรฉs
    'enable.auto.commit': False,          # โ† Commit manuel dans la transaction
})

producer = Producer({
    'bootstrap.servers': 'localhost:9092',
    'transactional.id': 'etl-processor-001',
    'enable.idempotence': True,
})

producer.init_transactions()
consumer.subscribe(['raw-events'])

while True:
    msg = consumer.poll(1.0)
    if msg is None:
        continue
    if msg.error():
        continue
    
    try:
        # 1. Dรฉmarrer la transaction
        producer.begin_transaction()
        
        # 2. Traiter le message
        processed = process_message(msg.value())
        
        # 3. ร‰crire le rรฉsultat
        producer.produce('processed-events', value=processed)
        
        # 4. Commit les offsets DANS la transaction
        producer.send_offsets_to_transaction(
            consumer.position(consumer.assignment()),
            consumer.consumer_group_metadata()
        )
        
        # 5. Commit atomique : รฉcriture + offset ensemble
        producer.commit_transaction()
        
    except Exception as e:
        producer.abort_transaction()
        print(f"Transaction failed: {e}")

1.4 Schema Registry Avancรฉ

En M24, tu as vu les bases du Schema Registry avec Avro. Approfondissons les modes de compatibilitรฉ et l'รฉvolution de schรฉmas.

Modes de compatibilitรฉ dรฉtaillรฉs

Mode Nouveau consumer lit ancien Ancien consumer lit nouveau Changements autorisรฉs
BACKWARD โœ… Oui โŒ Non Ajouter champs optionnels, supprimer champs
FORWARD โŒ Non โœ… Oui Ajouter champs, supprimer champs optionnels
FULL โœ… Oui โœ… Oui Ajouter/supprimer champs optionnels uniquement
NONE โ€” โ€” Tout (โš ๏ธ dangereux en production)
# Configurer la compatibilitรฉ globale
curl -X PUT http://localhost:8081/config \
  -H "Content-Type: application/json" \
  -d '{"compatibility": "BACKWARD"}'

# Configurer par sujet (override global)
curl -X PUT http://localhost:8081/config/orders-value \
  -H "Content-Type: application/json" \
  -d '{"compatibility": "FULL"}'

# Tester la compatibilitรฉ AVANT de publier un nouveau schรฉma
curl -X POST http://localhost:8081/compatibility/subjects/orders-value/versions/latest \
  -H "Content-Type: application/json" \
  -d '{"schema": "{...}"}'
# Rรฉponse: {"is_compatible": true} ou {"is_compatible": false}

ร‰volution de schรฉma โ€” Exemple pratique

# Version 1 du schรฉma
schema_v1 = '''
{
  "type": "record",
  "name": "Order",
  "fields": [
    {"name": "order_id", "type": "string"},
    {"name": "amount", "type": "double"}
  ]
}
'''

# Version 2 โ€” Ajouter un champ optionnel (BACKWARD compatible)
schema_v2 = '''
{
  "type": "record",
  "name": "Order",
  "fields": [
    {"name": "order_id", "type": "string"},
    {"name": "amount", "type": "double"},
    {"name": "currency", "type": "string", "default": "EUR"}
  ]
}
'''
# โœ… Les nouveaux consumers peuvent lire les anciens messages (currency = "EUR" par dรฉfaut)

2. RabbitMQ โ€” L'Alternative Queue-Based

RabbitMQ est un message broker traditionnel implรฉmentant le protocole AMQP. Contrairement ร  Kafka (log-based), RabbitMQ est queue-based avec un routage flexible.

Kafka vs RabbitMQ โ€” Comparaison dรฉtaillรฉe

Aspect Kafka RabbitMQ
Modรจle Log distribuรฉ (append-only) Message queue (FIFO)
Persistance Toujours sur disque Optionnelle (mรฉmoire ou disque)
Ordre Garanti par partition Garanti par queue
Replay โœ… Natif (offsets) โŒ Messages supprimรฉs aprรจs ACK
Routage Topics + Partitions Exchanges (fanout, direct, topic, headers)
Throughput Trรจs รฉlevรฉ (millions/sec) ร‰levรฉ (dizaines de milliers/sec)
Latence Millisecondes Sub-milliseconde
Use case principal Event streaming, analytics Task queues, RPC, notifications

Architecture RabbitMQ

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚                         RABBITMQ ARCHITECTURE                               โ”‚
โ”‚                                                                             โ”‚
โ”‚   Producer โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”                                                        โ”‚
โ”‚                    โ”‚                                                        โ”‚
โ”‚                    โ–ผ                                                        โ”‚
โ”‚            โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”                                                โ”‚
โ”‚            โ”‚   EXCHANGE    โ”‚  โ† Routing logic (type: fanout/direct/topic)  โ”‚
โ”‚            โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜                                                โ”‚
โ”‚                    โ”‚ Bindings (routing rules)                               โ”‚
โ”‚        โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”                                            โ”‚
โ”‚        โ”‚           โ”‚           โ”‚                                            โ”‚
โ”‚        โ–ผ           โ–ผ           โ–ผ                                            โ”‚
โ”‚   โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”                                       โ”‚
โ”‚   โ”‚ Queue 1 โ”‚ โ”‚ Queue 2 โ”‚ โ”‚ Queue 3 โ”‚  โ† Messages stockรฉs ici              โ”‚
โ”‚   โ””โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”˜                                       โ”‚
โ”‚        โ”‚           โ”‚           โ”‚                                            โ”‚
โ”‚        โ–ผ           โ–ผ           โ–ผ                                            โ”‚
โ”‚   Consumer 1   Consumer 2   Consumer 3                                      โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Types d'Exchanges

Exchange Routage Use case
Direct routing_key exact match Logs par niveau (errorโ†’error_queue)
Fanout Broadcast ร  toutes les queues liรฉes Notifications, cache invalidation
Topic Pattern matching (*.error, logs.#) Logs multi-critรจres (app.module.level)
Headers Match sur headers du message Routage complexe multi-attributs

Installation

# Docker
docker run -d --name rabbitmq \
  -p 5672:5672 \
  -p 15672:15672 \
  rabbitmq:3-management

# Management UI: http://localhost:15672 (guest/guest)

Producer Python (pika)

import pika
import json

# Connexion
connection = pika.BlockingConnection(pika.ConnectionParameters('localhost'))
channel = connection.channel()

# Dรฉclarer un exchange de type topic
channel.exchange_declare(exchange='data_events', exchange_type='topic', durable=True)

# Publier un message
message = {'event_type': 'order_created', 'order_id': 'ORD-001', 'amount': 99.99}

channel.basic_publish(
    exchange='data_events',
    routing_key='orders.created',
    body=json.dumps(message),
    properties=pika.BasicProperties(delivery_mode=2, content_type='application/json')
)

connection.close()

Consumer Python

import pika
import json

connection = pika.BlockingConnection(pika.ConnectionParameters('localhost'))
channel = connection.channel()

channel.queue_declare(queue='order_processor', durable=True)
channel.queue_bind(exchange='data_events', queue='order_processor', routing_key='orders.*')

def callback(ch, method, properties, body):
    message = json.loads(body)
    print(f"Received: {message}")
    ch.basic_ack(delivery_tag=method.delivery_tag)

channel.basic_qos(prefetch_count=1)
channel.basic_consume(queue='order_processor', on_message_callback=callback)
channel.start_consuming()

Quand utiliser RabbitMQ vs Kafka ?

โœ… RabbitMQ โœ… Kafka
Task queues (jobs async) Event streaming temps rรฉel
RPC (request/reply) Log aggregation
Routage complexe (exchanges) Replay de donnรฉes historiques
Faible latence critique (<1ms) Trรจs haut throughput (millions/sec)

3. Apache Pulsar โ€” Le Challenger Multi-Tenant

Apache Pulsar combine les avantages de Kafka (log-based) et RabbitMQ (queuing) avec une architecture cloud-native.

Kafka vs Pulsar

Aspect Kafka Pulsar
Architecture Brokers = Storage + Compute Sรฉparation Brokers / BookKeeper
Multi-tenancy Limitรฉ Natif (tenants, namespaces)
Geo-replication MirrorMaker (externe) Natif et synchrone
Queuing Non natif Natif (shared subscriptions)

Types de Subscriptions Pulsar

Type Description ร‰quivalent
Exclusive 1 seul consumer Kafka standard
Failover Failover automatique โ€”
Shared Load balanced (round-robin) RabbitMQ
Key_Shared Ordre par clรฉ Kafka partitions

Producer/Consumer Python

import pulsar
import json

client = pulsar.Client('pulsar://localhost:6650')

# Producer
producer = client.create_producer('persistent://public/default/orders')
producer.send(json.dumps({'order_id': 'ORD-001'}).encode('utf-8'))

# Consumer (shared = load balanced)
consumer = client.subscribe(
    'persistent://public/default/orders',
    subscription_name='order-processor',
    consumer_type=pulsar.ConsumerType.Shared
)

msg = consumer.receive()
print(json.loads(msg.data()))
consumer.acknowledge(msg)
client.close()

4. Debezium โ€” Change Data Capture en Profondeur

Debezium est une plateforme open-source de Change Data Capture (CDC) qui capture les changements dans les bases de donnรฉes et les streame vers Kafka.

En M24, Debezium รฉtait mentionnรฉ dans le quiz. Ici, on l'implรฉmente en profondeur.

Pourquoi le CDC ?

Approche traditionnelle CDC avec Debezium
Batch ETL (SELECT * toutes les heures) Streaming temps rรฉel
Query la DB source (charge CPU/IO) Lit le transaction log (lรฉger)
Dรฉtection des DELETEs difficile Capture TOUS les changements
Latence รฉlevรฉe (heures) Latence sub-seconde

Architecture Debezium

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚                         DEBEZIUM ARCHITECTURE                               โ”‚
โ”‚                                                                             โ”‚
โ”‚   โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”                                                           โ”‚
โ”‚   โ”‚  PostgreSQL โ”‚     Transaction Log (WAL)                                 โ”‚
โ”‚   โ”‚   (source)  โ”‚โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”                                        โ”‚
โ”‚   โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜                  โ”‚                                        โ”‚
โ”‚                                    โ–ผ                                        โ”‚
โ”‚   โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”    โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”    โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”    โ”‚
โ”‚   โ”‚    MySQL    โ”‚โ”€โ”€โ”€โ–ถโ”‚   KAFKA CONNECT     โ”‚โ”€โ”€โ”€โ–ถโ”‚   KAFKA TOPICS      โ”‚    โ”‚
โ”‚   โ”‚   (source)  โ”‚    โ”‚  + Debezium         โ”‚    โ”‚  (change events)    โ”‚    โ”‚
โ”‚   โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜    โ”‚    Connectors       โ”‚    โ”‚                     โ”‚    โ”‚
โ”‚                      โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜    โ”‚  โ€ข dbserver.schema  โ”‚    โ”‚
โ”‚   โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”                               โ”‚    .table           โ”‚    โ”‚
โ”‚   โ”‚   MongoDB   โ”‚โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜                          โ”‚
โ”‚   โ”‚   (source)  โ”‚                                         โ”‚                โ”‚
โ”‚   โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜                                         โ–ผ                โ”‚
โ”‚                                                โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”     โ”‚
โ”‚                                                โ”‚    Consumers        โ”‚     โ”‚
โ”‚                                                โ”‚  โ€ข Data Warehouse   โ”‚     โ”‚
โ”‚                                                โ”‚  โ€ข Elasticsearch    โ”‚     โ”‚
โ”‚                                                โ”‚  โ€ข Microservices    โ”‚     โ”‚
โ”‚                                                โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜     โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Connecteurs Debezium

Base de donnรฉes Mรฉthode de capture Maturitรฉ
PostgreSQL Logical replication (pgoutput) โญโญโญโญโญ
MySQL/MariaDB Binary log (binlog) โญโญโญโญโญ
MongoDB Oplog / Change Streams โญโญโญโญโญ
SQL Server CDC tables โญโญโญโญ
Oracle LogMiner โญโญโญโญ

Docker Compose complet

version: '3.8'
services:
  zookeeper:
    image: confluentinc/cp-zookeeper:7.5.0
    environment:
      ZOOKEEPER_CLIENT_PORT: 2181

  kafka:
    image: confluentinc/cp-kafka:7.5.0
    depends_on: [zookeeper]
    ports: ["9092:9092"]
    environment:
      KAFKA_BROKER_ID: 1
      KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181
      KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://kafka:29092,PLAINTEXT_HOST://localhost:9092
      KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: PLAINTEXT:PLAINTEXT,PLAINTEXT_HOST:PLAINTEXT

  postgres:
    image: postgres:15
    ports: ["5432:5432"]
    environment:
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: postgres
      POSTGRES_DB: source_db
    command: ["postgres", "-c", "wal_level=logical"]  # CRUCIAL

  connect:
    image: debezium/connect:2.5
    depends_on: [kafka, postgres]
    ports: ["8083:8083"]
    environment:
      BOOTSTRAP_SERVERS: kafka:29092
      GROUP_ID: debezium-connect
      CONFIG_STORAGE_TOPIC: connect_configs
      OFFSET_STORAGE_TOPIC: connect_offsets
      STATUS_STORAGE_TOPIC: connect_statuses

Enregistrer le connecteur

curl -X POST http://localhost:8083/connectors \
  -H "Content-Type: application/json" \
  -d '{
    "name": "postgres-connector",
    "config": {
      "connector.class": "io.debezium.connector.postgresql.PostgresConnector",
      "database.hostname": "postgres",
      "database.port": "5432",
      "database.user": "postgres",
      "database.password": "postgres",
      "database.dbname": "source_db",
      "topic.prefix": "cdc",
      "table.include.list": "public.orders",
      "plugin.name": "pgoutput",
      "slot.name": "debezium_slot",
      "transforms": "unwrap",
      "transforms.unwrap.type": "io.debezium.transforms.ExtractNewRecordState",
      "transforms.unwrap.drop.tombstones": "false",
      "transforms.unwrap.delete.handling.mode": "rewrite",
      "transforms.unwrap.add.fields": "op,source.ts_ms"
    }
  }'

Format des messages Debezium

Champ Description
before ร‰tat AVANT le changement (null pour INSERT)
after ร‰tat APRรˆS le changement (null pour DELETE)
op Opรฉration : c=create, u=update, d=delete, r=read (snapshot)
source Mรฉtadonnรฉes (table, transaction ID, LSN)

Consumer Python CDC

from confluent_kafka import Consumer
import json

consumer = Consumer({
    'bootstrap.servers': 'localhost:9092',
    'group.id': 'cdc-processor',
    'auto.offset.reset': 'earliest',
})
consumer.subscribe(['cdc.public.orders'])

while True:
    msg = consumer.poll(1.0)
    if msg is None:
        continue
    
    event = json.loads(msg.value())
    op = event.get('__op')
    data = {k: v for k, v in event.items() if not k.startswith('__')}
    
    if op in ('c', 'r'):  # INSERT ou SNAPSHOT
        print(f"INSERT: {data}")
    elif op == 'u':  # UPDATE
        print(f"UPDATE: {data}")
    elif op == 'd':  # DELETE
        print(f"DELETE: id={data.get('id')}")

Outbox Pattern

Le Outbox Pattern garantit la cohรฉrence entre les mises ร  jour DB et l'envoi d'รฉvรฉnements.

-- Table outbox
CREATE TABLE outbox (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    aggregate_type VARCHAR(255) NOT NULL,
    aggregate_id VARCHAR(255) NOT NULL,
    event_type VARCHAR(255) NOT NULL,
    payload JSONB NOT NULL,
    created_at TIMESTAMP DEFAULT NOW()
);

-- Dans une TRANSACTION
BEGIN;
    UPDATE orders SET status = 'shipped' WHERE id = 1;
    INSERT INTO outbox (aggregate_type, aggregate_id, event_type, payload)
    VALUES ('Order', '1', 'OrderShipped', '{"order_id": 1}');
COMMIT;
-- Debezium capture l'INSERT dans outbox โ†’ Kafka

Use Cases CDC

Use Case Description
Sync Data Warehouse Rรฉplication temps rรฉel vers Snowflake, BigQuery
Cache invalidation Invalider Redis quand la DB change
Search indexing Sync vers Elasticsearch
Microservices events Outbox pattern
Audit log Compliance, RGPD

5. Patterns de Messaging Distribuรฉ

5.1 Dead Letter Queue (DLQ)

MAX_RETRIES = 3

while True:
    msg = consumer.poll(1.0)
    headers = dict(msg.headers() or [])
    retry_count = int(headers.get('retry_count', b'0'))
    
    try:
        process_message(msg.value())
        consumer.commit(msg)
    except Exception as e:
        if retry_count >= MAX_RETRIES:
            # Envoyer vers DLQ
            producer.produce('orders-dlq', key=msg.key(), value=msg.value(),
                           headers=[('error', str(e))])
        else:
            # Retry
            producer.produce('orders-retry', key=msg.key(), value=msg.value(),
                           headers=[('retry_count', str(retry_count + 1))])
        consumer.commit(msg)

5.2 Saga Pattern

Create Order โ”€โ”€โ–ถ Reserve Stock โ”€โ”€โ–ถ Process Payment โ”€โ”€โ–ถ Ship Order
      โ”‚               โ”‚                  โ”‚
      โ–ผ               โ–ผ                  โ–ผ
Cancel Order โ—€โ”€โ”€ Release Stock โ—€โ”€โ”€ Refund Payment  (compensation)

5.3 Event Sourcing

events = [
    {'type': 'OrderCreated', 'order_id': 1, 'amount': 100},
    {'type': 'PaymentReceived', 'order_id': 1},
    {'type': 'OrderShipped', 'order_id': 1},
]

def rebuild_state(events):
    state = {}
    for e in events:
        if e['type'] == 'OrderCreated':
            state = {'id': e['order_id'], 'status': 'created'}
        elif e['type'] == 'PaymentReceived':
            state['status'] = 'paid'
        elif e['type'] == 'OrderShipped':
            state['status'] = 'shipped'
    return state

5.4 CQRS

Commands โ”€โ”€โ–ถ Events (Kafka) โ”€โ”€โ–ถ Projector โ”€โ”€โ–ถ Read Model (optimized)
   โ”‚                                              โ”‚
   โ–ผ                                              โ–ผ
Write DB                                      Query API
(PostgreSQL)                                  (Elasticsearch)

6. Exercices Pratiques

Exercice 1 : Kafka Transactions

Implรฉmenter un producer transactionnel qui รฉcrit sur 2 topics atomiquement.

Exercice 2 : RabbitMQ Task Queue

Crรฉer une task queue avec prioritรฉs et DLQ.

Exercice 3 : Pipeline CDC Complet

Dรฉployer PostgreSQL + Kafka + Debezium et sync vers un data warehouse.

Exercice 4 : Comparatif Performance

Comparer Kafka vs RabbitMQ (100K messages, throughput, latence).

Exercice 5 : Outbox Pattern

Implรฉmenter le pattern Outbox avec Debezium.


๐Ÿ“š Ressources


โžก๏ธ Prochaine รฉtape

๐Ÿ‘‰ Module suivant : 30_spark_scala_deep_dive โ€” Spark & Scala Deep Dive


๐ŸŽ‰ Fรฉlicitations ! Tu as terminรฉ le module Distributed Messaging.