Los sistemas distribuidos fallan de maneras pequeñas todo el tiempo: una solicitud es limitada (throttled), una llamada de red agota su tiempo de espera, un servicio de destino devuelve un error por un momento. El código resiliente cuenta con esto. El examen DVA-C02 evalúa si conoces las técnicas estándar y qué fallas corrige cada una.
Los reintentos son la primera herramienta, pero solo para errores transitorios como la limitación (ThrottlingException, HTTP 429), los errores de servidor HTTP 5xx y los tiempos de espera de red. Reintentar un error de validación o un AccessDenied solo repite la falla. Reintentar de inmediato en un bucle cerrado es dañino, porque todos los clientes golpean al servicio en apuros al mismo tiempo. El retroceso exponencial (exponential backoff) espera más tiempo después de cada intento fallido, por ejemplo 100 ms, 200 ms, 400 ms, 800 ms, hasta un límite y un número máximo de intentos. El jitter añade aleatoriedad a cada espera para que miles de clientes que fallaron en el mismo momento no reintenten todos en el mismo momento. Los SDK de AWS ya implementan reintentos con retroceso y jitter para las llamadas a la API de AWS; tú configuras el modo de reintento y el número máximo de intentos en lugar de escribirlo tú mismo, pero sí debes agregarlo tú para las llamadas a tus propios servicios o a servicios de terceros.
La idempotencia hace que los reintentos sean seguros. Una operación es idempotente si realizarla dos veces tiene el mismo efecto que realizarla una vez. Como los reintentos, la entrega al menos una vez de las colas estándar de SQS y los reintentos asíncronos de Lambda pueden entregar la misma solicitud más de una vez, tu código debe detectar duplicados. Las técnicas comunes son una clave de idempotencia proporcionada por el cliente (por ejemplo, un ID de pedido), una escritura condicional en DynamoDB como attribute_not_exists(orderId) que falla si el elemento ya fue procesado, y la idempotencia natural (asignar un valor en lugar de incrementarlo).
Los tiempos de espera (timeouts) evitan que una dependencia lenta consuma todos tus recursos. Configura tiempos de espera explícitos de conexión y de lectura en los clientes HTTP y del SDK que sean más cortos que el tiempo de espera de tu función o solicitud, para que tu código pueda registrar, reintentar o fallar de forma controlada en lugar de ser terminado a mitad de la operación. Una función Lambda detrás de API Gateway, por ejemplo, debería abandonar una llamada lenta a un servicio de destino bastante antes del propio tiempo de espera de integración de API Gateway.
La falla parcial ocurre cuando un lote contiene elementos buenos y malos. Si un registro de un lote de diez falla y lanzas un error, se reintenta todo el lote, incluidos los nueve que tuvieron éxito. Las API por lotes como BatchWriteItem de DynamoDB devuelven UnprocessedItems, y SendMessageBatch de SQS devuelve fallas por entrada; tu código debe reintentar solo esos. Para Lambda leyendo de SQS o de streams, las respuestas parciales de lote (partial batch responses) te permiten informar solo los elementos fallidos.
Una cola de mensajes fallidos (dead-letter queue, DLQ) es adonde van los mensajes después de fallar un número determinado de veces, para que un mensaje envenenado (uno que nunca se podrá procesar) deje de bloquear la cola y de desperdiciar cómputo. SQS usa una política de redrive con maxReceiveCount; las invocaciones asíncronas de Lambda pueden enviar los eventos fallidos a una DLQ de cola SQS o de tema SNS, o a un destino on-failure. Una DLQ solo es útil si configuras una alarma sobre su profundidad e investigas lo que llega allí.
Términos clave
- Exponential backoff (retroceso exponencial)
- Una estrategia de reintento en la que la espera entre intentos crece de forma multiplicativa, reduciendo la presión sobre un servicio en apuros.
- Jitter (variación aleatoria)
- Variación aleatoria añadida a los retrasos de reintento para que muchos clientes no reintenten en oleadas sincronizadas.
- Idempotency (idempotencia)
- La propiedad de que repetir una operación produce el mismo resultado que hacerla una vez, lo que hace seguros los reintentos y las entregas duplicadas.
- Poison message (mensaje envenenado)
- Un mensaje que falla al procesarse cada vez que se recibe y que se repetiría para siempre sin una cola de mensajes fallidos.
- Dead-letter queue (DLQ, cola de mensajes fallidos)
- Una cola que recibe los mensajes o eventos cuyo procesamiento falló después de un número configurado de intentos, para inspeccionarlos más tarde.
Una función Lambda de pagos procesa mensajes de SQS. Registra cada ID de pago en DynamoDB con un put condicional que usa attribute_not_exists, así que un mensaje entregado dos veces se cobra una sola vez. Su cola SQS tiene una política de redrive con maxReceiveCount de 5 y una DLQ, y una alarma de CloudWatch se dispara cuando la DLQ contiene algún mensaje.
Comprueba lo que sabes
¿Por qué agregar jitter al retroceso exponencial?
Sin aleatoriedad, los clientes que fallaron juntos reintentan juntos y recrean el pico de carga; el jitter reparte los reintentos a lo largo del tiempo.
¿Qué errores no deben reintentarse?
Los errores del cliente que volverán a fallar sin cambios, como errores de validación, solicitudes mal formadas o AccessDenied; reintenta solo errores transitorios como la limitación, los 5xx y los tiempos de espera agotados.
¿Cómo ayuda una DLQ con un mensaje envenenado en SQS?
Cuando el contador de recepciones del mensaje supera maxReceiveCount, SQS lo mueve a la DLQ, así deja de reintentarse y de bloquear el procesamiento, y puedes inspeccionarlo.