Cinq erreurs d’évolutivité à éviter avec votre application Kafka

Un homme d’affaires portant des lunettes utilise un ordinateur portable dans un bureau moderne

Apache Kafka est une plateforme de streaming d'événements hautement performante et évolutive. Pour profiter de tout le potentiel de Kafka, vous devez bien réfléchir au design de votre application. Il est extrêmement facile de développer des applications Kafka qui fonctionnent mal ou qui finissent par se heurter à un obstacle en matière d'évolutivité. Depuis 2015, IBM propose le service IBM Event Streams, un service Apache Kafka entièrement géré qui fonctionne sur IBM Cloud. Depuis lors, ce service a aidé de nombreux clients, ainsi que des équipes au sein d'IBM, à résoudre des problèmes d'évolutivité et de performances avec les applications Kafka qu'ils ont développées.

Cet article décrit certains des problèmes courants d'Apache Kafka et fournit quelques recommandations pour éviter de rencontrer des problèmes d'évolutivité avec vos applications.

1. Réduire au minimum l'attente liée aux allers-retours sur le réseau

Certaines opérations Kafka fonctionnent lorsque le client envoie des données au courtier et attend une réponse. Un aller-retour complet peut prendre 10 millisecondes, ce qui semble rapide, mais vous limite à un maximum de 100 opérations par seconde. C'est pourquoi il est recommandé d'éviter ce type d'opérations dans la mesure du possible. Heureusement, les clients Kafka vous permettent de limiter ce problème. Vous devez juste vous assurer d’en profiter.

Conseils pour maximiser le débit :

  1. Ne vérifiez pas si chaque message envoyé a bien été transmis. L'API Kafka vous permet de dissocier l'envoi d'un message de la vérification de sa réception par le courtier. Attendre la confirmation de réception d'un message peut introduire une latence réseau aller-retour dans votre application. Essayez donc de la minimiser dans la mesure du possible. Cela pourrait impliquer d'envoyer autant de messages que possible, avant de vérifier qu'ils ont tous été reçus. Il peut également s'agir de déléguer la vérification de la livraison des messages à un autre fil d'exécution au sein de votre application, afin qu'il puisse fonctionner parallèlement à l'envoi de nouveaux messages.
  2. Ne faites pas suivre le traitement de chaque message par une validation de décalage. La validation des décalages (de manière synchrone) est mise en œuvre sous la forme d'un aller-retour réseau avec le serveur. Vous pouvez soit valider les décalages moins fréquemment, soit utiliser la fonction de validation asynchrone des décalages afin d'éviter de subir les inconvénients de cet aller-retour pour chaque message que vous traitez. Soyez simplement conscient que valider les décalages moins fréquemment peut signifier que davantage de données devront être retraitées en cas de défaillance de votre application.

Si vous avez lu ce qui précède et que vous vous êtes dit : « Oh oh, cela ne va-t-il pas rendre mon application plus complexe ? », la réponse est oui, c'est probable. Il faut trouver un compromis entre le débit et la complexité des applications. Ce qui rend le temps aller-retour réseau particulièrement délicat, c'est qu'une fois cette limite atteinte, il peut être nécessaire d'apporter des modifications importantes à l'application pour améliorer encore le débit.

2. Ne pas confondre l'augmentation des délais de traitement avec des problèmes liés aux consommateurs

Une fonctionnalité utile de Kafka est qu'il surveille la « vitalité » des applications consommatrices et déconnecte celles qui pourraient avoir échoué. Pour ce faire, le courtier surveille la dernière fois que chaque client consommateur a appelé « poll » (terme utilisé par Kafka pour demander plus de messages). Si un client n'effectue pas de sondage assez fréquemment, le courtier auquel il est connecté en conclut qu'il a dû échouer et le déconnecte. Ce système est conçu pour permettre aux clients qui ne rencontrent pas de problèmes d'intervenir et de reprendre le travail du client défaillant.

Malheureusement, avec ce système, le courtier Kafka ne peut pas faire la distinction entre un client qui met longtemps à traiter les messages reçus et un client qui a réellement échoué. Considérons une application consommatrice qui effectue une boucle : 1) elle appelle poll et récupère un lot de messages ; ou 2) elle traite chaque message du lot, ce qui prend 1 seconde par message.

