Manejar errores y timeouts con códigos de estado gRPC

Video
25 min~4 min lectura

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

EscenarioCódigo gRPCAcción en ClienteImpacto Usuario
Pago procesado exitosamenteOKConfirmar ordenOrden completada
Timeout de 2 segundos excedidoDEADLINE_EXCEEDEDRetry 1 vez con nuevo timeout de 5sLigera demora
Servicio de pagos caídoUNAVAILABLECircuit breaker abre, fallback a cacheMensaje "Reintentar más tarde"
Tarjeta inválidaINVALID_ARGUMENTMostrar error inmediatoCorregir 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

  1. ¿Configuras timeouts diferentes para operaciones de lectura vs escritura?
  2. ¿Tu cliente distingue entre errores retryables y no retryables?
  3. ¿Implementas backoff exponencial con jitter para retries?
  4. ¿Monitoreas la tasa de códigos DEADLINE_EXCEEDED por servicio?
  5. ¿Usas circuit breakers para servicios críticos?
  6. ¿Propagas contextos correctamente en toda la cadena de llamadas?
  7. ¿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.

  1. Configura el entorno: Clona el repositorio con un servicio de simulación que devuelve errores aleatorios (UNAVAILABLE, DEADLINE_EXCEEDED, RESOURCE_EXHAUSTED).
  2. 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.
  3. 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.
  4. Simula carga: Ejecuta 1000 llamadas concurrentes y registra: éxitos, fallos por tipo de error, y activaciones del circuit breaker.
  5. 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.