Erros Comuns em gRPC que Você Deve Evitar (Guia Prático)

Erros Comuns em gRPC que Você Deve Evitar (Guia Prático)





Erros comuns em gRPC que você deve evitar

gRPC na prática • Debug e arquitetura

Erros comuns em gRPC que você deve evitar

Se você já “fez funcionar” com gRPC, mas depois sofreu com performance, contratos quebrados ou problemas de observabilidade,
este guia é para você. Vou passar pelos erros mais frequentes e como corrigir com mais segurança e previsibilidade.

✅ Contratos estáveis
⚡ Latência e streaming
🔎 Observabilidade
🧯 Erros e timeouts

1) Quebrar compatibilidade do contrato (proto) no primeiro “ajuste”

gRPC depende diretamente do contrato do .proto.
Quando você muda campos ou semântica de forma destrutiva, o resultado quase sempre é: deploys coordenados demais,
erros difíceis de diagnosticar e migração cara.

  • Renomear campos sem cuidado: evite renomeações que alterem o significado sem migração planejada.
  • Reutilizar números de tags: nunca reaproveite um field number. Tags identificam o campo na mensagem.
  • Remover campos cedo demais: remova só quando não houver clientes antigos. Em muitos cenários, “deprecated + período de convivência” é o caminho.
  • Mudar tipos de forma incompatível: trocar string por int32 (ou equivalente) quebra parsing e lógica dos consumidores.
  • Adicionar requisitos implícitos: se um campo passa a ser “obrigatório” na prática, você precisa tratar default/validação e compatibilidade.

2) Tratar streaming como “só mais um endpoint”

Streaming é poderoso, mas frequentemente usado sem estratégia. O custo aparece como memória alta, backpressure ignorado,
timeouts inesperados e consumo de recursos que “parece aleatório”.

  • Ignorar limites e tamanhos: defina limites de payload, número máximo de mensagens e tamanho por item.
  • Falta de backpressure: em fluxos longos, você precisa garantir que o produtor não “despeje” dados mais rápido do que o consumidor processa.
  • Streams sem cancelamento: sempre considere context / cancelamento do lado do servidor e do cliente para interromper trabalho.
  • Erros tratados como “eventos” sem parada: em caso de erro relevante, finalize o stream com status adequado (não apenas log).
  • Escolher o tipo de RPC errado:
    • Unary para requisições pontuais
    • Server streaming quando o servidor gera uma sequência
    • Client streaming quando o cliente envia uma sequência para agregação
    • Bidirectional quando ambos participam de forma contínua

3) Timeouts e controle de erro inconsistentes (e o caos começa)

gRPC oferece status codes e mensagens que devem ser usados com intenção.
O erro comum é devolver “qualquer coisa” e deixar o cliente descobrir sozinho — normalmente tarde demais.

  • Sem timeout: requisições sem deadline podem “segurar” threads, conexões e recursos sob carga.
  • Timeout fixo e irreal: não escolha um valor genérico; modele com base em SLO/SLAs e no comportamento do seu sistema.
  • Tratar o mesmo erro como “tudo dá pra tentar de novo”:
    retry deve ser criterioso (ex.: rede/transiente). Erros de validação e negócio não devem virar retry agressivo.
  • Status code errado: use códigos coerentes:
    • INVALID_ARGUMENT para validação
    • NOT_FOUND quando o recurso não existe
    • UNAVAILABLE para indisponibilidade/transiente
    • DEADLINE_EXCEEDED quando o prazo estoura
    • INTERNAL para falhas inesperadas
  • Perder contexto: inclua detalhes relevantes (sem vazar dados sensíveis) para debug e suporte.
// Exemplo genérico (pseudocódigo semelhante a Go/Java):
// Objetivo: deadline + mapeamento correto de status + cancelamento.

func (s *Server) CreateOrder(ctx context.Context, req *CreateOrderRequest) (*CreateOrderResponse, error) {
    // Garanta deadline no servidor (ou dependa do cliente, mas seja consistente)
    ctx, cancel := context.WithTimeout(ctx, 800*time.Millisecond)
    defer cancel()

    if req == nil || req.CustomerId == 0 {
        return nil, status.Error(codes.InvalidArgument, "customer_id inválido")
    }

    // Respeite cancelamento/backpressure
    if err := s.repo.SaveOrder(ctx, req); err != nil {
        // Erro de dependência pode virar Unavailable/DeadlineExceeded
        if errors.Is(err, context.DeadlineExceeded) {
            return nil, status.Error(codes.DeadlineExceeded, "tempo excedido para persistir pedido")
        }
        if errors.Is(err, repo.ErrTransient) {
            return nil, status.Error(codes.Unavailable, "falha temporária ao salvar pedido")
        }
        return nil, status.Error(codes.Internal, "falha inesperada ao salvar pedido")
    }

    return &CreateOrderResponse{OrderId: 123}, nil
}

4) Observabilidade fraca: não “enxerga” latência, distribuição e causa

gRPC pode ser rápido — até o momento em que não é. Sem métricas e tracing, você vira refém de “chutes” e logs soltos.
O resultado: incidentes longos e dificuldade para provar regressões.

  • Sem métricas por método: acompanhe pelo menos latência (p50/p95/p99), taxa de erro e contagem de chamadas por RPC.
  • Sem correlação de request: use identificadores (trace/span) para ligar client ↔ server e dependências internas.
  • Ignorar status code como métrica: “erro” genérico não basta. Métrica por codes.* acelera o diagnóstico.
  • Logs sem estrutura: log em texto livre atrapalha agregação. Prefira campos e padronize o que vai em cada log.
  • Não instrumentar interceptors: interceptors são o lugar natural para adicionar headers de rastreio, métricas e tratamento transversal.

Quer melhorar ainda mais? Continue pelo yurideveloper.com.br

Se você curte gRPC e quer ganhar eficiência de arquitetura e operação, recomendo ler os próximos posts:
programação defensiva, timeouts e retries, e boas práticas de observabilidade.