Diseño para gran escala y alta disponibilidad

Last reviewed 2023-08-05 UTC

En este documento del framework de arquitectura de Google Cloud, se proporcionan principios de diseño para diseñar tus servicios a fin de que puedan tolerar fallas y escalar en respuesta a la demanda del cliente. Un servicio confiable continúa respondiendo a las solicitudes de los clientes cuando hay una demanda alta en el servicio o cuando hay un evento de mantenimiento. Los siguientes principios y prácticas recomendadas de diseño de confiabilidad deben ser parte de la arquitectura del sistema y el plan de implementación.

Crea redundancia para obtener una mayor disponibilidad

Los sistemas con necesidades con alta confiabilidad no deben tener puntos únicos de fallo, y sus recursos deben replicarse en varios dominios con fallas. Un dominio con fallas es un grupo de recursos que pueden fallar de forma independiente, como una instancia de VM, una zona o una región. Cuando replicaste entre dominios con fallas, obtendrás un nivel de disponibilidad agregado más alto que el de las instancias individuales. Para obtener más información, consulta Regiones y zonas.

Como ejemplo específico de la redundancia que podría ser parte de la arquitectura de tu sistema, para aislar fallas en el registro DNS de zonas individuales, usa nombres de DNS zonales para que las instancias de la misma red accedan entre sí.

Diseña una arquitectura de varias zonas con conmutación por error para alta disponibilidad.

A fin de hacer que tu aplicación sea resiliente a las fallas zonales, puedes diseñarla para usar grupos de recursos distribuidos en varias zonas con replicación de datos, balanceo de cargas y conmutación por error automatizada entre zonas. Ejecuta réplicas zonales de cada capa de la pila de aplicaciones y elimina todas las dependencias de varias zonas en la arquitectura.

Replica los datos entre las regiones para la recuperación ante desastres

Replica datos a una región remota o archívalos para habilitar la recuperación ante desastres en caso de una interrupción regional o pérdida de datos. Cuando se usa la replicación, la recuperación es más rápida porque los sistemas de almacenamiento en la región remota ya tienen datos que están casi actualizados, además de la posible pérdida de una pequeña cantidad de datos debido al retraso de la replicación. Cuando usas el archivado periódico en lugar de la replicación continua, la recuperación ante desastres implica restablecer los datos de las copias de seguridad o los archivos en una región nueva. Por lo general, este procedimiento genera un tiempo de inactividad del servicio más largo que la activación de una réplica de la base de datos actualizada de forma continua y podría implicar más pérdida de datos debido al intervalo entre las operaciones de copia de seguridad consecutiva. Cualquiera que sea el enfoque que se use, toda la pila de la aplicación se debe volver a implementar y, luego, iniciarse en la región nueva, y el servicio no estará disponible mientras esto suceda.

Si deseas obtener un análisis detallado de los conceptos y las técnicas de recuperación ante desastres, consulta Arquitectura de recuperación ante desastres para interrupciones de la infraestructura de nube.

Diseña una arquitectura multirregional para la resiliencia ante las interrupciones regionales.

Si tu servicio necesita ejecutarse de forma continua, incluso en el caso excepcional de que falle una región completa, diséñalo para que use grupos de recursos de procesamiento distribuidos en diferentes regiones. Ejecuta réplicas regionales de cada capa de la pila de aplicaciones.

Usa la replicación de datos entre regiones y la conmutación por error automática cuando una región deje de funcionar. Algunos servicios de Google Cloud tienen variantes multirregionales, como Spanner. Para resistir ante fallas regionales, usa estos servicios multirregión en tu diseño cuando sea posible. Para obtener más información sobre las regiones y la disponibilidad de servicios, consulta Ubicaciones de Google Cloud.

Asegúrate de que no haya dependencias entre regiones para que el impacto de una falla a nivel de región se limite a esa región.

Elimina puntos únicos de fallo regionales, como una base de datos principal de una sola región que puede causar una interrupción global cuando no se puede acceder a ella. Ten en cuenta que las arquitecturas multirregionales suelen costar más, así que ten en cuenta la necesidad empresarial en comparación con el costo antes de adoptar este enfoque.

Si deseas obtener más información para implementar la redundancia en los dominios con fallas, consulta el documento de la encuesta Arquetipos de implementación para aplicaciones en la nube (PDF).

Elimina los cuellos de botella de escalabilidad

Identifica los componentes del sistema que no pueden crecer más allá de los límites de recursos de una sola VM o zona. Algunas aplicaciones escalan de forma vertical, en las que agregas más núcleos de CPU, memoria o ancho de banda de red en una sola instancia de VM para manejar el aumento de carga. Estas aplicaciones tienen límites estrictos en cuanto a su escalabilidad, y debes configurarlas de forma manual para manejar el crecimiento.

Si es posible, rediseña estos componentes para escalar de forma horizontal, como con la fragmentación o la partición entre VM o zonas. Para manejar el crecimiento en el tráfico o el uso, agrega más fragmentos. Usa los tipos de VM estándar que se pueden agregar de forma automática para controlar los aumentos en la carga por fragmento. Para obtener más información, consulta Patrones de apps escalables y resilientes.

