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.