12 - Kafka en pratique: producteurs, topics et partitions

Kafka est souvent le cœur d’une architecture event-driven. Dans un contexte bancaire, il n’est pas seulement un bus de messages: il devient la colonne vertébrale de la traçabilité et du replay. Ce chapitre se concentre sur la partie producer et la conception des topics: ce sont les décisions qui fixent la qualité du log pour les années à venir.

L’objectif n’est pas d’obtenir “un flux qui marche”, mais un flux fiable, traçable et exploitable, même lors d’incidents. En banque, une mauvaise configuration de producer peut se traduire par des pertes d’événements, des latences non maîtrisées, ou des doublons coûteux à corriger.

Du producer au topic: le chemin d’un event

Un producer sérialise un event, choisit une clé de partition, puis l’envoie vers un topic. Kafka persiste l’event dans une partition, réplique les données et retourne un ACK selon le niveau de garantie. La séquence est simple, mais chaque paramètre a un impact fort sur la fiabilité.

Trois questions fondamentales

Avant d’envoyer le moindre event, le producer doit répondre à trois questions: 1) Quelle est la clé de partition ? Elle définit l’ordre garanti. 2) Quel est le niveau de garantie ? (acks=all, idempotence, transactions). 3) Quel est le contrat de schéma ? (versioning, compatibilité).

Dans un système bancaire, ces choix ne sont pas techniques mais métiers: ils influencent l’audit, la cohérence comptable et la conformité.

Choisir la clé de partition: décision métier

L’ordre n’est garanti qu’à l’intérieur d’une partition. La clé de partition est donc l’élément le plus important pour préserver les invariants métier.

  • Pour des opérations de compte: account_id garantit l’ordre des débits/crédits.
  • Pour un flux de virement: transfer_id suffit si l’ordre par virement est suffisant, mais ne garantit pas l’ordre des opérations d’un compte.
  • Pour des flux multi-comptes (ex: virements entre banques): utiliser un identifiant stable qui correspond à l’agrégat métier dominant.

Piège classique: choisir une clé trop générique (ex: customer_id) qui mélange plusieurs comptes et casse l’ordre critique. Autre piège: une clé trop concentrée crée un hot partition et limite la scalabilité.

Partitioning & key definition

Une convention simple consiste à utiliser le pattern:

<entity-type>.<entity-key>

Exemples bancaires: - account.ACC-001 - transfer.T-913 - card.C-5549

Si l’ordre est non critique ou si l’on cherche une distribution équilibrée, une clé null est acceptable, mais elle supprime toute garantie d’ordering.

Paramètres producer: fiabilité avant performance

Le producer est responsable de la qualité du log. Dans un domaine bancaire, la fiabilité prime sur la latence pure. Les paramètres clés sont:

acks

  • acks=0: pas de garantie, risque de perte silencieuse.
  • acks=1: ACK du leader seulement.
  • acks=all: ACK après réplication sur l’ISR (in-sync replicas).

Recommandation bancaire: acks=all + min.insync.replicas >= 2.

Idempotence et transactions

L’idempotence garantit qu’un event n’est pas écrit deux fois même en cas de retry. Avec enable.idempotence=true, Kafka assigne un Producer ID et gère la séquence. Couplé aux transactions, on peut garantir un flux cohérent de bout en bout.

En banque, l’idempotence est essentielle pour éviter des doublons comptables. L’outbox pattern complète cette garantie en alignant la transaction métier et la publication Kafka.

Retries et backoff

Les retries ne doivent pas être infinis. En cas d’erreur transitoire, on retente; en cas d’erreur structurelle, on envoie vers une DLQ.

Un design robuste distingue: - erreurs transitoires (réseau, broker indisponible), - erreurs permanentes (schéma invalide, payload corrompu).

Batching et latence

Les paramètres batch.size et linger.ms déterminent la latence vs le débit. Dans un environnement bancaire, un léger buffering (ex: linger.ms=10) est acceptable si la latence reste dans le SLA.

Compression

La compression réduit la bande passante et le stockage: - snappy: rapide, bon compromis. - lz4: latence très faible. - zstd: ratio plus élevé, coût CPU plus fort.

Recommandation: snappy pour la plupart des flux temps réel, zstd pour les flux analytiques ou volumineux.

Contrat d’événements: schéma et metadata