Si no puedes rediseñar la aplicación, puedes reemplazar los componentes que administras con los servicios en la nube completamente administrados que están diseñados para escalar de forma horizontal sin necesidad de acciones del usuario.

Disminuye los niveles de servicio correctamente cuando haya sobrecarga

Diseña tus servicios para tolerar la sobrecarga. Los servicios deben detectar la sobrecarga y mostrar respuestas de menor calidad al usuario o dejar de recibir tráfico de forma parcial, no fallar por completo bajo sobrecarga.

Por ejemplo, un servicio puede responder a las solicitudes de los usuarios con páginas web estáticas y, también, inhabilitar de forma temporal el comportamiento dinámico que es más costoso de procesar. Este comportamiento se detalla en el patrón de conmutación por error en caliente de Compute Engine a Cloud Storage. O bien, el servicio puede permitir operaciones de solo lectura e inhabilitar de forma temporal las actualizaciones de datos.

Se debe notificar a los operadores para que corrijan la condición de error cuando se degrada un servicio.

Evita y mitiga los aumentos repentinos de tráfico

No sincronices solicitudes entre clientes. Si demasiados clientes envían tráfico en el mismo instante, se provocarán aumentos repentinos de tráfico que pueden causar errores en cascada.

Implementa estrategias de mitigación de aumentos en el servidor, como la limitación, la cola, la reducción de carga o la interrupción de circuitos, la degradación elegante y la priorización de solicitudes críticas.

Las estrategias de mitigación en el cliente incluyen una limitación del lado del cliente y una retirada exponencial con jitter.

Limpia y valida las entradas

Para evitar entradas erróneas, aleatorias o maliciosas que causen interrupciones del servicio o violaciones a la seguridad, limpia y valida los parámetros de entrada para las API y las herramientas operativas. Por ejemplo, Apigee y Google Cloud Armor pueden ayudar a proteger contra ataques de inserción.

Usa pruebas Fuzz con regularidad, en las que un agente de prueba llama a las API de forma intencional con entradas aleatorias, vacías o demasiado grandes. Realiza estas pruebas en un entorno de pruebas aislado.

Las herramientas operativas deben validar automáticamente los cambios de configuración antes de su lanzamiento y deben rechazarlos si falla la validación.

Seguridad ante fallas, de manera que preserve la función

Si se produce una falla debido a un problema, los componentes del sistema deberían fallar de una manera que permita que todo el sistema siga funcionando. Estos problemas pueden ser un error de software, una entrada o configuración incorrectas, una interrupción de instancia no planificada o un error humano. El proceso de tus servicios ayuda a determinar si debes ser demasiado permisivo o demasiado simple, en lugar de demasiado restrictivo.

Considera las siguientes situaciones de ejemplo y cómo responder a una falla:

  • Por lo general, es mejor que un componente de firewall con una configuración incorrecta o vacía falle y permita que el tráfico de red no autorizado pase por un período corto mientras el operador corrige el error. Este comportamiento mantiene el servicio disponible, en lugar de fallar el cierre y bloquear el 100% del tráfico. El servicio debe confiar en las verificaciones de autenticación y autorización más profundas en la pila de aplicaciones para proteger las áreas sensibles mientras pasa todo el tráfico.
  • Sin embargo, es mejor que un componente del servidor de permisos que controle el acceso a los datos del usuario falle y bloquee todo el acceso. Este comportamiento provoca una interrupción del servicio cuando la configuración está dañada, pero evita el riesgo de una filtración de datos del usuario confidenciales si no se abre.

En ambos casos, la falla debería generar una alerta de prioridad alta para que un operador pueda corregir la condición de error. Los componentes del servicio deben fallar en el caso de errores abiertos, a menos que presenten riesgos extremos para la empresa.

Diseña las llamadas a la API y los comandos operativos para que puedan reintentarse.

Las API y las herramientas operativas deben hacer que las invocaciones sean seguras para el reintento en la medida de lo posible. Un enfoque natural para muchas condiciones de error es reintentar la acción anterior, pero es posible que no sepas si el primer intento se realizó de forma correcta.

La arquitectura del sistema debería realizar acciones idempotentes: si realizas la acción idéntica en un objeto dos o más veces seguidas, debería producir los mismos resultados que una invocación única. Las acciones no idempotentes requieren un código más complejo para evitar la corrupción del estado del sistema.

Identifica y administra dependencias de servicio

Los diseñadores y propietarios de servicios deben mantener una lista completa de dependencias de otros componentes del sistema. El diseño del servicio también debe incluir la recuperación ante fallas de dependencias o una degradación elegante si no se puede realizar la recuperación completa. Ten en cuenta las dependencias de los servicios en la nube que usan tu sistema y las dependencias externas, como las API de servicio de terceros, que reconocen que cada dependencia del sistema tiene una tasa de fallas distinta de cero.

Cuando configures los objetivos de confiabilidad, reconoce que el SLO de un servicio está restringido de forma matemática por los SLO de todas sus dependencias críticas. No puedes ser más confiable que el SLO más bajo de una de las dependencias. Para obtener más información, consulta el cálculo de la disponibilidad del servicio.

