O gargalo raramente está onde a equipe procura

Quando uma tela fica lenta, o primeiro reflexo é revisar o código da aplicação. Na maioria dos incidentes que acompanhamos, o problema está no banco: um plano de execução que mudou, um índice que deixou de ser usado, uma tabela que cresceu sem manutenção ou um pool de conexões saturado.

Um caso típico: uma distribuidora de médio porte com histórico de pedidos acumulado há anos. O relatório mensal de faturamento por representante, que antes rodava em segundos, passou a demorar minutos depois de uma carga de dados legados. Nenhuma linha de código da aplicação havia mudado. A investigação mostrou dois fatores combinados: as estatísticas do planejador estavam desatualizadas após a carga em massa, e o filtro de data comparava uma coluna do tipo texto com um valor de data, o que impedia o uso do índice existente. O ajuste foi trocar o tipo da coluna, atualizar as estatísticas e criar um índice composto compatível com o filtro real. O relatório voltou ao tempo de resposta anterior sem trocar hardware.

Esse tipo de incidente é comum porque o banco degrada de forma silenciosa. Diferente de um erro de aplicação, que aparece no log, a performance ruim se acumula até virar reclamação de usuário.

Índices: o que decide se eles ajudam ou atrapalham

Índice não é gratuito. Cada índice adicional encarece toda escrita na tabela, porque precisa ser atualizado em cada INSERT, UPDATE e DELETE. Em tabelas com muita escrita, criar índice por precaução costuma trocar um problema de leitura por um de gravação.

Alguns critérios que usamos na revisão de índices:

  • Índices compostos devem seguir a ordem das colunas usadas no filtro real, do mais seletivo para o menos, respeitando o prefixo à esquerda.
  • Funções e conversões aplicadas à coluna filtrada podem invalidar o índice. Comparar data com texto é o caso mais frequente.
  • Coluna de baixa cardinalidade isolada (status, tipo, flag) raramente justifica um índice sozinha.
  • No MySQL com InnoDB, a chave primária define a organização física da tabela. Chave primária aleatória, como UUID, espalha as inserções e fragmenta as páginas, o que pesa em tabelas grandes.
  • No PostgreSQL, índices parciais e índices de expressão resolvem casos específicos, mas exigem que a consulta seja escrita exatamente no mesmo formato.

PostgreSQL e MySQL: manutenção que evita degradação silenciosa

Cada motor tem um conjunto próprio de rotinas que precisam ser acompanhadas.

No PostgreSQL, o autovacuum precisa dar conta do volume de atualizações e exclusões. Quando não dá, a tabela incha, os índices perdem eficiência e as consultas ficam mais lentas. As estatísticas usadas pelo planejador também envelhecem, e um plano que era bom deixa de ser. Além disso, cada conexão é um processo no sistema operacional, então aplicações com muitos workers concorrentes precisam de um pooler na frente.

No MySQL com InnoDB, o buffer pool é o parâmetro que mais influencia a leitura em disco, e ele deve ser dimensionado para o conjunto de dados ativo, não para o disco inteiro. O log de consultas lentas e o performance_schema são a fonte para descobrir o que realmente pesa. Transações longas atrapalham a limpeza de versões antigas e fazem as tabelas crescerem sem necessidade.

Na prática

Para quem mantém software em produção, quatro hábitos resolvem a maior parte dos problemas de performance antes que virem incidente:

  1. Habilite coleta contínua de consultas lentas (pg_stat_statements no PostgreSQL, slow query log no MySQL) e revise o ranking a cada ciclo.
  2. Trate toda consulta pesada nova como código: rode EXPLAIN, valide o plano e leve o resultado para a revisão.
  3. Crie índices por migração versionada, com justificativa e consulta associada, para que possam ser removidos quando não fizerem mais sentido.
  4. Acompanhe crescimento das tabelas, inchaço e saturação do pool de conexões como métricas de operação, com alerta.

Modelagem define o teto de performance, mas é a manutenção contínua que garante que o sistema continue perto desse teto meses depois da entrega.