commit&push

~/blog $ git show 2026-09-22

Your Retry Budget Is Longer Than Your Users' Patience

· 5 min read · Victor Benavides

--incidents--craft

Un panel interno mío falla a veces. No siempre, que es la parte molesta. Lo abres y en lugar de los números te sale el panel de error, y después recargas y funciona.

Las fallas intermitentes premian la paciencia por encima de la astucia, así que me puse a revisar en serio. Casi todo lo que sospechaba estaba equivocado, y la cosa que sí estaba mal llevaba meses en un archivo de configuración con aspecto de medida de seguridad.

Todo lo obvio estaba mal

Tenía cinco teorías. Medí las cinco y descarté las cinco.

Volumen de datos. La tabla detrás de la página tiene menos de veinticinco filas. Pedir veinticinco, cien o doscientas devuelve una respuesta idéntica byte por byte. No hay un problema de tamaño acá y nunca lo hubo.

Una consulta lenta, o la base limitada por cuota. Durante la falla la base estaba entre cero y tres por ciento de uso, con el CPU plano en cero. No estaba sufriendo. Estaba dormida.

Pausa automática del modo serverless. Una sospecha razonable, salvo que producción no es serverless. Corre en instancias aprovisionadas chicas, y eso importa más de lo que suena. Vuelvo a eso enseguida.

Agotamiento de conexiones en la capa de red. Cero fallidas, cero pendientes. Nada por ahí.

Arranque en frío del runtime. Esta sí me la creía. Midió 1.25 segundos de diferencia entre frío y caliente. El síntoma eran veintitantos segundos, así que es un orden de magnitud demasiado chico para explicar nada.

Mis propias herramientas fingieron un arranque en frío

Antes de la respuesta real, un desvío que me costó dos diagnósticos equivocados.

Estaba midiendo peticiones a través de un túnel hacia el clúster. La primera petición por un túnel recién abierto tarda como un segundo más que las siguientes, y eso no tiene nada que ver con el servicio al que llamas. Es el túnel armándose.

Así que medí, vi que la primera llamada era más lenta, y concluí que había encontrado un arranque en frío. Dos veces. El arreglo no tiene ninguna elegancia: tírale unas cuantas peticiones basura primero para calentar el túnel, y recién después empieza a medir. Si estás midiendo a través de cualquier proxy o reenviador, tu primer número habla de tus herramientas y no de tu sistema.

Después, el número que lo explicó todo

Saqué una semana de telemetría de conexiones. Siete días son 182 cubos de una hora. La base aceptó conexiones en dos de ellos.

Veinticinco conexiones exitosas en una semana, todas agrupadas en dos horas. Ciento ochenta horas sin ninguna. Nada toca esta base de datos de forma programada. Solo la despierta alguien que abre la página.

Lo que significa que el pool de conexiones nunca está caliente. No hay un goteo constante que mantenga viva una conexión, así que cada visita abre una nueva. El primer usuario siempre es el primer usuario.

Y en esa misma semana hubo exactamente dos fallas de conexión, las dos dentro de la hora del incidente que estaba persiguiendo. Dos tropiezos pasajeros, en un sistema donde nada más se conectaba nunca.

El presupuesto que nadie sumó

Acá está la parte que me gustaría que alguien me hubiera contado.

OBSERVADO18 a 27sQUIEN LLAMAel navegador se rinde a los 30sCONFIGURADOpiso de 60s para conectar, más 5 reintentos0s15s30s45s60s
Un eje, tres tramos. Quien llama se detiene a los 30 segundos. El piso del timeout de conexión ya es el doble, con cinco reintentos encima, así que la política puede gastar mucho más de lo que alguien está esperando.

El timeout de conexión tiene un piso de sesenta segundos. Encima de eso, la capa de datos reintenta hasta cinco veces. El navegador que hace la petición se rinde a los treinta segundos.

Así que un tropiezo pasajero de dos segundos queda en manos de una política de reintentos autorizada a gastar el doble de toda la paciencia de quien llama antes de terminar. Las fallas observadas duraron entre dieciocho y veintisiete segundos, que ya está rozando el límite, y la configuración permite bastante peor.

Eso no es resiliencia. Los reintentos sí terminan funcionando, y el éxito llega a una dirección donde ya no hay nadie parado. Lo único que compró la política fue una forma más lenta y más cara de mostrar el mismo error.

Un presupuesto de reintentos solo es una medida de seguridad si su peor caso entra dentro del plazo de quien está esperando. El mío nunca había sido comparado contra ese número. Los dos valores viven en repositorios distintos y los elegí yo, con años de diferencia, por razones que en su momento tenían sentido local en ambos casos.

De dónde salieron los sesenta segundos

El piso no es arbitrario. Las bases serverless se pausan cuando están ociosas y pueden tardar de veinte a cuarenta segundos en despertar, así que un timeout corto haría fallar cada primera petición. Sesenta segundos es un número defendible para esa situación.

Producción no es esa situación. Está aprovisionada, nunca se pausa, y no hay que despertarla. El piso protege contra un problema que este sistema no tiene, y de paso deja que una conexión condenada cuelgue mucho después del punto en que alguien seguía mirando.

Las configuraciones de seguridad heredadas son difíciles de ver justamente porque parecen responsables. Nadie audita un timeout generoso. Se lee como prudencia y no como una decisión que quizá ya no aplica.

Qué voy a cambiar

Todavía no está arreglado, así que lo describo como intención y no como logro.

Bajar el piso de sesenta segundos donde la base está aprovisionada, y dejar que una conexión mala falle rápido en vez de colgarse. Acotar el presupuesto de reintentos para que el peor caso quede cómodamente por debajo del plazo de quien llama, lo que significa unos tres reintentos con espera corta en lugar de cinco con espera larga. Y poner algo programado que toque la base cada pocos minutos, lo que mantiene viva una conexión y además tiene el efecto útil de notar problemas antes que una persona.

Ese último punto es el que más me interesa, porque la causa de fondo es que este sistema se usa tan poco que nunca llega a calentarse. Las cosas que se usan rara vez están permanentemente frías, y el costo cae entero sobre quien llega primero.

$ grep -rl --tag ~/blog