Cómo incrementar el MTBF - Disponibilidad y más allá: Descripción y mejora de la resiliencia de los sistemas distribuidos en AWS

Cómo incrementar el MTBF

El último componente para mejorar la disponibilidad es incrementar el tiempo medio entre errores (MTBF). Esto puede aplicarse tanto al software como a los servicios de AWS utilizados para ejecutarlo.

Cómo incrementar el MTBF de los sistemas distribuidos

Una forma de incrementar el MTBF es reducir los defectos en el software. Puede hacer esto de varias formas. Los clientes pueden usar herramientas como Revisor de Amazon CodeGuru para localizar y corregir errores comunes. También se deben realizar revisiones exhaustivas por pares del código, pruebas unitarias, pruebas de integración, pruebas de regresión y pruebas de carga del software antes de implementarlo en el entorno de producción. Aumentar la cobertura del código en las pruebas ayudará a garantizar que se prueben incluso las rutas de ejecución de código menos comunes.

Implementar cambios más pequeños también puede ayudar a evitar resultados inesperados al reducir la complejidad del cambio. Cada actividad brinda la oportunidad de identificar y corregir defectos antes de que puedan producirse.

Otro enfoque para prevenir los errores es realizar pruebas periódicas. La implementación de un programa de ingeniería del caos puede ayudar a comprobar si una carga de trabajo falla, validar los procedimientos de recuperación y ayudar a localizar y corregir los modos de error antes de que se produzcan en el entorno de producción. Los clientes pueden usar AWS Fault Injection Simulator junto con otras herramientas para experimentos de ingeniería del caos.

La tolerancia a errores es otra forma de evitar errores en un sistema distribuido. Los módulos que responden rápido a los errores, los reintentos con fluctuación y retroceso exponenciales, las transacciones y la idempotencia son técnicas que ayudan a hacer que las cargas de trabajo sean tolerantes a los errores.

Las transacciones son un grupo de operaciones que se rigen por las propiedades de atomicidad, uniformidad, aislamiento y durabilidad (ACID). Se definen de la siguiente manera:

  • Atomicidad: o bien se producen todas las acciones o no ocurrirá ninguna de ellas.

  • Coherencia: cada transacción deja la carga de trabajo en un estado válido.

  • Aislamiento: las transacciones realizadas simultáneamente dejan la carga de trabajo en el mismo estado que si se hubieran realizado de forma secuencial.

  • Durabilidad: una vez que se confirma una transacción, se conservan todos sus efectos, incluso en el caso de que se produzca un error en la carga de trabajo.

Los reintentos con fluctuación y retroceso exponenciales permiten superar los errores transitorios causados por errores Heisenbug, sobrecargas u otras condiciones. Cuando las transacciones son idempotentes, se pueden reintentar varias veces sin efectos secundarios.

Si tuviéramos en cuenta el efecto de un error Heisenbug en una configuración de hardware tolerante a errores, no nos preocuparía en absoluto, ya que la probabilidad de que el error Heisenbug aparezca tanto en el subsistema principal como en el redundante es infinitesimalmente pequeña. (Lea “Why Do Computers Stop and What Can Be Done About It?”; informe técnico 85.7 de Tandem Computers; junio de 1985). En los sistemas distribuidos, queremos lograr los mismos resultados con nuestro software.

Cuando se invoca un error Heisenbug, es imprescindible que el software detecte rápidamente un funcionamiento incorrecto y falle para volver a intentar la operación. Esto se logra mediante la programación defensiva y la validación de los datos de entrada, los resultados intermedios y los datos de salida. Además, los procesos están aislados y no comparten ningún estado con otros procesos.

Este enfoque modular garantiza que el alcance del impacto durante un error esté limitado. Los procesos fallan de forma independiente. Cuando un proceso falla, el software debe usar pares de procesos para reintentar el trabajo, lo que significa que un nuevo proceso puede asumir el trabajo de un proceso con errores. Para mantener la fiabilidad e integridad de la carga de trabajo, cada operación debe tratarse como una transacción ACID.

