Publications

Delayed Messages in Kafka: Three Designs for a Slow Consumer

A Kafka partition is read in order, so one client that stops answering holds back every message behind it. Where a message can wait without blocking the others, and what each answer costs

Résumé

Un consommateur Kafka livre des notifications à plusieurs clients. Le jour où l’un d’eux ne répond plus, le consommateur réessaie son message, et tout le reste attend derrière: une partition se lit dans l’ordre et l’offset est une position, pas un accusé de réception par message. Kafka 2.3 ne propose aucune livraison différée, et un consommateur qui attend trop longtemps sans appeler poll est exclu de son groupe. Le délai se conçoit.

Première approche: un groupe de consommateurs par client. Chaque groupe lit tout le topic, garde les messages de son client et ignore les autres. Un client lent ne retarde que lui, l’ordre par client est conservé, au prix d’une lecture complète du topic par client et d’un groupe à déployer pour chaque nouveau client. Deuxième approche: un seul topic de délai pour tout le monde. Le message non livré y est réécrit, enveloppé avec son topic d’origine, sa clé, sa valeur et son heure d’échéance, puis un consommateur planifié chaque minute le republie une fois le délai écoulé. C’est peu coûteux, mais l’ordre est perdu et la boucle a besoin d’une limite et d’une file des lettres mortes.

Une troisième approche, publiée par Uber Engineering en 2018, enchaîne des topics de retry par paliers, avec un délai croissant à chaque niveau. pause() et resume() permettent d’attendre sans quitter le groupe, et suffisent seuls quand toute la dépendance aval est en panne. Quel que soit le choix, chaque consommateur doit être idempotent, car recevoir deux fois le même message est probable.

Idées clés

  • Une partition Kafka se lit dans l’ordre et l’offset valide tout ce qui précède: un message qui échoue retient tous ceux qui le suivent, même ceux dont le client fonctionne.
  • Un groupe de consommateurs par client isole un client lent et garde l’ordre par client, au prix d’une lecture du topic entier par chaque groupe.
  • Un topic de délai unique, vidé chaque minute par un consommateur planifié, coûte peu mais perd l’ordre; un délai fixe garde ce topic trié par échéance, et la boucle exige une limite et une file des lettres mortes.
  • Les topics de retry par paliers donnent un délai croissant; pause() permet d’attendre sans déclencher de rééquilibrage, et suffit quand toute la dépendance aval est en panne.
  • Chaque approche livre certains messages deux fois: tout consommateur doit être idempotent.