Saltar al contenido
DevPedia

OperaciónGuía 8 de 11

Resiliencia en sistemas distribuidos

¿Cómo evitar que un fallo parcial se propague por todo el sistema? Mecanismos de resiliencia, cuándo usarlos y cuáles son sus límites.

Actualizado 10 min de lectura

Un sistema distribuido cambia por completo el modelo de fallos. En un proceso local, una función puede devolver un resultado o lanzar una excepción. En una interacción remota:

puede suceder que la request nunca llegue, que llegue dos veces, que el Service B procese la operación pero la respuesta se pierda, que el Service B tarde demasiado o que solo una parte del sistema esté fallando.

Un fallo parcial no se queda en el componente que falló. Si Payments espera sin límite a un proveedor caído, deja de atender checkout; si reintenta un cobro que ya se ejecutó, cobra dos veces. Los mecanismos de este artículo no evitan que algo falle: limitan el daño, hacen seguro reintentar y deciden qué se degrada cuando no se puede cumplir todo.

Timeout

Un timeout (tiempo máximo de espera) evita que una dependencia bloquee recursos indefinidamente:

Pero:

Timeout no significa que la operación no haya ocurrido.

Este detalle es crítico:

El cliente ve un timeout. La tarjeta ya fue cobrada.

Retry

Ante fallos transitorios, un reintento (retry) vuelve a enviar la misma operación:

Un reintento robusto suele considerar backoff exponencial, jitter, cantidad máxima de intentos y un presupuesto de tiempo. Sin ese tope, un proveedor lento se convierte en una amplificación de carga: cada cliente reintenta y el proveedor recibe todavía más tráfico.

Reintento + operación no idempotente

Esto es peligroso:

Por eso timeout, reintento e idempotencia deben diseñarse juntos.

Circuit breaker

Permite dejar de enviar requests a una dependencia que está fallando:

Cuando está OPEN, el sistema puede fallar rápidamente en lugar de acumular timeouts y seguir saturando la dependencia.

Idempotencia

Supongamos:

POST /payments
Idempotency-Key: abc123

En la primera request, el proveedor crea el payment pero la respuesta se pierde. En la segunda request, con el mismo Idempotency-Key, el sistema reconoce que esa operación ya fue procesada y devuelve el payment existente en lugar de crear uno nuevo:

Es fundamental para payments, orders, webhooks y message consumers.

Entrega al menos una vez (at-least-once)

Muchos sistemas de mensajería entregan cada mensaje al menos una vez:

Por eso una estrategia práctica es combinar entrega al menos una vez con un consumidor idempotente.

Bulkhead

El nombre viene de los mamparos de un barco: un compartimento inundado no debería hundir el resto. En software, se aíslan pools de recursos —conexiones, threads, semáforos— para que el fallo de un área no se lleve puesta a otra:

Si Notifications se degrada, no debería consumir todos los recursos disponibles para Payments.

Rate limiting

Controla el flujo entrante antes de que el sistema se sature:

Protege contra clientes abusivos, picos de tráfico y clientes que generan carga de más sin querer. Entre las implementaciones más conocidas están token bucket y leaky bucket. A diferencia del load shedding, el rate limiting actúa en el borde: decide cuánto aceptar, no qué descartar cuando ya estamos saturados.

Backpressure

Supongamos un producer a 20k msg/s y un consumer a 5k msg/s. La diferencia genera acumulación:

Backpressure consiste en que el consumidor o la cola frenen al productor cuando no dan abasto, en lugar de aceptar trabajo sin límite y dejar que la cola crezca sin tope.

Load shedding

Cuando el sistema no puede procesar todo:

Es preferible rechazar algunas requests antes que permitir que todo el sistema colapse.

Cola de mensajes fallidos (DLQ)

DLQ viene de Dead Letter Queue. Es la cola a la que van los mensajes que un consumer no pudo procesar:

La DLQ permite inspeccionar mensajes, corregir problemas, reprocesarlos más tarde y evitar que un mensaje defectuoso bloquee la cola de forma permanente.

Saga

Cuando una operación de negocio atraviesa varios servicios, no tenemos una única transacción ACID. Podemos implementar una Saga:

Si Inventory falla, se dispara una cadena de acciones compensatorias:

Degradación elegante (graceful degradation)

No todos los componentes deberían tener el mismo nivel de criticidad:

Si Recommendations está caído, Checkout sigue disponible y solo se pierde la feature de Recommendations. La arquitectura mantiene disponible lo crítico.

Cómo analizar un fallo

Un enfoque práctico es recorrer una serie de preguntas:

Herramientas

Dependiendo de la plataforma: Resilience4j, Envoy, Istio, AWS API Gateway, AWS SQS, Kafka, RabbitMQ, Kubernetes health probes. Las herramientas implementan mecanismos. No reemplazan el diseño de los modos de fallo.

Ninguno de estos mecanismos sirve si no se verifica que funcionan. Un circuit breaker que nunca se abrió en un entorno controlado es una hipótesis, no una protección: hace falta ejercitarlo con testing de performance bajo carga y con fallas inyectadas a propósito.

Compartir esta guía

Buscá por concepto, patrón o práctica.