Hace unos meses me pasó, y seguro que a ti también te ha pasado. Y es que tenía una aplicación desplegada usando Serverless que por más que había seguido todas las buenas prácticas al desarrollarla siempre me daba problemas de timeouts intermitentes. En este artículo quiero contarte cómo es que resolví mi problema y convertí este dolor de cabeza en una API completamente funcional utilizando únicamente 3 servicios de AWS y mucho café.
Creo que a varios de nosotros nos ha pasado, alguna vez, que creamos algo que no funciona del todo bien. Al inicio, cuando decidí embarcarme en el reto de crear una aplicación serverless, me fue difícil identificar las funciones que necesitamos crear y los servicios que mejor se ajustaban a lo que estaba creando. Pero finalmente, aterricé con una idea que es la que quiero compartirles hoy y que utiliza los siguientes servicios:
- AWS API Gateway que es como un guardián que facilita y controla el acceso a diferentes servicios o sistemas, asegurando que la comunicación sea más fácil y segura. En nuestro caso nuestra API.
- AWS Lambda es un servicio de computación sin servidor que ejecuta código en respuesta a eventos, sin necesidad de administrar la infraestructura subyacente.
- AWS Step Functions es un servicio que permite coordinar y orquestar de manera visual y escalable los diferentes pasos y acciones de un flujo de trabajo, facilitando la creación y gestión de aplicaciones y procesos complejos.
En mi caso la aplicación con la que estaba trabajando ya utilizaba una arquitectura serverless solo que el alcance de la solución estaba enfocada únicamente a un API Gateway con integraciones a funciones Lambda. Este approach por lo general funciona bastante bien siempre y cuando mantengas tus lambdas limitadas a una funcionalidad en específico. Pero he aquí la parte “compleja” que a muchos nos cuesta identificar ¿Que tanto código deben tener nuestras funciones?.
Me tope con varias funciones lambdas con demasiado código, es decir, para lograr obtener el resultado esperado se realizaban muchas pequeñas tareas, varias de ellas iban a buscar información a servicios de terceros en forma secuencial (ósea una tarea después de otra) lo que causaba que el tiempo de procesamiento se fuera por los cielos y en consecuencia, varios errores de timeouts.
Agregue el diagrama “Implementación Original” para ejemplificar cómo estaba dividido el código de una de las lambdas que daban error.
- Encontré que esta función ejecutaba 5 tareas de forma secuencial
- Los tiempos de cada una de estas tasks variaba en forma intermitente.
- Las tasks 2 y 4 tenían llamadas a servicios externos y se ejecutaban de forma síncrona es decir que esperaba a que el resultado se completará para poder continuar con la ejecución del resto del código.
- La sumatoria de todos los endpoints sobrepasaba los 30 segundos configurados en API Gateway.
Al investigar un poco más a fondo aprendí que al hablar acerca de timeout es importante que tengamos en cuenta cuales son los límites de nuestros servicios y sus dependencias.
Lambda por ejemplo maneja un límite de 15 min, esto significa que una función de Lambda se detendrá automáticamente después de ejecutarse durante ese período de tiempo.
El límite de API Gateway por default es de 30 seg, sin embargo puedes ajustarlo a tus necesidades (Y apunta esto en un lugar que recuerdes junto a tu post it con la contraseña de la compu 😀), sin embargo, nadie quiere esperar más 30 segundos para obtener una respuesta, eso sería como si las computadoras te dejarán en visto.
Solución:
Vamos a traer a la mesa nuestro tercer servicio, el que vino a ser mi salvación ☀️, AWS Step Functions, como ya mencione este es un servicio orquestador, lo que significa que nos permite diseñar y ejecutar nuestros procesos de acuerdo a nuestras necesidades. Analicemos la idea:
Implementación Nueva
En el diagrama “Implementación Nueva”, tomé las mismas tareas que tenía en nuestra Lambda original y la dividí. Separe cada segmento de la Lambda original en varias lambdas, y evalúe cuáles podrían ejecutarse de forma paralela, y cuales dependían de de los resultados anteriores. Sin cambio de lógica únicamente el desacoplar el código.
Este cambio representó una reducción del tiempo de respuesta a menos de la mitad, en el caso real se redujo más, debido a que también refactoricé el código y aplique buenas prácticas en el desarrollo de lambdas, como: El obtener valores de parameter store e información de dynamoDB fuera del handler, cambios pequeños que hicieron la gran diferencia. En el caso real se redujo en un 64% del tiempo original. 🙂
Ahora, poder replicar esta solución necesitas:
- Una cuenta de AWS: Crea una nueva cuenta
- Un entorno de desarrollo en Cloud9, para este ejemplo puedes elegir utilizar una nueva instancia EC2 tipo t2.micro (esta es la incluida en la capa gratuita de aws 😉)
¡Comencemos!
Paso 1: Dentro del entorno de Cloud9 clone el repositorio con el siguiente comando:
$ git clone https://github.com/hsaenzG/APITimeoutsFix.git
Despues de esto en ya tengo en el ambiente el codigo que necesito:
Paso 2: En este ejercicio cree una Plantilla de SAM para desplegar toda la infraestructura que necesito, esto no quiere decir que no puedo crear todo manualmente directamente desde la consola pero para facilidad del ejercicio en este caso desplegare mi infraestructura con los siguientes comandos:
cd APITimeoutsFix
$ sam build
El resultado se ve así:
Ahora desplegaré la infraestructura, para eso ejecutaré el siguiente comando
$ sam deploy
Este comando desplegará en la cuenta de AWS lo siguiente:
1. Api Gateway con una integración Lambda, este es el ejemplo de cómo encontré el código original y si ejecutas este endpoint podrás ver que te devuelve el error de timeout.
2. Api Gateway con una integración a un step functions este endpoint es el encargado de ejecutar la step machine la cual ejecuta de forma paralela varias lambdas para obtener el mismo resultado de la primera API, pero sin el error de rendimiento.
3. Una máquina de estado con la configuración del flujo necesario para obtener toda la información requerida de la saga de Harry Potter:
4. Cinco Funciones Lambdas
con las siguientes características:
- LambdaIntegration/app.mjs → En esta función se encuentra la lógica original
- stepFunctionIntegration/hechizos.mjs → En esta función se encuentra la lógica necesaria para llamar al endpoint que devuelve únicamente el listado de hechizos de la saga de Harry Potter
- stepFunctionIntegration/libros.mjs → En esta función se encuentra la lógica necesaria para llamar al endpoint que devuelve únicamente el listado de libros de la saga de Harry Potter
- stepFunctionIntegration/personajes.mjs → En esta función se encuentra la lógica necesaria para llamar al endpoint que devuelve únicamente el listado de personajes de la saga de Harry Potter
- stepFunctionIntegration/info.mjs → En esta función se encuentra lógica necesaria para llamar al endpoint que devuelve todo el listado de hechizos, libros, personajes e información general de la saga de Harry Potter.
Consideraciones importantes que puedes observar en estos ejemplos:
1. La integración del API Gateway con step functions se ejecuta de forma síncrona, eso significa que la API espera que la state machine termine su ejecución para devolver la respuesta, si no se configura de esta manera el response del endpoint no contendrá el resultado que necesitas porque la step machine se ejecutará de forma independiente al endpoint del API gateway.
2. Para este caso de uso utilice el tipo de flujo de trabajo Express en mi state machine esto porque este tipo de workflow es ideal para el backend de aplicaciones móviles, y en mi caso es utilizado como parte del backend de una aplicación web, asi que me funcionaba perfecto, pero tambien puedes aplicarlo a otros casos de uso como:
- Cargas de trabajo de procesamiento de eventos de volúmenes elevados como datos de Iot
- Procesamiento de streaming, transformación y procesamiento de los datos.
3. Es importante tener en cuenta que el tiempo que este tipo de state machine tiene un máximo de 5 min para su ejecución.
4. El servicio de Step Functions se encuentra incluido en la lista de servicios dentro del free tier de AWS, sin embargo únicamente cubre los flujos de trabajo Standard.
5. La facturación de las ejecuciones de un flujo de trabajo express se realiza por el número de ejecución + la duración de la ejecución + la memoria consumida durante la ejecución. para conocer más acerca de los costos del uso de step functions puedes ir acá: https://aws.amazon.com/es/step-functions/pricing/
Testing:
Ahora haré la prueba de ambos endpoints para ver el resultado de cada uno de ellos:
Paso 1: Probaré el endpoint original para eso utilizaré postman (Postman es una aplicación que se utiliza para probar, documentar y enviar solicitudes a API's) pero tu puedes utilizar la terminal o cualquier aplicación que te ayude probar tus API’s, como Insomnia o Paw.
Ejecuta el endpoint de la API: APITimeoutsFix, la ruta de tu API la puedes obtener en: API Gateway → APITimeOutsFix → Etapas →Prod
Request URL: https:///Prod/LambdaIntegration/
Request Method: POST
Body: {}
Como resultado obtengo este error de forma intermitente:
Esto es porque internamente hay problemas de timeout en la ejecución y justo es el resultado que esperaba.
Paso 2: Ahora ejecutaré la solución, igual yo utilizaré postman pero tu puedes utilizar la terminal o cualquier aplicación que te ayude probar tus API’s.
Ejecutaré el endpoint de la API: WizaringStepFunctionsApi, la ruta de mi API la puedes obtener en: API Gateway → WizaringStepFunctionsApi → Etapas →v2
Request URL: https:///v2/WizaringWorldParalell
Request Method: POST
Body: {}
En este caso la respuesta cambia de la siguiente forma:
En este endpoint si se logra obtener los resultados esperados siempre. 🙂
Paso 3: Como tip interesante si quieres saber como ver la ejecución paso a paso de la step machine que estamos utilizando puedes habilitar el log, de la siguiente forma: ve a Step Functions → State Machines → IAM Role (haz click en el rol) → IAM→ Añadir permisos → Asociar permisos → añade las politica CloudWatchFullAccess.
Luego vuelve a la state machine y la modificas, en la parte de logging habilita el nivel del log a ALL para tener tracking de todo.
Ahora al volver a ejecutar mi endpoint obtengo un detalle de la ejecución en la state machine:
Y al entrar a ver el detalle puedo ver el log detallado de toda la ejecución:
Este es un feature que en lo personal me gusta mucho y ayuda bastante cuando se está desarrollando y probando la state machine ya que nos ayuda a ver el detalle de nuestros inputs y outputs que recibimos.
Bueno, ahora limpiaré la cuenta de AWS para no incurrir en gastos para eso volveré a Cloud9 y ejecutaré el siguiente comando:
$ sam delete
De esta forma elimine toda la infraestructura que utilice en este artículo.
Esta es una de muchas formas con las cuales puedes resolver los problemas de timeout en tus aplicaciones serverless, no es la única pero en mi caso me funciono de maravilla porque aunque no elimine todas las llamadas a las Api’s externas ni reduje el código logre separar el flujo de trabajo de una forma eficiente, identificando qué tareas no tienen dependencia entre ellas y pueden ser ejecutadas de forma paralela, además, logre crear funciones atómicas las cuales ahora tienen un solo propósito y puedo utilizarlas en cualquier otro flujo de trabajo en donde las pueda necesitar, lo que ayuda a que mi aplicación crezca de una manera más limpia y reutilizando código.
Gracias por llegar hasta el final del artículo, por favor comenta abajo que te pareció y si tuviste algún problema para replicar la demo. Si quieres aprender más sobre Step Functions y API Gateway visita los siguientes links:
Top comments (0)