Control de errores transitorios

Todas las aplicaciones que se comunican con los recursos y servicios remotos deben ser sensibles a errores transitorios. Esto es especialmente cierto en el caso de las aplicaciones que se ejecutan en la nube, donde debido a la naturaleza del entorno y la conectividad mediante Internet, este tipo de errores suele encontrarse con más frecuencia. Entre los errores transitorios, se incluye la pérdida momentánea de conectividad de red con componentes y servicios, la falta de disponibilidad temporal de un servicio y los tiempos de espera que se producen cuando un servicio está ocupado. Estos errores suelen ser de corrección automática, por lo que si la acción se repite después de un intervalo adecuado, es probable que se realice correctamente.

Este artículo proporciona una guía general para el control de errores transitorios. Para obtener información sobre el control de errores transitorios al usar los servicios de Azure, consulte Guía de reintentos para los servicios de Azure.

¿Por qué se producen errores transitorios en la nube?

En cualquier entorno, plataforma o sistema operativo y en cualquier tipo de aplicación pueden producirse errores transitorios. En soluciones que se ejecutan en infraestructuras locales, el rendimiento y la disponibilidad de la aplicación y sus componentes se mantiene normalmente mediante una costosa y a menudo infrautilizada redundancia de hardware, y los componentes y los recursos están ubicados cerca entre sí. Este enfoque hace que el error sea menos probable, pero pueden producirse errores transitorios, ya que pueden producirse interrupciones causadas por eventos imprevistos, como el suministro de alimentación externo o los problemas de red, u otros escenarios de desastre.

El hospedaje en la nube, incluidos los sistemas de nube privada, pueden ofrecer una mayor disponibilidad general mediante el uso de recursos compartidos, redundancia, conmutación por error automática y asignación dinámica de recursos a través de muchos nodos de proceso de mercancías. Sin embargo, debido a la naturaleza de los entornos en la nube, es más probable que se produzcan errores transitorios. Hay varias razones para esto:

  • Se comparten muchos recursos en un entorno de nube y el acceso a estos recursos está sujeto a limitación para proteger los recursos. Algunos servicios rechazan las conexiones cuando la carga alcanza un nivel específico o un índice de rendimiento máximo para permitir el procesamiento de las solicitudes existentes y para mantener el rendimiento del servicio para todos los usuarios. La limitación ayuda a mantener la calidad del servicio para vecinos y otros inquilinos que usan el recurso compartido.

  • Los entornos en la nube usan un gran número de unidades de hardware estándar. Proporcionan rendimiento distribuyendo dinámicamente la carga entre múltiples unidades de procesamiento y componentes de la infraestructura. Ofrecen confiabilidad reciclando o reemplazando automáticamente las unidades con errores. Debido a esta naturaleza dinámica, es posible que en ocasiones puedan producirse errores transitorios y errores temporales de conexión.

  • A menudo, hay más componentes de hardware, incluida la infraestructura de red, como enrutadores y equilibradores de carga, entre la aplicación y los recursos y servicios que usa. Esta infraestructura adicional ocasionalmente puede introducir latencia de conexión adicional y errores transitorios de conexión.

  • Las condiciones de la red entre el cliente y el servidor pueden ser variables, especialmente cuando la comunicación cruza Internet. Incluso en ubicaciones locales, las cargas de tráfico muy intensas pueden ralentizar la comunicación y causar errores de conexión intermitentes.

Desafíos

