Em uma aplicação monolítica, chamadas de função são praticamente instantâneas — nanossegundos. Em sistemas distribuídos, cada chamada atravessa a rede, adicionando milissegundos de latência. Essa diferença de 6 ordens de magnitude muda tudo.
Este artigo explora como a latência de rede afeta performance e estratégias para minimizar seu impacto.
A rede é a mentira mais cara da computação distribuída.
O Custo Real da Rede
Comparação de latências
| Operação | Tempo típico |
|---|---|
| Acesso L1 cache | 0.5 ns |
| Acesso RAM | 100 ns |
| Leitura SSD | 150 μs |
| Round-trip mesmo datacenter | 0.5 ms |
| Round-trip mesma região | 1-5 ms |
| Round-trip cross-region | 50-150 ms |
| Round-trip intercontinental | 100-300 ms |
Uma chamada de rede no mesmo datacenter é 1 milhão de vezes mais lenta que acesso à RAM.
O problema se multiplica
Requisição do usuário
↓
API Gateway (1ms)
↓
Serviço A (2ms)
↓
Serviço B (2ms)
↓
Banco de dados (3ms)
↓
Resposta: 8ms apenas de rede
E isso assumindo que tudo dá certo na primeira tentativa.
Componentes da Latência de Rede
1. Propagação
Tempo para o sinal físico viajar pelo meio.
Velocidade da luz na fibra ≈ 200.000 km/s
São Paulo → Virginia ≈ 8.000 km
Tempo mínimo: 40ms (ida)
Round-trip: 80ms (mínimo teórico)
Você não pode vencer a física.
2. Transmissão
Tempo para colocar todos os bits no meio.
Payload 1MB em link de 1Gbps = 8ms
Payload 1MB em link de 100Mbps = 80ms
3. Processamento
Tempo em roteadores, firewalls, load balancers.
Cada hop adiciona microsegundos a milissegundos.
4. Enfileiramento
Espera quando links estão congestionados.
Pode variar de 0 a centenas de milissegundos.
Problemas Comuns
Chatty protocols
Muitas chamadas pequenas em vez de poucas chamadas grandes.
// Ruim: 100 chamadas de rede
for (id in ids) {
items.push(await fetch(`/api/items/${id}`));
}
// Bom: 1 chamada de rede
items = await fetch(`/api/items?ids=${ids.join(',')}`);
N+1 em serviços
O mesmo problema de N+1 do banco, mas entre serviços.
// Serviço de pedidos
orders = getOrders(userId) // 1 chamada
for order in orders:
customer = getCustomer(order.customerId) // N chamadas
Chamadas síncronas em cadeia
A → B → C → D → Banco
5ms 5ms 5ms 5ms = 20ms mínimo
Latência total é a soma de todas as chamadas.
Retry storms
Quando timeouts causam retries que causam mais carga que causa mais timeouts.
Serviço lento
↓
Timeout (2s)
↓
3 retries × 1000 clientes = 3000 requisições extras
↓
Serviço ainda mais lento
↓
Colapso
Estratégias de Mitigação
1. Reduza chamadas
Batching: agrupe operações
// Em vez de
await Promise.all(ids.map(id => getItem(id)));
// Use batch API
await getItems(ids);
Prefetching: busque dados antes de precisar
// Enquanto processa página 1, busque página 2
const page1 = await getPage(1);
const page2Promise = getPage(2); // Já iniciou
// ... processa página 1 ...
const page2 = await page2Promise;
2. Paralelismo
// Sequencial: 15ms
const a = await serviceA(); // 5ms
const b = await serviceB(); // 5ms
const c = await serviceC(); // 5ms
// Paralelo: 5ms
const [a, b, c] = await Promise.all([
serviceA(),
serviceB(),
serviceC()
]);
3. Cache agressivo
Evite chamadas de rede quando possível.
const cache = new Map();
async function getUser(id) {
if (cache.has(id)) return cache.get(id);
const user = await userService.get(id);
cache.set(id, user);
return user;
}
4. Compressão
Reduza bytes transmitidos.
Resposta JSON: 100KB
Comprimido (gzip): 15KB
Economia: 85% de bandwidth
5. Keep-alive connections
Evite overhead de estabelecer conexões.
Nova conexão TCP: ~1-3ms
Conexão existente: ~0ms overhead
6. Localidade
Mantenha serviços que se comunicam muito próximos.
Serviço A e B na mesma zona: 0.5ms
Serviço A e B em regiões diferentes: 50ms
7. Comunicação assíncrona
Quando possível, não espere resposta.
// Síncrono: bloqueia
await notificationService.send(email);
// Assíncrono: não bloqueia
messageQueue.publish('send-email', email);
Timeouts e Retries
Configurando timeouts
Timeout = p99 latência × 2 (ou mais)
Muito curto: falsos positivos Muito longo: recursos presos
Retry com backoff
async function fetchWithRetry(url, maxRetries = 3) {
for (let i = 0; i < maxRetries; i++) {
try {
return await fetch(url);
} catch (e) {
if (i === maxRetries - 1) throw e;
await sleep(Math.pow(2, i) * 100); // Exponential backoff
}
}
}
Circuit breaker
Pare de tentar quando o serviço está claramente com problemas.
if (failureRate > 50%) {
// Circuito aberto: falhe rápido
throw new Error('Service unavailable');
}
Métricas Essenciais
| Métrica | Por que importa |
|---|---|
| Latência p50, p95, p99 | Experiência real do usuário |
| Request rate | Volume de chamadas |
| Error rate | Falhas de comunicação |
| Retries | Indica problemas de estabilidade |
| Connection pool usage | Pressão de conexões |
| Bytes in/out | Volume de dados |
Conclusão
A rede é uma realidade inevitável em sistemas distribuídos. Para minimizar seu impacto:
- Reduza chamadas — batch, cache, prefetch
- Paraleleze — quando não há dependência
- Comprima — menos bytes = menos tempo
- Planeje localidade — serviços próximos = menor latência
- Use async — não espere quando não precisa
- Configure timeouts — não espere para sempre
- Monitore — você não pode melhorar o que não mede
Cada chamada de rede é uma aposta. Minimize suas apostas.