Si ce consommateur reçoit des lots de 10 messages, il s’écoulera environ 10 secondes entre les appels à « poll ». Par défaut, Kafka accorde jusqu'à 300 secondes (5 minutes) entre les appels avant de déconnecter le client. Donc tout devrait fonctionner correctement dans ce scénario. Cependant, que se passe-t-il lors d'une journée très chargée, lorsque des messages commencent à s'accumuler sur la page que l'application utilise ? Au lieu de simplement recevoir 10 messages à chaque appel, votre application reçoit 500 messages (par défaut, c'est le nombre maximum d'enregistrements pouvant être renvoyés par un appel). Cela se traduirait par un temps de traitement suffisant pour que Kafka détermine que l'instance de l'application a échoué et la déconnecte. Ceci est regrettable.

Et vous serez ravi d'apprendre que cela peut encore empirer. Il est possible qu'une sorte de boucle de rétroaction se produise. Lorsque Kafka commence à déconnecter les clients parce qu'ils n'appellent pas « poll » assez fréquemment, il y a moins d'instances de l'application pour traiter les messages. La probabilité d'un important retard dans le traitement des messages sur ce sujet augmente, ce qui accroît le risque que davantage de clients reçoivent de grands volumes de messages et prennent trop de temps pour les traiter. Finalement, toutes les instances de l'application consommatrice entrent dans une boucle de relance et aucun travail constructif n'est effectué.

Quelles mesures pouvez-vous prendre pour éviter que cela ne vous arrive ?

  1. Le temps maximum entre les appels peut être configuré à l'aide du paramètre « max.poll.interval .ms » du consommateur Kafka. Le nombre maximum de messages pouvant être renvoyés par un seul appel « poll » est également configurable à l'aide du paramètre « max.poll.records » . En règle générale, il est conseillé de réduire la valeur de « max.poll.records » dans les préférences et d'augmenter celle de « max.poll.interval.ms », car si vous fixez un intervalle maximal entre deux appels élevé, Kafka mettra plus de temps à identifier les consommateurs qui ont réellement échoué.
  2. Il est également possible de demander aux consommateurs Kafka d'interrompre et de reprendre le flux de messages. La mise en pause de la consommation empêche la méthode poll de renvoyer des messages, mais réinitialise tout de même le minuteur utilisé pour déterminer si le client a échoué. La mise en pause et la reprise constituent une tactique utile si vous : a) prévoyez que le traitement des messages individuels peut prendre beaucoup de temps ; et b) souhaitez que Kafka soit en mesure de détecter une défaillance du client au cours du traitement d'un message individuel.
  3. Ne négligez pas l'utilité des indicateurs du client Kafka. Le sujet des indicateurs pourrait faire l'objet d'un article à part entière, mais dans ce contexte, le consommateur expose les indicateurs pour le temps moyen et le temps maximal entre les sondages. Le suivi de ceux-ci peut aider à identifier les situations dans lesquelles un système en aval est la cause du traitement plus long que prévu de chaque message reçu de Kafka.

Nous reviendrons sur le sujet des problèmes liés à la consommation plus loin dans cet article, lorsque nous examinerons comment ils peuvent entraîner un rééquilibrage des groupes de consommateurs et les conséquences négatives que cela peut avoir.

3. Minimiser le coût des consommateurs inactifs

Sous le capot, le protocole utilisé par le consommateur Kafka pour recevoir les messages fonctionne en envoyant une requête « fetch » à un courtier Kafka. Dans le cadre de cette demande, le client indique ce que le courtier doit faire s'il n'y a pas de messages à renvoyer, y compris le délai d'attente avant d'envoyer une réponse vide. Par défaut, les consommateurs Kafka demandent aux courtiers d'attendre jusqu'à 500 millisecondes (contrôlé par la configuration du consommateur « fetch.max.wait.ms ») pour qu'au moins 1 octet de données de message soit disponible (contrôlé par la configuration « fetch.min.bytes ») .

Attendre 500 millisecondes ne semble pas déraisonnable, mais si votre application compte des utilisateurs qui sont principalement inactifs et s'étend à, disons, 5 000 instances, cela représente potentiellement 2 500 requêtes par seconde qui ne servent absolument à rien. Chacune de ces requêtes nécessite du temps de CPU pour être traitée par le courtier et, dans les cas extrêmes, peut avoir un impact sur les performances et la stabilité des clients Kafka qui souhaitent effectuer un travail utile.

