Soluciones habitacionales para aplicaciones multitenant

Hoy en día comercializar el software en forma de servicio, con cuotas recurrentes en lugar de una licencia perpetua, actualizaciones periódicas (si no continuas) y gestión externalizada, es cada vez más habitual. Los motivos son varios, desde el claro atractivo de cobrar más por (más o menos) lo mismo, hasta el mundo de posibilidades que nos ofrece la tan manida nube.

Cuando se trata de diseñar una aplicación preparada para este tipo de comercialización, uno de los puntos que hay que tener en cuenta es la forma en que vamos a gestionar los distintos clientes de nuestra aplicación. Si hablamos de software para empresas, nuestros clientes serán empresas con uno o más usuarios, y cada una de ellas trabajará con su propia información.

Existen varias formas de gestionar esto dependiendo de qué recursos estemos dispuestos a compartir entre cada una de nuestros clientes (tenants) y, como siempre, cada una de ellas tiene sus propias características que la hacen más o menos atractiva dependiendo del contexto.

OJO: aviso de antemano que mi experiencia en este ámbito es terriblemente limitada y se reduce a alguna aplicación pequeña. Mi objetivo es intentar poner en claro mis pensamientos y, si con un poco de suerte, consigo que en los comentarios me corrijáis y déis más ideas, estupendo.

Independencia total

unifamiliar

Una alternativa que, a priori, parece bastante sencilla, al menos desde el punto de vista de desarrollo, es mantener completamente aislados a nuestros clientes unos de otros. Para cada uno de ellos desplegaremos una instancia de nuestra aplicación, incluyendo servidor, base de datos, etc.

Sería equivalente a construir viviendas unifamiliares para nuestros clientes. Todos tienen la misma estructura, pero son virtualmente independientes. Probablemente necesitemos construir algo de infraestructura común, por ejemplo, para el acceso, pero por lo demás nuestro sistema prácticamente será igual que si tuvieramos un sólo cliente, lo que simplifica consideramente el desarrollo.

Además, este enfoque tiene otras ventajas, como que podríamos llegar a personalizar la aplicación para cada cliente si fuera necesario, creando distintas variantes de la misma sin tener que pensar si las modificaciones que hacemos para un cliente afectan a los demás. No hace falta pegarse con los vecinos para decidir si ponemos un ascensor o no, cada vivienda es independiente y puede hacer lo que necesite.

La gestión de la información es fácil: cada cliente tendrá su propia base de datos aislada, lo que nos permitirá ofrecerle backups propios sin dificultad o eliminar su información cuando deje de ser cliente nuestro y cumplir con los requisitos legales que sean necesarios.

Puesto que son despliegues independientes, los problemas de un tenant no deberían afectar al resto. Por desgracia, en la vida real más que chalets independientes tendremos chalets pareados, porque estaremos tendremos máquinas virtuales o contenedores que comparten máquina física, y es posible que algo nos molesten los ruidos del vecino.

En la parte negativa, con esta idea tendremos unos costes operacionales mayores porque necesitaremos mantener en producción más instancias de servidores y bases de datos. Los costes de infraestructura también crecerán porque es más complicado aprovechar bien los recursos, y es probable que tengamos máquinas (virtuales o físicas) infrautilizadas.

Otro problema es que si dejamos que las versiones desplegadas para cada tenant diverjan en exceso, al final ya no tenemos un producto, sino un produyecto y nos alejamos de las ventajas de tener algo paquetizado que podamos vender de forma sencilla tantas veces como queramos.

En realidad, esta opción no deja de ser una forma de resolver el problema evitándolo. No estamos construyendo una aplicación multitenant; tan sólo estamos desplegando muchas aplicaciones single tenant. A veces es una opción perfectamente válida y puede ser un primer paso hacia otras alternativas.

Independencia de datos

pau

El siguiente paso podría ser mantener parte de la infraestructura común, pero separar los datos de cada tenant. Podríamos tener un mismo servidor web (o un pool de servidores web, dependiendo de nuestra arquitectura) compartidos entre todos los tenants, y luego mantener bases de datos completamente independientes para cada tenant.

Sería similar a una vivir en una comunidad de vecinos. Una parte importante de la infraestructura es común (acceso a las viviendas, instalación eléctrica, fontanería, ascensores, etc)., pero luego cada uno cuenta con su propia casa y (hasta cierto punto), hace en ella lo que quiere.

En este escenario podemos aprovechar mejor la parte de frontend (los servidores web), y escalar el almacenamiento de datos según vayan apareciendo nuevos tenant. A nivel de desarrollo es algo más complicado que antes, porque necesitaremos asegurarnos de que cada tenant accede a la base de datos adecuada y que el resto de información dependiente del tenant (como ficheros de configuración) se trata correctamente. Al final, es relativamente sencillo mover casi toda es configuración a la base de datos, por lo que si resolvemos esa parte, el resto viene rodado.

Mantener los datos independizados mantiene las ventajas del enfoque anterior, pudiendo proporcionar a cada tenant copias de seguridad o acceso a sus datos (y sólo a sus datos) fácilmente y gestionarlos de forma acorde a la normativa legal que toque de cara a borrados y demás.

Al haber aumentado la parte compartida entre tenants, ya no es tan sencillo hacer modificaciones específicas para cada uno de ellos. Siguiendo con el ejemplo de la comunidad de vecinos, si queremos cambiar el modelo de ascensor, el horario de la piscina, las lámparas del descansillo o incluso el aspecto de los buzones, será necesario hacerlo de forma coordinada con el resto de vecinos.

También se incrementa la probabilidad de que problemas de un tenant afecten a otros. Si un tenant realiza una operación que introduce mucha carga en el frontend, el resto de tenants podrían verse afectados. Igual que en una comunidad de vecinos, si el de arriba monta una fiesta te acabarás enterando.

Este acoplamiento entre tenants hace que corramos el riesgo de acabar teniendo una aplicación excesivamente compleja de configurar y/o extender por la cantidad de funcionalidades solapadas que puede ser necesario soportar para dar cabida a los distintos requisitos de cada tenant y las interacciones entre ellas.

La carga operacional y los costes de infraestructura se reducen porque ya no necesitamos controlar instalaciones completas para cada tenant, pero seguimos necesitando una buena estrategia para gestionar las distintas bases de datos, mantener sus esquemas, copias de seguridad, planes de mantenimiento, etc.

Sistema compartido

shared

Si en el enfoque anterior unifícabamos la parte de frontend, el siguiente paso podría ser unificar también la parte de almacenamiento de información. Podríamos verlo como un piso de estudiantes, en el que ya no sólo compartimos la infraestructura general, sino que también tenemos que compartir cosas más básicas como la nevera o el baño.

Igual que en un piso compartido hace falta idenficar de quién es cada cosa y es frecuente ver notas en la comida de la nevera, cuando tenemos una aplicación multitenant en la que compartimos el sistema de almacenamiento habrá que poder diferenciar de quién es cada dato.

Existen varias formas de hacerlo. Si la base de datos lo soporta, podríamos utilizar un esquema diferente para cada tenant, lo que es bastante limpio y relativamente fácil de implementar en la aplicación. Siguiendo con el símil habitacional, sería parecido a repartir los estantes de la nevera. Otra opción es añadir en cada registro de la base de datos (o documento, o lo que sea que use la base de datos) un indicador del tenant al que pertenece. La equivalencia sería poner un post-it en cada cosa que hay en la nevera. Esto requiere un trabajo mucho mayor a nivel de aplicación y hay que tener especial cuidado para evitar comerse las cosas de otro fugas de información entre tenants.

Una ventaja directa de este sistema es que a nivel operacional sólo tenemos que mantener una única aplicación con su(s) base(s) de datos. Esto simplifica la parte de despliegue de nuevas versiones, monitorización, copias de seguridad, etc. Además, es más fácil optimizar los recursos empleados y reducir los costes de infraestructura. Sale más barato compartir piso que vivir en un chalet.

Si queremos escalarlo puede ser más complicado que con las opciones anteriores, pero con un poco de sentido común, si mantenemos el frontend sin estado, es relativamente fácil escalar añadiendo más máquinas, y podemos recurrir a técnicas de sharding para repartir los tenants en varias bases de datos (sin necesidad de ir a una base de datos por tenant), lo que nos debería permitir llegar bastante lejos.

Entre los problemas, además de tener el mismo acoplamiento a nivel de código que en el caso anterior, tenemos el acomplamiento a nivel de base de datos, por lo que es más fácil que los problemas de un tenant afecten a otro. Ya no sólo es que puedan sobrecargar el frontend, también pueden sobrecargar la parte de almacenamiento, o incluso dependiendo de lo mal que lo hayamos hecho, saturarla superando el máximo almacenamiento permitido y tirando abajo todo el sistema. Aquí ya no es que el vecino de arriba monte una fiesta, es que la fiesta está en tu salón. Todo esto es controlable, pero requiere un esfuerzo adicional. 

Una parte que se complica más con este esquema es poder ofrecer a cada tenant copias de seguridad o acceso directo a su almacenamiento de datos. Necesitaremos un sistema que nos permita copiar, borrar y gestionar partes de la base de datos por separado. Antes lo teníamos «gratis» con las herramientas propias de la base de datos, y ahora nos tocará correr a nosotros con ese esfuerzo.

Resumen

Seguro que existen muchas más alternativas para desarrollar aplicaciones multitenant, y además estoy convencido de que cada caso real tiene un montón de peculiaridades que hacen que deba ser analizado por separado.

Aun así, creo que los ejes en los que se mueven las distintas soluciones son los habituales en el desarrollo de desarollo: coste de desarrollo, coste de mantenimiento, flexibilidad, etc.

Cuando más desacoplemos los diferentes tenant de la aplicación menores serán los costes de desarrollo y más flexibilidad tendremos para gestionar, si fuera necesario, cada tenant como un proyecto diferente. Si tendemos a unificarlos, podremos minimizar los costes de explotación, lo que muchas veces puede ser determinante en este tipo de desarrollos.