Metodologia8 min

Código vs Arquitetura: onde está o verdadeiro problema

Às vezes o código é eficiente, mas a arquitetura limita. Outras vezes, a arquitetura é boa, mas o código é lento. Como distinguir?

"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:

  1. Trace primeiro - entenda o fluxo completo
  2. Profile os hotspots - CPU ou espera?
  3. Teste isolado - o componente sozinho é rápido?
  4. 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.

OCTOPUSarquiteturacódigootimização
Compartilhar:
Read in English

Quer entender os limites da sua plataforma?

Entre em contato para uma avaliação de performance.

Fale Conosco