Los errores transitorios pueden tener un gran impacto en la disponibilidad aparente de una aplicación, incluso si se ha probado exhaustivamente en todas las circunstancias previsibles. Para asegurarse de que las aplicaciones hospedadas en nube funcionen de manera fiable, debe asegurarse de que puedan responder a los siguientes desafíos:

  • La aplicación debe ser capaz de detectar los errores cuando se producen y determinar si es probable que estos errores sean transitorios, más duraderos o de tipo terminal. Es probable que los distintos recursos devuelvan respuestas diferentes cuando se produce un error y estas respuestas también pueden variar en función del contexto de la operación. Por ejemplo, la respuesta de un error cuando la aplicación lee del almacenamiento puede diferir de la respuesta de un error cuando se escribe en el almacenamiento. Muchos recursos y servicios tienen contratos de errores transitorios bien documentados. Sin embargo, cuando dicha información no está disponible, puede ser difícil detectar la naturaleza del error y si es probable que sea transitorio.

  • La aplicación debe poder volver a intentar la operación si se determina que es posible que el error sea transitorio. También debe realizar un seguimiento del número de veces que se reintenta la operación.

  • La aplicación debe usar una estrategia adecuada para los reintentos. La estrategia especifica el número de veces que la aplicación debe hacer reintentos, el intervalo entre cada intento y las acciones a realizar después de un intento fallido. El número adecuado de intentos y el retraso entre cada uno de ellos a menudo son difíciles de determinar. La estrategia variará en función del tipo de recurso y de las condiciones de funcionamiento actuales del recurso y de la aplicación.

Directrices generales

Las directrices siguientes pueden ayudarle a diseñar un mecanismo de control de errores transitorio adecuado para las aplicaciones.

Determinar si hay un mecanismo de reintento integrado

  • Muchos servicios proporcionan una biblioteca SDK o de cliente que contiene un mecanismo de control de errores transitorios. La directiva de reintentos que usa normalmente se adapta a la naturaleza y los requisitos del servicio de destino. Como alternativa, las interfaces REST de los servicios pueden devolver información que puede ayudar a determinar si un reintento es adecuado y cuánto tiempo se debe esperar antes del siguiente reintento.

  • Debe utilizar el mecanismo de reintento integrado cuando esté disponible, a menos que tenga requisitos específicos y conocidos que indiquen que es más apropiado un comportamiento de reintento diferente.

Determinar si la operación es adecuada para el reintento

  • Realice operaciones de reintento solo cuando los errores sean transitorios (normalmente indicado por la naturaleza del error) y cuando haya al menos alguna probabilidad de que la operación se realice correctamente cuando se vuelva a intentar. No tiene sentido reintentar operaciones que intentan una operación no válida, como una actualización de la base de datos a un elemento que no existe o una solicitud a un servicio o recurso que ha sufrido un error fatal.

  • En general, implemente reintentos solo cuando pueda determinar el efecto completo de hacerlo y cuándo se entienden bien las condiciones y se pueden validar. De lo contrario, deje que el código que realiza la llamada implemente los reintentos. Recuerde que los errores devueltos de los recursos y servicios situados fuera de su control pueden evolucionar con el tiempo, y es posible que tenga que revisar la lógica de detección de errores transitorios.

  • Al crear servicios o componentes, considere la posibilidad de implementar códigos y mensajes de error que ayuden a los clientes a determinar si deben reintentar las operaciones con errores. En particular, indique si el cliente debería reintentar la operación (quizás devolviendo un valor isTransient) y sugiera un intervalo adecuado antes del siguiente reintento. Si crea un servicio web, considere la posibilidad de devolver los errores personalizados que están definidos en los contratos de servicio. Aunque es posible que los clientes genéricos no puedan leer estos errores, son útiles para la creación de clientes personalizados.

