Cinco obstáculos de escalabilidad que debe evitar con su aplicación Kafka

Un hombre de negocios con anteojos usando una computadora portátil en una oficina moderna

Apache Kafka es una plataforma de streaming de eventos de alto rendimiento y altamente escalable. Para desbloquear todo el potencial de Kafka, es necesario considerar cuidadosamente el diseño de la aplicación. Es muy fácil escribir aplicaciones Kafka que funcionen mal o que finalmente se topen con un muro de escalabilidad. Desde 2015, IBM ha proporcionado el servicio IBM Event Streams, que es un servicio Apache Kafka totalmente gestionado que se ejecuta en IBM Cloud. Desde entonces, el servicio ha ayudado a muchos clientes, así como a equipos dentro de IBM, a resolver problemas de escalabilidad y rendimiento con las aplicaciones Kafka que han escrito.

Este artículo describe algunos de los problemas comunes de Apache Kafka y ofrece algunas recomendaciones sobre cómo evitar problemas de escalabilidad en sus aplicaciones.

1. Minimizar las esperas de ida y vuelta en la red

Ciertas operaciones de Kafka funcionan porque el cliente envía datos al broker y espera una respuesta. Un viaje de ida y vuelta completo puede tardar 10 milisegundos, lo que suena rápido, pero lo limita a un máximo de 100 operaciones por segundo. Por este motivo, se recomienda que intente evitar este tipo de operaciones siempre que sea posible. Afortunadamente, los clientes de Kafka le brindan formas de evitar esperar en estos tiempos de ida y vuelta. Solo necesita asegurarse de que los está aprovechando.

Consejos para maximizar el rendimiento:

  1. No verifique cada mensaje enviado si funcionó. La API de Kafka le permite desvincular el envío de un mensaje de la comprobación de si el agente recibió correctamente el mensaje. Esperar la confirmación de que se recibió un mensaje puede introducir latencia de ida y vuelta de la red en su aplicación, por lo que intente minimizar esto cuando sea posible. Esto podría significar enviar tantos mensajes como sea posible, antes de verificar que se hayan recibido todos. O podría significar delegar la verificación de la entrega exitosa de mensajes a otro hilo de ejecución dentro de su aplicación para que pueda ejecutarse en paralelo con usted enviando más mensajes.
  2. No siga el procesamiento de cada mensaje con una confirmación de desplazamiento. El desplazamiento (sincrónico) de confirmación se implementa como una red de ida y vuelta con el servidor. Confirma desplazamientos con menos frecuencia o utiliza la función de confirmación de desplazamiento asíncrona para evitar pagar el precio de este ida y vuelta por cada mensaje que se procese. Solo tenga en cuenta que el desplazamiento de confirmación con menos frecuencia puede significar que se deben volver a procesar más datos si falla su aplicación.

Si leyó lo anterior y pensó: "¿eso no hará que mi aplicación sea más compleja?", la respuesta es sí, probablemente eso pasará. Existe una compensación entre el rendimiento y la complejidad de las aplicaciones. Lo que hace que el tiempo de ida y vuelta de la red sea un obstáculo particularmente insidioso es que una vez que alcanza este límite, puede requerir cambios extensos en las aplicaciones para lograr mejoras adicionales en el rendimiento.

2. No permitir que el aumento de los tiempos de procesamiento se confunda con fallas del consumidor

Una característica útil de Kafka es que monitorea la "actividad" de las aplicaciones que consumen y desconecta cualquiera que pueda haber fallado. Esto funciona haciendo que el broker realice un seguimiento de la última vez que cada cliente consumidor llamó a un "sondeo" (terminología de Kafka para pedir más mensajes). Si un cliente no realiza un sondeo con suficiente frecuencia, el broker al que está conectado concluye que debe fallar y lo desconecta. Esto está diseñado para permitir que los clientes que no están experimentando problemas intervengan y retomen el trabajo del cliente fallido.

Desafortunadamente, con este esquema, el agente de Kafka no puede distinguir entre un cliente que tarda mucho tiempo en procesar los mensajes que recibió y un cliente que realmente falló. Considere una aplicación de consumo que realiza un bucle: 1) llama a un sondeo y obtiene un lote de mensajes; o 2) procesa cada mensaje del lote, tardando 1 segundo en procesar cada mensaje.

