Jour 34 Day 34 · mardi 22 septembre 2026 Tuesday 22 September 2026 Backend Avancé
Kafka & l'event-driven Kafka & event-driven architecture
Log distribué, partitions, consumer groups, replay : comprendre ce qui distingue Kafka d'une file de messages classique — et savoir dire honnêtement quand c'est overkill. Distributed log, partitions, consumer groups, replay: understand what sets Kafka apart from a classic message queue — and know how to say honestly when it's overkill.
L’essentiel
Kafka n’est pas une file de messages : c’est un log distribué append-only. La différence est fondamentale. Dans une queue classique (RabbitMQ), consommer un message le retire de la file : le message est un ordre à exécuter une fois. Dans Kafka, les événements sont écrits à la suite dans un journal immuable et les consommateurs se contentent d’avancer un curseur (offset) dans ce journal : lire ne détruit rien. Dix équipes peuvent lire le même flux, chacune à son rythme, et revenir en arrière.
Ce modèle fait de Kafka la colonne vertébrale des architectures event-driven : les services ne s’appellent plus directement, ils publient des faits (« commande n°42 créée ») que d’autres services consomment quand ils veulent. Découplage dans le temps, dans le débit, et dans le nombre de consommateurs.
| Kafka | RabbitMQ | |
|---|---|---|
| Modèle | Log distribué append-only | File + routage (exchanges) |
| Lecture | Non destructive : chaque consumer a son offset | Destructive : message ack = supprimé |
| Rétention | Par durée/taille (jours, ∞) → replay possible | Jusqu’à consommation |
| Ordre | Garanti par partition uniquement | Par queue (perdu avec plusieurs workers) |
| Débit | Très élevé (écriture séquentielle, batch) | Élevé, mais en deçà à gros volume |
| Routage | Simple : topics + clé de partition | Riche : exchanges, bindings, priorités |
| Idéal pour | Streaming, gros volumes, plusieurs lecteurs, replay | Tâches asynchrones, jobs, routage fin |
Comment ça marche
Un topic est découpé en partitions. Chaque partition est un journal ordonné et immuable ; chaque message y reçoit un offset croissant. C’est la partition, pas le topic, qui est l’unité d’ordre et de parallélisme.
Topic "orders" — 3 partitions, groupe "billing"
P0 |0|1|2|3|4|5|─▶ ────▶ consumer A ┐
P1 |0|1|2|3|─▶ ────▶ consumer B │ groupe
P2 |0|1|2|3|4|─▶ ────▶ consumer B ┘ "billing"
Autre groupe "analytics" : un consumer C lit
P0+P1+P2 avec ses propres offsets, indépendants.
- Producteurs : écrivent dans le topic. Avec une clé (
key=user-42), le hash de la clé choisit la partition → tous les événements d’une même clé finissent dans la même partition, donc dans l’ordre. Sans clé : round-robin. - Consumer groups : au sein d’un groupe, chaque partition est assignée à exactement un consumer. Trois partitions = trois consumers actifs au maximum ; le quatrième reste inactif. Ajouter/retirer un consumer déclenche un rebalance (réassignation des partitions).
- Offsets : chaque groupe committe sa position par partition. Kafka ne « pousse » rien et n’efface rien à la lecture : les messages expirent selon la rétention configurée (ex. 7 jours), qu’ils aient été lus ou non.
- Replay : puisque le journal reste là, on peut remettre l’offset à zéro et tout rejouer — pour reconstruire un cache, alimenter un nouveau service, ou rejouer après un bug de traitement. C’est le super-pouvoir que les queues classiques n’ont pas.
from kafka import KafkaProducer, KafkaConsumer
# --- Producteur ---
producer = KafkaProducer(bootstrap_servers="localhost:9092")
# La clé détermine la partition : même user → même partition
# → l'ordre des événements de user-42 est préservé
producer.send("orders", key=b"user-42", value=b'{"total": 99}')
producer.flush() # forcer l'envoi du batch
# --- Consommateur ---
consumer = KafkaConsumer(
"orders",
group_id="billing", # membres du groupe = partitions partagées
enable_auto_commit=False, # commit manuel : on contrôle le moment
auto_offset_reset="earliest", # 1er démarrage : lire depuis le début
)
for msg in consumer:
process(msg) # traiter AVANT de committer l'offset
consumer.commit() # crash avant cette ligne → le message
# sera relivré : at-least-once, donc
# process() doit être idempotent
⚠️ L’ordre global n’existe pas — Kafka garantit l’ordre à l’intérieur d’une partition, jamais entre partitions. Deux événements de clés différentes peuvent être consommés dans n’importe quel ordre. Toute la conception tient dans le choix de la clé : les événements qui doivent rester ordonnés entre eux (ceux d’une même commande, d’un même user) doivent partager la même clé. Dire « Kafka garantit l’ordre » sans cette nuance est une erreur classique en entretien.
Concepts clés à maîtriser
- Sémantiques de livraison : par défaut c’est at-least-once — si le consumer crash entre le traitement et le commit d’offset, le message est relivré. Committer avant de traiter donne de l’at-most-once (perte possible). L’exactly-once existe (producteur idempotent + transactions) mais son périmètre est surtout Kafka→Kafka (Kafka Streams) ; dès qu’un système externe est impliqué (DB, API), la réponse pragmatique est at-least-once + consumer idempotent (clé unique, upsert, dédoublonnage).
- Choix du nombre de partitions : c’est le plafond de parallélisme d’un groupe. Trop peu = consumers qui saturent ; beaucoup trop = surcoût de gestion et rebalances lents. Piège : augmenter le nombre de partitions change le mapping clé→partition pour les nouveaux messages — l’ordre par clé n’est plus garanti à cheval sur le changement.
- Consumer lag : l’écart entre le dernier offset produit et l’offset committé du groupe. C’est LA métrique à surveiller : un lag qui croît = consommateurs qui ne suivent plus.
- Event sourcing (survol honnête) : stocker les événements comme source de vérité (« CompteCredité +50 ») et reconstruire l’état en les rejouant, plutôt que stocker l’état courant. Souvent couplé à CQRS : séparer le modèle d’écriture (commandes → événements) du modèle de lecture (projections optimisées). Kafka en est un support naturel (log durable, replay), mais soyons honnêtes : l’event sourcing complet est un engagement architectural lourd (versionnage des événements, projections à maintenir, courbe d’apprentissage). La plupart des systèmes « event-driven » en production font plus simplement de la notification d’événements entre services — c’est déjà très bien.
- Kafka n’est pas une base de données ni un bus RPC : pas de requêtes, pas de lecture par clé, pas de réponse synchrone.
💡 La question inverse : quand Kafka est overkill — un monolithe, un seul consommateur, quelques centaines de messages par minute ? Une table
jobsen Postgres, RabbitMQ, ou Redis Streams font l’affaire pour une fraction du coût opérationnel. Kafka, c’est un cluster de brokers à opérer, du monitoring, des rebalances à comprendre. Le choisir se justifie par : gros volumes, plusieurs consommateurs indépendants, besoin de replay/rétention, ou streaming temps réel. Savoir dire « ici, Kafka serait overkill » est un excellent signal en entretien.
En entretien
« Kafka vs RabbitMQ ? » — Commencer par le modèle : RabbitMQ est une file (message consommé = supprimé, routage riche, parfait pour distribuer des tâches) ; Kafka est un log append-only (lecture non destructive par offset, rétention, replay, plusieurs groupes de consommateurs indépendants, débit massif). Puis un cas d’usage chacun : jobs asynchrones → RabbitMQ ; pipeline d’événements lu par facturation + analytics + audit → Kafka.
« Comment Kafka garantit-il l’ordre ? » — Il ne le garantit que par partition. Le producteur hash la clé pour choisir la partition : même clé → même partition → ordre préservé pour cette clé. Pas de clé, ou clés différentes → aucun ordre global. Le choix de la clé est donc une décision de design, pas un détail.
« Que se passe-t-il si j’ajoute un 4ᵉ consumer à un groupe sur un topic à 3 partitions ? » — Rien pour lui : une partition ne peut être assignée qu’à un consumer du groupe, donc il reste idle (utile seulement comme standby). Le parallélisme maximal d’un groupe = le nombre de partitions.
« Exactly-once, c’est possible ? » — Réponse honnête : Kafka fournit producteur idempotent et transactions, ce qui donne de l’exactly-once dans le périmètre Kafka→Kafka (Streams). Bout en bout avec une DB ou une API externe, on vise at-least-once + idempotence côté consommateur (contrainte unique, upsert, table de dédoublonnage). Répondre « oui, il suffit d’un flag » est un drapeau rouge.
« C’est quoi l’event sourcing ? » — Stocker la séquence d’événements comme source de vérité et dériver l’état en les rejouant ; souvent associé à CQRS (modèles écriture/lecture séparés). Avantages : audit complet, replay, projections multiples. Coût : complexité réelle (versionnage, projections). Bonus : préciser qu’on peut être event-driven sans faire d’event sourcing.
Pièges & idées reçues
- « Kafka est une queue » — non : lire ne supprime rien, la rétention est temporelle, et plusieurs groupes lisent le même flux indépendamment. La moitié des erreurs de design viennent de cette confusion.
- Consommer sans idempotence : l’at-least-once par défaut va produire des doublons un jour (crash, rebalance). Si le traitement n’est pas idempotent, c’est un bug latent, pas un détail.
- Le rebalance n’est pas gratuit : pendant la réassignation, la consommation s’interrompt. Des consumers qui redémarrent en boucle = un groupe qui ne consomme presque plus.
- Augmenter les partitions « pour scaler » sans penser au mapping clé→partition, qui change pour les nouveaux messages.
- Ignorer le consumer lag jusqu’au jour où le retard se compte en heures — c’est la métrique de santé n°1 d’un consommateur.
🎤 En entretien — si on vous demande de « concevoir un système de commandes avec Kafka », posez d’emblée la question de la clé de partitionnement (« order-id, pour garantir l’ordre des événements d’une même commande ») et mentionnez l’idempotence du consumer. Ces deux réflexes montrent que vous avez compris le modèle, pas juste retenu le vocabulaire.
Pour aller plus loin
- Kafka — documentation officielle, en particulier l’introduction « Kafka in a nutshell »
- Confluent Developer : cours gratuits, dont « Kafka 101 » (vidéos courtes)
- Turning the database inside-out — Martin Kleppmann, et son livre Designing Data-Intensive Applications (chapitre 11)
- Event Sourcing et CQRS — Martin Fowler, qui met lui-même en garde contre l’usage systématique
The essentials
Kafka is not a message queue: it’s a distributed append-only log. The difference is fundamental. In a classic queue (RabbitMQ), consuming a message removes it from the queue: the message is an order to execute once. In Kafka, events are appended to an immutable journal and consumers merely advance a cursor (offset) through that journal: reading destroys nothing. Ten teams can read the same stream, each at its own pace, and rewind.
This model makes Kafka the backbone of event-driven architectures: services no longer call each other directly, they publish facts (“order #42 created”) that other services consume whenever they want. Decoupling in time, in throughput, and in the number of consumers.
| Kafka | RabbitMQ | |
|---|---|---|
| Model | Distributed append-only log | Queue + routing (exchanges) |
| Reading | Non-destructive: each consumer has its offset | Destructive: acked message = deleted |
| Retention | By time/size (days, ∞) → replay possible | Until consumed |
| Ordering | Guaranteed per partition only | Per queue (lost with multiple workers) |
| Throughput | Very high (sequential writes, batching) | High, but below at large volume |
| Routing | Simple: topics + partition key | Rich: exchanges, bindings, priorities |
| Best for | Streaming, large volumes, multiple readers, replay | Async tasks, jobs, fine-grained routing |
How it works
A topic is split into partitions. Each partition is an ordered, immutable log; each message in it gets an increasing offset. The partition, not the topic, is the unit of ordering and parallelism.
Topic "orders" — 3 partitions, group "billing"
P0 |0|1|2|3|4|5|─▶ ────▶ consumer A ┐
P1 |0|1|2|3|─▶ ────▶ consumer B │ group
P2 |0|1|2|3|4|─▶ ────▶ consumer B ┘ "billing"
Another group "analytics": a consumer C reads
P0+P1+P2 with its own, independent offsets.
- Producers write to the topic. With a key (
key=user-42), the key’s hash picks the partition → all events for the same key end up in the same partition, hence in order. Without a key: round-robin. - Consumer groups: within a group, each partition is assigned to exactly one consumer. Three partitions = at most three active consumers; the fourth sits idle. Adding/removing a consumer triggers a rebalance (partition reassignment).
- Offsets: each group commits its position per partition. Kafka doesn’t “push” anything and deletes nothing on read: messages expire according to the configured retention (e.g. 7 days), read or not.
- Replay: since the journal stays put, you can reset the offset to zero and replay everything — to rebuild a cache, feed a new service, or reprocess after a bug. That’s the superpower classic queues don’t have.
from kafka import KafkaProducer, KafkaConsumer
# --- Producer ---
producer = KafkaProducer(bootstrap_servers="localhost:9092")
# The key determines the partition: same user → same partition
# → the order of user-42's events is preserved
producer.send("orders", key=b"user-42", value=b'{"total": 99}')
producer.flush() # force the batch out
# --- Consumer ---
consumer = KafkaConsumer(
"orders",
group_id="billing", # group members share the partitions
enable_auto_commit=False, # manual commit: we control the timing
auto_offset_reset="earliest", # first start: read from the beginning
)
for msg in consumer:
process(msg) # process BEFORE committing the offset
consumer.commit() # crash before this line → the message
# is redelivered: at-least-once, so
# process() must be idempotent
⚠️ Global ordering does not exist — Kafka guarantees order within a partition, never across partitions. Two events with different keys can be consumed in any order. The whole design lives in the choice of key: events that must stay ordered relative to each other (those of one order, one user) must share the same key. Saying “Kafka guarantees ordering” without this nuance is a classic interview mistake.
Key concepts to master
- Delivery semantics: the default is at-least-once — if the consumer crashes between processing and committing the offset, the message is redelivered. Committing before processing gives at-most-once (possible loss). Exactly-once exists (idempotent producer + transactions) but its scope is mostly Kafka→Kafka (Kafka Streams); as soon as an external system is involved (DB, API), the pragmatic answer is at-least-once + an idempotent consumer (unique key, upsert, deduplication).
- Choosing the partition count: it’s the parallelism ceiling of a group. Too few = saturated consumers; way too many = management overhead and slow rebalances. Trap: increasing the partition count changes the key→partition mapping for new messages — per-key ordering is no longer guaranteed across the change.
- Consumer lag: the gap between the latest produced offset and the group’s committed offset. THE metric to watch: a growing lag = consumers falling behind.
- Event sourcing (an honest overview): store the events as the source of truth (“AccountCredited +50”) and rebuild state by replaying them, instead of storing current state. Often paired with CQRS: separating the write model (commands → events) from the read model (optimized projections). Kafka is a natural fit (durable log, replay), but let’s be honest: full event sourcing is a heavy architectural commitment (event versioning, projections to maintain, learning curve). Most “event-driven” systems in production more simply do event notification between services — and that’s already plenty.
- Kafka is not a database nor an RPC bus: no queries, no lookup by key, no synchronous response.
💡 The reverse question: when Kafka is overkill — a monolith, a single consumer, a few hundred messages per minute? A
jobstable in Postgres, RabbitMQ, or Redis Streams do the job at a fraction of the operational cost. Kafka means a broker cluster to operate, monitoring, rebalances to understand. Choosing it is justified by: large volumes, several independent consumers, replay/retention needs, or real-time streaming. Being able to say “here, Kafka would be overkill” is an excellent interview signal.
In an interview
“Kafka vs RabbitMQ?” — Start with the model: RabbitMQ is a queue (consumed message = deleted, rich routing, perfect for distributing tasks); Kafka is an append-only log (non-destructive reads by offset, retention, replay, several independent consumer groups, massive throughput). Then one use case each: async jobs → RabbitMQ; an event pipeline read by billing + analytics + audit → Kafka.
“How does Kafka guarantee ordering?” — It only guarantees it per partition. The producer hashes the key to pick the partition: same key → same partition → order preserved for that key. No key, or different keys → no global order. Choosing the key is a design decision, not a detail.
“What happens if I add a 4th consumer to a group on a 3-partition topic?” — Nothing, for it: a partition can only be assigned to one consumer in the group, so it sits idle (useful only as standby). A group’s maximum parallelism = the partition count.
“Is exactly-once possible?” — Honest answer: Kafka provides an idempotent producer and transactions, which gives exactly-once within the Kafka→Kafka scope (Streams). End to end with a DB or external API, you aim for at-least-once + consumer-side idempotence (unique constraint, upsert, dedup table). Answering “yes, just set a flag” is a red flag.
“What is event sourcing?” — Storing the sequence of events as the source of truth and deriving state by replaying them; often paired with CQRS (separate write/read models). Benefits: full audit trail, replay, multiple projections. Cost: real complexity (versioning, projections). Bonus: point out you can be event-driven without doing event sourcing.
Pitfalls & misconceptions
- “Kafka is a queue” — no: reading deletes nothing, retention is time-based, and several groups read the same stream independently. Half of all design mistakes stem from this confusion.
- Consuming without idempotence: the default at-least-once will produce duplicates one day (crash, rebalance). If processing isn’t idempotent, that’s a latent bug, not a detail.
- Rebalances aren’t free: during reassignment, consumption pauses. Consumers stuck in a restart loop = a group that barely consumes.
- Adding partitions “to scale” without thinking about the key→partition mapping, which changes for new messages.
- Ignoring consumer lag until the backlog is measured in hours — it’s a consumer’s number one health metric.
🎤 In an interview — if asked to “design an order system with Kafka”, immediately raise the partition key question (“order-id, to guarantee ordering of one order’s events”) and mention consumer idempotence. Those two reflexes show you understood the model, not just memorized the vocabulary.
Going further
- Kafka — official documentation, especially the “Kafka in a nutshell” introduction
- Confluent Developer: free courses, including “Kafka 101” (short videos)
- Turning the database inside-out — Martin Kleppmann, and his book Designing Data-Intensive Applications (chapter 11)
- Event Sourcing and CQRS — Martin Fowler, who himself warns against using them everywhere