githublinkedinemail
← /articles

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.

5 minperformance

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.


Como identificar gargalo de performance em minutos
Como identificar gargalo de performance em minutos