Como identificar gargalo de performance em minutos
Toda app lenta tem UM gargalo dominante. Achismo não resolve. Aprenda a hierarquia de suspeitos, as ferramentas certas e o fluxo que vai direto na causa.
Como identificar gargalo de performance em minutos
Toda aplicação lenta tem um gargalo.
Geralmente UM, dominante. Não dez.
A diferença entre quem otimiza e quem fica chutando é saber onde olhar primeiro.
A regra de ouro: meça, não suponha
"Acho que é o banco."
"Acho que é o Ruby."
"Acho que é a serialização."
Achismo é a maior fonte de tempo perdido em otimização.
Antes de qualquer ação:
- onde o tempo está sendo gasto?
- em qual camada?
- em qual request?
- com qual frequência?
Sem isso, otimização vira loteria.
A hierarquia de suspeitos
Ordene assim. É o que aparece em 90% dos casos.
1. Banco de dados
├── N+1 queries
├── query sem índice
├── query escaneando muita linha
└── lock esperando
2. IO externo
├── chamada HTTP síncrona
├── upload/download
└── leitura de arquivo grande
3. Aplicação
├── serialização pesada (JSON gigante)
├── render de view complexa
├── alocação demais (pressão de GC)
└── lógica algorítmica ruim (loop O(n²))
4. Infraestrutura
├── falta de connection pool
├── thread starvation
├── memória estourando swap
└── CPU saturada
Comece pelo topo. Quase sempre está lá.
Ferramentas que pagam o salário
APM (Datadog, New Relic, Skylight, Scout)
Mostra:
- tempo médio por endpoint
- breakdown: DB / Ruby / view / external
- traces de requests lentas
Se você não tem APM em prod, pare tudo e configure.
Não dá pra otimizar o que não enxerga.
rack-mini-profiler
Local. Aparece um badge no canto da página com tempo de cada query.
gem 'rack-mini-profiler'
gem 'memory_profiler'
gem 'flamegraph'
gem 'stackprof'
Em dev, identifica N+1 e queries lentas na hora.
bullet
Detecta N+1 e te xinga no log:
USE eager loading detected
User => [:posts]
Add to your finder: :includes => [:posts]
Diferença entre devs senior e junior:
- junior comenta o bullet pra "log ficar limpo"
- senior resolve o que ele aponta
stackprof
Sampling profiler. Mostra onde a CPU estava em cada amostra:
require 'stackprof'
StackProf.run(mode: :cpu, out: 'tmp/stackprof.dump') do
expensive_operation
end
Depois:
stackprof tmp/stackprof.dump
Resultado: lista de métodos consumindo CPU, em ordem.
flamegraph
Visualização gráfica de stack traces. Ótimo pra ver onde o tempo se concentra.
pgBadger (Postgres)
Analisa logs do Postgres e mostra queries mais lentas, mais frequentes, com maior tempo total.
Quando o problema está no banco, é onde a verdade aparece.
Sintomas e diagnóstico rápido
"A app começou a ficar lenta de uma semana pra cá"
- Tabela cresceu sem índice acompanhando.
- Migração recente mudou comportamento de query.
- Novo endpoint sendo chamado em loop por front mal escrito.
"Lento só em horário de pico"
- Connection pool insuficiente.
- Lock no banco.
- CPU saturada.
"Lento sempre, mesmo com poucos users"
- N+1.
- Query sem índice.
- Serialização absurda (Active Model Serializers em coleção grande).
"Travadas aleatórias e curtas"
- GC pause.
- Thread starvation.
- IO bloqueante.
"Memória sobe e nunca desce"
- Bloat (jemalloc, MALLOC_ARENA_MAX).
- Cache crescendo.
- Closure segurando objeto.
Esses padrões cobrem a maioria. Decore.
Atalhos práticos
1. Olhe o log de produção.
Rails imprime tempo total e tempo de DB:
Completed 200 OK in 4523ms (Views: 234.5ms | ActiveRecord: 4001.2ms)
Se DB > 80% do total, vá direto pra query.
2. EXPLAIN ANALYZE no Postgres.
EXPLAIN ANALYZE SELECT * FROM users WHERE email = 'foo@bar.com';
Te diz se está usando índice, quantas linhas escaneou, custo estimado vs real.
Se vê Seq Scan numa tabela grande, achou o problema.
3. User.where(...).explain no Rails.
Mesmo coisa, via console:
User.where(active: true).joins(:posts).explain
4. Conta queries em request.
queries = []
ActiveSupport::Notifications.subscribe("sql.active_record") do |*, payload|
queries << payload[:sql]
end
# roda a request
puts queries.count
Mais de 30 queries por request é alerta. Mais de 100, urgência.
A armadilha da micro-otimização
Vejo isso toda semana:
# antes
users.map { |u| u.name }
# depois (alguém leu blog sobre symbol-to-proc)
users.map(&:name)
Ganho: irrelevante.
Enquanto isso o cara tem N+1 carregando 10.000 registros pra mostrar 5.
Micro-otimização sem profiling é massagem de ego.
A regra: gargalo de 10x merece 100% do seu tempo. Ganho de 5% nem mereceu sua atenção.
O fluxo correto
1. Mede (APM, profiler, logs)
↓
2. Identifica TOP 3 mais lentos
↓
3. Pega o pior — investiga só ele
↓
4. Reproduz local (request, query)
↓
5. Hipotetiza causa raiz
↓
6. Corrige
↓
7. Mede de novo — confirma ganho
↓
8. Repete
8 passos. Nenhum opcional.
A grande virada de chave
Performance não é "fazer Ruby ir mais rápido".
É descobrir onde o tempo está sendo gasto e atacar o ponto certo.
A maior parte do tempo de aplicação web Rails é em IO (banco, rede, disk).
Você não otimiza isso fazendo seu Ruby mais esperto.
Otimiza fazendo menos IO.
Conclusão
A diferença entre quem otimiza bem e quem só "tenta coisas":
- usa ferramenta certa
- mede antes e depois
- vai no maior ofensor
- ignora ego de "otimização clever"
Performance, no fundo, é disciplina.
Mais que conhecimento técnico.
Você pode dominar todas as ferramentas e ainda perder tempo se não souber por onde começar.
Comece pelo banco. Sempre.
Vai estar certo na maioria dos dias.