Un event exploitable doit contenir un envelope stable. Exemple:

{
  "event_id": "uuid",
  "type": "TransferInitiated",
  "occurred_at": "2024-01-13T10:00:00Z",
  "schema_version": 1,
  "correlation_id": "corr-123",
  "causation_id": "cmd-456",
  "payload": {
    "transfer_id": "T-913",
    "from_account": "ACC-001",
    "to_account": "ACC-987",
    "amount_minor": 59090,
    "currency": "EUR"
  }
}

Le schema_version permet l’évolution contrôlée. Le correlation_id trace un flux complet (utile pour audit). Le causation_id relie l’event à la command.

Design des topics: public vs private, fact vs delta

Un topic public est un contrat d’intégration. Il doit être stable, versionné, documenté. Un topic private peut évoluer plus vite.

  • Fact: fait complet, durable. Exemple: TransferSettled.
  • Delta: variation, utile en interne (ex: balance_delta).

En banque, les events publics doivent presque toujours être des facts pour éviter le couplage aux modèles internes.

Naming des topics

La gouvernance commence par le naming. Un format recommandé:

<environment>.<service>.<domain>.<type>.<action>.<version>

Exemples: - prod.corebanking.account.evt.opened.v1 - prod.payments.transfer.cmd.initiate.v1 - prod.risk.transfer.evt.scored.v1

Bonnes pratiques de base

  • Distinguer commandes et événements dans des topics séparés.
    • prod.payments.transfer.cmd.initiate.v1
    • prod.payments.transfer.evt.initiated.v1
  • Créer un topic de DLQ pour chaque flux critique.
    • prod.payments.transfer.dlq.v1
  • Documenter la rétention et la sensibilité des données.
  • Éviter les topics “fourre-tout” qui cassent la gouvernance.

Retention, compaction et log lifecycle

Kafka offre deux modes principaux: - Retention: conserver les events sur une durée fixe (ex: 7 jours). - Compaction: conserver le dernier event par clé.

En banque, les flux transactionnels utilisent la retention longue pour audit. Les flux de référentiels (bénéficiaires, profils KYC) utilisent la compaction pour garder le dernier état.

Une politique de retention doit être cohérente avec les obligations légales. Certains events doivent être conservés plusieurs années, d’autres doivent être purgeables pour conformité RGPD.

Sizing et capacité

Dimensionner un topic n’est pas seulement un problème technique. Il faut estimer: - le volume d’events par seconde, - la taille moyenne de chaque event, - le nombre de partitions nécessaires pour tenir le débit.

Un topic avec trop peu de partitions limite la scalabilité; trop de partitions multiplie les coûts opérationnels et le temps de rebalance. Une banque choisit souvent un compromis: partitions alignées sur la capacité cible + marge.

Producer, outbox et cohérence transactionnelle

Le problème classique est le dual write: la base est mise à jour mais pas Kafka (ou l’inverse). L’outbox pattern résout ce problème: 1) L’application écrit dans une table outbox dans la même transaction que l’état métier. 2) Un relay (poller/CDC) publie les events vers Kafka. 3) L’event est marqué comme traité.

Ce mécanisme garantit que l’état et l’event sont cohérents même en cas de panne. Dans un contexte bancaire, c’est souvent un standard de fait.

Sécurité et classification des données

Les events peuvent contenir des données sensibles (PII, montants, IBAN). Le producer doit respecter des règles strictes: - chiffrement TLS en transit, - ACLs sur topics, - masquage/tokenisation des données sensibles, - séparation des topics par niveau de sensibilité.

Exemple: un event public TransferInitiated peut contenir un identifiant pseudonymisé du compte, tandis que le détail complet reste dans un topic privé.

Exemple bancaire complet: initiation d’un virement

1) L’API reçoit InitiateTransfer. 2) Le service Virements valide les invariants. 3) Une ligne outbox est écrite avec TransferInitiated. 4) Un relay publie dans prod.payments.transfer.evt.initiated.v1. 5) Les consumers (fraude, ledger, notifications) consomment et projettent.

Chaque étape est traçable via correlation_id et peut être rejouée. En cas d’incident, on peut reconstruire l’état ou relancer un flux de manière fiable.

Configuration avancée du producer

max.in.flight.requests.per.connection