Determinar un número adecuado de reintentos y el intervalo

  • Optimice el número de reintentos y el intervalo para el tipo de caso de uso. Si no vuelve a intentarlo las veces suficientes, la aplicación no puede completar la operación y probablemente producirá un error. Si efectúa reintentos demasiadas veces o en un intervalo demasiado corto entre intentos, la aplicación puede retener potencialmente recursos como subprocesos, conexiones y memoria durante largos períodos, lo que afecta negativamente al estado de la aplicación.

  • Adapte los valores del intervalo de tiempo y el número de reintentos al tipo de operación. Por ejemplo, si la operación forma parte de una interacción con el usuario, el intervalo debe ser corto y solo deben intentarse unos pocos reintentos. Con este enfoque, puede evitar que los usuarios esperen una respuesta, que mantiene conexiones abiertas y puede reducir la disponibilidad para otros usuarios. Si la operación forma parte de un flujo de trabajo de larga duración o crítico, donde cancelar y reiniciar el proceso resulta lento o costoso, conviene esperar más tiempo entre intentos y efectuar más reintentos.

  • Tenga en cuenta que determinar los intervalos entre reintentos adecuados es la parte más difícil de diseñar una estrategia satisfactoria. Las estrategias típicas usan los siguientes tipos de intervalo de reintento:

    • Interrupción exponencial. La aplicación espera poco tiempo antes del primer reintento y, a continuación, aumenta exponencialmente el tiempo entre cada intento posterior. Por ejemplo, puede reintentar la operación después de 3 segundos, 12 segundos, 30 segundos y así sucesivamente.

    • Intervalos incrementales. La aplicación espera poco tiempo antes del primer reintento y, a continuación, aumenta incrementalmente el tiempo entre cada intento posterior. Por ejemplo, puede reintentar la operación después de 3 segundos, 7 segundos, 13 segundos y así sucesivamente.

    • Intervalos regulares. La aplicación espera el mismo período de tiempo entre cada intento. Por ejemplo, puede reintentar la operación cada 3 segundos.

    • Reintento inmediato. A veces, un error transitorio es breve, posiblemente causado por un evento como una colisión de paquetes de red o un pico en un componente de hardware. En este caso, es conveniente volver a intentar la operación inmediatamente porque puede tener éxito si se resolvió el problema en el tiempo que tarda la aplicación en ensamblar y enviar la solicitud siguiente. Sin embargo, nunca debería haber más de un reintento inmediato. Si se producen errores en el reintento inmediato, debe cambiar a estrategias alternativas, como el retroceso exponencial o las acciones de reversión.

    • Selección aleatoria. Cualquiera de las estrategias de reintento enumeradas anteriormente puede incluir una selección aleatoria para evitar que varias instancias del cliente envíen reintentos posteriores al mismo tiempo. Por ejemplo, una instancia puede reintentar la operación después de 3 segundos, 11 segundos, 28 segundos y así sucesivamente, mientras que otra instancia puede reintentar la operación después de 4 segundos, 12 segundos, 26 segundos y así sucesivamente. La selección aleatoria es una técnica útil que se puede combinar con otras estrategias.

  • Como norma general, use una estrategia de retroceso exponencial para las operaciones en segundo plano y estrategias de reintento de intervalo inmediato o periódico para las operaciones interactivas. En ambos casos, debe elegir el retraso y el número de reintentos para que la latencia máxima de todos los reintentos se encuentre dentro del requisito de latencia integral necesario.

  • Tenga en cuenta la combinación de todos los factores que contribuyen al tiempo de espera máximo global de una operación reintentada. Estos factores incluyen el tiempo necesario para que un error en la conexión genere una respuesta (normalmente establecido por un valor de tiempo de espera en el cliente), el retraso entre reintentos y el número máximo de reintentos. El total de todos estos tiempos puede provocar tiempos de duración generales de las operaciones prolongados, especialmente cuando se usa una estrategia de retraso exponencial en la que intervalo entre reintentos crece rápidamente después de cada error. Si un proceso debe cumplir un acuerdo de nivel de servicio específico (SLA), el tiempo total de la operación, incluidos todos los tiempos de espera y retrasos, debe encontrarse dentro del límite definido en el SLA.

  • No implemente estrategias de reintento excesivamente agresivas. Estas son estrategias que tienen intervalos demasiado cortos o reintentos que son demasiado frecuentes. Pueden tener un efecto adverso en el recurso o servicio de destino. Estas estrategias pueden impedir que el recurso o servicio se recupere de su estado de sobrecarga y continuará bloqueando o rechazando las solicitudes. Este escenario da lugar a un círculo vicioso, en el que cada vez se envían más solicitudes al recurso o servicio. Por consiguiente, su capacidad de recuperación se reduce aún más.

  • Tenga en cuenta el tiempo de espera de las operaciones al elegir los intervalos de reintento para evitar el inicio de intentos posteriores inmediatamente (por ejemplo, si el tiempo de espera es similar al intervalo de reintento). Considere también si necesita mantener el período total posible (el tiempo de espera más los intervalos de reintento) por debajo de un tiempo total específico. Si una operación tiene un tiempo de espera inusualmente corto o largo, el tiempo de espera puede influir en cuánto tiempo hay que esperar y con qué frecuencia reintentar la operación.

  • Utilice el tipo de la excepción y los datos que contiene, o los códigos de error y los mensajes devueltos desde el servicio, para optimizar el número de reintentos y el intervalo entre ellos. Por ejemplo, algunas excepciones o códigos de error (como el código HTTP 503 Servicio no disponible con un encabezado Retry-After en la respuesta) pueden indicar cuánto puede durar el error o que se ha producido un error en el servicio y no responderá los intentos posteriores.

