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:
- Habilite coleta contínua de consultas lentas (pg_stat_statements no PostgreSQL, slow query log no MySQL) e revise o ranking a cada ciclo.
- Trate toda consulta pesada nova como código: rode EXPLAIN, valide o plano e leve o resultado para a revisão.
- Crie índices por migração versionada, com justificativa e consulta associada, para que possam ser removidos quando não fizerem mais sentido.
- 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.