En règle générale, l'approche de Kafka en matière de mise à l'échelle consiste à ajouter des courtiers supplémentaires, puis à rééquilibrer uniformément les partitions de sujets entre tous les courtiers, anciens et nouveaux. Malheureusement, cette approche risque de ne pas fonctionner si vos clients bombardent Kafka de requêtes « fetch » inutiles. Chaque client enverra ces demandes à chaque courtier gérant une partition de sujet dont le client consomme les messages. Il est donc possible que, même après avoir mis à l'échelle le cluster Kafka et redistribué les partitions, la plupart de vos clients continuent d'envoyer des requêtes fetch à la majorité des courtiers.

Que pouvez-vous faire ?

  1. Modifier la configuration du consommateur Kafka peut contribuer à atténuer cet effet. Si vous souhaitez recevoir les messages dès leur arrivée, la valeur « fetch.min.bytes » doit rester à sa valeur par défaut, soit 1 ; cependant, la valeur « fetch.max.wait.ms » peut être augmentée, ce qui réduira le nombre de requêtes effectuées par les consommateurs inactifs.
  2. Dans une perspective plus large, votre application doit-elle disposer de plusieurs milliers d'instances, chacune consommant très rarement depuis Kafka ? Il peut y avoir de très bonnes raisons à cela, mais il existe peut-être des moyens de la concevoir de manière à utiliser Kafka plus efficacement. Nous aborderons certains de ces points dans la section suivante.

4. Choisir le nombre approprié de sujets et de partitions

Si vous découvrez Kafka après avoir utilisé d'autres systèmes de publication-abonnement (par exemple Message Queuing Telemetry Transport, ou MQTT), vous pourriez vous attendre à ce que les sujets Kafka soient très légers, voire éphémères. Ce n'est pas le cas. Kafka est beaucoup plus enclin à gérer des milliers de sujets. On attend également des sujets Kafka qu'ils aient une durée de vie relativement longue. Les pratiques telles que la création d'un sujet pour recevoir un seul message de réponse, puis la suppression du sujet, sont peu courantes avec Kafka et ne tirent pas parti de ses forces.

Au lieu de cela, planifiez des sujets qui s'inscrivent dans la durée. Ils peuvent par exemple correspondre à la durée de vie d'une application ou d'une activité. Essayez également de limiter le nombre de sujets à des centaines, voire à des milliers. Cela pourrait nécessiter d'adopter une perspective différente sur les messages associés à un sujet particulier.

Une question connexe qui revient souvent est : « Combien de partitions mon sujet devrait-il comporter ? » Traditionnellement, il est conseillé de surestimer, car l'ajout de partitions après la création d'un sujet ne modifie pas le partitionnement des données existantes contenues dans le sujet (et peut donc affecter les consommateurs qui s'appuient sur le partitionnement pour ordonner les messages au sein d'une partition). C’est un bon conseil ; toutefois, nous aimerions suggérer quelques considérations supplémentaires :

  1. Pour les sujets dont le débit est estimé en Mo/seconde, ou lorsque le débit est susceptible d'augmenter à mesure que vous développez votre application, nous vous recommandons vivement d'utiliser plusieurs partitions afin que la charge puisse être répartie entre plusieurs courtiers. Le service Event Streams exécute toujours Kafka avec un multiple de 3 courtiers. Au moment de la rédaction de cet article, il compte un maximum de 9 courtiers, mais ce chiffre augmentera peut-être à l'avenir. Si vous choisissez un multiple de 3 pour le nombre de partitions dans votre sujet, celui-ci peut être réparti de manière équilibrée entre tous les courtiers.
  2. Le nombre de partitions dans un sujet correspond à la limite du nombre de consommateurs Kafka pouvant partager de manière efficace la consommation des messages du sujet avec les groupes de consommateurs Kafka (nous y reviendrons plus tard). Si vous ajoutez plus de consommateurs à un groupe de consommateurs qu'il n'y a de partitions pour le sujet, certains consommateurs resteront inactifs et ne consommeront pas de données de message.
  3. Il n'y a pas de problème inhérent à l'utilisation de sujets à partition unique, à condition que vous soyez absolument certain qu'ils ne recevront jamais de trafic de messagerie important, ou que vous ne dépendiez pas de la hiérarchisation au sein d'un sujet et que vous soyez disposé à ajouter d'autres partitions ultérieurement.

5. Le rééquilibrage des groupes de consommateurs peut être étonnamment perturbant

