Amazon Simple Queue Service (SQS) es un poderoso servicio diseñado para administrar las necesidades de mensajería de microservicios robustos y desacoplados.
Se integra perfectamente con AWS Lambda, su socio nativo, como destino. SQS puede activar una función Lambda como destino y enviar los elementos del lote como entrada.
Sin embargo, en el lado de la función Lambda, manejar esta entrada puede resultar complicado, especialmente cuando se piensan errores y reintentos.
Este es el segundo artículo de una serie de tres partes sobre las mejores prácticas de SQS
Este artículo le enseñará cómo manejar fallas de procesamiento por lotes de Amazon SQS y dominar los reintentos automáticos con ejemplos de código de AWS Lambda Powertools para Python y AWS CDK.
Otros artículos de la serie:
El primer artículo le enseñará cómo manejar de manera eficiente lotes de Amazon SQS con AWS Lambda Powertools para Python y ejemplos de código de AWS CDK.
En la tercera parte de la serie, aprenderá sobre las mejores prácticas para las colas de mensajes inactivos y cómo manejar las fallas correctamente.
Todos los ejemplos de código CDK se pueden encontrar aquí .
Todo el código Lambda se puede encontrar aquí .
Tabla de contenido
Re:cap de SQS
En el primer artículo, en mis prácticas recomendadas de SQS, definimos el patrón de función de SQS a Lambda. Implementamos la infraestructura de AWS CDK y los códigos de controlador de funciones Lambda.
Sin embargo, no nos centramos en procesar los fallos. Eso está a punto de cambiar.
Tenga en cuenta que este artículo se basa en los ejemplos de código del primer artículo.
Mejores prácticas para reintentos de SQS
Repasemos las mejores prácticas de reintentos de SQS y el flujo de procesamiento por lotes.
SQS activa nuestra función Lambda con un evento SQS que contiene elementos del lote.
La función Lambda iterará cada elemento, lo procesará y continuará con el siguiente elemento.
Sin embargo, ¿qué sucede si un elemento no se puede procesar debido a problemas momentáneos de red, errores de servicios externos u otros problemas aleatorios, y cómo podemos recuperarnos?
Entonces, revisemos los métodos posibles para manejar las fallas sin perder la resiliencia y la autocuración.
Hay tres opciones para gestionar fallas:
Ejecutar un reintento durante el tiempo de ejecución de la función Lambda, es decir, utilizar la biblioteca tenacity (o cualquier otra biblioteca de reintentos) para decorar cualquier función lógica interna con reintentos automáticos. Sin embargo, dado que con SQS tratamos con lotes de registros, y no solo con uno, debemos tener en cuenta el tiempo de ejecución total de la función y dejar suficiente tiempo para el resto del lote y sus posibles reintentos, lo que puede ser un poco complicado y provocar un posible tiempo de espera de la función.
Utilice la compatibilidad integrada de SQS para reintentar . Se puede activar un reintento automático en dos casos de uso:
Una función genera una excepción al final de su ejecución, devolviendo así todo el lote a la cola.
Una función marca los mensajes parcialmente fallidos.
Un enfoque híbrido: utilice las opciones 1 y 2 juntas, que implementaremos como el método preferido en esta publicación.
Pongamos estos métodos en un diagrama:
(1) SQS activa una función con un lote. La función Lambda itera sobre el lote y maneja cada elemento del lote y vuelve a intentar su procesamiento (1a en el diagrama, si es necesario) con "tenacidad".
(2) La función marca al SQS qué artículos no se procesaron correctamente (después de intentos fallidos de reintento) o finaliza con una excepción si todo el lote falló. Si todos los artículos se procesan correctamente, la función marca que no se procesaron artículos.
(3) Si Lambda marcó elementos como que no se pudieron procesar, SQS invocará la función nuevamente con un lote que contiene solo los elementos que fallaron una vez que haya transcurrido el tiempo de espera de visibilidad de SQS.
Tenga en cuenta que una vez que se agregan nuevos elementos al SQS, los elementos del lote fallido serán parte del último lote que recibe la función, pero tenga en cuenta que los mensajes tienen un tiempo de retención para que no permanezcan para siempre en la cola.
A veces no deberías volver a intentarlo
Tenga en cuenta que errores como una validación o verificación de entrada no válida (por cuestiones de seguridad) no deben volver a intentarse, ya que fallarán una y otra vez.
Sin embargo, los problemas momentáneos de red, errores y tiempos de inactividad del servicio son de naturaleza dinámica y una estrategia de reintento podría resolver el problema de procesamiento.
Ahora que entendemos la teoría, escribamos el código de la función Lambda mejorada con el enfoque híbrido.
Manejador Lambda
Utilizaremos la utilidad de procesamiento por lotes de AWS Lambda Powertools en combinación con tenacidad para reintentos y manejo seguro. Procesaremos cada elemento del lote y, en caso de que se genere una excepción, volveremos a intentar procesarlo automáticamente. Sin embargo, si esos reintentos también fallan, utilizaremos la función de la utilidad de procesamiento por lotes para marcar el elemento del lote específico como un error parcial para que se devuelva al SQS.
SQS volverá a intentar ejecutar ese elemento específico e invocará la función Lambda nuevamente. En muchos casos, estos múltiples intentos de reintento pueden ser suficientes; sin embargo, en la segunda parte de esta publicación, analizaremos cómo manejar los errores de reintento con colas de mensajes fallidos.
Echemos un vistazo al controlador que definimos en el primer artículo.
En la línea 12, inicializamos el procesador por lotes y ponemos la clase de esquema de Pydantic que definimos y que es un lote de SQS. El procesador por lotes admite otros tipos de lotes , como los flujos de datos de Kinesis y los flujos de DynamoDB.
En las líneas 18 a 23, enviamos el evento a la función de inicio de la utilidad de procesamiento por lotes de Powertools. Le proporcionamos el procesador, el evento y nuestro controlador de elementos del lote. El controlador de elementos es nuestra función lógica interna que manejará cada elemento del lote.
Tenga en cuenta que mencioné que debería ser una función en la capa lógica y, en caso de que desee obtener más información sobre cómo estructurar su Lambda en capas (controlador, lógica y acceso a datos), asegúrese de leer mi publicación al respecto.
Todo el código Lambda se puede encontrar aquí .
Implementación de record_handler
Echemos un vistazo a la implementación de la función lógica interna de procesamiento de elementos.
En el primer artículo se veía así:
Ahora, queremos agregar "tenacidad" para reintentar los elementos fallidos durante el tiempo de ejecución de Lambda.
En la línea 9, definimos los casos de uso para activar un reintento automático. En este caso, definí como máximo dos reintentos y esperar 2^x * 1 segundo entre cada reintento, comenzando con 4 segundos y luego hasta 10 segundos. Este es un tiempo largo para reintentar, pero es un ejemplo. 'Tenacity' proporciona muchas condiciones de reintento, incluida la posibilidad de ignorar tipos específicos de excepciones y otros casos de uso avanzados. Lea más sobre esto aquí .
En las líneas 10 y 11, definimos la lógica de procesamiento de elementos básicos. Esta función debe ser segura para reintentar, es decir, es idempotente. Si no es así, debes asegurarte de que lo sea. Si quieres aprender sobre la idempotencia y cómo hacer que el código de tu Lambda sea idempotente, consulta mi publicación de blog aquí .
En la línea 15, registramos el artículo del pedido que recibimos.
En la línea 17, llamamos a la función lógica interna que procesará la función y volverá a intentarlo automáticamente (gracias a 'tenacidad') en caso de que se generen excepciones.
En las líneas 18 a 20, detectamos un RetryError que 'tenacity' genera cuando todos los intentos de reintento fallaron. Detectamos la excepción para iniciar sesión y agregar los metadatos necesarios para las medidas de depuración. Luego, la volvemos a generar para marcar la utilidad de procesamiento por lotes Powertools como un elemento fallido.
Mecánica de fallos parciales
La utilidad de procesamiento por lotes itera sobre los elementos del lote y llama a 'record_handler' para que gestione cada elemento. Una falla en esta función se correlaciona con la generación de una excepción no detectada. La utilidad de procesamiento por lotes de Powertools la detectará y marcará el elemento como fallido. Si 'record_handler' no genera una excepción, se considera que el elemento se procesó correctamente.
Hay tres valores de retorno potenciales para el controlador Lambda según la documentación de Powertools:
Todos los registros se procesaron correctamente . Devolveremos una lista vacía de errores de elementos {'batchItemFailures': []}. Todos los elementos se procesaron correctamente, ningún elemento regresa al SQS.
Éxito parcial con algunas excepciones . Devolveremos una lista de todos los números de secuencia e identificadores de elementos que no se procesaron. Solo los elementos que no se procesaron regresarán al SQS.
No se pudieron procesar todos los registros . Generaremos una excepción BatchProcessingError con una lista de todas las excepciones generadas durante el procesamiento y todos los elementos se devolverán al SQS.
Reflexiones finales
El procesamiento por lotes es uno de los casos de uso más básicos de cualquier aplicación sin servidor.
Es fácil equivocarse. Hay muchas advertencias; por ejemplo, agregas "tenacidad" pero olvidas extender el tiempo de espera general de la función, lo que podría llegar a un límite de tiempo.
Puede limitar el tamaño del lote, calcular el tiempo potencial por artículo, asumiendo que todos los artículos fallarán después del intento máximo de reintento y establecer el tiempo de espera en consecuencia.
Además, debe tener en cuenta los casos de uso en los que los reintentos adicionales no funcionan. En ese caso, debe utilizar una cola de mensajes fallidos. La próxima publicación del blog abordará las fallas y las mejores prácticas para las colas de mensajes fallidos.
Comments