Si este consumidor recibe lotes de 10 mensajes, pasarán aproximadamente 10 segundos entre llamadas y sondeos. De forma predeterminada, Kafka permitirá hasta 300 segundos (5 minutos) entre encuestas antes de desconectar el cliente, por lo que todo funcionaría bien en este escenario. Pero, ¿qué sucede en un día realmente ocupado cuando comienzan a acumularse mensajes sobre el tema del que consume la aplicación? En lugar de solo recibir 10 mensajes de cada llamada de sondeo, su aplicación recibe 500 mensajes (de forma predeterminada, este es el número máximo de registros que puede devolver una llamada a sondeo). Eso daría como resultado suficiente tiempo de procesamiento para que Kafka decida que la instancia de la aplicación ha fallado y la desconecte. Esto es una mala noticia.

Le encantará saber que esto puede empeorar. Es posible que ocurra una especie de bucle de feedback. A medida que Kafka comienza a desconectar a los clientes porque no llaman al sondeo con la frecuencia suficiente, hay menos instancias de la aplicación para procesar mensajes. La probabilidad de que haya una gran acumulación de mensajes sobre el tema aumenta, lo que incrementa la probabilidad de que más clientes reciban grandes lotes de mensajes y tarden demasiado en procesarlos. Eventualmente, todas las instancias de la aplicación consumidora entran en un bucle de reinicio y no se realiza ningún trabajo útil.

¿Qué medida puede tomar para evitar que esto ocurra?

  1. La cantidad máxima de tiempo entre llamadas de sondeo se puede configurar mediante la configuración de consumidor de Kafka "max.poll.interval.ms" . El número máximo de mensajes que puede devolver una sola encuesta también se puede configurar mediante la configuración "max.poll.records" . Como regla general, intente reducir el "max.poll.records" en las preferencias para aumentar "max.poll.interval.ms", porque establecer un intervalo de sondeo máximo grande hará que Kafka tarde más en identificar a los consumidores que realmente han fallado.
  2. También se puede indicar a los consumidores de Kafka que pausen y reanuden el flujo de mensajes. Al pausar el consumo, se evita que el método de sondeo devuelva mensajes, pero se sigue reiniciando el temporizador utilizado para determinar si el cliente ha fallado. Pausar y reanudar es una táctica útil si ambos: a) esperan que los mensajes individuales tarden mucho tiempo en procesarse; y b) quieren que Kafka pueda detectar una falla del cliente a la mitad del procesamiento de un mensaje individual.
  3. No pase por alto la utilidad de las métricas del cliente de Kafka. El tema de las métricas podría llenar un artículo completo por sí solo, pero en este contexto el consumidor expone métricas tanto para el tiempo promedio como para el tiempo máximo entre sondeos. El monitoreo de estas métricas puede ayudar a identificar situaciones en las que un sistema descendente es la razón por la que cada mensaje recibido de Kafka tarda más de lo esperado en procesarse.

Volveremos al tema de las fallas de los consumidores más adelante en este artículo, cuando veamos cómo pueden desencadenar el reequilibrio del grupo de consumidores y el efecto disruptivo que esto puede tener.

3. Minimizar el costo de los consumidores inactivos

Tras bambalinas, el protocolo utilizado por el consumidor de Kafka para recibir mensajes funciona enviando una solicitud de “obtención” a un broker de Kafka. Como parte de esta solicitud, el cliente indica qué debe hacer el broker si no hay ningún mensaje para devolver, incluido cuánto tiempo debe esperar el broker antes de enviar una respuesta vacía. De forma predeterminada, los consumidores de Kafka indican a los agentes que esperen hasta 500 milisegundos (controlados por la configuración del consumidor “fetch.max.wait.ms”) para que al menos 1 byte de datos del mensaje esté disponible (controlado con la configuración “fetch.min.bytes”) .

Esperar 500 milisegundos no parece descabellado, pero si su aplicación tiene consumidores que están mayormente inactivos y se escala a, digamos, 5000 instancias, eso supone potencialmente 2500 solicitudes por segundo que no hacen absolutamente nada. Cada una de estas solicitudes requiere tiempo de CPU en el broker para procesarse y, en el extremo, puede afectar el rendimiento y la estabilidad de los clientes de Kafka que desean hacer un trabajo útil.

