Eliminar las Interrupciones del Procesamiento de tareas reemplazando RabbitMQ con Apache Kafka Sin tiempo de inactividad

Escalar la infraestructura de backend para manejar el hipercrecimiento es uno de los muchos desafíos emocionantes de trabajar en DoorDash. A mediados de 2019, nos enfrentamos a importantes desafíos de escalado y a interrupciones frecuentes en Apio y RabbitMQ, dos tecnologías que impulsan el sistema que maneja el trabajo asincrónico, lo que permite funcionalidades críticas de nuestra plataforma, incluidas las tareas de pago de pedidos y de Dasher.

Resolvimos rápidamente este problema con un sistema de procesamiento de tareas asíncrono simple basado en Apache Kafka que detuvo nuestras interrupciones mientras continuábamos iterando en una solución robusta. Nuestra versión inicial implementó el conjunto más pequeño de características necesarias para acomodar una gran parte de las tareas de apio existentes. Una vez en producción, continuamos agregando soporte para más funciones de apio al tiempo que abordamos problemas novedosos que surgieron al usar Kafka.

Los problemas a los que nos enfrentamos al usar Apio y RabbitMQ

RabbitMQ y Apio eran piezas de misión crítica de nuestra infraestructura que impulsaban más de 900 tareas asíncronas diferentes en DoorDash, incluida la comprobación de pedidos, la transmisión de pedidos de comerciantes y el procesamiento de ubicaciones de Dasher. El problema al que se enfrentaba DoorDash era que RabbitMQ se caía con frecuencia debido a una carga excesiva. Si el procesamiento de tareas se interrumpía, DoorDash se interrumpía efectivamente y los pedidos no se podían completar, lo que resultaba en una pérdida de ingresos para nuestros comerciantes y Dashers, y una mala experiencia para nuestros consumidores. Nos enfrentamos a problemas en los siguientes frentes:

  • Disponibilidad: Interrupciones causadas por la disponibilidad reducida de la demanda.
  • Escalabilidad: RabbitMQ no pudo escalar con el crecimiento de nuestro negocio.
  • Observabilidad: RabbitMQ ofrecía métricas limitadas y los trabajadores del apio eran opacos.
  • Eficiencia operativa: Reiniciar estos componentes era un proceso manual que requería mucho tiempo.

Por qué nuestro sistema de procesamiento de tareas asíncrono no estaba muy disponible

El mayor problema que enfrentamos eran las interrupciones, y a menudo se producían cuando la demanda estaba en su punto máximo. RabbitMQ se caería debido a la carga, la pérdida excesiva de conexiones y otras razones. Los pedidos se detendrían, y tendríamos que reiniciar nuestro sistema o, a veces, incluso abrir un corredor completamente nuevo y realizar una conmutación por error manual para recuperarnos de la interrupción.

Al profundizar en los problemas de disponibilidad, encontramos los siguientes subtemas:

  • El apio permite a los usuarios programar tareas en el futuro con una cuenta atrás o ETA. Nuestro uso intensivo de estas cuentas regresivas resultó en aumentos de carga notables en el corredor. Algunas de nuestras interrupciones estaban directamente relacionadas con un aumento de tareas con cuentas regresivas. En última instancia, decidimos restringir el uso de cuentas regresivas en favor de otro sistema que teníamos establecido para programar el trabajo en el futuro.
  • Las ráfagas repentinas de tráfico dejarían a RabbitMQ en un estado degradado donde el consumo de tareas era significativamente menor de lo esperado. En nuestra experiencia, esto solo se podía resolver con un rebote de RabbitMQ. RabbitMQ tiene un concepto de Control de flujo en el que reducirá la velocidad de las conexiones que se publican demasiado rápido para que las colas puedan mantenerse al día. El control de flujo a menudo, pero no siempre, estaba involucrado en estas degradaciones de disponibilidad. Cuando se activa el control de flujo, los editores lo ven efectivamente como latencia de red. La latencia de red reduce nuestros tiempos de respuesta; si la latencia aumenta durante los picos de tráfico, las ralentizaciones significativas pueden dar lugar a una cascada a medida que las solicitudes se acumulan en el flujo ascendente.
  • Nuestros trabajadores web de python uWSGI tenían una característica llamada harakiri que estaba habilitada para matar cualquier proceso que excediera un tiempo de espera. Durante las interrupciones o ralentizaciones, harakiri provocó una interrupción de la conexión con los corredores de RabbitMQ, ya que los procesos se cancelaron y reiniciaron repetidamente. Con miles de trabajadores web funcionando en un momento dado, cualquier lentitud que activara harakiri a su vez contribuiría aún más a la lentitud al agregar carga adicional a RabbitMQ.
  • En la producción experimentamos varios casos en los que el procesamiento de tareas en los consumidores de apio se detuvo, incluso en ausencia de carga significativa. Nuestros esfuerzos de investigación no arrojaron evidencia de ninguna restricción de recursos que hubiera detenido el procesamiento, y los trabajadores reanudaron el procesamiento una vez que fueron devueltos. Este problema nunca fue causado de raíz, aunque sospechamos un problema en los propios trabajadores del apio y no en RabbitMQ.