La plupart des applications Kafka qui traitent des messages utilisent les fonctionnalités de groupe de consommateurs de Kafka pour coordonner quels clients traitent quelles partitions de sujet. Si vos souvenirs des groupes de consommateurs sont un peu flous, voici un bref rappel des points clés :

  • Les groupes de consommateurs coordonnent un ensemble de clients Kafka de manière à ce qu'un seul client reçoive les messages d'une partition de sujet particulière à un moment donné. Cela est utile si vous devez répartir les messages d'un sujet entre plusieurs instances d'une application.
  • Lorsqu'un client Kafka rejoint un groupe de consommateurs ou quitte un groupe de consommateurs auquel il avait précédemment adhéré, le groupe de consommateurs est rééquilibré. Généralement, les clients rejoignent un groupe de consommateurs lorsque l'application dont ils font partie est lancée, et le quittent lorsque l'application est arrêtée, redémarrée ou qu'elle rencontre des difficultés.
  • Lorsqu'un groupe se rééquilibre, les partitions thématiques sont redistribuées entre les membres du groupe. Ainsi, par exemple, si un client rejoint un groupe, des partitions thématiques peuvent être supprimées (ou « révoquées » selon la terminologie de Kafka) pour les donner au nouveau client. L’inverse est également possible : lorsqu’un client quitte un groupe, les partitions de sujet qui lui sont attribuées sont redistribuées parmi les membres restants.