Evitar antipatrones

  • En la mayoría de los casos, evite las implementaciones que incluyan capas duplicadas de código de reintento. Evite los diseños que incluyan mecanismos de reintento en cascada, o que implementen reintentos en cada etapa de una operación que implica a una jerarquía de solicitudes, a menos que tenga requisitos específicos que requieran hacerlo. En estas circunstancias excepcionales, use directivas que impidan números excesivos de reintentos y de períodos de retraso y asegúrese de que comprende las consecuencias. Por ejemplo, supongamos que un componente realiza una solicitud a otro, que luego accede al servicio de destino. Si implementa el reintento con un recuento de tres en ambas llamadas, hay nueve intentos de reintento en total en el servicio. Muchos servicios y recursos implementan un mecanismo de reintento integrado. Debe investigar cómo puede deshabilitar o modificar estos mecanismos si tiene que implementar reintentos a un nivel superior.

  • Nunca implemente un mecanismo de reintento infinito. Al hacerlo, es probable que evite la recuperación del recurso o servicio de situaciones de sobrecarga y que provoque que la limitación y las conexiones rechazadas continúen durante un período más largo. Utilice un número finito de reintentos o implemente un patrón como el disyuntor para permitir que el servicio se recupere.

  • Nunca realice un reintento inmediato más de una vez.

  • Evite el uso de un intervalo de reintento regular al acceder a los servicios y recursos de Azure, especialmente si hay un número elevado de reintentos. El mejor enfoque en este escenario es una estrategia de retroceso exponencial con una funcionalidad de disyuntor.

  • Evite que varias instancias del mismo cliente o varias instancias de clientes diferentes envíen reintentos simultáneamente. Si es probable que se produzca este escenario, introduzca la selección aleatoria en los intervalos de reintento.

Prueba de la estrategia de reintento e implementación

  • Pruebe completamente la estrategia de reintento en el conjunto de circunstancias más amplio posible, especialmente cuando tanto la aplicación como los recursos o servicios de destino que use estén bajo una carga extrema. Para comprobar el comportamiento durante las pruebas, puede:

    • Insertar errores transitorios y no transitorios en el servicio. Por ejemplo, envíe solicitudes no válidas o agregue código que detecte solicitudes de prueba y responda con distintos tipos de errores. Para ver ejemplos que utilizan TestApi, consulte Pruebas de inyección de errores con TestApi e Introducción a TestApi (Parte 5: API de inyección de errores de código administrado).

    • Cree un simulacro del recurso o servicio que devuelva el intervalo de errores que puede devolver el servicio real. Cubra todos los tipos de error que la estrategia de reintento esté diseñada para detectar.

    • En el caso de los servicios personalizados que cree e implemente, fuerce que se produzcan errores transitorios deshabilitando o sobrecargando temporalmente el servicio. (No intente sobrecargar ningún recurso compartido ni servicios compartidos en Azure).

    • Para las API basadas en HTTP, considere la posibilidad de usar una biblioteca en las pruebas automatizadas para cambiar el resultado de las solicitudes HTTP, mediante la adición de tiempos de ida y vuelta adicionales o cambiando la respuesta (por ejemplo, el código de estado HTTP, los encabezados, el cuerpo u otros factores). Esto permite realizar una prueba determinista de un subconjunto de condiciones de error para los errores transitorios y otros tipos de errores.

    • Realice pruebas con un alto factor de carga y simultaneidad para asegurarse de que el mecanismo y la estrategia de reintento funcionen correctamente en estas condiciones. Estas pruebas también ayudarán a garantizar que el reintento no tenga un efecto adverso en el funcionamiento del cliente o cause contaminación cruzada entre solicitudes.