En general, todos estos problemas de disponibilidad eran inaceptables para nosotros, ya que la alta confiabilidad es una de nuestras principales prioridades. Dado que estas interrupciones nos costaban mucho en términos de pedidos perdidos y credibilidad, necesitábamos una solución que abordara estos problemas lo antes posible.

Por qué nuestra solución heredada no escalaba

El siguiente problema más grande fue la escala. DoorDash está creciendo rápidamente y estábamos alcanzando rápidamente los límites de nuestra solución existente. Necesitábamos encontrar algo que se mantuviera al día con nuestro crecimiento continuo, ya que nuestra solución heredada tenía los siguientes problemas:

Alcanzar el límite de escalado vertical

Estábamos utilizando la solución RabbitMQ de un solo nodo disponible más grande que estaba disponible para nosotros. No había camino para escalar verticalmente más y ya estábamos empezando a empujar ese nodo a sus límites.

El modo de alta disponibilidad limitó nuestra capacidad

Debido a la replicación, el modo de alta disponibilidad (HA) primario-secundario redujo el rendimiento en comparación con la opción de un solo nodo, lo que nos deja aún menos espacio libre que la solución de un solo nodo. No podíamos permitirnos el lujo de intercambiar el rendimiento por disponibilidad.

En segundo lugar, el modo de HA primario-secundario no redujo, en la práctica, la gravedad de nuestras interrupciones. Las failovers tardaban más de 20 minutos en completarse y a menudo se atascaban requiriendo intervención manual. Los mensajes a menudo también se perdían en el proceso.

Rápidamente nos estábamos quedando sin espacio para la cabeza a medida que DoorDash continuaba creciendo y llevando nuestro procesamiento de tareas a sus límites. Necesitábamos una solución que pudiera escalarse horizontalmente a medida que aumentaban nuestras necesidades de procesamiento.

Cómo Apio y RabbitMQ ofrecían una observabilidad limitada

Saber qué está pasando en cualquier sistema es fundamental para garantizar su disponibilidad, escalabilidad e integridad operativa.

Mientras navegábamos por los problemas descritos anteriormente, notamos que :

  • Estábamos limitados a un pequeño conjunto de métricas RabbitMQ disponibles para nosotros.
  • Teníamos una visibilidad limitada de los propios trabajadores del apio.

Necesitábamos poder ver métricas en tiempo real de cada aspecto de nuestro sistema, lo que significaba que también era necesario abordar las limitaciones de observabilidad.

Los desafíos de eficiencia operativa

También nos enfrentamos a varios problemas con el funcionamiento de RabbitMQ:

  • A menudo tuvimos que pasar por error nuestro nodo RabbitMQ a uno nuevo para resolver la degradación persistente que observamos. Esta operación era manual y consumía mucho tiempo para los ingenieros involucrados y, a menudo, tenía que realizarse a altas horas de la noche, fuera de las horas punta.
  • No había expertos internos en Apio o RabbitMQ en DoorDash en los que pudiéramos apoyarnos para ayudar a diseñar una estrategia de escalado para esta tecnología.

El tiempo de ingeniería empleado en el funcionamiento y mantenimiento de RabbitMQ no fue sostenible. Necesitábamos algo que respondiera mejor a nuestras necesidades actuales y futuras.

Soluciones potenciales a nuestros problemas con el Apio y RabbitMQ

