Serverless: Managing database connections

En el pasado articulo se realizaba una pequeña introducción al modelo de arquitectura serverless, detallando su funcionamiento así como alguna de las ventajas y desventajas que aportaba.

Entre los puntos negativos se citaba la complejidad de gestionar las conexiones a las bases de datos, a consecuencia de la capacidad de escalado de este modelo de solución. Por este motivo, en el presente artículo se pretende detallar algunas de las buenas prácticas para tratar de gestionar esta problemática.

Caso de uso

Se dispone de una función stateless desplegada sobre las 3 grandes plataformas cloud que interacciona con una base de datos relacional. Su ejecución se dispara vía petición HTTP.

Problemática

En un modelo de arquitectura serverless conviven, al menos, tantas instancias de una función como peticiones simultaneas se registren en dicho momento, y cada una de ellas se ejecuta en un contenedor o máquina virtual totalmente aislada.

Por lo tanto, y a diferencia de una arquitectura de microservicios, la cantidad de conexiones abiertas a la base de datos será proporcional al número de ejecuciones, ya que no comparten un pool con el que reutilizarlas.

Esto, unido a la capacidad de escalado, puede llevar a alcanzar el número máximo de conexiones abiertas de la base de datos y saturarla.

Fuente original

Corrurrencia y aislamiento de recursos

Antes de comenzar a explorar las diferentes alternativas para dar solución a la casuística presentada, es importante conocer como gestiona internamente cada proveedor cloud la concurrencia y aislamiento de recursos en un modelo FaaS.

AWS / GPC

Tanto en la nube de Amazon como en la de Google se crea una instancia para cada ejecución de una función, es decir, en caso de recibir 3 peticiones simultáneas se crearán 3 instancias diferentes. Cada una de ellas estará totalmente aislada del resto y contendrá sus propios recursos de CPU, RAM o almacenamiento.

Fuente Original

AWS emplea FireCracker para dar vida a dichas instancias, una tecnología de virtualización open source que se encarga de crear y correr máquinas virtuales ligeras denominadas microVMs, la cuales combinan la propiedades de seguridad y aislamiento de la virtualización con la potabilidad de los contenedores. Gracias a ello, logra resolver los problemas de seguridad que presentan los contenedores y reducir el tiempo de arranque.

Google en cambio opta por Knative, una plataforma open source basada en Kubernetes para crear, desplegar y gestionar los procesos serverless. A diferencia de FireCracker, las funciones se ejecutan sobre contenedores.

Azure

En Azure por el contrario, una instancia es capaz de correr múltiples ejecuciones de una función de forma concurrente, de tal manera que comparten recursos entre ellas. Es decir, en caso de recibir 3 peticiones de forma concurrente, una única instancia o host es capaz de dar servicio a todas ellas.

Fuente Original

Esta solución presenta tanto aspectos positivos como negativos. Por un lado, el hecho de compartir instancia permite reducir costes, reutilizar conexiones a la base de datos y minimizar la problemática presentada anteriormente.

Por desgracia, el rendimiento se ve fuertemente penalizado si las funciones a ejecutar requieren de un uso intensivo de CPU.

Soluciones

Reutilización de las conexiones

Una forma de minimizar el problema es reutilizar las conexiones entre las distintas ejecuciones en un mismo host. Como se comentaba en el anterior artículo, una vez ejecutado el código el contenedor se queda en estado “warm” durante un periodo de tiempo para no tener que padecer de cold start.

Es posible sacar provecho de ello estableciendo la conexión a la base de datos fuera de la función a ejecutar, ya que según especifica la documentación de AWS Lambda, cualquier variable fuera de la función se congelará entre invocaciones y podrá ser reutilizada.

const mysql = require('mysql');
 
var client = mysql.createConnection({
// Connection info
}).connect();
 
module.exports.handler = (event, context, callback) => {
        context.callbackWaitsForEmptyEventLoop = false;
	client.query('SELECT * FROM USERS', function (error, results) {
		callback(null, results)
	});
}

De este modo se reutiliza continuamente la misma conexión y se ahorra el tiempo que se requiere para establecerla, lo que se traduce en más de 200ms por ejecución.

Por desgracia, la solución planteada tiene el handicap de que en caso de caerse la conexión con la base de datos, las sucesivas ejecuciones en el mismo contenedor fallarán. Por lo tanto, una mejor aproximación es hacer uso de un pool (limitado a una única conexión) y obtener la conexión del pool en cada ejecución.

const mysql = require('mysql');
 
const pool = mysql.createPool({
	host: {Your Host},
    user: {Your Username},
    password: {Your Password},
    database: {Your Database},
    port: 3306
});

module.exports.handler = (event, context, callback) => {
	pool.getConnection((err, con)=>{
	con.query('SELECT * FROM USERS', function (error, results) {
		callback(null, results)
	});
}

A pesar de no tratarse de una solución propiamente dicha, sí que ayuda a paliar el problema. Ahora bien, existe un último factor a tener en cuenta, ¿Que ocurre cuando el proveedor cloud mata el contenedor tras varias horas de uso? ¿Cuándo se cierra definitivamente la conexión con la base de datos?

La respuesta es nunca. Por lo tanto y dado que no existe un hook que indique la destrucción del contenedor, será necesario establecer un tiempo de vida máximo reducido para cada conexión.

Cache

Otra buena medida complementaria a la anterior es el uso intensivo de la cache para las consultas de datos más comunes. De nuevo, es importante conocer los protocolos de conexión que soporta el producto seleccionado así como el número máximo de conexiones simultaneas.

Microservicios de acceso al dato

Una solución igualmente valida pero menos elegante es delegar las operaciones de base de datos sobre un microservicio que exponga una API REST.

De esta forma es el microservicio quien se encarga de gestionar las conexiones y operativa de la base de datos y la función en cambio, la que contiene la lógica de negocio más pesada. En resumidas cuentas, se pretende construir una fachada que permita HTTP(s) REST como medio de comunicación con la base de datos.

Esta solución sin embargo acarrea algunos inconvenientes como el hecho de tener siempre levantados dos o más microservicios para garantizar la alta disponibilidad, con los costes que esto acarrea, o el incremento de los tiempos de respuesta y tráfico de red.

HTTP(s)

La solución más empleada y la que se puede encontrar en todas las arquitecturas de referencia es hacer uso de una base de datos que exponga una API REST con la que realizar las operaciones: MongoDB, Couchbase, Aws DinamoDB, Google Cloud Data Storage…

Esto implica que no es necesario establecer y mantener una conexión permanente con la base de datos, lo que unido a las capacidades de escalado que ofrecen algunos de los productos en entornos cloud, hace que la capa de persistencia deje de ser el cuello de botella.

Fuente Original

Conclusión

En conclusión, aquellas soluciones de persistencia que ofrecen una API REST se adecuan mejor al modelo serverless, si bien sigue siendo necesario conocer tanto su modelo de escalado como su coste. Sobra decir que la modalidad “pago por uso” se adapta como anillo al dedo.

En caso de no disponerlo, es conveniente aplicar las medidas comentadas con el fin de paliar el efecto de cuello de botella.

Referencias

Se recomienda encarecidamente leer los siguientes artículos que han servido de base para el escrito:

Advertisements

Serverless: Introduction

En 2018 era una de las tendencias a seguir de cerca y este año comienza a irrumpir tímidamente en las grandes empresas. Pero, ¿Que es Serverless? ¿Viene a sustituir a los microservicios ahora que el mercado apuesta claramente por ellos?

En el presente artículo se pretende realizar una breve introducción a este concepto, así como destacar las ventajas y desventajas que presenta.

¿Que es Serverless?

Serverless, también conocido como FaaS (Functions as a Service), es un modelo de arquitectura cloud que permite al desarrollador ejecutar pequeños fragmentos de códigos o funciones stateless, durante un determinado periodo de tiempo, sin necesidad de aprovisionar ni administrar servidores.

Es el proveedor de la nube (AWS, Azure, Google) quien se encarga de la gestión del entono de ejecución y únicamente se nos factura por el tiempo en el que el código está siendo ejecutado.

¿Como funciona?

El código se despliega en la plataforma cloud deseada y se configura para ser invocado en base a eventos. Se entiende por evento desde una petición HTTP hasta acciones de otros servicios de la nube, como por ejemplo, almacenar un fichero en un S3 o registrar una determinada traza en CloudWatch. (Ambos servicios de AWS)

Una vez registrado el evento, el proveedor cloud crea un contenedor con el entorno de ejecución adecuado para el lenguaje de programación empleado y ejecuta la función desplegada.

Este contenedor será reutilizado en posteriores ejecuciones, siempre y cuando no supere los 5 minutos de inactividad, dato que puede variar en función del proveedor y el estado de la infraestructura. En caso de superar dicho umbral, el contenedor será destruido.

Finalmente, es importante señalar que las peticiones entrantes no se encolan, es decir, en caso de llegar 3 peticiones simultáneas, se crearán 3 instancias de la función y se ejecutarán en paralelo.

¿Que ventajas aporta?

Serverless

Como su propio nombre indica, la principal ventaja que aporta una arquitectura Serverless es que permite despreocuparse por completo de la gestión de los servidores, para centrar todos los esfuerzos en lo que realmente importa, que es la lógica de negocio.

Es el proveedor proveedor cloud pertinente quien se encarga tanto del aprovisionamiento, administración y mantenimiento del entorno de ejecución, así como del escalado, la alta disponibilidad, la centralización de los logs, el tracing distribuido o la monitorización.

En la siguiente imagen se puede observar como este modelo no es más que la evolución lógica a la que tiende el mercado tras la irrupción de la nube.

Se pasó de un modelo on premise, en el que el desarrollador debía encargase desde la lógica de negocio de la aplicación hasta la administración completa del servidor, pasando por los modelos IassS y PaaS, en los que la parte de infraestructura pasaba a ser paulatinamente responsabilidad del proveedor de la nube, hasta llegar a un modelo FaaS, en el que la única preocupación del desarrollador es la lógica de negocio.

Fuente original

Pago por uso

Una de las claves del serverless es que únicamente se nos facturara por el tiempo en el que la función se esté ejecutando, al contrario que en un IaaS o un PaaS, en el que hay que pagar por el mero hecho de tener el código desplegado.

Dicho de otro modo, la facturación se ajusta al uso real que se hace de la infraestructura.

Escalabilidad

El escalado es otro de los apartados que sale muy favorecido de este modelo de arquitectura.

Por un lado, está el hecho de que es el proveedor cloud quien se encarga levantar nuevas instancias en función de la carga de trabajo, por lo que lo que la limitación a nivel de hardware deja de ser un problema (Existen limitaciones a nivel de software, pero varían en función del proveedor).

Es decir, en caso de llegar 1000 peticiones de forma concurrente se crearán mil instancias de la función para dar respuesta a la demanda, de ahí a la necesidad comentada anteriormente de que el código desarrollado sea stateless.

Por otro lado, en una arquitectura serverless el escalado se realiza a grano fino, o lo que es lo mismo, en lugar de escalar una aplicación o un microservicio, se escala una función. Esto permite reducir costes ya que únicamente se paga por los recursos utilizados.

Time to market

La reducción del time to market es otra de claras ventajas que presenta este modelo.

El hecho de desarrollar pequeñas piezas de código desacopladas, no necesariamente escritas en el mismo lenguaje de programación, unido a los despliegues automatizados, reduce significativamente el tiempo que lleva desarrollar una nueva funcionalidad en comparación con las tradicionales aplicaciones monolíticas o microservicios.

Lo mismo ocurre con el mantenimiento y evolutivos, ya que siempre es más sencillo desarrollar sobre pequeños fragmentos de código acotados.

¿Que desventajas presenta?

Cold start

No iban a ser todas buenas noticias y el cold start entra en escena como la principal desventaja a tener en cuenta, especialmente si se pretenden desarrollar soluciones en tiempo real o con un bajo tiempo de respuesta.

Al inicio del artículo se comenta el hecho de que, cada vez se dispara un evento, el proveedor cloud se encarga de crear un contenedor con el entorno de ejecución adecuado, inyectar el código fuente y ejecutar la función. Una vez ejecutada, el contenedor se mantiene vivo para su reutilización en sucesivas peticiones, siempre y cuando no supere los 5 minutos de inactividad.

Dicho esto, se denomina cold start al tiempo que requiere el proveedor cloud para descargar el código fuente, configurar el contenedor e inicializar la aplicación para poder ejecutar la función.

Este lapso de tiempo varía significativamente en función de lenguaje de programación y la memoria asignada para su ejecución.

Fuente original

Como se puede observar en la comparativa adjuntada, lenguajes como Java o .NET tienen un cold start desmedido independientemente de la memoria asignada, especialmente si se pretenden ejecutar peticiones síncronas. Python, Go o Node Js por el contrario podrían llegar a aguantar el tipo con los recursos adecuados.

A estas alturas alguno podría pensar que tampoco es para tanto, al fin y al cabo, lo procesos asíncronos pueden asumir eso tiempo y los síncronos tan solo lo padecen la primera vez que se crea el contenedor, ¿no?

Es cierto que, en general, los procesos asíncronos pueden asumir ese tiempo. Ahora bien, tal y como comentábamos al principio del artículo,
las peticiones entrantes en una arquitectura serverless no se encolan y en caso de llegar 3 peticiones simultaneas se crearán 3 contenedores para dar respuesta a la demanda, con el consecuente cold start en cada una de ellas.

En la siguiente imagen se puede observar precisamente esto; El resultados de ejecutar 100 peticiones sobre una función, con una concurrencia de 10.

Fuente original

Backpresure

Anteriormente comentaba que uno de las grandes ventajas de una arquitectura FaaS era su capacidad prácticamente “ilimitada” de escalado, si bien esto puede provocar que hagan acto de presencia otros problemas en el modelo de solución.

El primer actor a observar es la base de datos. Generalmente la típica Oracle Database soporta un limitado número de conexiones abiertas de manera simultánea, de ahí al uso de los pools para su reutilización. Pero en un modelo serverless esta solución deja de tener cabida, debido a que se crean tantas instancias como peticiones simultáneas se produzcan, con su correspondiente pool y conexión a la base a datos.

Existen diversas alternativas para dar solución a esta casuística, como tratar de reaprovechar las conexiones a través de los hot-context, acceder a la bases de datos vía HTTP en aquellas que lo soporten (MongoDB, DynamoDB…), delegar la gestión de los datos en un microservicio, o pensar en BDs serverless como Aurora. Analizaremos este tema en profundidad en otro artículo.

Pero la base de datos puede no ser la única afectada por el escalado. Si se disponen de dependencia con APIs de terceros como PayPal, Stripe o Twitter por ejemplo, puede ser un problema, ya que limitan el máximo número de peticiones por segundo.

Algo similar puede ocurrir en caso de tener dependencia con los sistemas legacy de la empresa. Su capacidad de escalado no es comparable con la de los microservicios o funciones y pueden acabar ahogados en situaciones de estrés.

Herramientas de desarrollo

Otro de los elementos a tener en cuenta es el bajo grado de madurez de las herramientas de desarrollo. No hay más que acceder a los repositorios GIT de aws-sam-cli, cloud-functions-emulator o azure-functions-core-tools, utilidades para desarrollar, testear y debuggear funciones en local, para comprobar que se encuentran aún en estado alpha o beta.

Es previsible que en los próximos meses evolucionen a una velocidad de vértigo, pero es conveniente tenerlo en cuenta si se va a comenzar a desarrollar ahora.

Conclusión

En conclusión, del mismo modo que la migración a un escenario de microservicios trajo consigo beneficios y nuevos retos, con el modelo serverless ocurre algo similar.

Ahora bien, al igual que los microservicios, no son el remedio a todos los problemas, por lo que conviene analizar cuidadosamente el de uso para tratar de diseñar la mejor solución.

Aclaración

Aunque durante el artículo se vincula estrechamente la arquitectura serverless con un modelo cloud, es necesario matizar que no es estrictamente necesario, ya que iniciativas como Knative o Fission permiten ejecutar funciones en entornos on premise.

Referencias

Se recomienda encarecidamente leer los siguientes artículos que han servido de base para el escrito: