Microserviços se tornaram o padrão para sistemas modernos. Eles oferecem benefícios reais: independência de deploy, escalabilidade granular, autonomia de times. Mas esses benefícios vêm com custos de performance que muitas vezes são subestimados.
Este artigo explora os desafios de performance em arquiteturas de microserviços e estratégias para mitigá-los.
Microserviços não são grátis. Você troca complexidade interna por complexidade de rede.
O Custo da Distribuição
De função para rede
Monolito:
userService.getUser(id) → 0.001ms (chamada de função)
Microserviço:
HTTP GET /users/{id} → 1-5ms (chamada de rede)
Diferença de 1.000x a 5.000x por chamada.
Latência acumulada
Monolito:
Requisição → Processo interno → Resposta
2ms total
Microserviços:
Requisição → API Gateway → Auth → User Service → Order Service → Resposta
1ms 2ms 3ms 4ms 3ms
= 13ms total
Falhas em cascata
Em um monolito, uma falha geralmente afeta apenas parte do sistema. Em microserviços, uma falha pode propagar:
Payment Service lento
↓
Order Service timeout esperando
↓
API Gateway acumula conexões
↓
Todos os endpoints ficam lentos
↓
Sistema inteiro degradado
Problemas Comuns
1. Over-fetching e Under-fetching
Over-fetching: buscar mais dados que necessário
// Cliente precisa apenas de nome
GET /users/123
{
"id": 123,
"name": "João",
"email": "...",
"address": {...},
"preferences": {...},
"history": [...] // 50KB de dados desnecessários
}
Under-fetching: precisar de múltiplas chamadas
// Para montar uma página
const user = await getUser(id); // Chamada 1
const orders = await getOrders(user.id); // Chamada 2
const products = await getProducts(orders.map(o => o.productId)); // Chamada 3
2. Distributed Monolith
Microserviços fortemente acoplados que precisam ser deployados juntos.
Serviço A muda interface
↓
Serviço B precisa atualizar
↓
Serviço C depende de B
↓
Deploy coordenado de A, B, C
↓
Pior dos dois mundos
3. Overhead de serialização
Objeto em memória: acesso direto
Objeto via rede: serializar → transmitir → deserializar
JSON grande:
Serialização: 5ms
Transmissão: 10ms
Deserialização: 5ms
= 20ms overhead
4. Service mesh overhead
Sidecars adicionam latência a cada chamada.
App → Sidecar → Rede → Sidecar → App
0.5ms 0.5ms
+1ms por hop (mínimo)
Padrões para Melhor Performance
1. API Composition
Agregue dados em um único endpoint.
// Em vez de 3 chamadas do cliente
GET /users/123
GET /orders?userId=123
GET /products?ids=1,2,3
// Uma chamada para um aggregator
GET /user-dashboard/123
{
"user": {...},
"orders": [...],
"products": [...]
}
2. GraphQL
Cliente especifica exatamente o que precisa.
query {
user(id: 123) {
name
orders {
id
total
}
}
}
3. CQRS (Command Query Responsibility Segregation)
Modelos separados para leitura e escrita.
Escrita: microserviços normalizados
Leitura: views desnormalizadas otimizadas
Usuário pede dashboard → View materializada → Resposta rápida
4. Event-Driven Architecture
Comunique via eventos em vez de chamadas síncronas.
// Síncrono
orderService.createOrder(order)
→ inventoryService.reserve(items) // Espera
→ paymentService.charge(amount) // Espera
→ notificationService.send(email) // Espera
// Assíncrono
orderService.createOrder(order)
→ publish("OrderCreated")
// Outros serviços reagem aos eventos
// Sem espera, sem cascata
5. Cache entre serviços
// Serviço A cache dados do Serviço B
async function getUser(id) {
const cached = await redis.get(`user:${id}`);
if (cached) return JSON.parse(cached);
const user = await userService.get(id);
await redis.setex(`user:${id}`, 300, JSON.stringify(user));
return user;
}
6. Bulk APIs
Suporte a operações em batch.
// Ineficiente
GET /products/1
GET /products/2
GET /products/3
// Eficiente
GET /products?ids=1,2,3
// ou
POST /products/batch
{"ids": [1, 2, 3]}
Observabilidade Distribuída
Distributed tracing
Essencial para entender latência end-to-end.
Request ID: abc-123
├── API Gateway (2ms)
├── Auth Service (5ms)
├── User Service (15ms) ← Gargalo!
│ └── Database (12ms)
└── Response (total: 22ms)
Métricas por serviço
| Métrica | Por que importa |
|---|---|
| Latência por endpoint | Identificar endpoints lentos |
| Error rate por dependência | Identificar serviços problemáticos |
| Request rate | Volume de comunicação |
| Timeout rate | Problemas de integração |
Logs correlacionados
Mesmo request ID em todos os serviços.
[abc-123] API Gateway: Received request
[abc-123] Auth: Token validated
[abc-123] User: Fetching user 456
[abc-123] User: Database query took 12ms
Quando NÃO Usar Microserviços
Microserviços não são sempre a resposta.
Evite quando:
- Time pequeno (< 10 pessoas)
- Domínio não é bem entendido
- Latência é crítica e cada ms importa
- Não tem infraestrutura de observabilidade
- Não tem capacidade de operar sistemas distribuídos
O monolito bem estruturado pode ter:
- Deploy simples
- Debugging fácil
- Latência mínima
- Transações ACID
Conclusão
Microserviços são um trade-off:
| Ganho | Custo |
|---|---|
| Independência de deploy | Complexidade operacional |
| Escalabilidade granular | Latência de rede |
| Autonomia de times | Observabilidade mais difícil |
| Resiliência (se bem feito) | Falhas em cascata (se mal feito) |
Para performance em microserviços:
- Minimize chamadas — agregadores, batch, cache
- Use async quando possível — eventos, filas
- Invista em observabilidade — tracing, métricas, logs
- Design para falha — timeouts, circuit breakers, fallbacks
- Questione a necessidade — nem tudo precisa ser microserviço
Microserviços são uma decisão organizacional com consequências técnicas. Entenda o custo antes de pagar.