Home / Desenvolvimento / Por que seu ERP está lento e não é culpa da infraestrutura

Por que seu ERP está lento e não é culpa da infraestrutura

Por que seu ERP está lento e não é culpa da infraestrutura

A consultoria de cloud entregou o que estava no contrato. RDS com read replicas, Redis como camada de cache, auto scaling de dois pra oito servidores. Assinou aceite, recebeu o segundo boleto. O ERP continua lento.

Isso não é raro. É a regra. E o motivo é simples: a infraestrutura foi dimensionada pra um sistema que ninguém diagnosticou de verdade. Subiu máquina, banda, RAM, IOPS — e o gargalo real continuou intocado, no mesmo lugar de antes, rodando o mesmo código de 2018 que faz quarenta e sete SELECTs por transação de venda e devolve cinquenta mil linhas pra popular um dropdown de seleção de armazém.

Este post mostra por que isso acontece, onde mora o problema de verdade, e como provar — com ferramentas de chão de fábrica — que a próxima injeção de dinheiro em servidor não vai resolver.

A consultoria entregou o que o contrato pedia — e o ERP continua lento

Existe um viés cognitivo forte em TI que confunde capacidade com performance. Mais CPU, mais RAM, mais banda, mais nós no cluster — tudo isso aumenta a capacidade do sistema absorver carga. Mas performance é outra coisa: é o tempo que uma operação específica leva pra completar.

Um sistema pode ter 80% da CPU ociosa e ainda assim ser lento, se cada operação individualmente leva dez segundos pra terminar. Adicionar mais um servidor a um sistema assim só te dá um sistema lento com mais um servidor ocioso. Multiplica o custo, divide o problema por zero.

O que a consultoria de cloud normalmente entrega, quando o pedido é “tornar o ERP mais rápido”:

– Migração pra RDS com read replicas — separa leitura de escrita, ok. Mas se a sua query de relatório é um SELECT * FROM vendas JOIN clientes ON ... JOIN produtos ON ... JOIN estoque ON ... sem WHERE útil, a read replica só te dá mais um lugar onde rodar a mesma query ruim.

– Redis como cache — funciona pra dados de sessão, perfil de usuário, token de autenticação. Não funciona pra inventário que muda a cada venda, nem pra relatório financeiro que depende de saldo congelado no fim do mês.

– Auto scaling de dois pra oito servidores — funciona pra absorver pico de login no início do expediente. Não funciona pra query mal escrita que leva doze segundos independente de quantos nós estão rodando.

Nenhuma dessas três entregas é ruim. O erro não é técnico, é de diagnóstico: contratou a solução antes de entender o problema.

O falso vilão: a infraestrutura

A primeira reação quando o ERP começa a travar é olhar pros gráficos de monitoramento. CPU no banco em 30%, IOPS baixo, rede folgada. Aí vem a pergunta: “se a infra não está no limite, por que está lento?”

A resposta curta: porque infra no limite é só um dos motivos pra um sistema ser lento. Não é o único, e quase nunca é o principal.

Os motivos reais, em ordem de frequência do que eu vejo em PMEs operando ERP em produção:

  1. Queries que devolvem conjuntos absurdamente grandes — relatórios que pedem SELECT * e esperam que o front-end filtre. Inventário completo carregado pra mostrar uma lista de armazéns.
  2. Queries em loop não identificadas — ORM que faz lazy loading de relacionamento e dispara um SELECT por linha retornada. Cem linhas, cem queries. O clássico N+1.
  3. Transações longas segurando lock — uma venda que valida estoque, atualiza preço, recalcula comissão, gera log, atualiza contabilidade, e tudo isso dentro de um BEGIN ... COMMIT que segura linha na tabela produtos por oito segundos. Outra venda esperando.
  4. Índices ausentes — coluna de busca sem índice. Banco faz sequential scan, lê a tabela inteira, descarta 99,9% das linhas. Dez mil linhas hoje, cem mil em seis meses, um milhão em dois anos. Linear, sem dó.
  5. ORM que esconde o SQL real — você escreve .find({ ativo: true }) e o ORM traduz pra algo razoável. Até o dia em que o ORM decide que findAll com include profundo é uma boa ideia. Aí ninguém mais entende o que está rodando, e ninguém tem como otimizar sem descer pro SQL.
  6. Memória mal-gerenciada — objeto gigante carregado no heap e nunca liberado. Garbage collector roda, sistema trava por dois segundos, todo mundo reclama. Solução: jogar mais RAM. Problema volta em três meses.

Em todos esses casos, a infraestrutura pode estar em 5% de uso. A latência não vem da máquina. Vem do código.

Onde o gargalo realmente mora

Vou destrinchar os três casos que mais aparecem em produção, com sintoma, causa e o que fazer.

Queries em loop: o famoso N+1