Dependencias de inicio

Los servicios se comportan de manera diferente cuando se inician en comparación con su comportamiento de estado estable. Las dependencias de inicio pueden diferir de forma significativa de las dependencias de entorno de ejecución de estado estable.

Por ejemplo, durante el inicio, un servicio puede necesitar cargar información del usuario o de la cuenta de un servicio de metadatos del usuario que rara vez invoca. Cuando muchas réplicas de servicio se reinician después de una falla o mantenimiento de rutina, las réplicas pueden aumentar considerablemente la carga en las dependencias de inicio, en especial cuando las memorias caché están vacías y deben volver a propagarse.

Prueba el inicio del servicio con carga y aprovisiona las dependencias de inicio según corresponda. Considera un diseño para degradar con facilidad mediante el guardado de una copia de los datos que recupera de las dependencias de inicio críticas. Este comportamiento permite que tu servicio se reinicie con datos potencialmente obsoletos, en lugar de no poder iniciarse cuando una dependencia crítica tiene una interrupción. El servicio puede cargar datos nuevos, cuando sea posible, para volver a su funcionamiento normal.

Las dependencias de inicio también son importantes cuando inicias un servicio en un entorno nuevo. Diseña tu pila de aplicaciones con una arquitectura en capas, sin dependencias cíclicas entre capas. Las dependencias cíclicas pueden parecer tolerables porque no bloquean los cambios incrementales en una sola aplicación. Sin embargo, las dependencias cíclicas pueden hacer que sea difícil o imposible reiniciar después de un desastre que elimina toda la pila de servicios.

Minimiza las dependencias críticas

Minimiza la cantidad de dependencias críticas de tu servicio, es decir, otros componentes cuya falla generará inevitablemente interrupciones del servicio. Para que tu servicio sea más resiliente a las fallas o la lentitud en otros componentes de los que depende, ten en cuenta los siguientes principios y técnicas de diseño de ejemplo a fin de convertir dependencias críticas en dependencias no críticas:

  • Aumenta el nivel de redundancia en las dependencias críticas. Agregar más réplicas disminuye la probabilidad de que un componente completo no esté disponible.
  • Usa solicitudes asíncronas en otros servicios en lugar de bloquear una respuesta o usa mensajes de publicación o suscripción para separar las solicitudes de las respuestas.
  • Almacena en caché las respuestas de otros servicios para realizar recuperaciones de la falta de disponibilidad a corto plazo de las dependencias.

A fin de que las fallas de tu servicio sean menos perjudiciales para otros componentes que dependen de él, ten en cuenta los siguientes principios y técnicas de diseño de ejemplo:

  • Usa colas de solicitudes priorizadas y otorga mayor prioridad a las solicitudes en las que un usuario espera una respuesta.
  • Entrega respuestas desde una caché para reducir la latencia y la carga.
  • Seguridad ante fallas, de manera que preserve la función
  • Disminuir fácilmente cuando hay una sobrecarga de tráfico.

Asegúrate de que todos los cambios puedan revertirse

Si no hay una forma bien definida de deshacer ciertos tipos de cambios en un servicio, cambia el diseño del servicio para admitir la reversión. Prueba los procesos de reversión de forma periódica. Las API para cada componente o microservicio deben tener versiones, con retrocompatibilidad de modo que las generaciones anteriores de clientes sigan funcionando de forma correcta a medida que la API evoluciona. Este principio de diseño es esencial para permitir el lanzamiento progresivo de cambios de API, con reversión rápida cuando es necesario.

La reversión puede ser costosa a fin de implementarse en aplicaciones para dispositivos móviles. Firebase Remote Config es un servicio de Google Cloud para facilitar la reversión de funciones.

No puedes revertir los cambios en el esquema de la base de datos con facilidad, por lo que debes ejecutarlos en varias fases. Diseña cada fase para permitir la lectura segura del esquema y actualizar las solicitudes según la última versión de tu aplicación y la anterior. Este enfoque de diseño te permite revertir de forma segura si hay un problema con la versión más reciente.

Recomendaciones

Para aplicar la guía en el framework de arquitectura a tu propio entorno, sigue estas recomendaciones:

  • Implementa una retirada exponencial con aleatorización en la lógica de reintento de error de las aplicaciones cliente.
  • Implementa una arquitectura multirregional con conmutación por error automática para obtener alta disponibilidad.
  • Usa el balanceo de cargas para distribuir las solicitudes de los usuarios en fragmentos y regiones.
  • Diseña la aplicación para que se degrade bajo sobrecarga con facilidad. Entrega respuestas parciales o proporciona funciones limitadas en lugar de fallar por completo.
  • Establecer un proceso basado en datos para la planificación de capacidad y usar pruebas de carga y previsiones del tráfico a fin de determinar cuándo aprovisionar recursos.
  • Establece procedimientos de recuperación ante desastres y pruébalos de forma periódica.

¿Qué sigue?

Explora otras categorías en el framework de arquitectura, como el diseño del sistema, la excelencia operativa y la seguridad, la privacidad y el cumplimiento.