Administración de las configuraciones de directivas de reintentos

  • Una directiva de reintentos es una combinación de todos los elementos de la estrategia de reintento. Define el mecanismo de detección que determina si es probable que un error sea transitorio, el tipo de intervalo que se debe usar (por ejemplo, periódico, retroceso exponencial y selección aleatoria), los valores de intervalo reales y el número de reintentos.

  • Implemente reintentos en muchos lugares, incluso en la aplicación más sencilla, y en cada capa de las aplicaciones más complejas. En lugar de codificar de forma rígida los elementos de cada directiva en varias ubicaciones, considere el uso de un punto central para almacenar todas las directivas. Por ejemplo, almacene los valores como el intervalo y el número de reintentos en los archivos de configuración de la aplicación, léalos en tiempo de ejecución y cree las directivas de reintento mediante programación. Esto facilita la administración de la configuración, así como la modificación y el ajuste preciso de los valores para responder a requisitos y escenarios cambiantes. Sin embargo, diseñe el sistema para almacenar los valores en lugar de volver a leer un archivo de configuración cada vez y asegúrese de que se usen valores predeterminados adecuados si no se pueden obtener los valores de la configuración.

  • En una aplicación de Azure Cloud Services, considere la posibilidad de almacenar los valores que se usan para crear las directivas de reintento en tiempo de ejecución en el archivo de configuración de servicio para que pueda cambiarlos sin necesidad de reiniciar la aplicación.

  • Aproveche las estrategias de reintento integradas o predeterminadas disponibles en las API de cliente que use, pero solo cuando resulten adecuadas para su escenario. Estas estrategias son normalmente para fines generales. En algunos escenarios pueden ser lo único necesario, pero en otros escenarios no ofrecen la gama completa de opciones necesarias para satisfacer sus requisitos específicos. Para determinar los valores más adecuados, debe realizar pruebas para entender cómo afectará la configuración a la aplicación.

Registro y seguimiento de los errores transitorios y no transitorios

  • Como parte de su estrategia de reintento, incluya el control de excepciones y otra instrumentación que registre los reintentos. Es de esperar algún error transitorio y un reintento ocasionales, y no indican un problema. Sin embargo, un número de reintentos periódico y creciente es a menudo un indicador de un problema que podría provocar un error o degradar el rendimiento y la disponibilidad de la aplicación.

  • Registre los errores transitorios como entradas de advertencia en lugar de entradas de error, de manera que los sistemas de supervisión no los detecten como errores de aplicación que podrían desencadenar alertas falsas.

  • Considere la posibilidad de almacenar un valor en las entradas de registro que indique si los reintentos fueron originados por una limitación en el servicio o por otros tipos de errores, como los errores de conexión, para que pueda diferenciarlos durante el análisis de los datos. A menudo, un aumento del número de errores de limitación es un indicador de un defecto de diseño de la aplicación o la necesidad de cambiar a un servicio premium que ofrece hardware específico.

  • Considere la posibilidad de medir y registrar el tiempo total transcurrido para las operaciones que incluyen un mecanismo de reintento. Esta métrica es un buen indicador del efecto global de los errores transitorios en los tiempos de respuesta al usuario, la latencia de los procesos y la eficacia de los casos de uso de la aplicación. Registre también el número de reintentos que se producen para poder comprender los factores que contribuyen al tiempo de respuesta.

  • Considere la posibilidad de implementar un sistema de telemetría y supervisión que pueda generar alertas cuando el número y la tasa de errores, el promedio de reintentos o los tiempos transcurridos totales para que las operaciones se realicen correctamente está aumentando.