Sintoma: uma listagem de pedidos está levando oito segundos. A tabela tem só cinco mil pedidos. Não faz sentido.

Causa: o ORM faz um SELECT na tabela pedidos, devolve cinco mil linhas, e pra cada linha dispara um SELECT adicional na tabela clientes pra carregar o nome do cliente. Cinco mil mais cinco mil — dez mil queries pra montar uma tela que devia levar duzentos milissegundos.


# ERRADO: dispara N+1
pedidos = Pedido.objects.filter(ativo=True)  # 1 query
for pedido in pedidos:
    print(pedido.cliente.nome)  # +1 query por iteração

# CERTO: eager loading, 1 query com JOIN
pedidos = Pedido.objects.select_related('cliente').filter(ativo=True)
for pedido in pedidos:
    print(pedido.cliente.nome)  # sem query adicional

A diferença é de oito segundos pra oitenta milissegundos. Mesma tela, mesma infraestrutura, código diferente.

Transações longas segurando lock

Sintoma: durante o horário de pico, o sistema “engasga”. Dez segundos pra salvar uma venda. Às vezes dá timeout.

Causa: a transação de venda faz tudo num único BEGIN ... COMMIT. Validação de estoque, recálculo de preço, atualização de comissão, log de auditoria, integração contábil. Cada UPDATE pega lock de linha. Outra venda tentando reservar o mesmo produto espera. E espera. E espera.


-- ERRADO: transação monstro
BEGIN;
  SELECT estoque FROM produtos WHERE id = 42 FOR UPDATE;  -- lock
  UPDATE produtos SET estoque = estoque - 1 WHERE id = 42;
  INSERT INTO vendas (...);
  UPDATE clientes SET saldo = saldo + 99.90 WHERE id = 7;  -- lock
  INSERT INTO comissoes (...);
  INSERT INTO log_auditoria (...);
  -- chamada HTTP síncrona pra API contábil
COMMIT;

-- MELHOR: transações curtas, locks mínimos
BEGIN;
  UPDATE produtos
    SET estoque = estoque - 1
    WHERE id = 42 AND estoque > 0;  -- lock instantâneo
  INSERT INTO vendas (...);
COMMIT;  -- commit rápido

-- coisas que podem falhar e reverter ficam pra depois, em fila assíncrona
-- integração contábil, auditoria, e-mail — tudo depois do commit

Índices ausentes

Sintoma: relatório que levava dois segundos em 2020 leva vinte segundos em 2026. Mesma query, mais dados. O banco está “devagar”.

Causa: a coluna de filtro não tem índice. Sem índice, o banco faz sequential scan — lê a tabela inteira, descarta 99,9% das linhas, devolve as relevantes. Em 2020 eram dez mil linhas, scaneava em dois segundos. Em 2026 são quinhentas mil, scaneia em quarenta segundos.


-- descobrir a query que está devagar
SELECT * FROM pg_stat_statements
ORDER BY mean_exec_time DESC
LIMIT 10;

-- ver o plano de execução
EXPLAIN ANALYZE
SELECT * FROM pedidos
WHERE cliente_id = 1234
  AND data_emissao >= '2026-01-01';

Se o plano de execução diz Seq Scan on pedidos, falta índice. Solução:


CREATE INDEX CONCURRENTLY idx_pedidos_cliente_data
  ON pedidos (cliente_id, data_emissao);

Índice composto, criado sem bloquear a tabela (CONCURRENTLY). Em sistemas com tabela de cem milhões de linhas, a diferença entre ter e não ter esse índice é a diferença entre relatório em duzentos milissegundos e relatório em trinta segundos.

Ferramentas de diagnóstico: EXPLAIN, APM, slow log

Antes de subir servidor novo, medir. Não chutar, não acreditar no dev que diz “está tudo bem no código”, não contratar consultoria de cloud pra te vender a solução que você não diagnosticou. Medir.

Três ferramentas, em ordem de retorno rápido:

1. Slow query log do próprio banco

Postgres, MySQL, SQL Server, Oracle — todos têm. Liga, espera um dia, lê o top dez. Aquelas dez queries são 80% do seu problema de performance. Reescrevê-las ou indexá-las é o trabalho da semana que vem.


-- Postgres: habilita log de queries acima de 500ms
ALTER SYSTEM SET log_min_duration_statement = 500;
SELECT pg_reload_conf();

-- Postgres: queries mais lentas
SELECT round(mean_exec_time::numeric, 2) AS ms_medio,
       calls,
       query
FROM pg_stat_statements
ORDER BY mean_exec_time DESC
LIMIT 10;

2. EXPLAIN ANALYZE na query suspeita

Quando você acha que sabe qual é a query lenta, roda EXPLAIN ANALYZE e lê o plano. Procura por:

Seq Scan em tabela grande — falta índice.

Nested Loop com custo alto — duas tabelas sendo cruzadas linha a linha.

