3 patrones de diseño imprescindibles que deberías conocer para tu sistema en cloud: Retry, Valet Key y Sharding

3 patrones de diseño imprescindibles que deberías conocer para tu sistema en cloud: Retry, Valet Key y Sharding
Sin comentarios Facebook Twitter Flipboard E-mail

El desarrollo en Cloud implica una serie de diferencias que debo de tener en cuenta cuando voy a desarrollar aplicaciones para ser publicadas en este entorno.

Si bien voy a obtener acceso a capacidades de crecimiento horizontal y vertical en mi infraestructura y servicios, con niveles de disponibilidad y seguridad difícilmente obtenibles en mis instalaciones locales; también es cierto que un despliegue a la Nube significa mucho más que correr mi código en este nuevo entorno.

Y más si estoy realizando un desarrollo desde cero, o realizando sufriendo en mis carnes una migración/transformación. En donde tengo que tener muy presente, como poco, estos 3 básicos patrones de diseño.

Retry Pattern

Tengo mis servicios en Cloud, y me comunico con ellos o a través de una aplicación local (sea de escritorio o nativa de mi dispositivo móvil), o por medio de un cliente Web que está publicado en un Web App.

Todo funciona de manera correcta hasta que me encuentro con lo que se llama una “Transient Fault”, o fallo temporal del servicio, y mi aplicación no debe romperse ni quedarse congelada.

Lo primero es saber que puedo tener tres tipos de sucesos que hagan inaccesible al servicio:

  • Que el propio servicio Cloud se haya caído o roto. En teoría esto no debería de ser posible que ocurriera, pero todas las grandes plataformas han sufrido incidencias graves en donde han ofrecido un rendimiento muy limitado o, directamente, han quedado inaccesibles.
  • Que el problema esté en las comunicaciones. Ya sea porque tenga interrupciones, caídas, una latencia demasiado alta o un ancho de banda demasiado pequeño. Pudiendo, además, estar situado el cuello de botella en cualquiera de los nodos por donde pasa la comunicación, este bajo mi control o no.
  • Que el problema esté en mi servicio. Porque esté bajo una demanda que sobrepase las capacidades del mismo, porque esté bajo un ataque, o por errores que degraden el rendimiento hasta un nivel que produzca fallos.

Sea cual sea el fallo temporal, en mi aplicación debería implementar un patrón de “Reintento”, para que esta incidencia sea soportada por el sistema. Y así, dependiendo de la importancia de la interrupción, actúe de una forma u otra.

Retry

Mi aplicación lanza una petición contra un servicio remoto en Cloud, y se encuentra con que el servicio no responde, por lo cual hago que espere un tiempo y que lo vuelva a intentar otra vez en un lapso temporal determinado.

Un buena política de reintentos hace nuestra aplicación mucho más resiliente

Para evitar que los propios reintentos puedan saturar el sistema cuando vuelva a estar funcionando, configuro que el tiempo entre reintento y reintento sea cada vez más largo, hasta que supere un límite en donde (finalmente) me devuelve un mensaje de error o excepción que alerte de un fallo que no es temporal.

Otra forma, si tengo muchos clientes accediendo de forma simultánea, es que el tiempo entre reintentos sea aleatorio (dentro de unos límites), huyendo también de poder tumbar el sistema al volverse a levantar.

Es decir, hay que tener cuidado en la configuración de los tiempos de reintento definidos en la implantación del patrón, ya que son muy dependientes de las exigencias del negocio; y posiblemente sea necesario un trabajo de optimización y ajuste regular, basado en métricas.

Por otra parte, aunque no sea parte del patrón, pero si resultado, tengo que escoger entre varias posibilidades de notificación al usuario del fallo:

  • Ofrecerle un sustituto. Por ejemplo, si está buscando en el catálogo de series le muestro el catálogo de películas. Esto implica una lógica de redirección basada en las preferencias de los usuarios y puede ser un tema tan complejo como efectivo y de agradecer. La principal ventaja, es que el usuario sigue utilizando la plataforma mientras se recupera el servicio.
  • No decir nada. El usuario se queda esperando a que el servicio se recupere, manteniéndose en la última pantalla de interacción. Transmitiendo la sensación de que no ha ocurrido nada.
  • Avisar lo antes posible. Es la estrategia adecuada si el número de usuarios es muy elevado, ya que evita una saturación por reintentos. En este caso se muestra una notificación que indique que no se puede cumplir la petición y se ofrece enlaces para salir de la petición al servicio caído.

Sin embargo, hay consideraciones a tener en cuenta al decidir implementar este patrón.

Una política demasiado agresiva de reintentos puede llevar al servicio a sus límites operativos. Además, es crítico que las peticiones puedan ser aceptadas y procesadas de forma unitaria, sin tener dependencias que requieren un momento u orden específico de llegada.

Este patrón debe ser evitado en el caso de que la realidad o las pruebas indiquen que el tipo de fallo con el que me puedo encontrar es generalmente permanente, o las operaciones del servicio al que lanzo la petición consumen mucho tiempo.

Valet Key

Cuando estamos hablando de una aplicación “normal” web o de escritorio, sin duda debo aplicar la programación asíncrona como estándar de los procesos pesados que limitan la experiencia de usuario.

Pero debo ir un paso más allá y adoptar uno de los patrones más conocidos y utilizados tanto en plataformas Cloud, como en todas aquellas en donde se realiza la autenticación y validación de un usuario para, por ejemplo, pueda acceder a una librería de fotos y descargarlas en su máquina local.

Si estuviera realizando un desarrollo acoplado, aun siendo asíncrono, llamaría primero al servicio para identificarme como usuario; dicho servicio comprobaría que tengo autorización y permisos adecuados e invocaría al servicio de almacenamiento para devolver las fotos solicitadas.

Sin embargo hay una forma un poco más indirecta, pero mucho más potente para desacoplar ambas operaciones y que cada servicio sea responsable solamente de su ámbito.

Valet Key

Como se observa en la figura, el cliente primero hace una llamada de identificación y autenticación al worker de la aplicación dedicado a ello. Este, el servicio de validación de usuarios, aplica la lógica que sea necesaria para comprobar que el usuario puede acceder a los recursos que está solicitando, y construye un token que le es devuelto al cliente.

A continuación, el cliente envía la petición de los recursos al servicio de almacenamiento junto con el token recibido; en el cual se incluye el periodo de validez del mismo – para dificultar que pueda ser utilizado de forma maliciosa- y los privilegios que se le han otorgado.

Hay otros 21 patrones que deberíamos conocer para nuestras arquitecturas en Cloud

Fíjate que con este patrón reparto la necesidad de procesamiento entre dos servicios, consiguiendo una optimización de los recursos que va a tener un impacto favorable en el coste de operación y desacoplando entre sí los servicios, con todas las ventajas de mantenimiento, despliegue y escalado que conlleva.

Otra ventaja es el poder almacenar una traza detallada de cada solicitud de requisitos recibida en cada uno de los módulos por separado.

Pero, por el lado negativo, tengo el aumento de la complejidad del código y que el worker de autorización no tiene constancia ni seguimiento de las operaciones realizadas en el servicio de almacenamiento con los token que ha entregado.

Y por ello no se puede aplicar el Valet Key si tenemos que realizar alguna operación o validación antes de que los datos sean almacenados o enviados al cliente.

Sharding Pattern

Las bases de datos, en su concepción clásica, tienen una capacidad limitada de escalado. Es decir, si quiero que mi instancia tenga más capacidad de cálculo primero le incremento la memoria y optimizo el sistema de almacenamiento (escalado vertical), y luego empiezo a replicar la arquitectura física (escalado horizontal).

Eso sí, a un coste de infraestructura, puesta en producción y mantenimiento especialmente altos.

También me enfrento con las posibles limitaciones de ancho de banda - aún más si estoy en un sistema Cloud con comunicaciones no dedicadas – y con desafíos ante las latencias que puedo sufrir si tengo a mis clientes situados en localizaciones geográficas distantes entre sí.

Para solventar estos inconvenientes, el patrón Sharding define la forma de optimizar la explotación de la base de datos por medio de la división horizontal del conjunto de datos en nodos diferenciados.

Sharing

En un ejemplo muy simplificado, podría separar los datos de la tabla de usuarios de forma que los primeros 5 millones estuvieran en el shard 1, los siguientes 5 en el shard 2 y así a continuación.

Cada shard tendrá su propia instancia, obteniendo mejores prestaciones en las operaciones al ser el número de registros menor que si tuviera una gigantesca BD con cientos de millones de registros.

Siendo, además, más simple escalar - de forma vertical-, y mucho más si estamos utilizando una base de datos en SaaS, ya que todas incluyen este patrón por defecto de forma interna.

Cada uno de estos patrones es una capsula de conocimiento para resolver los retos del Cloud

El mayor problema, sin duda, es la complejidad añadida al codificar el patrón desde de cero, obviando que los ORM más modernos y los servicios de bases de datos en Cloud ya incluyen este modo de dividir y explotar los datos. A lo que hay que sumar la decisión de cuando ha llegado el momento de aplicarlo. Ya que está orientado a la optimización de grandes volúmenes de datos, y obtener una sustancial mejora en las IOPS del repositorio de datos.

Por último, Sharding es un patrón que se conjunta y complementa con otras técnicas de división del conjunto de datos, ya sea de forma vertical, por dominio, por funcionalidad, etc.

Y mucho más

Estos tres patrones específicos para desarrollo en Cloud, deben ser conocidos y reconocidos por los desarrolladores. Pero no por ello son los únicos.

De hecho, Microsoft, en su libro Cloud Design Patterns, describe 24 patrones (incluyendo los descritos en este artículo) que todo arquitecto de Cloud debe incluir en su toolbox de soluciones.

Event Sourcing, CQRS, Circuit Breaker, GateKeeper, Piper & filters, Throttling o cualquier otro de ellos son, esencialmente, capsulas de conocimiento que encierran soluciones estándar para los retos consustanciales al desarrollo sobre este paradigma.

Más información | Cloud Design Patterns: Prescriptive Architecture Guidance for Cloud Applications

En Genbeta Dev | Migraciones al cloud en primera persona

Comentarios cerrados
Inicio