Normalmente, el enfoque de Kafka para escalar es agregar más agentes y luego reequilibrar uniformemente las particiones de temas en todos los agentes, tanto antiguos como nuevos. Desafortunadamente, este enfoque podría no ser útil si sus clientes están bombardeando Kafka con solicitudes de recuperación innecesarias. Cada cliente enviará solicitudes de obtención a cada agente que lidere una partición temática de la que el cliente está consumiendo mensajes. Así que es posible que, incluso luego de escalar el clúster Kafka y redistribuir particiones, la mayoría de sus clientes estén enviando peticiones de obtención a la mayoría de los brokers.

Entonces, ¿qué puede hacer?

  1. Cambiar la configuración del consumidor de Kafka puede ayudar a reducir este efecto. Si desea recibir mensajes tan pronto como lleguen, “fetch.min.bytes” debe permanecer en su valor predeterminado de 1; sin embargo, el ajuste "fetch.max.wait.ms" puede aumentar a un valor mayor y, al hacerlo, se reducirá el número de solicitudes realizadas por los consumidores inactivos.
  2. En un ámbito más amplio, ¿su aplicación necesita tener potencialmente miles de instancias, cada una de las cuales consume con muy poca frecuencia de Kafka? Puede haber muy buenas razones para que lo haga, pero quizás haya formas de diseñarlo para hacer un uso más eficiente de Kafka. Abordaremos algunas de estas consideraciones en la siguiente sección.

4. Elegir el número adecuado de temas y particiones

Si llega a Kafka con experiencia en otros sistemas de publicación y suscripción (por ejemplo, Message Queuing Telemetry Transport, o MQTT para abreviar), entonces puede esperar que los temas de Kafka sean muy ligeros, casi efímeros. Pero no lo son. Kafka se siente mucho más cómodo con una serie de temas que se miden en miles. También se espera que los temas de Kafka sean relativamente duraderos. Prácticas como crear un tema para recibir un único mensaje de respuesta y luego eliminar el tema son poco comunes con Kafka y no aprovechan las fortalezas de Kafka.

En su lugar, planifica temas que tengan una larga vigencia. Quizás compartan la vida útil de una aplicación o una actividad. También trate de limitar el número de temas a cientos o quizás miles. Esto podría requerir adoptar una perspectiva diferente sobre qué mensajes se intercalan sobre un tema en particular.

Una pregunta relacionada que surge a menudo es: "¿Cuántas particiones debe tener mi tema?". Tradicionalmente, se recomienda sobreestimar, ya que agregar particiones después de crear un tema no cambia la partición de los datos existentes almacenados en el tema (y, por lo tanto, puede afectar a los consumidores que dependen de la partición para ofrecer el orden de los mensajes dentro de una partición). Este es un buen consejo; sin embargo, nos gustaría sugerir algunas consideraciones adicionales:

  1. Para temas que pueden esperar un rendimiento medido en MB/segundo, o donde el rendimiento podría crecer a medida que escala su aplicación, recomendamos encarecidamente tener más de una partición, de modo que la carga pueda distribuirse entre varios agentes. El servicio Event Streams siempre ejecuta Kafka con un múltiplo de 3 brokers. Al momento de redactar este artículo, tiene un máximo de hasta 9 brokers, pero quizás esto se incremente en el futuro. Si elige un múltiplo de 3 para el número de particiones de su tema, se podrá equilibrar de manera uniforme entre todos los brokers.
  2. El número de particiones en un tema es el límite de cuántos consumidores de Kafka pueden compartir de manera útil mensajes de consumo del tema con grupos de consumidores de Kafka (más sobre esto más adelante). Si agrega más consumidores a un grupo de consumidores que las particiones que hay en el tema, algunos consumidores permanecerán inactivos sin consumir datos de mensajes.
  3. No hay nada intrínsecamente malo en tener temas de una sola partición, siempre y cuando esté absolutamente seguro de que nunca recibirán un tráfico de mensajes significativo, o no confiará en ordenar dentro de un tema y estará dispuesto a agregar más particiones más adelante.

5. El reequilibrio del grupo de consumidores puede ser sorprendentemente disruptivo

La mayoría de las aplicaciones de Kafka que consumen mensajes usan las capacidades del grupo de consumidores de Kafka para coordinar qué clientes consumen de qué particiones de tema. Si su recuerdo de los grupos de consumidores es un poco confuso, aquí hay un repaso rápido de los puntos clave:

  • Los grupos de consumidores coordinan un grupo de clientes de Kafka de modo que solo un cliente recibe mensajes de una partición de tema en particular en un momento dado. Esto es útil si necesita compartir los mensajes sobre un tema entre varias instancias de una aplicación.
  • Cuando un cliente de Kafka se une a un grupo de consumidores o deja un grupo de consumidores al que se había unido anteriormente, el grupo de consumidores se reequilibra. Por lo general, los clientes se unen a un grupo de consumidores cuando se inicia la aplicación de la que forman parte y lo abandonan porque la aplicación se apaga, reinicia o falla.
  • Cuando un grupo se reequilibra, las particiones temáticas se redistribuyen entre los miembros del grupo. Por ejemplo, si un cliente se une a un grupo, es posible que a algunos de los clientes que ya están en el grupo se les quiten particiones temáticas (o “revocadas” en la terminología de Kafka) para dárselas al cliente que se acaba de unir. Lo contrario también es cierto: cuando un cliente abandona un grupo, las particiones de temas asignadas a él se redistribuyen entre los miembros restantes.

A medida que Kafka ha madurado, se han ideado algoritmos de reequilibrio cada vez más sofisticados, y seguirá ocurriendo. En las primeras versiones de Kafka, cuando un grupo de consumidores se reequilibraba, todos los clientes del grupo tenían que dejar de consumir, las particiones de tema se redistribuían entre los nuevos miembros del grupo y todos los clientes comenzaban a consumir de nuevo. Este enfoque tiene dos inconvenientes (no se preocupe, estos se han mejorado desde entonces):

  1. Todos los clientes del grupo dejan de consumir mensajes mientras se produce el reequilibrio. Esto tiene repercusiones evidentes en el rendimiento.
  2. Los clientes de Kafka suelen intentar mantener un búfer de mensajes que aún no se han entregado a la aplicación y obtener más mensajes del intermediario antes de que se agote el búfer. La intención es evitar que la entrega de mensajes a la aplicación se detenga mientras se obtienen más mensajes del agente de Kafka (sí, como se mencionó anteriormente en este artículo, el cliente de Kafka también está tratando de evitar esperar los viajes de ida y vuelta de la red). Desafortunadamente, cuando un reequilibrio hace que se revoquen particiones de un cliente, entonces se deben descartar todos los datos almacenados en búfer para la partición. Del mismo modo, cuando el reequilibrio provoca que se asigne una nueva partición a un cliente, este comenzará a almacenar datos en el búfer a partir de la última posición confirmada para la partición, lo que podría provocar un pico en el rendimiento de la red desde el broker hasta el cliente. Esto se debe a que el cliente al que se ha asignado recientemente la partición vuelve a leer los datos de los mensajes que anteriormente habían sido almacenados en el búfer por el cliente al que se le ha revocado la partición.

Los algoritmos de reequilibrio más recientes han realizado mejoras significativas, para usar la terminología de Kafka, agregando "adherencia" y "cooperación":

  • Los algoritmos “adherentes” intentan garantizar que, luego de un reequilibrio, la mayor cantidad posible de miembros del grupo mantengan las mismas particiones que tenían antes del reequilibrio. Esto minimiza la cantidad de datos de mensajes almacenados en búfer que se descartan o se vuelven a leer de Kafka cuando se produce el reequilibrio.
  • Los algoritmos “cooperativos” permiten a los clientes seguir consumiendo mensajes mientras se produce un reequilibrio. Cuando un cliente tiene una partición asignada antes de un reequilibrio y mantiene la partición después de que se haya producido el reequilibrio, puede seguir consumiendo particiones ininterrumpidas por el reequilibrio. Esto es sinérgico con la “adherencia”, que actúa para mantener las particiones asignadas al mismo cliente.