Con los problemas descritos anteriormente, consideramos las siguientes soluciones:

  • Cambiar el corredor de apio de RabbitMQ a Redis o Kafka. Esto nos permitiría continuar usando Apio, con un almacén de datos de respaldo diferente y potencialmente más confiable.
  • Agregue soporte para múltiples corredores a nuestra aplicación Django para que los consumidores puedan publicar en N corredores diferentes según la lógica que queramos. El procesamiento de tareas se fragmentará entre varios corredores, por lo que cada corredor experimentará una fracción de la carga inicial.
  • Actualice a las versiones más recientes de Apio y RabbitMQ. Se esperaba que las versiones más nuevas de Apio y RabbitMQ solucionaran los problemas de confiabilidad, lo que nos compraría tiempo, ya que ya estábamos extrayendo componentes de nuestro monolito Django en paralelo.
  • Migre a una solución personalizada respaldada por Kafka. Esta solución requiere más esfuerzo que las otras opciones que enumeramos, pero también tiene más potencial para resolver todos los problemas que teníamos con la solución heredada.

Cada opción tiene sus pros y sus contras:

  • Disponibilidad mejorada con compatibilidad con ElasticCache y multi-AZ
  • Broker mejorado observabilidad con ElasticCache como broker
  • Eficiencia operativa mejorada
  • Experiencia operativa interna y experiencia con Redis
  • Un intercambio de broker es directo como opción compatible en Apio
  • La rotación de conexiones Harakiri no degrada significativamente el rendimiento de Redis
Opción Pros Contras
Redis como broker
  • Incompatible con el modo agrupado de Redis
  • Redis de nodo único no escala horizontalmente
  • Sin mejoras en la observabilidad de Apio
  • Esta solución no resuelve el problema observado en el que los trabajadores de apio dejaron de procesar tareas
Kafka como agente
  • Kafka puede estar altamente disponible
  • Kafka es escalable horizontalmente
  • Mejor observabilidad con Kafka como broker
  • Mejora de la eficiencia operativa
  • DoorDash tenía experiencia interna en Kafka
  • Un intercambio de broker es una opción compatible en Apio
  • La rotación de conexiones Harakiri no degrada significativamente el rendimiento de Kafka
  • Kafka aún no es compatible con Apio
  • No aborda el problema observado en el que los trabajadores de apio dejan de procesar tareas
  • No hay mejoras en la observabilidad del apio
  • A pesar de la experiencia interna, no habíamos operado Kafka a escala en DoorDash.
Varios corredores
  • Disponibilidad mejorada
  • Escalabilidad horizontal
  • Sin mejoras en la observabilidad
  • Sin mejoras en la eficiencia operativa
  • No soluciona el problema observado en el que los trabajadores de Apio dejan de procesar tareas
  • No soluciona el problema con la pérdida de conexión inducida por harakiri
  • /li>
Las versiones de actualización
  • Podrían mejorar el problema en el que RabbitMQ se queda atascado en un estado degradado
  • Podría mejorar el problema en el que los trabajadores de apio se quedan atascados
  • Podría comprarnos espacio para implementar una estrategia a más largo plazo
  • No se garantiza la corrección de los errores observados
  • No solucionará de inmediato nuestros problemas de disponibilidad, escalabilidad, observabilidad y eficiencia operativa
  • Las versiones más recientes de RabbitMQ y Apery requieren versiones más recientes de Python.
  • No soluciona el problema con la pérdida de conexiones inducida por harakiri
Solución Kafka personalizada
  • Kafka puede estar altamente disponible
  • Kafka es escalable horizontalmente
  • Mejor observabilidad con Kakfa como agente
  • Mejora de la eficiencia operativa
  • Experiencia interna en Kafka
  • Un agente de el cambio es directo
  • La rotación de conexiones Harakiri no degrada significativamente el rendimiento de Kafka
  • Aborda el problema observado en el que los trabajadores de apio dejan de procesar tareas
  • Requiere más trabajar para implementar que todas las demás opciones
  • A pesar de la experiencia interna, no habíamos operado Kafka a escala en DoorDash

Nuestra estrategia para la incorporación de Kafka

Dado el tiempo de actividad del sistema requerido, ideamos nuestra estrategia de incorporación basado en los siguientes principios para maximizar los beneficios de confiabilidad en el menor tiempo posible. Esta estrategia implicó tres pasos:

  • Comenzar a correr: Queríamos aprovechar los fundamentos de la solución que estaban construyendo como estábamos repitiendo en otras partes de ella. Comparamos esta estrategia con conducir un coche de carreras mientras intercambiamos una nueva bomba de combustible.
  • Opciones de diseño para una adopción perfecta por parte de los desarrolladores: Queríamos minimizar el esfuerzo desperdiciado por parte de todos los desarrolladores que pudiera haber resultado de definir una interfaz diferente.
  • Despliegue incremental sin tiempo de inactividad: En lugar de probar una versión grande y llamativa en la naturaleza por primera vez con una mayor probabilidad de fallas, nos centramos en enviar características independientes más pequeñas que podrían probarse individualmente en la naturaleza durante un período de tiempo más largo.

