Apache Kafka pour les Data Engineers : Architecture KRaft, Partitions et Pipelines Exactly-Once
Guide approfondi sur Apache Kafka pour les data engineers. Architecture KRaft sans ZooKeeper, strategies de partitionnement, consumer groups, CDC avec Debezium, transactions exactly-once et Share Groups, avec exemples de code Python et questions d'entretien.

Apache Kafka constitue aujourd'hui un pilier incontournable des architectures data modernes. Depuis sa creation chez LinkedIn en 2011, cette plateforme de streaming distribue a evolue pour devenir le standard de facto du transport d'evenements en temps reel. Netflix, Uber, Airbnb, Spotify -- les plus grandes plateformes technologiques s'appuient sur Kafka pour traiter des milliers de milliards de messages quotidiens. Pour les data engineers, la maitrise approfondie de Kafka -- de l'architecture des partitions aux garanties de livraison exactly-once -- constitue une competence fondamentale, regulierement evaluee en entretien technique et indispensable au quotidien pour concevoir des pipelines de donnees robustes et scalables.
Depuis la version 4.0, Apache Kafka a definitivement abandonne ZooKeeper. L'architecture KRaft (Kafka Raft) gere desormais le consensus et les metadonnees directement au sein des brokers, eliminant une couche de complexite operationnelle considerable. Tout deploiement Kafka en 2026 doit utiliser le mode KRaft.
Architecture KRaft : deployer Kafka 4.x sans ZooKeeper
L'abandon de ZooKeeper represente le changement architectural le plus significatif de l'histoire de Kafka. ZooKeeper necessitait un cluster separe de 3 a 5 noeuds, ajoutait une source de latence lors des elections de leaders, et constituait une surface d'attaque supplementaire en production. Le mode KRaft elimine cette dependance en integrant le protocole de consensus Raft directement dans les processus Kafka.
Dans un cluster KRaft, certains noeuds endossent le role de controller (responsables des metadonnees), tandis que d'autres agissent comme brokers (responsables du stockage et de la distribution des messages). Un noeud peut combiner les deux roles dans les environnements de petite taille. Le quorum controller maintient un log de metadonnees replique, garantissant la coherence du cluster meme en cas de defaillance d'un noeud.
Le fichier Docker Compose ci-dessous illustre le deploiement d'un cluster Kafka 4.2 a trois noeuds en mode KRaft, adapte au developpement local et aux tests d'integration :
# docker-compose.yml — Kafka 4.x KRaft cluster (no ZooKeeper)
services:
kafka-1:
image: apache/kafka:4.2.0
environment:
KAFKA_NODE_ID: 1
KAFKA_PROCESS_ROLES: broker,controller
KAFKA_CONTROLLER_QUORUM_VOTERS: 1@kafka-1:9093,2@kafka-2:9093,3@kafka-3:9093
KAFKA_LISTENERS: PLAINTEXT://:9092,CONTROLLER://:9093
KAFKA_INTER_BROKER_LISTENER_NAME: PLAINTEXT
KAFKA_CONTROLLER_LISTENER_NAMES: CONTROLLER
KAFKA_LOG_DIRS: /var/lib/kafka/data
KAFKA_NUM_PARTITIONS: 6
KAFKA_DEFAULT_REPLICATION_FACTOR: 3
KAFKA_MIN_INSYNC_REPLICAS: 2
ports:
- "9092:9092"Chaque noeud cumule ici les roles broker et controller, une configuration appropriee pour les environnements de developpement. La variable KAFKA_CONTROLLER_QUORUM_VOTERS enumere les trois noeuds participant au quorum Raft. Le facteur de replication de 3, combine a MIN_INSYNC_REPLICAS: 2, garantit que le cluster tolere la defaillance d'un noeud sans perte de donnees ni interruption du service de production.
En production, il est recommande de separer les roles : des noeuds dedies au controller (3 ou 5 selon la taille du cluster) et des noeuds dedies au broker. Cette separation isole la gestion des metadonnees du traitement des messages et ameliore la previsibilite des performances.
Strategies de partitionnement : distribuer la charge intelligemment
Le partitionnement constitue le mecanisme central de parallelisme dans Kafka. Chaque topic est segmente en partitions, chacune representant un log ordonne et immutable. L'attribution d'un message a une partition est determinee par sa cle de partitionnement : tous les messages partageant la meme cle atterrissent systematiquement dans la meme partition, preservant ainsi l'ordre chronologique pour cette cle.
Le choix de la cle de partitionnement n'est pas anodin. Il conditionne trois aspects fondamentaux du pipeline : l'ordre de traitement des evenements, la repartition de charge entre les consommateurs, et la capacite a scaler horizontalement.
# partition_strategy.py — Choosing the right partition key
from confluent_kafka import Producer
import json
def create_producer():
return Producer({
'bootstrap.servers': 'kafka-1:9092',
'acks': 'all',
'enable.idempotence': True,
'max.in.flight.requests.per.connection': 5
})
def publish_order_event(producer, event):
# Key by customer_id: all events for one customer stay ordered
key = str(event['customer_id']).encode('utf-8')
value = json.dumps(event).encode('utf-8')
producer.produce(
topic='order-events',
key=key,
value=value,
headers=[('source', b'order-service'), ('version', b'2')]
)
producer.flush()
def publish_clickstream(producer, event):
# Key by session_id: ordering within a session matters
# NOT by user_id — one user may have multiple sessions
key = str(event['session_id']).encode('utf-8')
value = json.dumps(event).encode('utf-8')
producer.produce(
topic='clickstream',
key=key,
value=value
)Le premier cas (publish_order_event) utilise le customer_id comme cle. Cette strategie garantit que l'ensemble des evenements relatifs a un client donne sont traites sequentiellement, un imperatif pour des operations comme la mise a jour d'un solde ou le suivi d'un parcours d'achat. Le second cas (publish_clickstream) partitionne par session_id plutot que par user_id, ce qui distribue plus equitablement la charge lorsqu'un utilisateur ouvre plusieurs sessions simultanement.
Une cle de partitionnement dont la distribution est fortement desequilibree engendre des hot partitions. Par exemple, dans un systeme de marketplace ou un seul vendeur genere 70 % du trafic, le partitionnement par seller_id concentrerait la majorite des messages dans une seule partition, saturant le broker responsable. La regle fondamentale : la cle doit presenter une cardinalite elevee et une distribution uniforme.
Dimensionner le nombre de partitions
Le nombre de partitions d'un topic definit le plafond de parallelisme cote consommateur. Avec 12 partitions, un consumer group ne peut pas depasser 12 consommateurs actifs -- tout consommateur supplementaire reste en veille.
La formule de dimensionnement de base consiste a diviser le debit cible par la capacite de traitement d'un consommateur individuel. Pour un topic recevant 150 Mo/s avec des consommateurs capables de traiter 10 Mo/s chacun, 15 partitions constituent le minimum. En pratique, prevoir une marge de 20 a 30 % est judicieux, car l'augmentation du nombre de partitions d'un topic existant redistribue les cles entre les partitions et brise temporairement les garanties d'ordre.
Consumer Groups et gestion fine des offsets
Les consumer groups permettent de distribuer le traitement d'un topic entre plusieurs instances d'un meme service. Kafka attribue chaque partition a exactement un consommateur du groupe, assurant qu'aucun message n'est traite en double au sein d'un groupe donne.
La gestion des offsets represente l'aspect le plus critique et le plus frequent source d'erreurs dans les pipelines Kafka. Par defaut, Kafka commite les offsets automatiquement a intervalles reguliers (enable.auto.commit = True). Ce comportement est dangereux pour les pipelines de donnees : en cas de crash entre le commit automatique et le traitement effectif, des messages sont perdus ; en cas de crash apres le traitement mais avant le commit, des messages sont retraites.
Le pattern recommande pour les pipelines de donnees est le commit manuel apres traitement reussi :
# consumer_pipeline.py — Consumer group with manual offset management
from confluent_kafka import Consumer, KafkaError
import json
def create_consumer(group_id: str):
return Consumer({
'bootstrap.servers': 'kafka-1:9092',
'group.id': group_id,
'auto.offset.reset': 'earliest',
'enable.auto.commit': False, # Manual commit for at-least-once
'max.poll.interval.ms': 300000,
'session.timeout.ms': 45000,
'isolation.level': 'read_committed' # Only read committed transactions
})
def process_batch(consumer: Consumer, batch_size: int = 500):
"""Process records in micro-batches for throughput."""
consumer.subscribe(['order-events'])
buffer = []
while True:
msg = consumer.poll(timeout=1.0)
if msg is None:
continue
if msg.error():
if msg.error().code() == KafkaError._PARTITION_EOF:
continue
raise Exception(msg.error())
record = json.loads(msg.value().decode('utf-8'))
buffer.append(record)
if len(buffer) >= batch_size:
# Process the full batch (write to warehouse, etc.)
write_to_warehouse(buffer)
# Commit AFTER successful processing
consumer.commit(asynchronous=False)
buffer.clear()
def write_to_warehouse(records: list):
# Insert batch into data warehouse (BigQuery, Snowflake, etc.)
passLe parametre isolation.level: read_committed filtre les messages issus de transactions avortees, indispensable lorsque des producteurs transactionnels alimentent le topic. Le commit synchrone (asynchronous=False) apres l'ecriture effective dans le data warehouse garantit une semantique at-least-once. Le traitement par micro-batches de 500 messages equilibre latence et debit -- un compromis a ajuster selon les exigences du pipeline.
Deux parametres meritent une attention particuliere : max.poll.interval.ms definit le temps maximal entre deux appels a poll() avant que Kafka considere le consommateur comme defaillant et declenche un rebalancing ; session.timeout.ms determine le delai apres lequel l'absence de heartbeat provoque l'eviction du consommateur. Des valeurs trop courtes provoquent des rebalancings intempestifs, des valeurs trop longues retardent la detection des pannes.
Kafka Connect et le Change Data Capture avec Debezium
Le Change Data Capture (CDC) constitue l'un des patterns les plus transformateurs du data engineering moderne. Au lieu d'interroger periodiquement les bases de donnees sources via des requetes SQL (pattern pull), le CDC intercepte les modifications directement depuis le journal de transactions (WAL pour PostgreSQL, binlog pour MySQL) et les publie en temps reel dans des topics Kafka.
Debezium, le connecteur CDC open-source de reference, s'integre dans l'ecosysteme Kafka Connect. Cette architecture presente trois avantages majeurs : une latence de propagation inferieure a 100 ms, aucune charge supplementaire sur la base source (la lecture du WAL est un mecanisme passif), et la capture exhaustive de toutes les operations y compris les suppressions (DELETE), impossible a obtenir avec un polling par timestamp.
{
"name": "postgres-cdc-source",
"config": {
"connector.class": "io.debezium.connector.postgresql.PostgresConnector",
"database.hostname": "postgres-primary",
"database.port": "5432",
"database.user": "replication_user",
"database.dbname": "production",
"topic.prefix": "cdc",
"table.include.list": "public.orders,public.customers,public.products",
"slot.name": "debezium_slot",
"plugin.name": "pgoutput",
"publication.name": "dbz_publication",
"snapshot.mode": "initial",
"transforms": "route",
"transforms.route.type": "org.apache.kafka.connect.transforms.RegexRouter",
"transforms.route.regex": "cdc\\.public\\.(.*)",
"transforms.route.replacement": "warehouse.$1"
}
}Cette configuration capture les tables orders, customers et products d'une base PostgreSQL. Le parametre snapshot.mode: initial declenche une capture integrale de l'etat courant des tables au premier demarrage, avant de basculer en mode streaming des modifications incrementales. La transformation RegexRouter renomme les topics de sortie : cdc.public.orders devient warehouse.orders, facilitant le routage vers les systemes en aval.
Le slot de replication (debezium_slot) garantit que PostgreSQL conserve les segments WAL necessaires a Debezium, meme si celui-ci prend du retard dans sa consommation. Cependant, un connecteur arrete pendant une periode prolongee peut provoquer une accumulation importante de fichiers WAL sur le serveur PostgreSQL, risquant de saturer le disque. En production, il est indispensable de monitorer la taille du slot de replication et de configurer des alertes appropriees.
Prêt à réussir tes entretiens Data Engineering ?
Entraîne-toi avec nos simulateurs interactifs, fiches express et tests techniques.
Semantique exactly-once : transactions de bout en bout
La semantique exactly-once represente le niveau de garantie le plus eleve dans un pipeline de streaming. Sans ce mecanisme, un pipeline fonctionne soit en at-most-once (risque de perte de messages), soit en at-least-once (risque de doublons). Les transactions Kafka, introduites dans la version 0.11 et perfectionnees depuis, permettent d'atteindre l'exactly-once en combinant trois elements : l'idempotence du producteur, les transactions atomiques et l'isolation read_committed cote consommateur.
Le pattern consume-transform-produce illustre cette semantique : le pipeline lit un message source, applique une transformation, produit le resultat dans un topic de sortie et commite l'offset source, le tout dans une transaction atomique unique.
# exactly_once_pipeline.py — Transactional consume-transform-produce
from confluent_kafka import Consumer, Producer
import json
def transactional_pipeline():
consumer = Consumer({
'bootstrap.servers': 'kafka-1:9092',
'group.id': 'etl-transformer',
'enable.auto.commit': False,
'isolation.level': 'read_committed'
})
producer = Producer({
'bootstrap.servers': 'kafka-1:9092',
'transactional.id': 'etl-transformer-001',
'acks': 'all',
'enable.idempotence': True
})
producer.init_transactions()
consumer.subscribe(['raw-events'])
while True:
msg = consumer.poll(1.0)
if msg is None or msg.error():
continue
# Begin atomic transaction
producer.begin_transaction()
try:
raw = json.loads(msg.value())
enriched = transform(raw)
# Write transformed record to output topic
producer.produce(
'enriched-events',
key=msg.key(),
value=json.dumps(enriched).encode('utf-8')
)
# Commit consumer offset within the same transaction
producer.send_offsets_to_transaction(
consumer.position(consumer.assignment()),
consumer.consumer_group_metadata()
)
producer.commit_transaction()
except Exception:
producer.abort_transaction()
def transform(record: dict) -> dict:
# Apply business logic transformations
record['processed_at'] = '2026-04-20T00:00:00Z'
record['pipeline_version'] = '2.1'
return recordLe transactional.id identifie de maniere unique chaque instance du pipeline. Lors d'un redemarrage, Kafka utilise cet identifiant pour detecter et annuler les transactions inachevees de l'instance precedente, empechant toute duplication. La methode send_offsets_to_transaction integre le commit de l'offset consommateur dans la meme transaction que la production du message transforme : les deux operations reussissent ou echouent ensemble, garantissant l'atomicite du traitement.
Les transactions Kafka ne sont pas gratuites en termes de performance. Chaque transaction genere des ecritures supplementaires dans les topics internes __transaction_state et __consumer_offsets. Pour les pipelines a tres haut debit, il est essentiel de regrouper plusieurs messages dans une seule transaction (typiquement 100 a 500 messages par batch) plutot que de creer une transaction par message. Cette approche reduit drastiquement l'overhead transactionnel tout en conservant les garanties exactly-once.
Share Groups : un nouveau modele de consommation (KIP-932)
Les Share Groups, introduits dans Kafka 4.0 et stabilises dans Kafka 4.2, representent une evolution fondamentale du modele de consommation. Dans le modele classique des consumer groups, chaque partition est assignee exclusivement a un seul consommateur. Les Share Groups rompent cette contrainte en permettant a plusieurs consommateurs de traiter simultanement les messages d'une meme partition, avec un systeme d'acknowledgement individuel par message.
Ce modele emprunte les mecanismes des files de messages traditionnelles (RabbitMQ, Amazon SQS) tout en s'appuyant sur l'infrastructure durable et repliquee de Kafka. Il repond a un besoin specifique : les workloads ou la distribution uniforme de la charge prime sur l'ordre de traitement.
| Caracteristique | Consumer Groups classiques | Share Groups (KIP-932) | |----------------|---------------------------|------------------------| | Assignation des partitions | Une partition = un consommateur | Une partition = plusieurs consommateurs | | Ordre de traitement | Garanti au sein d'une partition | Non garanti | | Acknowledgement | Par offset (commit) | Par message individuel | | Cas d'usage principal | Pipelines avec ordre strict | Traitement de files de travail | | Scalabilite | Limitee au nombre de partitions | Independante du nombre de partitions | | Retraitement | Rejeu depuis un offset | Redelivery par message (NACK) | | Disponibilite | Stable depuis Kafka 0.9 | Early access dans Kafka 4.0, stable dans 4.2 |
Les deux modeles coexistent et se completent. Un pipeline ETL exigeant l'ordre chronologique des evenements continue d'utiliser les consumer groups classiques. La generation de rapports, l'envoi de notifications ou l'execution de taches de calcul distribue beneficient directement des Share Groups, dont la scalabilite n'est plus contrainte par le nombre de partitions du topic.
Questions d'entretien Kafka pour les data engineers
Les entretiens techniques pour des postes de data engineer en 2026 evaluent systematiquement la maitrise de Kafka. Les questions suivantes couvrent les concepts les plus frequemment abordes.
Comment Kafka assure-t-il l'ordre des messages ? L'ordre est garanti exclusivement au sein d'une partition. Les messages partageant une meme cle de partitionnement sont diriges vers la meme partition et traites sequentiellement. Aucun ordre global n'est garanti entre partitions differentes. Un topic a partition unique offre un ordre total mais elimine tout parallelisme.
Quelle distinction entre at-least-once, at-most-once et exactly-once ? At-most-once : l'offset est commite avant le traitement ; un crash provoque une perte de messages. At-least-once : l'offset est commite apres le traitement ; un crash entraine un retraitement. Exactly-once : les transactions atomiques, l'idempotence du producteur et l'isolation read_committed garantissent un traitement unique de chaque message, meme en cas de defaillance.
Qu'est-ce que le rebalancing et comment le minimiser ?
Le rebalancing survient lorsqu'un consommateur rejoint ou quitte un consumer group. Kafka suspend temporairement le traitement et redistribue les partitions. Pour reduire l'impact, utiliser le Cooperative Sticky Assignor, qui ne reassigne que les partitions affectees au lieu de toutes les partitions du groupe. Dimensionner correctement session.timeout.ms et max.poll.interval.ms evite les rebalancings intempestifs.
Quand privilegier Kafka Streams par rapport a Apache Flink ? Kafka Streams est une bibliotheque Java embarquee dans l'application, sans cluster de traitement dedie. Cette approche convient aux transformations de complexite moderee. Apache Flink est un moteur de traitement distribue autonome, adapte aux traitements complexes (windowing avance, pattern detection, jonctions multi-flux) et aux volumes extremes depassant le million de messages par seconde.
Comment gerer l'evolution des schemas ? Le Schema Registry (Confluent ou Apicurio) centralise la gestion des schemas Avro, Protobuf ou JSON Schema. Les politiques de compatibilite (BACKWARD, FORWARD, FULL) empechent les modifications destructrices de casser les consommateurs existants. Le schema est reference par identifiant dans chaque message, permettant au consommateur de deserialiser correctement quelle que soit la version.
Quels sont les indicateurs de monitoring essentiels ? Le consumer lag (ecart entre l'offset produit et l'offset consomme) detecte les retards de traitement. Les under-replicated partitions signalent des problemes de replication. La latence des requetes produce/fetch revele les goulots d'etranglement. L'utilisation disque anticipe les saturations. Prometheus avec les metriques JMX de Kafka, associe a Grafana, fournit la stack de monitoring standard.
Conclusion
Apache Kafka s'est impose comme l'infrastructure de streaming de reference pour les data engineers. Les concepts cles a retenir :
- KRaft (Kafka 4.x) elimine la dependance a ZooKeeper et simplifie radicalement le deploiement, la configuration et l'administration des clusters
- Le partitionnement determine l'ordre de traitement et la scalabilite -- une cle mal choisie genere des hot partitions qui degradent l'ensemble du pipeline
- Le commit manuel des offsets avec
enable.auto.commit: Falseetisolation.level: read_committedconstitue le socle d'un consumer pipeline fiable en mode at-least-once - Le CDC via Debezium capture les modifications de bases de donnees en temps reel depuis le WAL, avec une latence inferieure a 100 ms et sans impact sur la base source
- Les transactions exactly-once garantissent l'atomicite du pattern consume-transform-produce, a condition de regrouper les messages en batches pour maitriser l'overhead
- Les Share Groups (KIP-932) introduisent un modele de consommation par message, adapte aux files de travail ou l'ordre de traitement n'est pas requis
- Le monitoring du consumer lag, des partitions sous-repliquees et de la latence est indispensable pour prevenir les incidents silencieux en production
Passe à la pratique !
Teste tes connaissances avec nos simulateurs d'entretien et tests techniques.
Tags
Partager
Articles similaires

Top 25 Questions d'Entretien Data Engineering en 2026
Les questions d'entretien data engineering les plus fréquentes en 2026 : SQL avancé, pipelines temps réel, architecture lakehouse, Spark, Airflow et optimisation des coûts cloud.

ETL vs ELT en 2026 : Guide Complet de l'Architecture des Pipelines de Données
Guide technique comparant les architectures ETL et ELT pour les pipelines de données en 2026. Inclut des exemples de code Python et dbt, des tableaux comparatifs de coûts, et des recommandations pratiques pour choisir la bonne approche.

Apache Spark avec Python : construire des pipelines de données étape par étape
Tutoriel pratique PySpark couvrant les opérations DataFrame, la construction de pipelines ETL et les fonctionnalités de Spark 4.0. Inclut des exemples de code prêts pour la production destinés aux data engineers qui préparent leurs entretiens techniques.