Planteamiento y cuándo aplica esta pregunta
Una API de checkout llama de forma sincrónica a un servicio de impuestos de terceros. La dependencia normalmente tiene un p50 de 80 ms y un p99 de 180 ms, pero ocasionalmente presenta fallos de conexión, respuestas lentas, códigos HTTP 429, 503 y casos en los que la solicitud pudo haber llegado al servidor pero la respuesta se perdió. La API de checkout tiene un objetivo de 800 ms de extremo a extremo. La cadena de llamadas entre servicios puede tener cinco capas de profundidad y varios SDK ya podrían reintentar por defecto.
Diseñe la política de llamadas. Explique cómo dividir el deadline en timeouts por intento, qué fallos son reintentables, cómo configurar los intentos máximos, el backoff exponencial y el jitter, cómo se abre y se recupera un circuit breaker, y cómo el aislamiento, la degradación, las métricas y la inyección de fallos demuestran que un fallo local no se convertirá en un fallo en cascada.
La latencia y la profundidad son suposiciones para la entrevista. La habilidad central radica en la semántica de fallos, la protección de recursos y el control de recuperación para llamadas sincrónicas de servicio a servicio, por lo que la categoría es backend. La pregunta existente sobre órdenes idempotentes se enfoca en el contrato de base de datos para una operación de escritura. Esta pregunta utiliza la idempotencia únicamente para decidir si es seguro reintentar tras un timeout; su eje central es el presupuesto de tiempo y carga a lo largo de la cadena de llamadas.
Qué evalúa el entrevistador
La primera señal es si el candidato elabora un balance de tiempo. Un deadline de extremo a extremo de 800 ms no equivale a un timeout de 800 ms para la dependencia. La validación local, el encolamiento, la serialización y el procesamiento de la respuesta requieren presupuesto. Cada intento también debe contemplar las fases de conexión, TLS y respuesta, y un nuevo reintento solo debe comenzar si queda tiempo suficiente.
La segunda señal es reintentar según la semántica del fallo. Un fallo previo al establecimiento de la conexión, un 503 temporal o un 429 con directivas de reintento pueden ser reintentables. Parámetros erróneos, autenticación y autorización no se solucionan con el paso del tiempo. Cuando una escritura sufre un timeout tras haberse enviado, el resultado puede ser "confirmado pero con respuesta desconocida". Se requiere una clave de idempotencia estable, una consulta de estado o un proceso de conciliación antes de que sea seguro reintentar.
La tercera señal es la amplificación de reintentos. Si cada servicio en una cadena de cinco capas realiza hasta tres intentos, la dependencia final puede recibir 3^5 = 243 llamadas. Los reintentos deben residir en una sola capa que comprenda el deadline de negocio, con límites en los intentos, en el tiempo total transcurrido y mediante un presupuesto de reintentos basado en tokens.
Por último, el entrevistador busca un bucle de recuperación. Un timeout acota una espera individual, un reintento maneja un fallo breve, un circuit breaker rechaza llamadas durante un fallo sostenido y un bulkhead limita la concurrencia y la ocupación de colas. Una respuesta sólida también acota los sondeos en estado semiabierto (half-open), rechaza fallbacks deshonestos y monitoriza las llamadas lógicas de forma independiente a los intentos físicos.
Preguntas para clarificar antes de responder
- ¿La llamada a la dependencia es una lectura o una escritura con efectos secundarios? Las lecturas suelen ser repetibles. Una escritura con timeout puede tener un resultado desconocido y requiere una clave de idempotencia, consulta de estado o conciliación.
- ¿Los 800 ms son un deadline estricto o un objetivo de observación? Aquí es el punto tras el cual el llamador ya no espera. La cancelación debe propagarse downstream para no procesar trabajo para un llamador ausente.
- ¿Qué errores declara la dependencia como reintentables? La clasificación debe seguir el protocolo y el contrato del proveedor, en especial
Retry-Afteren 429, 503, fallos de conexión y errores de negocio. "Cualquier cosa distinta de 200" no constituye una política. - ¿El SDK, proxy o service mesh ya realizan reintentos? Haga un inventario de los valores predeterminados y los límites de intentos de cada capa. De lo contrario, un reintento visible en la aplicación puede desencadenar una tormenta de reintentos.
- ¿Cuáles son la distribución de latencia y la capacidad de la dependencia? Deduzca los timeouts a partir de los percentiles, la tasa tolerada de falsos timeouts y el margen de red; luego valide conexiones nuevas, tráfico entre regiones y picos de carga.
- ¿Qué degradación es aceptable? Si el cálculo de impuestos tiene requisitos de cumplimiento normativo, un impuesto "exitoso" inventado por defecto resulta inseguro. Un fallo explícito, una revisión manual o un checkout diferido deben regirse por un contrato de negocio.
- ¿Cuál es el límite de aislamiento del circuit breaker? Utilice un proveedor, región, endpoint u operación independiente. Un shard con fallos no debe desconectar recursos que funcionan correctamente.
Estructura de respuesta en 30 segundos
"Dividiría el deadline de extremo a extremo de 800 ms en un presupuesto de tiempo y propagaría un deadline absoluto downstream. Cada llamada tiene timeouts de conexión y de solicitud, y un nuevo intento solo se inicia si encaja en el presupuesto restante. Clasifico los fallos según su recuperabilidad y si se conocen los efectos secundarios: respeto las directivas del servidor para 429, reintento solo fallos breves de conexión y respuestas 5xx seleccionadas, y exijo idempotencia o conciliación de estado cuando el resultado de una escritura es desconocido. Los reintentos residen en una sola capa, usan como máximo dos intentos con backoff exponencial acotado y jitter, y consumen un presupuesto de tokens que limita el tráfico adicional durante fallos. Los fallos sostenidos abren el circuito y fallan rápido; tras el enfriamiento, solo pasan unas pocas sondas semiabiertas. Un límite de concurrencia separado y una cola acotada protegen los recursos locales. Luego, la inyección de fallos verifica el deadline, el conteo de intentos, los efectos duplicados y la ruta de recuperación."
Análisis detallado paso a paso
Comience convirtiendo el tiempo en un presupuesto explícito. En este ejercicio, reserve 120 ms de los 800 ms para trabajo local previo a la llamada de la dependencia y 100 ms para procesar el resultado. Esto deja un presupuesto de 580 ms para la dependencia. Una configuración inicial comprobable es un primer intento con límite de 220 ms, un backoff aleatorio en [0, 80) ms y un segundo intento con límite de 220 ms. El peor de los casos consume 520 ms y deja 60 ms en el presupuesto de la dependencia. Estos no son constantes universales; los valores de producción deben calibrarse a partir de los percentiles downstream, los falsos timeouts tolerados, el retardo de red y las pruebas de carga.
Propague un deadline absoluto o un presupuesto restante que decrezca de forma monótona a través de la cadena de llamadas. Antes de reintentar, vuelva a calcular remaining. Si es menor que "límite del siguiente intento + tiempo mínimo de finalización", retorne en lugar de iniciar una llamada que no podrá concluir. Defina con precisión la semántica de los timeouts de conexión y de solicitud, incluyendo si cubren DNS, TLS y la espera en el pool de conexiones. Una nueva instancia puede precalentar conexiones antes de aceptar tráfico para que el tiempo de handshake no se confunda con lentitud en la dependencia. Propague la cancelación, asumiendo aún que esta puede llegar después de que el extremo remoto haya confirmado el trabajo.
A continuación, construya una matriz de semántica de fallos:
| Resultado | ¿Reintentar? | Precondiciones y acción |
|---|---|---|
| Fallo antes de establecer la conexión | Sí | Queda suficiente presupuesto; use backoff y jitter |
| HTTP 429 | Condicional | Respete Retry-After; la espera y el siguiente intento deben caber en el deadline |
| HTTP 503 o 5xx seleccionados | Condicional | El proveedor lo declara transitorio y queda presupuesto de reintentos |
| HTTP 400, 401 o 403 | Por lo general no | Corrija parámetros, credenciales o permisos; esperar no solucionará la solicitud |
| Timeout tras enviar una escritura | Nunca a ciegas | Reutilice la clave de idempotencia o consulte y concilie el estado de la operación |
| Cancelación del llamador o deadline expirado | No | Detenga la adición de trabajo y devuelva un fallo explícito |
Tres compuertas controlan cada reintento: el error es reintentable, queda tiempo suficiente y el presupuesto de reintentos tiene un token disponible. Use backoff exponencial acotado y jitter aleatorio para descorrelacionar los clientes que fallan al mismo tiempo. Prefiera los tiempos de reintento indicados por el servidor cuando existan y sean utilizables. El conteo de intentos máximos incluye la llamada inicial; este diseño parte de dos intentos totales. Lea con atención la redacción en los SDK, ya que maxAttempts = 2 y "dos reintentos" pueden significar dos llamadas totales y tres llamadas totales, respectivamente.
Reintente en la única capa que mejor comprenda el deadline de negocio. Si las cinco capas realizan tres intentos, la carga teórica sobre la capa inferior se multiplica por 243. Monitoree downstream_attempts / logical_requests como el factor de amplificación de reintentos. Debe situarse cerca de 1 en condiciones normales y limitarse explícitamente durante los fallos. Un token bucket o cuota equivalente de reintentos se agota a medida que aumentan los fallos, pausando los reintentos o permitiéndolos a un ritmo bajo, evitando así que los clientes saturen la dependencia cuando es más vulnerable.
Envuelva la operación de la dependencia específica en un circuit breaker. El estado cerrado permite llamadas y mide fallos atribuibles y llamadas lentas sobre una ventana deslizante con un tamaño de muestra mínimo. Si se supera el umbral, el circuito se abre. El estado abierto no llama a la dependencia y devuelve inmediatamente un fallo reconocible o una degradación aprobada por el negocio. Tras un periodo de enfriamiento, el estado semiabierto (half-open) permite únicamente un número reducido de sondeos concurrentes. Si suficientes sondeos tienen éxito, el circuito se cierra; un fallo crítico lo reabre. Los umbrales dependen del tráfico y de las características de recuperación. "20 llamadas y 50% de fallos" es un ejemplo, no una constante universal.
El circuit breaker conlleva costos. El estado modal complejiza las pruebas y la recuperación, un enfriamiento prolongado retrasa la recuperación y un límite de aislamiento muy amplio puede bloquear shards sanos. Registre cada transición de estado, limite la concurrencia de sondeos en half-open y delimite el breaker a un dominio de fallo real. Para fallos breves y de bajo riesgo, timeouts estrictos, reintentos en una sola capa y una cuota de reintentos pueden ser suficientes. No agregue un breaker solo para nombrar otro patrón.
Los recursos locales aún requieren aislamiento. Asigne a la dependencia de impuestos su propio límite de concurrencia, pool de conexiones y cola acotada para que las llamadas lentas no consuman todos los hilos o conexiones del checkout. Las llamadas encoladas también consumen presupuesto del deadline; descarte el trabajo que no pueda finalizar al salir de la cola. Un fallback debe ser verídico y explicable. "Los impuestos no están disponibles temporalmente" o un flujo de trabajo manual pueden ser válidos; asumir un impuesto desconocido como cero y declarar el checkout exitoso no lo es.
Pruebe los invariantes bajo condiciones de fallo. Inyecte rechazo de conexión, una respuesta lenta de 250 ms, códigos 429 con distintos valores de Retry-After, 503, un 400 no reintentable y "escritura confirmada con respuesta perdida". Verifique que una solicitud lógica realice como máximo dos intentos downstream, que la latencia total no supere los 800 ms, que los errores permanentes no se reintenten y que las escrituras desconocidas no se dupliquen. Luego, sostenga los fallos hasta que el circuito se abra. Compruebe el rechazo rápido, las sondas half-open acotadas, el cierre exitoso tras la recuperación y un bulkhead sin saturación.
Las métricas en producción deben separar las llamadas lógicas de los intentos físicos: éxito de extremo a extremo y p95/p99, resultados y latencia por intento, timeouts en fase de conexión versus fase de solicitud, amplificación de reintentos, tasa de recuperación por reintento y latencia añadida, balance del presupuesto de reintentos, estado del breaker y conteo de rechazos, resultados de sondas en half-open, concurrencia del pool y profundidad de la cola, además de conflictos de idempotencia y resultados de conciliación. Observar únicamente el éxito final puede ocultar un sistema que compra disponibilidad a costa del triple de tráfico downstream.
Ejemplo de respuesta de alta calidad
"Primero confirmaría que esta es una consulta de impuestos sincrónica y que el llamador deja de esperar tras 800 ms. Si reservo 120 ms para trabajo previo a la llamada y 100 ms para manejar la respuesta, la dependencia dispone de 580 ms. Mi política inicial utiliza dos intentos totales: cada uno puede tomar como máximo 220 ms, con [0, 80) ms de backoff con jitter entre ellos. Antes de cada intento, verifico el deadline restante propagado, de modo que una cola larga no dispare mecánicamente una segunda llamada.
Los fallos requieren clasificación. Un fallo antes del establecimiento de la conexión y un 503 transitorio definido por el proveedor pueden reintentarse dentro del presupuesto. Un 429 sigue Retry-After, pero si la espera excediera el deadline, fallo de inmediato. No reintento 400, 401 ni 403. Si la operación escribe estado, un timeout tras el envío implica un resultado desconocido. Debo reutilizar una clave de idempotencia o consultar y conciliar el estado en lugar de emitir una nueva solicitud a ciegas.
Hago un inventario de los reintentos en el SDK, gateway y service mesh, y coloco el reintento en una sola capa que tenga visibilidad del deadline de negocio y aplique un presupuesto de reintentos por tokens. Tres intentos en cada una de cinco capas pueden amplificar la llamada inferior 243 veces, por lo que monitoreo los intentos físicos divididos por las solicitudes lógicas.
Para fallos sostenidos, delimito un circuit breaker por proveedor de impuestos y operación. El estado cerrado mide la tasa de fallos con una muestra mínima, abierto falla rápido y semiabierto permite solo unos pocos sondeos antes de la recuperación. La dependencia también recibe un pool de concurrencia separado y una cola acotada. La degradación solo devuelve un estado explícito aprobado por el negocio; nunca inventa un impuesto en cero.
Finalmente, inyecto fallos para verificar el máximo de dos intentos, el límite de 800 ms, la ausencia de reintentos ante errores permanentes, la no duplicación de escrituras y la recuperación del circuito mediante sondas acotadas. En producción observo conjuntamente el éxito lógico, los resultados por intento, la amplificación de reintentos, el balance del presupuesto, el estado del breaker y la saturación del bulkhead."
Errores comunes
- Configurar el timeout de la dependencia en los 800 ms completos → No queda tiempo para la finalización local y la dependencia retiene recursos después de que el llamador desiste → Reste los presupuestos locales y de red del deadline y propague el restante.
- Reintentar cada respuesta no exitosa → Los errores de parámetros y permisos no se resuelven solos, por lo que los reintentos solo añaden carga y latencia → Clasifique por protocolo, código de error y resultado de efectos secundarios.
- Habilitar tres intentos en cada capa → Cinco capas pueden amplificar una llamada en 243 intentos en la capa inferior → Reintente en una sola capa adecuada y mida la amplificación.
- Reenviar una escritura con timeout usando un nuevo ID → El primer intento pudo haberse confirmado, causando cargos o recursos duplicados → Reutilice una clave de idempotencia o consulte y concilie el estado.
- Usar backoff exponencial sin jitter ni topes → Los clientes aún pueden reintentar en oleadas sincronizadas → Acote los intentos y el tiempo transcurrido, y aleatorice el retardo.
- Tratar un circuit breaker como sustituto del timeout → Las llamadas en vuelo aún ocupan hilos, conexiones y colas → Mantenga timeouts por intento y aísle la concurrencia.
- Restablecer todo el tráfico tan pronto inicia el estado semiabierto → Una dependencia en recuperación se satura de nuevo → Permita sondeos acotados y exija éxito de recuperación explícito.
- Usar un breaker global para todos los proveedores y endpoints → Un fallo local bloquea recursos sanos → Delimite el estado del breaker a dominios de fallo independientes.
- Devolver siempre éxito desde un fallback → Se presentan datos desconocidos como correctos y se rompe la semántica de negocio → Use solo degradación aprobada con limitaciones explícitas.
- Observar únicamente la tasa de éxito final → Una tormenta de reintentos puede ocultar temporalmente el deterioro downstream → Observe llamadas lógicas, intentos físicos, latencia añadida y saturación.
Preguntas de seguimiento y respuestas
Pregunta de seguimiento 1: ¿Qué múltiplo del p99 downstream debería ser el timeout?
No hay un múltiplo fijo. Elija una tasa aceptable de falsos timeouts, comience a partir del percentil de latencia correspondiente y añada margen para retardo de red, llamadas entre regiones, establecimiento de conexión y pequeñas variaciones. El resultado debe encajar dentro del deadline upstream. Cuando el p99 está cerca del p50, una pequeña variación de latencia puede provocar muchos timeouts, por lo que un margen adicional puede ser apropiado. Verifique si DNS, TLS y la espera en el pool de conexiones están incluidos en el temporizador.
Pregunta de seguimiento 2: ¿Son reintentables tanto 429 como 503?
Solo de forma condicional. Prefiera Retry-After para 429, pero la espera y un nuevo intento deben encajar en el presupuesto restante. De lo contrario, devuelva un fallo reconocible. Reintente 503 solo cuando el contrato del proveedor lo clasifique como transitorio. Ambos consumen presupuesto de reintentos; ninguno debe convertir la señal de sobrecarga de un servidor en más tráfico inmediato.
Pregunta de seguimiento 3: ¿Por qué una alta tasa de recuperación por reintentos puede indicar aun así un sistema no saludable?
El éxito final puede conseguirse a expensas de carga y latencia adicionales. Si 100 llamadas lógicas generan 180 intentos downstream, la amplificación es de 1.8. Cuando la dependencia está cerca de su capacidad máxima, ese 80% extra puede retrasar la recuperación. Evalúe la tasa de recuperación junto con la amplificación, la latencia de los intentos, el agotamiento del presupuesto y la saturación downstream.
Pregunta de seguimiento 4: ¿Se siguen necesitando rate limiting o bulkheads cuando el circuito está abierto?
Sí. El breaker bloquea nuevas llamadas a una dependencia, pero las solicitudes locales ya pueden estar en vuelo o encoladas, y otras dependencias aún pueden consumir recursos. Los límites de concurrencia, pools separados y colas acotadas controlan la capacidad local; el rate limiting de entrada controla la nueva carga. Las sondas en semiabierto también necesitan un pequeño margen independiente en lugar de una capacidad compartida ilimitada.
Pregunta de seguimiento 5: ¿Es un problema que cada instancia mantenga su propio estado de breaker?
Los estados pueden divergir, pero un estado fuertemente compartido añade otra dependencia sincrónica y más latencia. Un diseño común otorga a cada instancia la misma configuración y estado local; con tráfico alto, cada una ve suficientes muestras y la señal de fallo se propaga naturalmente. Instancias con bajo volumen o dominios de fallo coordinados globalmente pueden justificar un service mesh o una capa centralizada. En cualquier caso, defina el alcance, el tamaño de la muestra y el tráfico adicional máximo durante los fallos.
Pregunta de seguimiento 6: ¿Cuándo debería convertirse esto en una cola asincrónica?
Si la respuesta HTTP no requiere el resultado de impuestos, o si la recuperación puede tomar mucho más tiempo que el deadline del usuario, el procesamiento asincrónico es una mejor opción. Persista una tarea, devuelva un estado consultable y permita que un consumidor aplique su propia política de reintentos y dead-letter. Una cola no elimina la necesidad de idempotencia, expiración o degradación, pero traslada la recuperación prolongada fuera de las conexiones sincrónicas y del presupuesto de 800 ms.
Pregunta de seguimiento 7: ¿Cómo implementaría esto sin causar un incidente de configuración?
Comience registrando las decisiones de timeout, reintento y circuito que se habrían tomado, sin añadir reintentos reales ni rechazar tráfico. Confirme que los SDK no oculten intentos extra. Luego realice un despliegue canary por proveedor o en unas pocas instancias, limite el presupuesto global de reintentos y mantenga un interruptor de apagado rápido. Monitoree el volumen de intentos, la latencia de cola de extremo a extremo, la duración del estado abierto, los fallos en semiabierto y la capacidad de la dependencia antes de expandir el despliegue.