Comenzar a trabajar

Cambiar a Kafka representó un cambio técnico importante en nuestra pila, pero fue muy necesario. No teníamos tiempo que perder, ya que cada semana perdíamos negocios debido a la inestabilidad de nuestra solución heredada de RabbitMQ. Nuestra primera y principal prioridad era crear un producto mínimo viable (MVP) para brindarnos estabilidad provisional y darnos el espacio necesario para iterar y prepararnos para una solución más integral con una adopción más amplia.

Nuestro MVP consistía en productores que publicaban Nombres Completos de tareas (FQN) y decapaban argumentos a Kafka mientras nuestros consumidores leían esos mensajes, importaban las tareas del FQN y las ejecutaban de forma sincrónica con los argumentos especificados.

La arquitectura de Producto Mínimo Viable(MVP) que decidimos construir incluía un estado provisional en el que publicaríamos tareas mutuamente exclusivas tanto en el legado (líneas discontinuas rojas) como en los nuevos sistemas (líneas sólidas verdes), antes del estado final en el que dejaríamos de publicar tareas en RabbitMQ.1

Figura 1: La arquitectura de Producto Viable Mínimo(MVP) que decidimos construir incluía un estado provisional en el que publicaríamos tareas mutuamente exclusivas tanto en el legado (líneas discontinuas rojas) como en los nuevos sistemas (líneas sólidas verdes), antes del estado final en el que dejaríamos de publicar tareas en RabbitMQ.

Opciones de diseño para una adopción perfecta por parte de los desarrolladores

A veces, la adopción de desarrolladores es un desafío mayor que el desarrollo. Hicimos esto más fácil implementando un envoltorio para la anotación @task de Celery que enrutaba dinámicamente los envíos de tareas a cualquiera de los sistemas en función de indicadores de características configurables dinámicamente. Ahora se podría usar la misma interfaz para escribir tareas para ambos sistemas. Con estas decisiones en marcha, los equipos de ingeniería no tuvieron que hacer ningún trabajo adicional para integrarse con el nuevo sistema, salvo la implementación de una sola marca de característica.

Queríamos lanzar nuestro sistema tan pronto como nuestro MVP estuviera listo, pero aún no era compatible con todas las mismas características que Apio. Apio permite a los usuarios configurar sus tareas con parámetros en su anotación de tareas o cuando envían su tarea. Para permitirnos un lanzamiento más rápido, creamos una lista blanca de parámetros compatibles y elegimos admitir el menor número de características necesarias para admitir la mayoría de las tareas.

Aumentamos rápidamente el volumen de tareas al MVP basado en Kafka, comenzando con tareas de bajo riesgo y baja prioridad primero. Algunas de estas eran tareas que se ejecutaban en horas fuera de las horas pico, lo que explica los picos de la métrica descrita anteriormente.

Figura 2: Aumentamos rápidamente el volumen de tareas al MVP basado en Kafka, comenzando con tareas de bajo riesgo y baja prioridad primero. Algunas de estas eran tareas que se ejecutaban en horas fuera de las horas pico, lo que explica los picos de la métrica descrita anteriormente.

Como se ve en la Figura 2, con las dos decisiones anteriores, lanzamos nuestro MVP después de dos semanas de desarrollo y logramos una reducción del 80% en la carga de tareas de RabbitMQ otra semana después del lanzamiento. Lidiamos con nuestro problema principal de las interrupciones rápidamente, y a lo largo del proyecto apoyamos más y más características esotéricas para permitir la ejecución de las tareas restantes.

Despliegue incremental, tiempo de inactividad cero

La capacidad de cambiar de clústeres de Kafka y cambiar entre RabbitMQ y Kafka dinámicamente sin impacto en el negocio fue extremadamente importante para nosotros. Esta capacidad también nos ayudó en una variedad de operaciones, como el mantenimiento de clústeres, el desprendimiento de carga y las migraciones graduales. Para implementar esta implementación, utilizamos indicadores de características dinámicas tanto en el nivel de envío de mensajes como en el lado de consumo de mensajes. El costo de ser completamente dinámico aquí era mantener nuestra flota de trabajadores funcionando a doble capacidad. La mitad de esta flota estaba dedicada a RabbitMQ, y el resto a Kafka. Ejecutar la flota de trabajadores a doble capacidad fue definitivamente gravoso para nuestra infraestructura. En un momento dado, incluso creamos un clúster de Kubernetes completamente nuevo para alojar a todos nuestros trabajadores.