Au fur et à mesure que Kafka a évolué, des algorithmes de rééquilibrage de plus en plus sophistiqués ont été (et continuent d'être) développés. Dans les premières versions de Kafka, lorsqu'un groupe de consommateurs se rééquilibrait, tous les clients du groupe devaient cesser de consommer, les partitions de sujets étaient redistribuées entre les nouveaux membres du groupe et tous les clients recommençaient à consommer. Cette approche présente deux inconvénients (ne vous inquiétez pas, ils ont été améliorés depuis) :

  1. Tous les clients du groupe arrêtent de recevoir des messages pendant le rééquilibrage. Cela a des répercussions évidentes sur le débit.
  2. Les clients Kafka essaient généralement de conserver une mémoire tampon des messages qui n'ont pas encore été transmis à l'application et de récupérer d'autres messages auprès du courtier avant que la mémoire tampon ne soit épuisée. L'objectif est d'éviter que la livraison des messages à l'application ne soit interrompue pendant que d'autres messages sont récupérés auprès du courtier Kafka (en effet, comme mentionné précédemment dans cet article, le client Kafka tente également d'éviter les temps d'attente liés aux allers-retours sur le réseau). Malheureusement, lorsqu'un rééquilibrage entraîne la révocation des partitions d'un client, toutes les données mises en mémoire tampon pour la partition doivent être supprimées. De même, lorsqu’un rééquilibrage entraîne l’attribution d’une nouvelle partition à un client, ce dernier commence à mettre en mémoire tampon les données à partir du dernier décalage validé pour la partition, ce qui peut entraîner un pic de débit réseau entre le courtier et le client. Cela est dû au fait que le client auquel la partition a été nouvellement attribuée relit les données de message qui avaient été précédemment mises en mémoire tampon par le client auquel la partition a été révoquée.

Les algorithmes de rééquilibrage plus récents ont apporté des améliorations significatives en ajoutant, pour reprendre la terminologie de Kafka, de la « cohérence » (stickiness) et de la « coopération » (cooperation) :

  • Les algorithmes « sticky » visent à garantir qu'après un rééquilibrage, le plus grand nombre possible de membres du groupe conservent les mêmes partitions qu'avant le rééquilibrage. Cela minimise la quantité de données de messages mises en mémoire tampon qui sont supprimées ou relues depuis Kafka lors du rééquilibrage.
  • Les algorithmes « coopératifs » permettent aux clients de continuer à consommer des messages pendant qu’un rééquilibrage s’effectue. Lorsqu'un client s'est vu attribuer une partition avant un rééquilibrage et qu'il conserve cette partition après le rééquilibrage, il peut continuer à les consommer. Cela agit en synergie avec la « cohérence », qui permet de laisser les partitions attribuées au même client.

Malgré ces améliorations apportées aux algorithmes de rééquilibrage plus récents, si vos applications sont fréquemment soumises à des rééquilibrages de groupes de consommateurs, vous constaterez toujours un impact sur le débit global des messages et gaspillerez de la bande passante réseau, car les clients suppriment et récupèrent à nouveau les données de messages mises en mémoire tampon. Voici quelques suggestions sur ce que vous pouvez faire :

  1. Assurez-vous de pouvoir repérer les rééquilibrages. À l’échelle, la collecte et la visualisation des indicateurs sont la meilleure option. Dans ce genre de situation, la diversité des sources d'indicateurs permet de dresser un tableau complet. Le courtier Kafka dispose d'indicateurs pour la quantité d'octets de données envoyées aux clients, ainsi que pour le nombre de groupes de consommateurs rééquilibrés. Si vous collectez des indicateurs à partir de votre application ou de son environnement d'exécution qui indiquent quand les redémarrages se produisent, vous pouvez alors les corréler avec les indicateurs du courtier pour confirmer que le rééquilibrage constitue un problème pour vous.
  2. Évitez les redémarrages inutiles des applications lorsque, par exemple, une application tombe en panne. Si vous rencontrez des problèmes de stabilité avec votre application, cela peut entraîner des rééquilibrages beaucoup plus fréquents que prévu. La recherche dans les journaux d'application des messages d'erreur courants émis par un plantage d'application, par exemple les traces de pile, peut aider à identifier la fréquence des problèmes et fournir des informations utiles pour déboguer le problème sous-jacent.
  3. Utilisez-vous le meilleur algorithme de rééquilibrage pour votre application ? Au moment de la rédaction du présent document, la norme de référence est le « CooperativeStickyAssignor »   cependant, la valeur par défaut (à partir de Kafka 3.0) est d'utiliser le « RangeAssignor » (et l'algorithme d'affectation antérieur) plutôt que le CooperativeStickyAssignor. La documentation Kafka décrit les étapes de migration nécessaires pour que vos clients puissent adopter le système coopératif d'assignation persistante. Il convient également de noter que, bien que l'assignateur logique coopératif soit un choix polyvalent, il existe d'autres assignateurs adaptés à des cas d'utilisation spécifiques.
  4. Les membres d'un groupe de consommateurs sont-ils fixes ? Par exemple, vous exécutez toujours 4 instances distinctes et hautement disponibles d'une application. Vous pourriez profiter de la fonctionnalité d'appartenance à un groupe statique de Kafka. En attribuant des identifiants uniques à chaque instance de votre application, l'appartenance à un groupe statique vous permet d'éviter tout rééquilibrage.
  5. Commencer le décalage actuel lorsqu'une partition est révoquée de votre instance d'application. Le client consommateur de Kafka offre un service d'écoute pour les événements de rééquilibrage. Si une instance de votre application est sur le point de se voir retirer une partition, l'auditeur offre la possibilité de valider un décalage pour la partition qui est sur le point d'être retirée. L’avantage de valider un décalage au moment où la partition est révoquée est qu’il garantit que le membre du groupe reprendra à partir de ce point, plutôt que de risquer de retraiter certains des messages de la partition.

Et maintenant ?

Vous êtes désormais experte en matière de dimensionnement des applications Kafka. Vous êtes invité à mettre ces points en pratique et à essayer l'offre Kafka entièrement gérée sur IBM Cloud. Si vous rencontrez des difficultés lors de l'installation, consultez le guide de démarrage et les FAQ.

 
Solutions connexes
IBM Event Streams

IBM® Event Streams est un logiciel de transmission d’événements en continu basé sur la plateforme open source Apache Kafka. Il est disponible en tant que service entièrement géré sur IBM® Cloud ou en auto-hébergement.

Découvrir Event Streams
Logiciels et solutions d’intégration

Libérez le potentiel de votre entreprise avec les solutions d’intégration IBM qui connectent les applications et les systèmes pour fournir un accès rapide et sécurisé aux données critiques.

Découvrir les solutions d’intégration
Services de conseil en cloud

Déverrouillez de nouvelles capacités et stimulez l’agilité métier grâce aux services de conseils cloud d’IBM.

Découvrir les services de conseil cloud
Passez à l’étape suivante

IBM® Event Streams est un logiciel de transmission d’événements en continu basé sur la plateforme open source Apache Kafka. Il est disponible en tant que service entièrement géré sur IBM® Cloud ou en auto-hébergement.

  1. Découvrir Event Streams
  2. Obtenir plus d'informations