Lección 13 de 20 · Lectura y práctica
Manejar errores y timeouts con códigos de estado gRPC
Tipo de lección: VideoConcepto claveEn sistemas distribuidos, los errores y timeouts no son excepciones sino eventos esperados. gRPC utiliza códigos de estado específicos para categorizar estos eventos, permitiendo un manejo.
Esta es una lección del contenido anterior de Cursalo. Si ya estudiaste o compraste este curso, entra con tu cuenta para guardar tu progreso y abrir el resto de las clases. No necesitas comprar nada de nuevo.
Tipo de lección: Video
Concepto clave
En sistemas distribuidos, los errores y timeouts no son excepciones sino eventos esperados. gRPC utiliza códigos de estado específicos para categorizar estos eventos, permitiendo un manejo estructurado que va más allá del simple "éxito/fracaso". Imagina un sistema de mensajería entre oficinas: no es lo mismo que un mensaje se pierda (DEADLINE_EXCEEDED) a que el destinatario esté ocupado (UNAVAILABLE) o que el mensaje sea ilegible (INVALID_ARGUMENT).
Los códigos de estado gRPC se dividen en cuatro categorías principales: OK (éxito), errores de cliente (4xx equivalentes), errores de servidor (5xx equivalentes) y errores de red/tiempo. La clave en producción es que cada código debe desencadenar una estrategia de recuperación específica, como retry con backoff exponencial para UNAVAILABLE, pero no para INVALID_ARGUMENT.
Cómo funciona en la práctica
Implementar un manejo robusto requiere configurar tanto el cliente como el servidor. En el servidor, debes devolver códigos específicos usando excepciones o respuestas de error. En el cliente, debes interceptar estos códigos y decidir la acción.
Ejemplo básico en Go:
// Servidor: Devolver error con código específico
status.Error(codes.DeadlineExceeded, "Timeout procesando solicitud")
// Cliente: Manejar el error
err := client.CallRPC(ctx, request)
if err != nil {
st, ok := status.FromError(err)
if ok {
switch st.Code() {
case codes.DeadlineExceeded:
// Loggear y posiblemente retry con contexto renovado
case codes.ResourceExhausted:
// Implementar circuit breaker
default:
// Manejo genérico
}
}
}Configurar timeouts es crítico: usa context.WithTimeout en el cliente y verifica ctx.Err() en el servidor para abortar operaciones largas.
Caso de estudio
Sistema de pagos en e-commerce con microservicios: Servicio de Ordenes llama a Servicio de Pagos. Durante Black Friday, el servicio de pagos sufre alta carga.
| Escenario | Código gRPC | Acción en Cliente | Impacto Usuario |
|---|---|---|---|
| Pago procesado exitosamente | OK | Confirmar orden | Orden completada |
| Timeout de 2 segundos excedido | DEADLINE_EXCEEDED | Retry 1 vez con nuevo timeout de 5s | Ligera demora |
| Servicio de pagos caído | UNAVAILABLE | Circuit breaker abre, fallback a cache | Mensaje "Reintentar más tarde" |
| Tarjeta inválida | INVALID_ARGUMENT | Mostrar error inmediato | Corregir datos |
En producción, monitorea la distribución de códigos de estado: más del 1% de DEADLINE_EXCEEDED puede indicar necesidad de escalar o optimizar.
Errores comunes
- Usar timeouts genéricos: No aplicar timeouts específicos por operación. Solución: Define timeouts basados en percentiles P99 de latencia histórica.
- Ignorar códigos de estado: Tratar todos los errores igual. Solución: Implementar lógica de recuperación diferenciada por código.
- Retry infinito: Reintentar en errores no retryables como INVALID_ARGUMENT. Solución: Usar bibliotecas como retry-go con políticas configuradas.
- No propagar contextos: Perder información de deadline en llamadas anidadas. Solución: Pasar siempre el contexto original o derivado.
- Falta de circuit breakers: Continuar llamando a servicios caídos. Solución: Integrar patrones como hystrix o resilience4j.
Checklist de dominio
- ¿Configuras timeouts diferentes para operaciones de lectura vs escritura?
- ¿Tu cliente distingue entre errores retryables y no retryables?
- ¿Implementas backoff exponencial con jitter para retries?
- ¿Monitoreas la tasa de códigos DEADLINE_EXCEEDED por servicio?
- ¿Usas circuit breakers para servicios críticos?
- ¿Propagas contextos correctamente en toda la cadena de llamadas?
- ¿Tienes fallbacks definidos para cuando los servicios están UNAVAILABLE?
Implementar cliente resiliente con retry y circuit breaker
En este ejercicio, mejorarás un cliente gRPC básico para que maneje errores de producción reales.
- Configura el entorno: Clona el repositorio con un servicio de simulación que devuelve errores aleatorios (UNAVAILABLE, DEADLINE_EXCEEDED, RESOURCE_EXHAUSTED).
- Implementa retry inteligente: Modifica el cliente para reintentar solo en códigos retryables (UNAVAILABLE, DEADLINE_EXCEEDED) con backoff exponencial: 100ms, 200ms, 400ms, máximo 3 intentos.
- Añade circuit breaker: Configura un circuit breaker que se abra después de 5 fallos consecutivos y se cierre tras 30 segundos. Usa la biblioteca "goresilience" o similar.
- Simula carga: Ejecuta 1000 llamadas concurrentes y registra: éxitos, fallos por tipo de error, y activaciones del circuit breaker.
- Optimiza: Ajusta los timeouts basado en los resultados: si más del 5% son DEADLINE_EXCEEDED, aumenta el timeout inicial.
Pistas
- Usa status.FromError() para obtener el código gRPC del error.
- Para el circuit breaker, considera implementar un patrón de estado (closed, open, half-open).
- Prueba con diferentes tasas de error en el servicio simulado para ver cómo se comporta tu cliente.
Evalua tu comprension
Completa el quiz interactivo de arriba para ganar XP.