Durante la fase inicial de desarrollo, esta flexibilidad nos sirvió bien. Una vez que tuvimos más confianza en nuestro nuevo sistema, buscamos formas de reducir la carga en nuestra infraestructura, como ejecutar múltiples procesos de consumo por máquina de trabajo. A medida que cambiábamos varios temas, pudimos comenzar a reducir el número de trabajadores de RabbitMQ mientras manteníamos una pequeña capacidad de reserva.

Ninguna solución es perfecta, itera según sea necesario

Con nuestro MVP en producción, teníamos el espacio necesario para iterar y pulir nuestro producto. Clasificamos cada función de Apio faltante por el número de tareas que la usaron para ayudarnos a decidir cuáles implementar primero. Las funciones utilizadas por solo unas pocas tareas no se implementaron en nuestra solución personalizada. En su lugar, reescribimos esas tareas para no usar esa característica específica. Con esta estrategia, finalmente eliminamos todas las tareas del apio.

El uso de Kafka también introdujo nuevos problemas que necesitaban nuestra atención:

  • Bloqueo de cabeza de línea que resultó en retrasos en el procesamiento de tareas
  • Las implementaciones activaron el reequilibrio de particiones que también resultó en retrasos

El problema de bloqueo de cabeza de línea de Kafka

Los temas de Kafka se particionan de manera que un solo consumidor (por grupo de consumidores) lee los mensajes de sus particiones asignadas en el orden en que llegaron. Si un mensaje en una sola partición tarda demasiado en procesarse, se detendrá el consumo de todos los mensajes detrás de él en esa partición, como se ve en la Figura 3, a continuación. Este problema puede ser particularmente desastroso en el caso de un tema de alta prioridad. Queremos poder continuar procesando mensajes en una partición en caso de que ocurra un retraso.

En el problema de bloqueo de cabeza de línea de Kafka, un mensaje lento en una partición (en rojo) bloquea todos los mensajes detrás de él para que no se procesen. Otras particiones continuarían procesándose como se esperaba.

Figura 3: En el problema de bloqueo de cabeza de línea de Kafka, un mensaje lento en una partición (en rojo) bloquea todos los mensajes detrás de ella para que no se procesen. Otras particiones continuarían procesándose como se esperaba.

Mientras que el paralelismo es, fundamentalmente, un problema de Python, los conceptos de esta solución también son aplicables a otros lenguajes. Nuestra solución, que se muestra en la Figura 4, a continuación, consistía en alojar un proceso Kafka-consumidor y varios procesos de ejecución de tareas por trabajador. El proceso Kafka-consumidor es responsable de obtener mensajes de Kafka y colocarlos en una cola local que es leída por los procesos de ejecución de tareas. Continúa consumiendo hasta que la cola local alcanza un umbral definido por el usuario. Esta solución permite que los mensajes en la partición fluyan y solo un proceso de ejecución de tareas se detendrá por el mensaje lento. El umbral también limita el número de mensajes en vuelo en la cola local (que pueden perderse en caso de fallo del sistema).

Figura 4: Nuestro trabajador Kafka sin bloqueo consiste en una cola de mensajes local y dos tipos de procesos: un proceso kafka-consumidor y múltiples procesos de tarea-ejecutor. Si bien un consumidor de kafka puede leer desde varias particiones, para simplificar, representaremos solo una. Este diagrama muestra que un mensaje de procesamiento lento (en rojo) solo bloquea un solo ejecutor de tareas hasta que se completa, mientras que otros mensajes detrás de él en la partición continúan siendo procesados por otros ejecutores de tareas.

Figura 4: Nuestro trabajador Kafka sin bloqueo consiste en una cola de mensajes local y dos tipos de procesos: un proceso kafka-consumidor y múltiples procesos de tarea-ejecutor. Si bien un consumidor de kafka puede leer desde varias particiones, para simplificar, representaremos solo una. Este diagrama muestra que un mensaje de procesamiento lento (en rojo) solo bloquea un solo ejecutor de tareas hasta que se completa, mientras que otros mensajes detrás de él en la partición continúan siendo procesados por otros ejecutores de tareas.

