Agregar proveedores lentos e inestables sin caerse con ellos
Si tu producto consulta APIs de terceros para responder, tu disponibilidad es el producto de la de todos ellos. Con diez proveedores al 99,5 % cada uno, un sistema que los espera a todos baja del 96 %. El trabajo consiste en responder bien cuando fallen, porque van a fallar.
Cuándo aparece el problema
Que un proveedor se caiga del todo es el caso fácil: se detecta en milisegundos y se gestiona.
Aparece cuando un proveedor se pone lento. Ahí es donde se cae todo lo demás y el mecanismo es siempre el mismo:
- Un proveedor que respondía en 200 ms empieza a tardar 8 segundos.
- Tus peticiones a ese proveedor dejan de terminar y cada una retiene un hilo o una conexión.
- Se agota el conjunto de conexiones, que es compartido.
- Las peticiones que no tienen nada que ver con ese proveedor empiezan a fallar.
- Tu sistema está caído por culpa de una función secundaria de la que casi nadie tira.
El paso 4 es el que sorprende la primera vez. Un proveedor lento agota un recurso común y se lleva por delante el resto, mucho más allá de su propia función.
Con 8 s de espera y 50 conexiones, bastan 7 peticiones por segundo hacia el proveedor lento para agotarlas todas.
Por qué el reintento empeora las cosas
La reacción intuitiva ante un fallo es reintentar. Y el reintento sin control es lo que convierte una degradación en una caída.
Si un proveedor tarda porque está saturado, mandarle tres veces cada petición triplica exactamente la carga que lo saturó. El resultado es que un proveedor que iba a recuperarse en dos minutos no se recupera, porque tú y los otros clientes que también reintentan lo mantienen tumbado.
Los reintentos hacen falta, pero con tres condiciones que casi nunca están juntas:
- Solo sobre fallos transitorios. Un error de validación reintentado sigue siendo un error de validación.
- Con espera creciente y variación aleatoria. Sin la variación, todos tus procesos reintentan a la vez y el pico se repite idéntico cada pocos segundos.
- Con un tope. Dos o tres y con presupuesto total: si el porcentaje de reintentos supera un umbral, se dejan de reintentar aunque cada uno tuviera derecho.
Los cuatro patrones
1 · Presupuesto de latencia
Antes de escribir código, decide cuánto tiempo tiene el usuario. Si la respuesta debe estar en 2 segundos y hablas con tres proveedores, ninguno puede tener 2 segundos de tiempo de espera.
El presupuesto se reparte de arriba abajo y cada llamada recibe el suyo. Un tiempo de espera copiado del ejemplo de la documentación del proveedor, o dejado en el valor por defecto de la biblioteca, significa que no hay presupuesto en absoluto.
Ningún tiempo de espera infinito, en ningún sitio. Es la regla más barata de aplicar y la que más veces está incumplida.
2 · Cortacircuitos
Cuando un proveedor acumula fallos, se deja de llamar durante un rato. Las peticiones fallan inmediatamente, sin consumir conexiones ni esperar. Cada cierto tiempo se prueba con una petición suelta a ver si ha vuelto.
Tiene tres estados: cerrado (todo normal), abierto (no se llama, se falla rápido) y semiabierto (se prueba con una). La implementación está en cualquier biblioteca; lo que importa son los umbrales: cuántos fallos abren el circuito, cuánto se queda abierto y cuántos aciertos lo cierran. Esos números salen de medir. Copiarlos de otro sitio no sirve.
El beneficio real va más allá de proteger al proveedor: libera tus recursos para que el resto de tu producto siga funcionando.
3 · Mamparos
Del casco de un barco: si una sección se inunda, no se hunde el barco entero.
En la práctica, cada proveedor tiene su propio conjunto de conexiones y su propio límite de concurrencia. Si el proveedor lento tiene asignadas diez conexiones, puede agotar diez y ninguna más. El resto del sistema no se entera.
Es el patrón que resuelve el paso 4 del mecanismo del principio y el que más se olvida porque no se nota hasta el día que hace falta.
El compartimento del lento se llena igual. La diferencia es que ahora eso solo apaga su función, y el resto del producto sigue.
4 · Degradación parcial
La decisión es de producto: qué se enseña cuando falta una pieza.
Las opciones, por orden de preferencia:
- Servir lo que sí hay. Ocho proveedores de diez respondieron: enseña esos ocho y di que faltan resultados. Es casi siempre mejor que no enseñar nada.
- Servir dato cacheado y marcarlo. Un precio de hace diez minutos suele valer más que un error.
- Ocultar la función. Si la parte que falla es secundaria, que desaparezca en vez de romper la página.
- Fallar claramente. La última opción y solo cuando servir algo incompleto sería peor que no servir nada. En un cobro, por ejemplo.
Esta decisión la toma quien conoce el negocio y hay que tomarla antes, porque a las tres de la tarde de un martes ya no hay tiempo de decidirla.
Cómo se ve en producción
Tres cosas que hay que poder ver, o los cuatro patrones anteriores son fe:
- Latencia por proveedor, en percentiles. La media miente siempre. Lo que importa es el p95 y el p99, porque el 5 % de peor experiencia es el que se queja.
- Tasa de error y estado del circuito por proveedor. Si un circuito lleva veinte minutos abierto, hay que saberlo mientras pasa.
- Cuántas respuestas fueron parciales. Es la métrica que nadie monta y la que de verdad dice si tu producto está sirviendo lo que promete.
Y una alerta que conviene tener: la degradación silenciosa. Un sistema que sirve resultados parciales sin quejarse puede estar funcionando a medias durante semanas sin que nadie lo note, porque técnicamente responde 200.
Lo que sigue fallando aunque hagas todo esto
- El proveedor que responde correctamente con datos incorrectos. Ningún patrón de resiliencia detecta un 200 con basura dentro. Hace falta validar la respuesta y casi nadie lo hace.
- El cambio no anunciado. Un campo que cambia de tipo, un formato de fecha distinto. Los contratos de las APIs ajenas no son tan estables como su documentación sugiere.
- Los límites de tasa que no conocías. Muchos proveedores no publican el suyo real y se descubre el día que tu producto crece.
- La caída correlacionada. Si tres proveedores están en la misma nube, no son tres riesgos independientes.
Cuándo esto es sobreingeniería
Si llamas a un proveedor en una operación que no está en el camino del usuario y su caída solo retrasa un trabajo en segundo plano: no montes nada de esto. Pon un tiempo de espera razonable, un reintento con espera creciente y una cola de fallidos. Y sigue con lo tuyo.
El umbral práctico son dos condiciones a la vez: la llamada está en el camino del usuario y hay más de una dependencia externa. Con una sola dependencia y sin usuario esperando, el tiempo de espera y el reintento cubren casi todo el riesgo por muy poco esfuerzo.
Lo que sí conviene hacer siempre, desde la primera integración y cueste lo que cueste: poner un tiempo de espera explícito. No hay ningún caso en que esperar indefinidamente a un tercero sea la decisión correcta.
Nimboo construye producto entero, incluidas las integraciones que el producto necesita para funcionar: pasarela, correo, calendario, firma. Cómo se decide todo esto en la fase de diseño está en SaaS a medida y los precios de cada fase en /precios/.