A pesar de estas mejoras en los algoritmos de reequilibrio más recientes, si sus aplicaciones están sujetas con frecuencia a reequilibrios de grupos de consumidores, seguirá viendo un impacto en el rendimiento general de la mensajería y desperdiciando ancho de banda de red a medida que los clientes descartan y recuperan datos de mensajes almacenados en búfer. Estas son algunas sugerencias sobre lo que puede hacer:

  1. Asegúrese de que puede detectar cuándo se está produciendo un reequilibrio. A escala, recopilar y visualizar métricas es su mejor opción. Esta es una situación en la que una amplia variedad de fuentes métricas ayuda a construir una imagen completa. El broker Kafka tiene métricas tanto para la cantidad de bytes de datos enviados a los clientes como para la cantidad de grupos de consumidores que se reequilibran. Si está recopilando métricas de su aplicación, o su tiempo de ejecución, que muestran cuándo se producen los reinicios, correlacionarlo con las métricas del broker puede proporcionar una confirmación adicional de que el reequilibrio es un problema para usted.
  2. Evita reinicios innecesarios de aplicaciones cuando, por ejemplo, una aplicación se bloquea. Si está experimentando problemas de estabilidad con su aplicación, esto puede llevar a un reequilibrio mucho más frecuente de lo previsto. Buscar en los registros de la aplicación mensajes de error comunes emitidos por una falla de la aplicación, por ejemplo, seguimientos de pila, puede ayudar a identificar con qué frecuencia ocurren los problemas y proporcionar información útil para depurar el problema subyacente.
  3. ¿Está utilizando el mejor algoritmo de reequilibrio para su aplicación? En el momento de escribir este artículo, el estándar de referencia es el "CooperativeStickyAssignor"; sin embargo, el valor predeterminado (a partir de Kafka 3.0) es usar el “RangeAssignor” (y el algoritmo de asignación anterior) en lugar del asignador fijo cooperativo. La documentación de Kafka describe los pasos de migración necesarios para que sus clientes recojan el asignador fijo cooperativo. También cabe señalar que, si bien el asignador fijo cooperativo es una buena opción general, existen otros asignadores diseñados para casos de uso específicos.
  4. ¿Los miembros de un grupo de consumidores son fijos? Por ejemplo, quizá siempre ejecute 4 instancias altamente disponibles y distintas de una aplicación. Quizá pueda aprovechar la función de pertenencia estática de grupos de Kafka. Al asignar identificadores únicos a cada instancia de su aplicación, la pertenencia a grupos estáticos le permite evitar por completo el reequilibrio.
  5. Confirme el desplazamiento actual cuando se revoca una partición de la instancia de la aplicación. El cliente de consumo de Kafka proporciona un oyente para eventos de reequilibrio. Si una instancia de su aplicación está a punto de perder una partición, el oyente le ofrece la oportunidad de confirmar un desplazamiento por la partición que está a punto de perder. La ventaja de comprometer un desplazamiento en el punto en que se revoca la partición es que garantiza que cualquier miembro del grupo al que se le asigne la partición retome desde este punto, en lugar de volver a procesar potencialmente algunos de los mensajes de la partición.

Las últimas noticias tecnológicas, respaldadas por los insights de expertos

Manténgase al día sobre las tendencias más importantes e intrigantes de la industria sobre IA, automatización, datos y más con el boletín Think. Consulte la Declaración de privacidad de IBM.

¡Gracias! Ya está suscrito.

Su suscripción se entregará en inglés. En cada boletín, encontrará un enlace para darse de baja. Puede gestionar sus suscripciones o darse de baja aquí. Consulte nuestra Declaración de privacidad de IBM para obtener más información.

¿Qué sigue?

Ahora ya es un experto en escalar aplicaciones Kafka. Le invitamos a poner en práctica estos puntos y probar la oferta de Kafka totalmente gestionada en IBM Cloud. Si tiene algún problema con la configuración, consulte la Guía de inicio y las preguntas frecuentes.

 
Soluciones relacionadas
IBM Event Streams

IBM® Event Streams es un software de transmisión de eventos construido sobre Apache Kafka de código abierto. Está disponible como servicio totalmente gestionado en IBM® Cloud o para autohospedaje.

Explore Event Streams
Software y soluciones de integración

Desbloquee el potencial empresarial con las soluciones de integración de IBM, y conecte aplicaciones y sistemas para acceder a datos críticos de forma rápida y segura.

Explore las soluciones de integración
Servicios de consultoría en la nube

Desbloquee nuevas capacidades e impulse la agilidad empresarial con los servicios de asesoramiento en la nube de IBM.

Explore los servicios de consultoría en la nube
Dé el siguiente paso

IBM® Event Streams es un software de transmisión de eventos construido sobre Apache Kafka de código abierto. Está disponible como servicio totalmente gestionado en IBM® Cloud o para autohospedaje.

Explore Event Streams Más información