Sort com external merge — sort estourando memória, indo pra disco.

Rows estimated vs Rows actual com diferença de 10x ou mais — estatísticas desatualizadas, banco está escolhendo plano ruim por estimar mal.


EXPLAIN (ANALYZE, BUFFERS, FORMAT TEXT)
SELECT p.id, p.data_emissao, c.nome
FROM pedidos p
JOIN clientes c ON c.id = p.cliente_id
WHERE p.data_emissao >= '2026-01-01'
  AND c.ativo = true
ORDER BY p.data_emissao DESC
LIMIT 50;

3. APM — Application Performance Monitoring

New Relic, Datadog, Elastic APM, OpenTelemetry com Jaeger — qualquer um deles. Eles mostram o trace de uma requisição do começo ao fim. Você vê: 8.7 segundos de response time, dos quais 8.4 segundos estão em query no banco. Sem precisar chutar, sem precisar brigar com o time de dev pra saber se a query é lenta ou se é a rede.

APM é a diferença entre discutir performance em reunião e discutir performance com print na mão.

Exemplo real: a migração que não foi de servidor

Cliente de ERP em PME de varejo, duzentas lojas, sistema legado em PHP/MySQL próprio. Tráfego de pico em horário de fechamento de caixa. Banco travava todo dia das 18h às 19h. A diretoria decidiu: contratar consultoria de cloud, migrar pra AWS, RDS com read replicas, Redis, escalonar pra oito servidores.

Receita clássica. Eu fui chamado pra revisar antes de gastar o dinheiro.

Primeiro passo: EXPLAIN ANALYZE nas dez queries mais lentas do slow log. Sete delas faziam SELECT * em tabelas com mais de vinte milhões de linhas, sem WHERE útil, pra popular dropdowns no front-end. Três delas faziam join de cinco tabelas com LIKE '%termo%'.

A consultoria propunha gastar oitocentos mil reais em migração. Reescrevi as dez queries, criei quatro índices compostos, tirei o LIKE com prefixo de wildcard, troquei dois SELECT * por SELECT campo1, campo2. Ajustes de ORM. Cache de dropdowns que mudam uma vez por dia.

Resultado: latência do horário de pico caiu de doze segundos pra oitocentos milissegundos. Servidor único, mesma máquina de antes. Sem migração, sem cloud, sem Redis. A consultoria foi dispensada.

Custo: três semanas de um desenvolvedor sênior. Trezentos mil reais economizados.

O que ninguém te conta sobre “escalar pra resolver”

“Escalar pra resolver” funciona quando o problema é: muitas requisições simultâneas, sistema absorvendo carga de forma razoável mas sem capacidade sobrando. Aí sim, mais servidor resolve.

Não funciona quando o problema é: cada requisição é lenta por si só, e adicionar mais servidor só dá mais requisições lentas em paralelo, mantendo a percepção de sistema travado.

A diferença é throughput vs latency. Throughput é quantas requisições por segundo o sistema absorve. Latency é quanto tempo cada uma leva. São coisas diferentes. Mais servidor sobe throughput. Não mexe em latency de uma query ruim.

Outro erro comum: contratar consultoria de cloud antes de contratar diagnóstico de performance. O primeiro passo nunca é migrar. É medir. É ler o slow log. É rodar EXPLAIN. É instalar um APM e ver o trace real. Só depois de mapear o problema você decide se a solução é de código, de modelagem, de cache, ou — em último caso — de mais hardware.

Fechamento: o problema tem dono, e não é o cloud

Existe um desconforto saudável em admitir que o sistema é lento por causa do próprio código. Porque a solução não está em compra, está em mudança. Mudança de processo, mudança de prioridade, mudança de cultura técnica. Ninguém fatura uma nota fiscal vendendo isso.

Mas é a realidade. Você pode ter todos os servidores do mundo. Se seus desenvolvedores não entendem como dados fluem pelo sistema, vai estar lento mesmo.

O ponto de partida é o mesmo que serve pra qualquer diagnóstico de produção: medir antes de mudar. Slow query log. EXPLAIN ANALYZE. APM. Dez queries no top, dez correções. Oitenta por cento do problema resolvido em uma sprint.

E quando alguém vier te vender a próxima consultoria de cloud, a pergunta certa é uma só: “o que vocês vão medir antes de mudar alguma coisa?”. Se a resposta for “vamos ler o slow log e rodar EXPLAIN”, talvez prestem. Se a resposta for “vamos migrar pra AWS e ver no que dá”, dispensa antes da segunda reunião.


Se esse post bateu com algo que você viu em produção, vale a leitura do post anterior sobre clean code que destruiu a produtividade do time. Os dois problemas são primos: dogma de livro aceito sem medir, ou dogma de fornecedor aceito sem medir. Mesma família, sintomas diferentes.

Marcado:

Deixe um Comentário

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *