Pular para conteúdo

Otimização de Desempenho

Quase toda "lentidão de banco" que chega para nós não é lentidão de banco: é uma consulta sem índice, um ORM emitindo trezentas consultas por requisição, ou um conjunto de dados que deixou de caber em memória. Trocar o servidor por um maior mascara o problema por alguns meses e devolve a mesma conta depois.

O método é sempre o mesmo: medir, encontrar as poucas consultas que respondem pela maior parte do tempo, corrigir a causa, medir de novo.

Camadas onde a latência se acumula: aplicação, pool de conexões, planejador, cache e I/O de disco

A ordem importa: corrigir uma camada torna a de baixo mais barata. O caminho inverso não funciona.


1. Medir antes de tocar

Sem número, otimização é palpite.

Fonte O que revela
pg_stat_statements (PostgreSQL) Consultas por tempo total acumulado — o ranking que importa
performance_schema / slow query log (MySQL) Consultas lentas e as executadas muitas vezes
Métricas de sistema CPU, latência de I/O, memória, conexões ativas
Métricas de replicação Lag, que denuncia escrita pesada ou réplica subdimensionada
Rastreamento na aplicação Onde a requisição gasta tempo: banco, rede, ou o próprio código

Tempo total vence tempo individual

Uma consulta de 2 segundos executada 10 vezes por dia importa menos que uma de 20 ms executada 200 mil vezes. Ordene por tempo total acumulado, não pela consulta mais lenta.


2. Aplicação: o problema mais barato de corrigir

  • N+1 — uma consulta para listar, mais uma por item. É o padrão mais comum e o de maior ganho: resolve-se com JOIN ou carregamento em lote.
  • SELECT * — traz colunas que ninguém usa, impede índice de cobertura e infla a rede.
  • Paginação por OFFSET altoOFFSET 100000 faz o banco varrer e descartar 100 mil linhas. Paginação por chave (keyset) não tem esse custo.
  • Ausência de lote — mil INSERT individuais custam muito mais que um INSERT com mil linhas.
  • Transação longa — segura recursos, atrasa limpeza de versões antigas e aumenta o lag das réplicas.

3. Plano de execução

Ler o plano é o que separa correção de tentativa.

-- PostgreSQL: com tempo real e páginas lidas, não só a estimativa
EXPLAIN (ANALYZE, BUFFERS) SELECT ... ;

-- MySQL
EXPLAIN ANALYZE SELECT ... ;

O que procurar:

  • Varredura sequencial em tabela grande — falta índice, ou o índice existe e não pode ser usado (função sobre a coluna, tipo incompatível, LIKE '%algo').
  • Estimativa muito distante do real — estatísticas desatualizadas. Rode ANALYZE antes de concluir qualquer outra coisa.
  • Ordenação em disco — memória de trabalho insuficiente para o ORDER BY/GROUP BY.
  • Junção pelo método errado — quase sempre consequência de estimativa ruim, não do planejador.

4. Índices

Índice bom resolve; índice demais custa em toda escrita.

  • Índice composto na ordem certa — colunas de igualdade primeiro, faixa por último. Um índice em (cliente_id, criado_em) serve a consulta por cliente e por cliente + período; o inverso não.
  • Índice de cobertura — quando o índice contém todas as colunas da consulta, o banco nem toca a tabela.
  • Índice parcial — em PostgreSQL, indexar só as linhas que importam (WHERE status = 'ativo') reduz tamanho e custo de manutenção.
  • Índices não usados são prejuízo — ocupam disco, tornam cada INSERT mais caro e ainda inflam o backup. As estatísticas de uso do banco mostram quais nunca foram lidos.
  • Índice duplicado(a) é redundante quando existe (a, b).

5. Memória e cache

Banco Parâmetro central Regra de partida
MySQL / InnoDB innodb_buffer_pool_size ~60–70% da RAM em servidor dedicado
PostgreSQL shared_buffers + cache do SO ~25% em shared_buffers, contando com o cache do sistema
PostgreSQL work_mem Por operação de ordenação — cuidado: multiplica por conexão
ClickHouse Limite de memória por consulta Evita que uma agregação derrube o nó

O sinal a acompanhar é a taxa de acerto do cache. Quando ela cai de forma sustentada, o conjunto ativo cresceu além da RAM — e o próximo passo é memória ou particionamento, não mais índice.


6. Conexões

Banco de dados não gosta de milhares de conexões. Cada uma consome memória e disputa CPU.

  • Pool obrigatórioPgBouncer no PostgreSQL, ProxySQL no MySQL, ou o pool do próprio framework, bem configurado.
  • Menos conexões, mais vazão — acima de certo ponto, aumentar o pool piora o desempenho: a fila migra do pool para dentro do banco, onde é mais cara.
  • Separar leitura de escrita — leitura direcionada às réplicas alivia o primário e é o caminho mais direto para escalar leitura.

7. Storage e I/O

O que sobra depois de tudo é físico:

  • latência de escrita do volume, especialmente para o log de transação;
  • IOPS disponível contra o exigido no pico;
  • separação entre volume de dados e volume de log;
  • compressão, que troca CPU por I/O — quase sempre um bom negócio em carga analítica.

Ganhos típicos

Correção Ganho observado
Índice ausente em consulta de alta frequência 10× a 1000× na consulta
Eliminação de N+1 5× a 50× na latência da requisição
buffer pool dimensionado para o conjunto ativo 2× a 20× em carga de leitura
Pool de conexões corrigido Fim dos picos de erro e latência mais estável
Leitura direcionada a réplica Alívio proporcional ao volume de leitura

Servidor maior é a última alternativa, não a primeira

Aumentar hardware antes de olhar plano de execução resolve por um tempo e cobra depois — com uma conta maior e o mesmo problema.


Páginas Relacionadas