"O código está otimizado, mas o sistema continua lento." Ou: "Refatoramos tudo, mas a performance é a mesma." Esses sintomas indicam confusão entre problemas de código e problemas de arquitetura. Este artigo ensina a distinguir entre os dois e atacar o problema certo.
Otimizar código em uma arquitetura quebrada é polir o Titanic enquanto ele afunda.
A Diferença Fundamental
Problemas de código
Características:
- Localizados em uma função/classe/módulo
- Resolvidos com refatoração pontual
- Impacto limitado ao componente
- Não requerem mudança de design
Exemplos:
- Algoritmo O(n²) que deveria ser O(n)
- Loop desnecessário
- Query N+1
- Serialização ineficiente
Problemas de arquitetura
Características:
- Distribuídos por todo o sistema
- Requerem mudança de design
- Impacto sistêmico
- Não resolvidos com otimização local
Exemplos:
- Comunicação síncrona onde deveria ser assíncrona
- Acoplamento excessivo entre serviços
- Banco de dados errado para o caso de uso
- Falta de cache em camada crítica
Sintomas de Problema de Código
1. Hotspot localizado
Trace mostra:
┌─ Service A: 50ms
│ └─ Function X: 45ms ← 90% do tempo aqui
├─ Service B: 30ms
└─ Service C: 20ms
Diagnóstico:
Problema localizado em Function X
→ Solução: Otimizar Function X
2. CPU alto em um componente
Métricas:
Service A: CPU 95%
Service B: CPU 15%
Service C: CPU 10%
Diagnóstico:
Código ineficiente em Service A
→ Profiler vai identificar a função
3. Query específica lenta
Slow query log:
SELECT * FROM orders
WHERE user_id = ?
AND status = 'pending'
Time: 2.5s
EXPLAIN:
Seq Scan on orders (sem índice)
Diagnóstico:
Problema de código (falta índice)
→ Solução: CREATE INDEX
Sintomas de Problema de Arquitetura
1. Latência distribuída uniformemente
Trace mostra:
┌─ Service A: 200ms
│ └─ Call to B: 180ms
├─ Service B: 180ms
│ └─ Call to C: 160ms
└─ Service C: 160ms
└─ Call to D: 140ms
Diagnóstico:
Cadeia de chamadas síncronas
→ Solução: Repensar o design (async? agregação?)
2. Todos os serviços lentos
Métricas sob carga:
Service A: p95 = 2s (normal: 100ms)
Service B: p95 = 1.8s (normal: 80ms)
Service C: p95 = 1.5s (normal: 50ms)
Diagnóstico:
Saturação sistêmica, não localizada
→ Problema de capacidade ou design
3. Gargalo move quando otimiza
Antes:
DB é gargalo → Adiciona cache
Depois:
Cache é gargalo → Aumenta cluster
Depois:
Network é gargalo → ???
Diagnóstico:
Arquitetura não escala
→ Precisa redesenhar, não otimizar pontos
Framework de Diagnóstico
Passo 1: Trace de ponta a ponta
Colete trace completo:
Request → Gateway → Service A → DB
→ Service B → Cache
→ Service C → External API
Analise:
- Onde está o tempo?
- É localizado ou distribuído?
- Há padrão (sempre o mesmo componente)?
Passo 2: Profile de componentes individuais
Para cada componente lento:
- Rode profiler (CPU, memory, I/O)
- Identifique funções top
- Verifique se é código ou espera
Exemplo:
Profile de Service A mostra:
- 80% do tempo em http.call() ← Espera (arquitetura)
- 15% em json.parse() ← Código
- 5% em business logic ← Código
Passo 3: Teste de isolamento
Teste componente isolado:
- Remova dependências (mock/stub)
- Aplique mesma carga
- Meça latência
Se isolado é rápido → Problema de arquitetura
Se isolado é lento → Problema de código
Soluções por Tipo de Problema
Para problemas de código
Algoritmo ineficiente:
- Refatorar para complexidade menor
- Usar estrutura de dados adequada
Query lenta:
- Adicionar índice
- Reescrever query
- Desnormalizar se necessário
Serialização:
- Trocar formato (JSON → Protobuf)
- Reduzir payload
Memory:
- Pooling de objetos
- Stream processing
- Lazy loading
Para problemas de arquitetura
Chamadas síncronas excessivas:
- Introduzir messaging (async)
- Agregar chamadas (batch)
- Cache de resultados
Acoplamento:
- Separar domínios
- Event-driven architecture
- CQRS para leitura/escrita
Banco inadequado:
- Polyglot persistence
- Read replicas
- Banco especializado (time-series, graph)
Falta de cache:
- Cache distribuído
- Cache de borda (CDN)
- Cache em camadas
Exemplos Práticos
Exemplo 1: Parece código, é arquitetura
Sintoma:
"API de listagem lenta (2s)"
Análise inicial:
Desenvolvedor assume: "Query do banco lenta"
Investigação:
- Query leva 50ms ✓
- Service leva 2s total
- Trace mostra: 20 chamadas para serviço de imagem
Diagnóstico real:
N+1 em nível de serviço
Para cada produto, chama serviço de imagem
Solução (arquitetura):
- Batch: buscar todas imagens em uma chamada
- Ou: Embedding de URL no produto
- Ou: CDN com URL previsível
Exemplo 2: Parece arquitetura, é código
Sintoma:
"Sistema inteiro lento sob carga"
Análise inicial:
Desenvolvedor assume: "Precisamos de mais servidores"
Investigação:
- Escalar não resolve
- Todos os pods CPU alto
- Profile mostra: regex em loop
Diagnóstico real:
Regex catastrófico em validação de email
Chamado milhares de vezes por request
Solução (código):
- Trocar regex por validação simples
- Ou: Compilar regex uma vez (singleton)
- Resultado: 10x mais capacidade com mesmo hardware
Exemplo 3: Ambos os problemas
Sintoma:
"Checkout lento e instável"
Investigação:
Problema 1 (código):
- Cálculo de frete roda 3x (duplicado)
- JSON parsing ineficiente
Problema 2 (arquitetura):
- 8 chamadas síncronas para completar
- Sem fallback quando serviço externo lento
- Sem cache de dados raramente alterados
Solução:
1. Fixes de código (rápido):
- Remover duplicação
- Otimizar parsing
2. Refactor de arquitetura (planejado):
- Agregar chamadas
- Adicionar circuit breaker
- Implementar cache
Árvore de Decisão
O sistema está lento
│
▼
┌───────────────────────┐
│ Trace mostra gargalo │
│ em único componente? │
└───────────┬───────────┘
│
┌────┴────┐
│ │
▼ ▼
Sim Não
│ │
▼ ▼
┌─────────┐ ┌───────────────┐
│ Profile │ │ Latência é em │
│ do │ │ espera (I/O) │
│componente│ │ ou CPU? │
└────┬────┘ └──────┬────────┘
│ │
▼ ┌────┴────┐
CPU alto? │ │
│ Espera CPU
┌────┴────┐ │ │
│ │ ▼ ▼
Sim Não Arquitetura Código
│ │ (comunicação) (distribuído)
▼ ▼
Código Arquitetura
(local) (I/O bound)
Conclusão
Distinguir entre código e arquitetura:
- Trace primeiro - entenda o fluxo completo
- Profile os hotspots - CPU ou espera?
- Teste isolado - o componente sozinho é rápido?
- Aplique solução correta - não use martelo em parafuso
A regra de ouro:
- Problema localizado → Otimização de código
- Problema distribuído → Revisão de arquitetura
- Ambos → Código primeiro (mais rápido), arquitetura depois
Não adianta ter o código mais eficiente do mundo se a arquitetura o faz esperar.
Este artigo faz parte da série sobre a metodologia OCTOPUS de Performance Engineering.