Antes de empezar, un poco de contexto. La infraestructura está alojada en AWS y la arquitectura estaba basada en servicios Serverless:
- API Gateway
- Lambdas (Corrían un servicio de Node bastante simple que recibía una serie de parámetros, los serializaba e insertaba en una base de datos)
Para base de datos se está utilizando RDS con MySQL. La arquitectura a grosso modo se veía tal que así:
Esta arquitectura tenía tres problemas grandes:
- Debido al volumen de peticiones constantes que estábamos recibiendo, el Nº de invocaciones en simultáneo de las Lambdas se nos quedaba corto. Incluso con el incremento del límite a 10K tocábamos techo (por defecto son 1K).
- Por la confección de la Lambda en Node, en el RDS se abrían un Nº muy alto de conexiones, lo que obligaba a subir en recursos el RDS ya que utilizábamos una cantidad de RAM enorme (llegamos a tener una instancia
db.r6i.4xlarge). - El coste de mantener esto era enorme. En solo una semana se llegó a tener un coste en Lambdas de +$6.5K y AWS daba una previsión de gasto de +$36K. Y todo esto sin contar el dineral de gasto que estaba teniendo el RDS.
Cambiando la arquitectura
Una vez detectados los problemas y cuellos de botella de la arquitectura actual teníamos que pensar como reducir el coste drásticamente y además ser capaces de atender correctamente todo este tráfico:
Uno de los últimos picos de llamadas al API GW
En Helmcode barajamos la posibilidad de añadir algún sistema de colas de forma intermedia que nos permitiese invocar Lambdas solo en momentos puntuales pero nuestro volumen no solo fluctúa en picos sino que es constante, 24/7. Y justo uno de los problemas del Serverless es que es muy caro a la hora de atender peticiones de forma recurrente y con un volumen tan alto.
Dado que además la función Lambda era un servicio en Node bastante simple y su refactor para bajarlo a una API no nos llevaría tiempo, decidimos cambiar la arquitectura completa. Para ello:
- Como punto de entrada utilizamos ALB (Application Load Balancer)
- Aprovechamos nuestros clústers de Kubernetes (EKS + Autoscaling Groups) que ya corrían otros servicios.
- En cuanto al servicio de Node, decidimos dividirlo en 2 servicios:
- Un Worker que leía del servicio de streaming e insertaba por lotes los eventos en la base de datos para no saturarla.
Arquitectura con Kubernetes
Algunas cosas a destacar e interesantes en la configuración actual de Kubernetes:
- Se han desplegado 2 deployments, uno para la API y otro para el Worker. Tienen un HPA (Horizontal Pod Autoscaling) de:
- Worker: mínimo 5 Pods y máximo 1o Pods.
Con esta nueva arquitectura ya éramos capaces de soportar el tráfico que teníamos. Además hemos solventado dos grandes problemas:
- Los límites de invocaciones recurrentes de las Lambdas. Con Kubernetes tenemos más capacidad de autoescalado.
- La escritura en base de datos al ser ahora por lotes en lugar de directamente, hizo que cayera en picado el Nº de conexiones recurrentes, que a su vez se tradujo en una demanda mucho menor de recursos:
Conexiones simultáneas al RDS
Show me the money!
Tras poco mas de una semana con todo funcionando con la nueva arquitectura, ya podemos empezar a sacar varias conclusiones y sobre todo a hacer números de cuánto estamos ahorrando.
Infraestructura Serverless + RDS:
- API Gateway: +$1.2K con una previsión de gasto de +$7.2K
- Lambdas: +$6.5K con una previsión de gasto en el mes de +$36K.
- RDS: con una instancia tipo
db.r6i.4xlargela previsión de coste mensual era de +$1.5K
🔥 Total previsto al mes: +$45K
Infraestructura en K8s + Elasticache + RDS:
- Application Load Balancer: +$160, previsión al mes de ~$650
- Computo:
- EKS: +$103 con una previsión de +$300
🤑 Total previsto al mes: ~$2K
El ahorro previsto por este cambio de arquitectura es de ~$43K al mes. De hecho posiblemente incluso podamos ahorrar aún más porque aún seguimos evaluando si podemos bajar en recursos alguno de los nodos, tenemos que verificar si podemos ajustar los tipos de instancias y comprobar finalmente Saving Plans, reservas de instancias, etc.
Pero esto, posiblemente, sea ya para otro post 👋