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.
- 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.