La disruptividad de los despliegues

Desplegamos nuestra aplicación Django varias veces al día. Un inconveniente de nuestra solución que notamos es que una implementación desencadena un reequilibrio de las asignaciones de particiones en Kafka. A pesar de usar un grupo de consumidores diferente por tema para limitar el alcance del reequilibrio, las implementaciones aún provocaban una desaceleración momentánea en el procesamiento de mensajes, ya que el consumo de tareas tuvo que detenerse durante el reequilibrio. Las ralentizaciones pueden ser aceptables en la mayoría de los casos cuando realizamos versiones planificadas, pero pueden ser catastróficas cuando, por ejemplo, estamos haciendo una versión de emergencia para corregir un error. La consecuencia sería la introducción de una desaceleración del procesamiento en cascada.

Las versiones más recientes de Kafka y clientes apoyan el reequilibrio cooperativo incremental, lo que reduciría enormemente el impacto operativo de un reequilibrio. Actualizar a nuestros clientes para soportar este tipo de reequilibrio sería nuestra solución preferida en el futuro. Desafortunadamente, el reequilibrio cooperativo incremental aún no es compatible con nuestro cliente Kafka elegido.

Victorias clave

Con la conclusión de este proyecto, logramos mejoras significativas en términos de tiempo de actividad, escalabilidad, observabilidad y descentralización. Estas victorias fueron cruciales para garantizar el crecimiento continuo de nuestro negocio.

No más interrupciones repetidas

Detuvimos las interrupciones repetidas casi tan pronto como comenzamos a implementar este enfoque personalizado de Kafka. Las interrupciones provocaban experiencias de usuario extremadamente pobres.

  • Al implementar solo un pequeño subconjunto de las características de apio más utilizadas en nuestro MVP, pudimos enviar el código de trabajo a producción en dos semanas.
  • Con el MVP en su lugar, pudimos reducir significativamente la carga en RabbitMQ y Apio a medida que continuamos endureciendo nuestra solución e implementando nuevas características.

El procesamiento de tareas ya no era el factor limitante para el crecimiento

Con Kafka en el corazón de nuestra arquitectura, creamos un sistema de procesamiento de tareas que es altamente disponible y escalable horizontalmente, lo que permite a DoorDash y a sus clientes continuar su crecimiento.

Observabilidad aumentada masivamente

Dado que se trataba de una solución personalizada, pudimos incorporar más métricas en casi todos los niveles. Cada cola, trabajador y tarea era completamente observable a un nivel muy granular en entornos de producción y desarrollo. Este aumento de la observabilidad fue una gran victoria no solo en el sentido de la producción, sino también en términos de productividad de los desarrolladores.

Descentralización operativa

Con las mejoras de observabilidad, pudimos templatizar nuestras alertas como módulos de Terraform y asignar explícitamente propietarios a cada tema e, implícitamente, a más de 900 tareas.

Una guía de operación detallada para el sistema de procesamiento de tareas hace que la información sea accesible para todos los ingenieros para depurar problemas operativos con sus temas y trabajadores, así como para realizar operaciones generales de administración de clústeres de Kafka, según sea necesario. Las operaciones diarias son de autoservicio y rara vez se necesita asistencia de nuestro equipo de infraestructura.

Conclusión

En resumen, llegamos al límite de nuestra capacidad para escalar RabbitMQ y tuvimos que buscar alternativas. La alternativa que elegimos fue una solución personalizada basada en Kafka. Si bien hay algunos inconvenientes en el uso de Kafka, encontramos una serie de soluciones alternativas, descritas anteriormente.

Cuando los flujos de trabajo críticos dependen en gran medida del procesamiento de tareas asincrónico, garantizar la escalabilidad es de suma importancia. Al experimentar problemas similares, siéntase libre de inspirarse en nuestra estrategia, que nos otorgó el 80% del resultado con el 20% del esfuerzo. Esta estrategia, en el caso general, es un enfoque táctico para mitigar rápidamente los problemas de confiabilidad y ganar el tiempo que se necesita para una solución más robusta y estratégica.

Agradecimientos

Los autores agradecen a Clement Fang, Corry Haines, Danial Asif, Jay Weinstein, Luigi Tagliamonte, Matthew Anger, Shaohua Zhou y Yun-Yu Chen por contribuir a este proyecto.

Foto de tian kuan en Unsplash

Deja una respuesta

Tu dirección de correo electrónico no será publicada.