Ce paramètre contrôle le nombre de requêtes non acquittées en vol. En cas de retries, un max.in.flight trop élevé peut réordonner les messages. Avec enable.idempotence=true, Kafka gère l’ordre, mais les banques préfèrent souvent une valeur prudente (ex: 5) pour éviter toute ambiguïté.

delivery.timeout.ms et request.timeout.ms

delivery.timeout.ms définit le délai maximum avant qu’un envoi soit considéré comme échoué. Un réglage trop bas génère des erreurs inutiles; trop haut, il retarde la détection d’incidents. En banque, on choisit un délai compatible avec les SLA (ex: 120s pour les flux critiques).

retries et retry.backoff.ms

Les retries doivent être configurés pour absorber les erreurs transitoires sans créer de tempêtes. Un retry.backoff.ms modéré évite de surcharger le broker lors d’une panne partielle.

transactional.id

Si vous utilisez les transactions Kafka, transactional.id doit être stable par instance. Cela permet de garantir qu’un producer remplacé ne publiera pas pas de doublons. Cette discipline est essentielle pour des écritures comptables.

Observabilité côté producer

Un producer en production doit être monitoré, au même titre qu’un consumer. Les métriques utiles incluent: - record-send-rate et record-send-total pour le débit. - record-error-rate pour détecter les anomalies. - batch-size-avg et batch-size-max pour le tuning. - compression-rate-avg pour évaluer l’efficacité.

Dans une banque, ces métriques doivent être corrélées à la latence end-to-end afin de détecter un goulot d’étranglement en amont du flux.

Scripts et commandes utiles

Exemple de création de topic pour un flux bancaire:

kafka-topics.sh --bootstrap-server localhost:9092 \
  --create \
  --replication-factor 3 \
  --partitions 6 \
  --topic prod.payments.transfer.evt.initiated.v1 \
  --config retention.ms=604800000 \
  --config segment.bytes=1073741824 \
  --config cleanup.policy=delete

Cette configuration illustre un topic répliqué (résilience), partitionné (scalabilité), et avec une rétention d’une semaine (audit court). Pour un flux comptable, la rétention peut être bien plus longue.

Exemple de configuration producer (profil bancaire)

acks=all
enable.idempotence=true
max.in.flight.requests.per.connection=5
retries=2147483647
delivery.timeout.ms=120000
request.timeout.ms=30000
linger.ms=10
batch.size=32768
compression.type=snappy

Ce profil privilégie la fiabilité et un débit stable. Il est adapté aux flux critiques où la perte d’event est inacceptable. Pour des flux moins sensibles, on peut réduire la latence en ajustant linger.ms ou batch.size.

Formats de sérialisation: JSON, Avro, Protobuf

Le format impacte la compatibilité et la performance. JSON est lisible mais verbeux, Avro et Protobuf offrent un schéma strict et une taille réduite. En banque, où les contracts doivent être stables, Avro ou Protobuf sont souvent préférés. Le Schema Registry permet de valider la compatibilité en CI et réduit les risques de rupture lors des déploiements. Un compromis courant est de garder un envelope JSON lisible et un payload encodé en Avro.

Gestion du backpressure côté producer

Quand les brokers sont saturés ou que les partitions sont en ISR réduite, le producer peut subir des timeouts. Il faut alors appliquer une stratégie de backpressure: limiter le débit, bufferiser localement, ou refuser temporairement les demandes côté API. Dans un flux bancaire critique, il est préférable de ralentir plutôt que de perdre des events. Les métriques d’erreur et de latency doivent déclencher un mécanisme de protection avant que l’API ne dégrade l’expérience client.\n*** End Patch}“}}

Checklist pratique pour producteurs

  • La clé de partition est-elle alignée avec l’agrégat métier ?
  • acks=all et min.insync.replicas sont-ils configurés ?
  • Idempotence activée ? Transactions nécessaires ?
  • Schéma versionné et validé en CI ?
  • Rétention/compaction définies selon l’obligation légale ?
  • Topic naming conforme et documenté ?

Conclusion

La pratique Kafka côté producer ne se résume pas à “envoyer un message”. Elle engage la cohérence, l’auditabilité et la résilience du système. Dans une banque, ces choix sont structurants pour la conformité et l’exploitation. Maîtriser la clé de partition, la fiabilité et le contrat d’event est la base pour construire un flux event-driven durable.