Administración de las operaciones que producen un error continuamente

  • Tenga en cuenta cómo va a controlar las operaciones que siguen produciendo errores en cada intento. Situaciones como esta son inevitables.

    • Aunque una estrategia de reintento define el número máximo de veces que se debe reintentar una operación, ello no impide que la aplicación repita la operación de nuevo con el mismo número de reintentos. Por ejemplo, si se produce un error grave en un servicio de procesamiento de pedidos que lo pone fuera de funcionamiento de manera permanente, la estrategia de reintento puede detectar un tiempo de espera de conexión y considerarlo un error transitorio. El código reintenta la operación un número especificado de veces y, a continuación, renuncia. Sin embargo, cuando otro cliente realiza un pedido, la operación se intenta de nuevo (aunque vaya a fallar siempre).

    • Para evitar reintentos continuos de operaciones que producen un error continuamente, debe considerar la posibilidad de implementar el patrón de disyuntor. Cuando se usa este patrón, si el número de errores dentro de un período de tiempo especificado supera un umbral, las solicitudes se devuelven al autor de la llamada inmediatamente como errores, sin intentar acceder al recurso o servicio con errores.

    • La aplicación puede comprobar periódicamente el servicio, de forma intermitente y con intervalos largos entre solicitudes para detectar cuando está disponible. El intervalo adecuado depende de factores como la importancia de la operación y la naturaleza del servicio. Puede ser cualquier valor entre unos minutos y varias horas. Cuando la prueba se realiza correctamente, la aplicación puede reanudar las operaciones normales y pasar solicitudes al servicio recién recuperado.

    • Mientras tanto, es posible que pueda revertir a otra instancia del servicio (quizás en un centro de datos o una aplicación diferente), usar un servicio similar que ofrezca una funcionalidad (quizás más sencilla) compatible o realizar alguna operación alternativa basada en la esperanza de que el servicio pase a estar disponible pronto. Por ejemplo, puede ser adecuado almacenar las solicitudes al servicio en una cola o almacén de datos y volver a intentarlo posteriormente. O bien, es posible que pueda redirigir al usuario a una instancia alternativa de la aplicación, reducir el rendimiento de la aplicación pero seguir ofreciendo funcionalidad aceptable o simplemente presentar un mensaje al usuario que indique que la aplicación no está disponible actualmente.

Otras consideraciones

  • Al decidir los valores para el número de reintentos y los intervalos de reintento para una directiva, tenga en cuenta si la operación del servicio o recurso forma parte de una operación de larga duración o de varios pasos. Puede ser difícil o costoso compensar los demás pasos operativos que ya se han realizado correctamente cuando se produce un error en uno. En este caso, un intervalo muy largo y un gran número de reintentos pueden ser aceptables siempre y cuando esa estrategia no bloquee otras operaciones manteniendo o bloqueando recursos escasos.

  • Tenga en cuenta si al volver a intentar la misma operación pueden producirse incoherencias en los datos. Si se repiten algunas partes de un proceso de varios pasos y las operaciones no son idempotentes, puede que se produzcan incoherencias. Por ejemplo, si se repite una operación que incrementa un valor, se produce un resultado no válido. Repetir una operación que envía un mensaje a una cola puede provocar una incoherencia en el consumidor del mensaje si el consumidor no puede detectar mensajes duplicados. Para evitar estos escenarios, diseñe cada paso como una operación idempotente. Para obtener más información, consulte Patrones de idempotencia.

  • Tenga en cuenta el ámbito de las operaciones que se volverán a intentar. Por ejemplo, puede ser más fácil implementar el código de reintento en un nivel que abarque varias operaciones y reintentarlas de nuevo todas si se produce un error en una. Sin embargo, esto podría provocar problemas de idempotencia u operaciones de reversión innecesarias.

  • Si elige un ámbito de reintento que abarca varias operaciones, tenga en cuenta la latencia total de todas ellas al determinar los intervalos de reintento, al supervisar los tiempos transcurridos de la operación y antes de generar alertas de los errores.

  • Tenga en cuenta cómo puede afectar su estrategia de reintento a vecinos y otros inquilinos de una aplicación compartida y al usar servicios y recursos compartidos. Las directivas de reintento agresivas puede ocasionar un número creciente de errores transitorios a estos otros usuarios y para las aplicaciones que comparten los recursos y servicios. Del mismo modo, es posible que la aplicación se vea afectada por las directivas de reintento implementadas por otros usuarios de los servicios y recursos. En el caso de las aplicaciones críticas para la empresa, es posible que quiera usar servicios Premium que no sean compartidos. Esto le brinda un mayor control sobre la carga y la consiguiente limitación de estos recursos y servicios, que puede ayudar a justificar el costo adicional.