Quando um sistema não escala mais, a pergunta crucial é: estamos batendo em um limite físico ou arquitetural? Limites físicos requerem mais recursos. Limites arquiteturais requerem redesenho. Confundir os dois leva a investimentos errados.
Você não pode otimizar seu caminho através de um limite físico. Mas pode redesenhar através de um limite arquitetural.
Limites Físicos
O que são
Limites impostos pela física e hardware. Não podem ser superados com software.
Exemplos
1. Velocidade da luz (latência de rede)
São Paulo → Nova York: ~5.500 km
Velocidade da luz em fibra: ~200.000 km/s
Latência mínima teórica: 27.5 ms (ida)
Latência mínima round-trip: 55 ms
→ Nenhum software torna isso mais rápido
2. Largura de banda de disco
SSD NVMe típico: 3.500 MB/s read
Se precisa ler 10 GB: mínimo 2.9 segundos
→ Único caminho: menos dados ou mais discos
3. IOPS de storage
SSD típico: 100.000 IOPS
Se precisa 500.000 IOPS: precisa de 5 SSDs
→ Não há otimização que mude isso
4. Largura de banda de memória
DDR4: ~50 GB/s
Se processa 100 GB: ~2 segundos mínimo
→ Limite do hardware
5. Ciclos de CPU
CPU 3 GHz: 3 bilhões de ciclos/segundo
Operação complexa: 1000 ciclos
Máximo: 3 milhões de operações/segundo
→ Limite fundamental do processador
Como identificar
Sinais de limite físico:
✓ Recurso em 100% de utilização
✓ Adicionar mais software não ajuda
✓ Só resolve com mais/melhor hardware
✓ Matematicamente impossível ser mais rápido
Limites Arquiteturais
O que são
Limites impostos por decisões de design. Podem ser superados com redesenho.
Exemplos
1. Lock contention
# Arquitetura limitante
lock = Lock()
def process(item):
with lock: # Só uma thread por vez
return do_work(item)
# Capacidade: 1 thread, não importa quantas CPUs
# Redesenho
def process(item):
partition = get_partition(item)
with partition_locks[partition]: # Lock por partição
return do_work(item)
# Capacidade: N threads (N partições)
2. Single point of serialization
Arquitetura limitante:
Todos os requests → Um banco → Resposta
Capacidade: limitada pelo banco único
Redesenho:
Requests → Load Balancer → N bancos (sharding)
Capacidade: N × capacidade individual
3. Algoritmo ineficiente
# O(n²) - limite arquitetural
def find_duplicates(items):
duplicates = []
for i, item in enumerate(items):
for other in items[i+1:]:
if item == other:
duplicates.append(item)
return duplicates
# O(n) - redesenho
def find_duplicates(items):
seen = set()
duplicates = []
for item in items:
if item in seen:
duplicates.append(item)
seen.add(item)
return duplicates
4. Arquitetura síncrona
Limitante:
Request → API → DB → Cache → External API → Response
Latência: soma de todas as etapas
Redesenho:
Request → API → [DB + Cache + External] paralelo → Response
Latência: máximo das etapas
5. Modelo de dados inadequado
-- Limitante: JOIN em tabela de bilhões
SELECT * FROM orders
JOIN order_items ON orders.id = order_items.order_id
WHERE orders.user_id = 123;
-- Redesenho: desnormalização
SELECT * FROM orders_denormalized
WHERE user_id = 123;
Como identificar
Sinais de limite arquitetural:
✓ Recurso NÃO está em 100%
✓ Adicionar hardware não resolve
✓ Há filas ou bloqueios
✓ Componentes esperam uns pelos outros
✓ "Sempre foi assim" justifica o design
Comparação
| Aspecto | Físico | Arquitetural |
|---|---|---|
| Causa | Hardware/Física | Decisões de design |
| Solução | Mais/melhor hardware | Redesenho |
| Custo | $$ (infra) | Tempo de engenharia |
| Limite | Fundamental | Removível |
Diagnóstico
Passo 1: Medir utilização
CPU: 95% ← Pode ser físico ou arquitetural
Memória: 40% ← Provavelmente não é limite
Disco: 100% ← Pode ser físico
Rede: 20% ← Provavelmente não é limite
Passo 2: Identificar gargalo
# Profiling revela onde tempo é gasto
CPU:
- 80% em função X ← Limite arquitetural (algoritmo)
- 80% em syscalls ← Pode ser I/O
I/O:
- Esperando lock ← Arquitetural
- Esperando disco ← Pode ser físico
Passo 3: Testar hipótese
# Se físico: adicionar recurso deve ajudar
# Adicionar mais CPU
→ Performance melhora? Físico.
→ Não melhora? Arquitetural.
# Se arquitetural: otimização deve ajudar
# Mudar algoritmo/design
→ Performance melhora significativamente? Arquitetural confirmado.
Estratégias de Otimização
Para limites físicos
1. Escalar horizontalmente
- Mais máquinas
- Mais discos
- Mais réplicas
2. Upgrade de hardware
- CPU mais rápido
- SSD NVMe
- Mais memória
3. Aproximar dados
- CDN
- Cache local
- Edge computing
4. Reduzir demanda
- Compressão
- Sampling
- Agregação
Para limites arquiteturais
1. Eliminar contenção
- Locks granulares
- Lock-free structures
- Particionamento
2. Paralelizar
- Operações independentes em paralelo
- Pipeline de processamento
- Async I/O
3. Otimizar algoritmos
- Melhor complexidade O()
- Estruturas de dados adequadas
- Caching estratégico
4. Redesenhar fluxos
- Remover dependências
- Batch processing
- Event-driven
Casos Reais
Caso 1: "Banco está lento"
Observação: Queries levam 500ms
CPU do banco: 30%
Disco: 20%
Diagnóstico: Não é limite físico
Investigação: Query usa full table scan
Solução: Adicionar índice
Resultado: Queries em 5ms (100x melhoria)
→ Limite era arquitetural (falta de índice)
Caso 2: "API não escala"
Observação: 1000 req/s máximo, 4 CPUs a 100%
Adicionando mais CPUs: Não melhora
Diagnóstico: Parece físico, mas...
Investigação: Uma goroutine segura lock global
Solução: Remover lock desnecessário
Resultado: 10.000 req/s com mesmos 4 CPUs
→ Limite era arquitetural (lock)
Caso 3: "Latência trans-oceânica"
Observação: 200ms latência São Paulo → Europa
Rede: 10% utilização
Diagnóstico: Velocidade da luz
Solução possível: Edge na Europa
Resultado: 20ms latência
→ Limite era físico (contornado com arquitetura)
Conclusão
Antes de otimizar, diagnostique:
- Meça utilização de todos os recursos
- Profile para encontrar gargalos
- Teste hipóteses antes de investir
Regras práticas:
Recurso a 100% + adicionar ajuda = Físico
Recurso a 100% + adicionar não ajuda = Arquitetural mascarado
Recurso < 100% + lentidão = Arquitetural
Lembre-se:
- Limites físicos são democratas: afetam todos igualmente
- Limites arquiteturais são tiranias: criados por decisões passadas
Não jogue hardware em um problema de arquitetura. Não redesenhe quando precisa de mais disco.