Esto permite que un proceso falle sin dañar el estado de la carga de trabajo al abortar la transacción y revertir los cambios realizados. Así, el proceso de recuperación vuelve a intentar la transacción desde un estado válido conocido y se reinicia con elegancia. De esta forma, el software puede ser tolerante a los errores Heisenbug.

Sin embargo, no trate que el software sea tolerante a los errores Bohrbug. Estos defectos deben detectarse y eliminarse antes de que la carga de trabajo entre en el entorno de producción, ya que ningún nivel de redundancia logrará el resultado correcto. (Lea “Why Do Computers Stop and What Can Be Done About It?”; informe técnico 85.7 de Tandem Computers; junio de 1985).

La última forma de incrementar el MTBF es reducir el alcance del impacto provocado por un error. Usar el aislamiento de errores mediante modularización para crear contenedores de errores es una manera básica de hacerlo, como se describió anteriormente en Tolerancia a errores y aislamiento de errores. La reducción del índice de errores mejora la disponibilidad. AWS utiliza técnicas como la división de los servicios en planos de control y planos de datos, la independencia de las zonas de disponibilidad (AZI), el aislamiento regional, las arquitecturas basadas en celdas y la partición aleatoria para aislar los errores. Los clientes de AWS también pueden utilizar estos patrones.

Analicemos como ejemplo un escenario en el que una carga de trabajo ubica a los clientes en diferentes contenedores de errores de una infraestructura que presta servicio a como máximo el 5 % de los clientes totales. En uno de estos contenedores de errores se produce un evento que aumenta la latencia una vez transcurrido el tiempo de espera del cliente en un 10 % de las solicitudes. Durante este evento, para el 95 % de los clientes, el servicio estuvo disponible al 100 %. En el caso del 5 % restante, el servicio parecía estar disponible al 90 %. Esto se traduce en una disponibilidad de 1 − (5 % de clientes× 10 % de sus solicitudes) = 99,5 % en lugar del 10 % de las solicitudes con errores para el 100 % de los clientes (lo que se traduce en una disponibilidad del 90 %).

Regla 11

El aislamiento de errores reduce el alcance del impacto e incrementa el MTBF de la carga de trabajo mediante la reducción del índice general de errores.

Incremento del MTBF de las dependencias

El primer método para incrementar el MTBF de una dependencia de AWS es mediante el aislamiento de errores. Muchos servicios de AWS ofrecen un nivel de aislamiento en la AZ, lo que significa que un error en una AZ no afecta al servicio de otra AZ.

El uso de instancias de EC2 redundantes en varias AZ aumenta la disponibilidad del subsistema. AZI ofrece capacidad de reserva dentro de una sola región, lo que permite aumentar la disponibilidad de los servicios de AZI.

Sin embargo, no todos los servicios de AWS funcionan por AZ. Muchos otros ofrecen aislamiento regional. En este caso, cuando la disponibilidad diseñada del servicio regional no respalde la disponibilidad general requerida para la carga de trabajo, podría plantearse aplicar un enfoque de varias regiones. Cada región ofrece una instanciación aislada del servicio, lo que equivale a capacidad de reserva.

Existen varios servicios que facilitan la creación de un servicio de varias regiones. Por ejemplo:

Este documento no profundiza en las estrategias para crear cargas de trabajo de varias regiones, por lo que debe sopesar los beneficios en cuanto a disponibilidad de las arquitecturas de varias regiones y el costo, la complejidad y las prácticas operativas adicionales y necesarias para satisfacer los objetivos de disponibilidad deseados.

El siguiente método para incrementar el MTBF de la dependencia consiste en diseñar la carga de trabajo para que sea estable desde el punto de vista estático. Supongamos que, por ejemplo, tiene una carga de trabajo que proporciona información sobre un producto. Cuando sus clientes solicitan un producto, el servicio realiza una solicitud a un servicio de metadatos externo que recupera los detalles del producto. Luego, su carga de trabajo devuelve toda esa información al usuario.

