O índice que faltava: como 13 milhões de leituras desnecessárias estavam a travar um ERP em produção
Um caso prático de diagnóstico de performance em SQL Server, quando o problema está num produto vertical de terceiros e não controlamos o código.
01O sintoma
Um cliente nosso opera há anos com um ERP vertical — uma solução específica do sector dele, desenvolvida por um fornecedor português, instalada em dezenas de empresas. A queixa era recorrente: lentidão perceptível numa das operações mais frequentes do dia-a-dia. Nada dramático, nada que parasse a operação. Apenas aquela fricção permanente em que cada acção demora uns segundos a mais do que deveria, multiplicada por dezenas de utilizações diárias, multiplicada por meses.
O cliente vivia com o problema há bastante tempo. A percepção generalizada era a habitual nestes casos: «a base de dados está grande», «o servidor precisa de mais RAM», «isto é do Windows». Explicações vagas que não resolvem nada mas acalmam a frustração por uns dias.
Quando nos pediram para investigar, a primeira pergunta que nos fizemos não foi «o que está mal?» mas sim «o que está o servidor realmente a fazer quando esta acção é executada?». Essa mudança de enquadramento é, na nossa experiência, metade do caminho para qualquer diagnóstico técnico sério.
02O contexto técnico
O servidor corria numa VM com 16 GB de RAM, Windows Server, SQL Server Standard. A base de dados não era pequena, mas também não era obscena — estávamos longe do volume onde a dimensão, por si só, explicaria degradação perceptível em operações transaccionais simples.
Sendo um ERP de terceiros, não tínhamos acesso ao código da aplicação. Não podíamos abrir o Visual Studio e pôr um profiler na camada de dados. O que tínhamos era acesso ao SQL Server — e, em 90% dos casos de lentidão em ERPs verticais, isso basta.
03A investigação
A abordagem foi directa: em vez de teorizar sobre onde poderia estar o problema, deixámos o próprio SQL Server dizer-nos. O SQL Server mantém estatísticas extraordinariamente detalhadas sobre cada query executada — quantas vezes correu, quantas leituras lógicas fez, quanto tempo de CPU consumiu, qual o pior tempo de execução. Basta saber onde olhar.
A query que usámos para extrair o top offenders foi algo como:
SELECT TOP 20
qs.execution_count,
qs.total_logical_reads,
qs.total_logical_reads / qs.execution_count AS avg_logical_reads,
qs.total_worker_time / qs.execution_count AS avg_cpu_time,
SUBSTRING(qt.text, qs.statement_start_offset / 2 + 1,
(CASE WHEN qs.statement_end_offset = -1
THEN DATALENGTH(qt.text)
ELSE qs.statement_end_offset END
- qs.statement_start_offset) / 2 + 1) AS query_text
FROM sys.dm_exec_query_stats qs
CROSS APPLY sys.dm_exec_sql_text(qs.sql_handle) qt
ORDER BY qs.total_logical_reads DESC
O resultado foi imediatamente revelador. No topo da lista, um DELETE simples numa tabela de parâmetros auxiliares, com apenas três colunas no WHERE. Estatísticas:
Para quem não vive em SQL Server diariamente, o significado prático: uma operação que deveria tocar numa única linha estava a percorrer, em média, 11.692 páginas de dados de cada vez. Em termos humanos, o ERP pedia à base de dados «apaga esta linha» e a base de dados respondia «perfeito, deixa-me ler a tabela inteira primeiro para encontrar qual é».
Multiplicado pelo número de vezes que a operação é desencadeada ao longo do dia — e em muitas aplicações empresariais, a mesma query é disparada dezenas de vezes por cada interacção do utilizador — tínhamos a explicação completa para os picos de I/O de disco que o cliente relatava.
04A causa
A causa era banal: falta de um índice. A tabela auxiliar não tinha qualquer índice sobre as três colunas que a aplicação usa em praticamente todas as queries que a tocam. Cada SELECT e cada DELETE obrigava o SQL Server a fazer um full table scan — percorrer a tabela linha a linha até encontrar as que interessavam.
Em tabelas pequenas, isto passa despercebido. À medida que a tabela cresce — e numa empresa activa, com uso diário do ERP há anos, cresce sempre — o custo de cada operação aumenta proporcionalmente ao tamanho da tabela. O que começou por ser imperceptível aos dois meses de uso torna-se frustrante aos dois anos e insuportável aos cinco.
05A solução técnica
A solução é quase embaraçosamente simples:
CREATE INDEX IX_Tabela_ColA_ColB_ColC
ON TabelaAuxiliar (ColunaA, ColunaB, ColunaC)
INCLUDE (ColunaValor)
Três colunas no índice, uma coluna adicional incluída para que as queries de SELECT não precisem sequer de voltar à tabela principal para ler o valor (uma técnica chamada covering index). Em tabelas desta dimensão, a criação do índice demora alguns segundos. O efeito é imediato: cada operação passa a tocar numa ou poucas páginas em vez de 11.692.
06A decisão comercial
Aqui começa a parte que distingue um técnico competente de um consultor sénior. Tecnicamente, eu podia criar o índice e pronto. Seria invisível para o cliente, resolveria o problema, e provavelmente ninguém dava por nada.
Comercialmente, seria um erro.
Estávamos a alterar uma base de dados de um produto de terceiros. Uma actualização futura do ERP podia recriar a tabela e eliminar o índice. Se alguma coisa corresse mal mais tarde — uma migração, um upgrade, um problema sem relação — o fornecedor podia sempre apontar o dedo: «vocês mexeram na base de dados». E, do ponto de vista de ecossistema, o fornecedor tem o direito de saber que o produto dele tem um problema de desenho que afecta potencialmente todos os clientes, não apenas este.
A decisão foi enviar-lhes um diagnóstico técnico escrito, com as queries de prova, as estatísticas, e a sugestão concreta de índice. Não em tom de denúncia nem de lição, mas como parceiro do ecossistema a reportar um problema que identificámos em produção.
A resposta foi construtiva. O fornecedor reconheceu o problema, confirmou que fazia sentido, e comprometeu-se a incluir a correcção numa actualização do produto. Enquanto isso, aplicámos o índice manualmente como medida temporária, devidamente documentada, e o cliente viu a lentidão desaparecer literalmente no dia seguinte.
07O que este caso ensina
Para quem gere infra-estrutura de PMEs portuguesas que dependem de ERPs verticais — e são muitas — há três conclusões práticas deste caso.
Lentidão não explicada não é mistério, é falta de diagnóstico.
Em 90% dos casos, o próprio SQL Server contém toda a evidência necessária para identificar o problema, mas é preciso saber o que perguntar. Queries contra sys.dm_exec_query_stats, sys.dm_db_index_usage_stats e sys.dm_os_wait_stats resolvem a grande maioria dos casos em menos de uma hora.
Não ter acesso ao código não impede o diagnóstico.
Mesmo num ERP fechado, a camada de dados é observável. Todas as aplicações deixam rasto na base de dados — o rasto é suficiente para perceber o que está mal.
A abordagem ao fornecedor é tão importante quanto o diagnóstico técnico.
Alterar silenciosamente a base de dados de um produto de terceiros pode resolver o problema no curto prazo, mas cria fragilidade contratual e quebra a relação com quem tem a responsabilidade real de corrigir o produto. O caminho profissional é documentar, reportar, e articular-se como parceiro competente.
O que mais me surpreende nestes casos é a frequência com que problemas de anos desaparecem em dias quando alguém pega num diagnóstico a sério. A experiência do utilizador num ERP não é um destino inevitável — é frequentemente o resultado de uma correcção simples que ninguém nunca fez.
