Planteamiento y contexto
Una solicitud de API llama a los servicios gRPC de usuario, catálogo y recomendaciones en secuencia. El usuario puede salir de la página mientras la llamada de recomendaciones es lenta. El sistema debe limitar la latencia de extremo a extremo, liberar recursos del servidor, evitar la amplificación de reintentos y mantener las escrituras consultables cuando ocurre una cancelación tras un efecto secundario externo.
Las guías de gRPC definen un plazo (deadline) como el límite superior de espera del cliente y describen DEADLINE_EXCEEDED; la cancelación termina una RPC que ya no se necesita. Una respuesta sólida separa la propagación del presupuesto, los límites de reintentos, la limpieza de streams y los resultados desconocidos en lugar de asignar a cada capa un tiempo de espera (timeout) independiente de cinco segundos.
Qué evalúa el entrevistador
Busque un plazo absoluto o presupuesto restante, la propagación hacia las RPC secundarias y bases de datos, reintentos que consuman el mismo presupuesto total y la negativa a inferir que «sin respuesta» significa «la escritura no se realizó». El candidato también debe nombrar campos observables y un plan de pruebas con inyección de fallos.
Preguntas para clarificar
- ¿Cuáles son el SLO de nivel superior, el plazo del cliente y la política de resultados parciales?
- ¿Las tres llamadas son de solo lectura o incluyen pagos y otros efectos secundarios?
- ¿Qué estados admiten reintentos y existe una clave de idempotencia?
- ¿Se pueden reproducir los mensajes del stream y existe un cursor o número de secuencia?
- ¿La base de datos y las API externas admiten cancelación?
- Tras una cancelación, ¿el contrato estipula consulta de estado, compensación o conciliación?
Respuesta de 30 segundos
«Cree un plazo absoluto en el borde (edge); los elementos secundarios heredan únicamente el presupuesto restante. Propague la cancelación a cada RPC, base de datos y worker cancelable. Reintente únicamente fallos transitorios seguros con un límite de intentos, retroceso exponencial (backoff), variación aleatoria (jitter) y el mismo plazo total. Las escrituras utilizan un ID de operación o consulta de estado, no un nuevo reintento ciego. Las llamadas de streaming cierran iteradores y conexiones, y los registros guardan el presupuesto restante, el motivo de cancelación, el intento y el estado final».
Solución paso a paso
Paso 1: Definir el presupuesto de tiempo de extremo a extremo
Trate el plazo del cliente como un límite superior. Cada servicio calcula remaining = deadline - now, establece los plazos secundarios no más tarde de ese punto y reserva tiempo para colas, serialización y gestión de respuestas.
parent_deadline = 2.0s from request start
child_deadline = min(parent_deadline, now + remaining_budget)No otorgue a cada capa dos segundos adicionales: una cadena de tres saltos podría superar los seis segundos. Utilice un reloj monotónico localmente para medir colas y trabajo mientras el framework de RPC transporta el plazo.
Paso 2: Propagar la cancelación y detener el trabajo
La navegación fuera de la página, la cancelación explícita y la expiración del plazo deben llegar al manejador (handler) del servidor. Pase el token de cancelación del manejador a las bases de datos, clientes HTTP, esperas en cola e iteradores de streams. Devolver un error mientras el trabajo en segundo plano continúa es una fuga de recursos.
La cancelación debe liberar conexiones, archivos temporales y bloqueos; dejar de generar nuevos mensajes; y registrar las etapas completadas. No puede deshacer una transacción confirmada (committed), por lo que se requiere una consulta de estado o compensación posterior.
Paso 3: Reintentar dentro del presupuesto restante
Reintente únicamente errores explícitamente transitorios como UNAVAILABLE, con un número máximo de intentos, retroceso exponencial y jitter. Cada intento utiliza el mismo plazo; la expiración omite los reintentos restantes.
for attempt in 1..maxAttempts:
if remaining(deadline) <= backoff: stop
result = call(child, deadline, cancellation)
if result is success or permanent_error: return result
sleep(jittered_backoff, cancellation)
return DEADLINE_EXCEEDEDElija un único responsable de reintentos en toda la cadena de llamadas. Rastree la amplificación de reintentos y los intentos por capa para que un proxy y un cliente no multipliquen el trabajo.
Paso 4: Separar lecturas de escrituras
Las lecturas pueden reintentarse cuando el contrato lo permite. Las escrituras necesitan un ID de operación, una restricción de unicidad y una consulta de estado. Si la cancelación ocurre tras una escritura externa, el emisor obtiene un resultado UNKNOWN y no debe crear un nuevo ID de inmediato.
PENDING -> CONFIRMED
\-> FAILED
\-> UNKNOWN (reconcile before retry)El endpoint de estado devuelve el resultado de la entidad de autoridad. Un plazo controla la espera; no crea una reversión (rollback) atómica entre sistemas.
Paso 5: Gestionar los ciclos de vida de RPC de streaming
Establezca un plazo total y, cuando sea apropiado, un tiempo de espera por inactividad. Verifique la cancelación en cada lectura. Cuando el cliente se desconecta, detenga la generación y cierre el cursor o la suscripción a la base de datos. Reconéctese con un cursor o número de secuencia en lugar de reproducir los efectos secundarios desde el principio.
Registre la hora del último mensaje, el motivo de cancelación, las reconexiones y el trabajo acumulado (backlog). Durante un apagado gradual (graceful shutdown), permita que los streams activos finalicen dentro de su plazo restante o devuelvan un estado de cancelación reconocible.
Paso 6: Verificar la propagación y los límites de recursos
Pruebe la cancelación del elemento principal, el tiempo de espera de cada elemento secundario, el trabajo lento en la base de datos, UNAVAILABLE transitorios, errores permanentes, interrupciones de stream, pérdida de respuesta tras una escritura, reintentos multicapa y el apagado gradual del servidor. Aserte que los elementos secundarios se detengan antes del plazo principal, que la cancelación no deje trabajo en segundo plano y que los intentos se mantengan dentro del presupuesto.
Registre el método RPC, trace ID, plazo restante, intento, estado, origen de cancelación y el ID de operación sin cargas útiles confidenciales. Las pruebas de carga deben observar conexiones, colas, amplificación de reintentos, CPU y latencia de cola (tail latency) en lugar de limitarse únicamente al promedio de éxito.
Respuesta modelo
«Creo un plazo absoluto en el borde y propago el presupuesto restante a los tres servicios. Cada manejador pasa la cancelación a su base de datos, llamada HTTP e iterador de stream, y libera recursos antes de retornar. Las llamadas de lectura reintentan únicamente errores transitorios seguros, con un plazo total único y jitter limitado».
«Las escrituras portan un ID de operación; tras una cancelación marco el resultado como UNKNOWN y consulto el estado. Los streams utilizan un plazo total, cursor y política de reconexión. Inyecto cancelación del principal, tiempos de espera secundarios, respuestas perdidas y apagado gradual, y luego verifico que no existan fugas ni amplificación de reintentos».
Errores comunes
- Restablecer un tiempo de espera completo en cada capa → la cadena supera el SLO del borde → propague un único plazo absoluto.
- Retornar desde el manejador y considerarlo cancelado → el trabajo posterior continúa en ejecución → propague la cancelación y limpie los recursos.
- Reintentar cada error → la carga se amplifica → reintente según la semántica, el presupuesto y el límite de intentos.
- Crear un nuevo ID de escritura tras un timeout → la primera escritura pudo haber tenido éxito → consulte la autoridad o compense.
- Reconectar un stream desde el principio → se duplican mensajes y efectos → utilice un cursor, secuencia y consumidor idempotente.
- Registrar solo el error final → ninguna capa explica el agotamiento del presupuesto → registre el plazo restante y el intento por cada salto.
Preguntas de seguimiento y respuestas
Pregunta de seguimiento 1: ¿Puede un servicio secundario extender el plazo del principal?
No. Si la operación necesita más tiempo, hágala asíncrona y devuelva un ID de operación. Extender silenciosamente una solicitud síncrona invalida el SLO del borde.
Pregunta de seguimiento 2: ¿Puede la cancelación revertir una transacción de base de datos confirmada?
No de forma confiable. Detiene el trabajo que no ha iniciado o completado; el trabajo confirmado requiere consulta de estado, compensación o conciliación. La API debe exponer un estado observable.
Pregunta de seguimiento 3: ¿Cómo se evitan los reintentos dobles?
Asigne un único responsable de reintentos y permita que las demás capas propaguen errores. Comparta los campos de intento y presupuesto total, límite los intentos y supervise la amplificación en cada capa.
Pregunta de seguimiento 4: ¿Debe cancelarse siempre un stream inactivo?
Distinga entre un stream de larga duración intencional y uno abandonado. Utilice un plazo total, tiempo de espera por inactividad o latido (heartbeat) y devuelva un estado reconectable en lugar de retener recursos indefinidamente.
Pregunta de seguimiento 5: ¿Qué pasa si el servidor continúa procesando después del plazo?
Haga que el manejador y el trabajo posterior sean conscientes de la cancelación. Mueva el trabajo que no se puede cancelar a una cola en segundo plano acotada con un ID de operación y límite de recursos, en lugar de mantener ocupado el subproceso (thread) de la solicitud.