Sin embargo, si el servicio de metadatos no está disponible, las solicitudes realizadas por los clientes fallarán. En cambio, puede extraer o enviar los metadatos de forma local y asíncrona a su servicio para utilizarlos en las respuestas a las solicitudes. Así se elimina la llamada síncrona al servicio de metadatos desde la ruta crítica.

Además, dado que su servicio sigue estando disponible aunque el servicio de metadatos no lo esté, puede eliminarlo como una dependencia en el cálculo de la disponibilidad. Este ejemplo se basa en la suposición de que los metadatos no cambian con frecuencia y que es mejor publicarlos obsoletos que incurrir en errores de solicitud. Otro ejemplo similar es el de servir datos obsoletos para DNS, que permite que los datos se conserven en la memoria caché una vez se agota el tiempo de vida (TTL). Se utiliza como respuesta cuando no se dispone de una actualizada.

El último método de incrementar el MTBF de una dependencia es reducir el alcance del impacto provocado por un error. Como se mencionó anteriormente, un error no es un evento binario, sino que existen grados de error. Este es el efecto de la modularización; el error se limita a las solicitudes o a los usuarios a los que presta servicio ese contenedor.

Esto se traduce en menos errores durante un evento, lo que, en última instancia, aumenta la disponibilidad general de la carga de trabajo al limitar el alcance del impacto.

Cómo reducir las fuentes de impacto más habituales

En 1985, Jim Gray descubrió durante un estudio en Tandem Computers que los errores se debían principalmente a dos factores: el software y las operaciones. (Lea “Why Do Computers Stop and What Can Be Done About It?”; informe técnico 85.7 de Tandem Computers; junio de 1985). 36 años después, esto sigue siendo cierto. A pesar de los avances tecnológicos, no existe una solución fácil para estos problemas y las principales fuentes de error no han cambiado. Al principio de esta sección se abordaron los errores en el software, por lo que ahora vamos a centrarnos en las operaciones y en reducir la frecuencia con la que ocurren estos errores.

Comparativa entre la estabilidad y las características

Si volvemos a consultar el gráfico del índice de errores del software y el hardware de la sección Disponibilidad de los sistemas distribuidos, podemos observar que en cada versión de software se añaden nuevos defectos. Esto significa que cualquier cambio en la carga de trabajo conlleva un mayor riesgo de error. Estos cambios suelen consistir en nuevas funciones, por ejemplo, lo que constituye un corolario. Las cargas de trabajo de mayor disponibilidad favorecerán la estabilidad frente a las nuevas características. Por lo tanto, una de las formas más sencillas de mejorar la disponibilidad es implementarlas con menor frecuencia u ofrecer menos. Las cargas de trabajo que se implementan con más frecuencia tendrán por naturaleza una disponibilidad menor que las que no lo hacen. Sin embargo, las cargas de trabajo que no incorporan nuevas características no satisfacen la demanda de los clientes y pueden perder utilidad con el tiempo.

¿Cómo podemos seguir innovando y lanzando características de forma segura? La estandarización es la respuesta. ¿Cuál es la forma correcta de implementación? ¿Cómo se solicitan las implementaciones? ¿Qué estándares hay para las pruebas? ¿Cuánto tiempo se espera entre una etapa y otra? ¿Cubren sus pruebas unitarias suficiente código de software? Estas son preguntas a las que la estandarización responderá y evitará problemas causados por factores como la falta de pruebas de cargas, la omisión de las etapas de implementación o la implementación demasiado rápida en demasiados hosts.

La forma de implementar la estandarización es mediante la automatización. Reduce la posibilidad de que se cometan errores humanos y permite a los ordenadores hacer aquello que se les da bien; es decir, hacer siempre lo mismo una y otra vez de la misma manera. La manera de combinar la estandarización y la automatización es establecer objetivos. Los objetivos incluyen evitar cambios manuales, acceder al host únicamente a través de sistemas de autorización contingente, escribir pruebas de carga para cada API, etc. La excelencia operativa es una norma cultural que puede requerir cambios sustanciales. Establecer y hacer un seguimiento del rendimiento en relación con un objetivo ayuda a impulsar un cambio cultural que tendrá una gran repercusión en la disponibilidad de la carga de trabajo. El Pilar de excelencia operativa del marco AWS Well-Architected Framework brinda prácticas recomendadas integrales para la excelencia operativa.

Seguridad de los operadores

El otro factor importante que contribuye a los eventos operativos que provocan errores son las personas. Los seres humanos cometemos errores. Podemos usar credenciales incorrectas, introducir comandos incorrectos, pulsar Intro demasiado pronto o saltarnos un paso importante. Tomar medidas manuales de forma sistemática causa errores.

Una de las principales causas de los errores de los operadores son las interfaces de usuario confusas, poco intuitivas o incoherentes. Jim Gray también señaló en su estudio de 1985 que “las interfaces que solicitan información al operador o le piden que realice alguna función deben ser simples, coherentes y tolerantes a los errores del operador”. (Lea “Why Do Computers Stop and What Can Be Done About It?”; informe técnico 85.7 de Tandem Computers; junio de 1985). Esta idea sigue siendo cierta en la actualidad. En las últimas tres décadas, en el sector, se han dado numerosos ejemplos en los que una interfaz de usuario confusa o compleja, la falta de confirmación o instrucciones, o incluso un lenguaje humano poco amigable hicieron que un operador se equivocara.

Regla 12

Facilite que los operadores hagan lo correcto.

Prevención de sobrecargas

El último factor de impacto habitual son los clientes, los usuarios reales de la carga de trabajo. Las cargas de trabajo que funcionan bien suelen utilizarse mucho, pero a veces ese uso supera su capacidad de escalabilidad. Pueden ocurrir muchas cosas: los discos pueden llenarse, los grupos de subprocesos pueden agotarse, el ancho de banda de la red puede saturarse o se pueden alcanzar los límites de conexión a la base de datos.

No existe un método infalible para evitar estos problemas, pero la supervisión proactiva de la capacidad y la utilización a través de las métricas del estado operativo proporcionará alertas tempranas cuando se produzcan estos errores. Técnicas como el desbordamiento de carga, el uso de interruptores y reintentos con fluctuación y retroceso exponenciales pueden ayudar a minimizar el impacto y mejorar el índice de éxito, pero estas situaciones siguen siendo errores. El escalado automatizado basado en las métricas del estado operativo puede ayudar a reducir la frecuencia con la que se producen errores debidos a una sobrecarga, pero es posible que no pueda responder con la suficiente rapidez a los cambios en la utilización.

Si necesita garantizar la capacidad disponible de forma continua para los clientes, debe hacer concesiones en cuanto a la disponibilidad y el costo. Una forma de garantizar que la falta de capacidad no provoque una falta de disponibilidad consiste en proporcionar a cada cliente una cuota y garantizar que la capacidad de la carga de trabajo se adapte para cubrir el 100 % de las cuotas asignadas. Cuando los clientes superan su cuota, se exponen a limitaciones, pero no se produce ningún error ni la disponibilidad se ve afectada. También tendrá que realizar un seguimiento minucioso de su base de clientes y pronosticar la utilización futura para mantener suficiente capacidad aprovisionada. Esto garantizará que la carga de trabajo no se vea obligada a fallar debido al consumo excesivo por parte de los clientes.

Tomemos como ejemplo una carga de trabajo que proporciona un servicio de almacenamiento. Cada servidor de la carga de trabajo puede admitir 100 descargas por segundo, a los clientes se les proporciona una cuota de 200 descargas por segundo y hay 500 clientes. Para poder soportar este volumen de clientes, el servicio debe ofrecer una capacidad de 100 000 descargas por segundo, lo que requiere 1000 servidores. Si algún cliente supera su cuota, se verá expuesto a una limitación, lo que garantizará una capacidad suficiente para el resto de los clientes. Este es un ejemplo sencillo de cómo evitar una sobrecarga sin rechazar unidades de trabajo.