Por Julian Wood, Senior Developer Advocate AWS
AWS Lambda provee un servicio de cómputo sin servidores (serverless) que puede escalar desde una simple petición hasta cientos de miles por segundo. Cuando diseñe su aplicación, especialmente para cargas grandes, es de ayuda entender como Lambda maneja la escalabilidad y el rendimiento.
Existen 2 componentes que considerar: concurrencia y transacciones por segundo.
Concurrencia de un sistema es la habilidad de procesar más de una tarea simultáneamente. Puedes medir la concurrencia en un punto en el tiempo para ver cuantas tareas está realizando el sistema en paralelo. El número de transacciones que un sistema puede procesar por segundo, no es lo mismo que la concurrencia, porque una transacción puede tomar más o menos de un segundo en ser procesada.
Este blog muestra como funciona la concurrencia y las transacciones por segundo en el ciclo de vida de Lambda. También muestra formas de medir, controlar y optimizarlas.
Ambiente de ejecución de Lambda
El código de Lambda se invoca en un ambiente de ejecución seguro y aislado. La siguiente imagen muestra el ciclo de vida de una petición a una función.
Primera invocación a la función
En la primera petición que invoca la función, Lambda crea un nuevo ambiente de ejecución. Luego corre el código de inicialización (init) de la función, que es el código que se encuentra afuera de la función principal que ejecuta la Lambda. Luego Lambda corre el código handler de la función como su invocación. Esto recibe un payload y procesa la lógica del negocio.
Cada ambiente de ejecución procesa una sola ejecución a la vez. Mientras el ambiente de ejecución procesa la petición, no puede procesar otra petición.
Después de que la Lambda termina de procesar la petición, el ambiente de ejecución está listo para procesar una petición nueva para la misma función. Como el código de inicialización ya corrió, para la 2a petición, la Lambda únicamente corre el código de la función handler de la invocación.
Lambda está lista para procesar peticiones adicionales para la misma función
Si una petición adicional llega mientras se esta procesando la petición 1, Lambda creará un nuevo ambiente de ejecución. En este ejemplo, Lambda crea nuevos ambientes de ejecución para las peticion es 2, 3, 4 y 5. Correrá la función de inicialización y la invocación al handler.
Lambda creando nuevos ambientes de ejecución
Cuando lleguen las peticiones, Lambda reutilizará los ambientes disponibles, y creará nuevos si es necesario. El siguiente ejemplo muestra el comportamiento adicional para las peticiones después de la 5.
Lambda reutilizando ambientes de ejecución.
Una vez que termine la ejecución 1, el ambiente estará disponible para procesar otra petición. Cuando llegue la petición 6, Lambda re-usa el ambiente de ejecución de la petición 1 y ejecuta la invocación. Este proceso continúa para las peticiones 7 y 8, que reutilizan el ambiente de ejecución para las peticiones 2 y 3. Cuando la petición 9 llega, Lambda crea un nuevo ambiente de ejecución ya que no existe uno disponible. Cuando la petición 10 llega, utiliza el ambiente que fue liberado de la petición 4.
El número de ambientes determina la concurrencia. Esta es la suma de todas las peticiones concurrentes para la ejecución actual de un punto específico en el tiempo. Para un solo ambiente de ejecución, el número de peticiones concurrentes es 1.
Peticiones concurrentes de un solo ambiente de ejecución
Para el ejemplo de las peticiones 1-10, la concurrencia de la función Lambda de un punto específico en el tiempo es el siguiente:
Concurrencia de Lambda en tiempos específicos
time |
concurrency |
t1 |
3 |
t2 |
5 |
t3 |
4 |
t4 |
6 |
t5 |
5 |
t6 |
2 |
Cuando el número de peticiones disminuye, Lambda detiene los ambientes de ejecución sin usar para liberar la capacidad de escalabilidad de otras funciones.
Duración de invocación, concurrencia, y transacciones por segundo
El número de transacciones que Lambda puede procesar por segundo es la suma de todas las invocaciones por ese periodo. Si una función toma 1 segundo en ejecutarse, y hay 10 invocaciones concurrentes, Lambda crea 10 ambientes. En este caso, Lambda procesa 10 peticiones por segundo.
Si la duración de la función se va a la mitad, ósea a 500 ms, la concurrencia Lambda permanece en 10. No obstante, las transacciones por segundo son ahora 20.
Transacciones lambda por segundo para una invocación de 500 ms por segundo
Si la función toma 2 segundos en correr, durante el segundo inicial, las transacciones por segundo son 0. Sin embargo, en promedio en el tiempo, las transacciones por segundo son 5.
Se puede ver la concurrencia usando metricas de Amazon Cloudwatch. Use el nombre de métrica ConcurrentExecutions para ver las invocaciones concurrentes para todas las funciones individuales
También puede estimar las peticiones concurrentes del número de peticiones por unidad de tiempo, en este caso segundos, y la duración promedio, usando la fórmula y las siguientes métricas de CloudWatch:
RequestsPerSecond x
AvgDurationInSeconds = peticiones concurrentes
Si la función Lambda toma un promedio de 500 ms para ejecutarse, a 100 ejecuciones por segundo, el número de peticiones concurrectes es 50:
100 peticiones/segundo x 0.5 seg = 50 peticiones concurrentes
Si la duración de la función la bajamos a 250 ms y el número de peticiones concurrentes la duplicamos a 200 peticiones/segundo, el número de peticiones concurrentes se mantiene igual:
200 peticiones/segundo x 0.250 seg = 50 peticiones concurrentes
Reducir la duración de la función puede aumentar las transacciones por segundo que una función puede procesar. Para más información de la reducción de la duración puede ver este
video de re:Invent
Cuotas de escalamiento
Existen 2 cuotas de escalamiento que considerar en concurrencia. Cuota por concurrencia en la cuenta y cuota de concurrencia de ráfaga. La concurrencia en cuenta es el número máximo de concurrencia que puedes tener en una Región en particular. Esta es compartida en todas las funciones de una cuenta. La concurrencia regional por default comienza en 1000, puedes aumentarla por medio de un ticket de servicio.
La concurrencia de ráfaga provee una ráfaga inicial de tráfico para cada función, entre 500 y 3000 por minuto, dependiendo de la Región.
Después de esta ráfaga inicial, las funciones pueden escalar en 500 invocaciones más por minuto en todas las Regiones. Si se alcanza el máximo número de peticiones concurrentes, las peticiones siguientes serán rechazadas.
Para invocaciones síncronas, Lambda regresará un error de throttling (error 429) al que lo invocó, que tendrá que re-intentar la petición. Con invocaciones asíncronas y el mapeo de la fuente del evento que lo invoca, Lambda automáticamente re-intentará la petición. Para mayor detalle lea: Error handling and automatic retries in AWS Lambda
Ejemplo de cuota a escala
La siguiente sección guiará en como la concurrencia de cuenta y ráfaga trabajan para una aplicación de ejemplo.
Anticipando el manejo de carga adicional los que construyen la aplicación solicitaron que se subiera la concurrencia de cuenta a 7,000. No existe otra función Lambda corriendo en esta cuenta, así que esta función puede utilizar toda la concurrencia disponible de la cuenta.
Concurrencia por cuenta y de ráfaga de Lambda
- 08:59: La aplicación ya tiene un stream continuo de peticiones, usando 1,000 ambientes de ejecución concurrentes. Cada invocación de lambda toma 250 ms, así que las transacciones por segundo son un total de 4,000.
- 09:00: Hay un pico de tráfico de 5,000 peticiones sostenidas. 1,000 peticiones usarán los ambientes que existen actualmente. Lambda utilizará 3,000 de la ráfaga de concurrencia disponible para crear nuevos ambientes que den servicio a la carga extra. 1,000 peticiones son rechazadas ya que no se tiene suficiente capacidad de ráfaga de concurrencia para manejar todas las 5,000 peticiones. Transacciones por segundo son 16,000.
- 09:01: Lambda escala por otras 500 invocaciones concurrentes por minuto. 500 peticiones siguen siendo rechazadas. La aplicación ahora puede manejar 4,500 peticiones concurrentes.
- 09:02: Lambda escala otras 500 invocaciones concurrentes por minuto. No existen peticiones negadas. La aplicación ahora puede manejar 5,000 peticiones.
- 09:03: La aplicación maneja de forma sostenida 5,000 peticiones. La cuota de concurrencia de ráfaga sube 500.
- 09:04: La aplicación tiene otro pico de tráfico, esta vez 3,000 peticiones continuas de forma inesperada llegan, para un total de 8,000 peticiones. 5,000 peticiones usan los ambientes de ejecución existentes. Lambda usa los 1,000 ambientes de ejecución que tiene disponibles para manejar la carga adicional. 1,000 peticiones son rechazadas porque no se tiene suficiente ráfaga de concurrencia. Otras 1,000 peticiones son rechazadas porque se alcanzó la cuota de concurrencia de la cuenta.
- 09:05: Lambda escala otras 500 peticiones concurrentes. Ahora la aplicación maneja 6,500 peticiones. 500 peticiones son rechazadas ya que no tenemos suficiente ráfaga de concurrencia para manejarlas. 1,000 peticiones son rechazadas ya que se alcanzó la cuota de concurrencia de la cuenta.
- 09:06: La Lambda escala otras 500 peticiones recurrente. La aplicación ahora puede manejar 7,000 peticiones. 1,000 peticiones siguen siendo rechazadas ya que la cuota de concurrencia por cuenta fue alcanzada.
- 09:07: La aplicación sigue manejando 7,000 peticiones concurrentes. 1,000 peticiones siguen siendo rechazadas por la cuota de concurrencia por cuenta fue alcanzada. Transacciones por segundo 28,000.
La cuota de servicio en los Servicios de AWS ayuda a manejar las cuotas de varios servicios de AWS. Así como buscar cuál es la cuota del servicio que le interesa, usted puede manejar aumentos en los limites de cuota desde la consola de Service Quotas.
Consola de cuotas de servicio
Concurrencia reservada
Puede configurar la concurrencia reservada de las funciones Lambda para colocar un máximo límite para una función. Esto asigna una parte de la cuota de concurrencia para una función en específico. Esto protege y asegura que una función no rechace peticiones y siempre pueda escalar al valor reservado de concurrencia.
También puede ayudarlo a proteger cuando los recursos se reducen. Si una base de datos o un API externo solo puede manejar 2 conexiones concurrentes, puede asegurara que Lambda no escale a más de 2 peticiones concurrentes. Esto asegurará que Lambda no sobre cargue los servicios del backend. También puede usar concurrencia reservada para evitar situaciones que pueda asegurar que su función solo corra una invocación concurrente a la vez.
Concurrencia reservada
También puede configurar la concurrencia a cero, lo que detendría cualquier invocación de la función y actúa como un apagado. Esto puede ser útil para detener las invocaciones de Lambda cuando se tiene un problema en las fuentes, te puede ayudar a solucionar el problema antes de quitar o incrementar la concurrencia y permitir que las invocaciones continúen.
Concurrencia aprovisionada
El proceso de inicialización de la función puede agregar latencia para sus aplicaciones. Usted puede reducir esta latencia configurando Concurrencia Aprovisionada para una versión o alias de una función. Esto prepara el ambiente de ejecución, corriendo el proceso de inicialización de la función, para que la función esté lista cuando se necesite.
Esto es principalmente útil para peticiones síncronas y asegurar que tengas suficiente concurrencia antes de un pico de tráfico esperado. Aún así puede crecer más usando la concurrencia estándar. El siguiente ejemplo muestra Concurrencia Aprovisionada configurada a 10. Lambda corre el proceso inicial para 10 funciones, y cuando nuevas peticiones lleguen, inmediatamente ejecutan la invocación
Concurrencia aprovisionada
Puedes hacer uso de Auto Scaling de Aplicaciones para ajustar la Concurrencia Aprovisionada automáticamente basada en la utilización de la métrica de la Lambda.
Métricas operacionales
Existen métricas en Amazon CloudWatch disponibles para monitorear su cuenta y concurrencia de funciones para asegurar que su aplicación escale como es esperado. Monitoree las métricas de Invocations y Duration de las funciones para entender su disponibilidad. Throttles muestra las invocaciones rechazadas.
ConcurrentExecutions monitorea el número total de ambientes de ejecución que están procesando eventos. Asegure que esto no llegue a su límite de concurrencia en su cuenta para no caer en rechazos. Use esta métrica en funciones individuales para observar cuales están usando concurrencia de cuenta, y también asegúrese que su concurrencia reservada no sea muy alta. Por ejemplo: una función puede tener una concurrencia reservada de 200, pero use realmente solo 10.
UnreservedConcurrentExecutions muestra el número de invocaciones de una función que no tenga concurrencia reservada. Esto es su buffer de concurrencia que tiene disponible.
Use ProvisionedConcurrencyUtilization para asegurar que no este pagando por Concurrencia Aprovisionada que no este usando. Esta métrica muestra el porcentaje de Concurrencia Aprovisionada que está en uso.
ProvisionedConcurrencySpilloverInvocations muestra las invocaciones de función que utilizan concurrencia estándar, por arriba del valor de Concurrencia Aprovisionada que tenga configurado. Esto puede mostrar si es necesario aumentar la Concurrencia Aprovisionada de la función.
Conclusión
Lambda provee de un servicio de computo altamente escalable. Entender como el escalado y límite de Lambda funciona puede ayudar a diseñar su aplicación, sobre todo para cargas masivas. Este post explica la concurrencia y transacciones por segundo. Muestra como funciona la cuota de concurrencia por cuenta y por ráfaga.
Puede configurar concurrencia reservada para asegurar que su función pueda siempre escalar, y también puede proteger sus recursos de backend. Use Concurrencia Aprovisionada para escalar de forma avanzada las invocaciones Lambda.
Para más recursos serverless, visite Serverless Land.
Acerca del autor
Julian Wood es un Senior Developer Advocate de AWS
Traductores
Rodrigo Cabrera es arquitecto de soluciones de AWS.
Servio Reyes es arquitecto de soluciones en AWS México