Como nós otimizamos uma consulta de 16 segundos para 0.2 segundos no Laravel — com mais de 150 milhões de registros.
No Pcontrol, nosso CRM de vendas especializado em geração e gestão de leads, lidamos com um volume imenso de dados. São mais de 150 milhões de registros em uma única tabela. À medida que nosso sistema evolui e os clientes crescem, percebemos um desafio importante: consultas simples estavam demorando muito.
O Problema:
Em uma das telas do Pcontrol, uma conta com apenas 400 registros levava 26 segundos para carregar. Curiosamente, outra conta, com mais de 500 registros, carregava em apenas 2 segundos — usando o mesmo código.
Isso acendeu nosso alerta: não era o volume de dados retornado que impactava, e sim como o banco acessava esses dados.
A Investigação:
Iniciamos a análise com alguns recursos como o do MySQL. O primeiro foi o SHOW FULL PROCESSLIST que nos mostrou as queries mais pesadas, as que lavaram mais tempo para ser finalizadas.
Então analisamos estas queries com o EXPLAIN do próprio MySQL e percebemos que, mesmo com índices individuais nas colunas usadas em WHERE, ORDER BY e JOIN, o MySQL ainda fazia leituras completas da tabela.
Como usando Laravel, adicionamos o recurso DB::listen() nativo do framework para ouvir todos os eventos de consulta SQL executados. Esse recurso é extremamente útil para debug ou para logar queries e seus tempos de execução.
Diagnóstico antes dos ajustes:
O que o MySQL fazia sem índices compostos:
- Lia toda a tabela leads (mais de 150 milhões de registros)
- Filtrava account_id (eliminando aproximadamente 99%)
- Depois source_id, status e etc
- Só então fazia JOINs e ordenava pela coluna created_at
Resultado: consumo absurdo de I/O e CPU, mesmo para contas pequenas.
Os principais ajustes que implementamos no Pcontrol:
Pre-check:
- Pré-filtragem lógica em PHP
- Passamos a usar uma método auxiliar chamado getLeadPreCheck();
- Ela reduz a complexidade de execução da query principal com um controle mais fino do escopo.
O uso do pré-check com consulta limitada (limit 1) é uma prática inteligente de performance e tem nome sim dentro dos padrões de projeto e otimizações:
Podemos associá-la a Short-Circuit Evaluation, Guard Clauses e até a uma forma prática de Query Optimization via Early Exit.
Vamos aprofundar esse conceito dentro do seu contexto com o Pcontrol, focado em performance e design de código.
Essa função consulta rapidamente o banco de dados para verificar se existe ao menos um registro que atenda aos filtros solicitados. Se não houver nenhum, a aplicação já retorna um resultado vazio e evita que toda a query complexa, com joins e paginação, seja executada.
Por que isso melhora a performance? Imagine uma query com:
- Vários filtros (account_id, source_id, status, created_at)
- JOINs com outras 4 ou 5 tabelas
- Ordenação (ORDER BY) e paginação (LIMIT, OFFSET)
Agora imagine que o filtro inserido pelo usuário elimina 100% dos registros (ex: um source_id inexistente para aquela account_id). Rodar toda essa query seria desperdício de recursos.
Com o LIMIT 1, o banco só precisa localizar um único registro válido.
Se encontrar: executa a query pesada. Se não encontrar, retorna imediatamente. Isso é uma forma de “cortar caminho” computacional, economizando CPU e I/O de banco de dados.
Qual o nome desse padrão?
Esse tipo de prática pode ser classificado ou relacionado a:
1. Guard Clause (Cláusula de Guarda)
Um conceito de design de código, onde você interrompe o fluxo o mais cedo possível se uma condição já invalida o processo. No nosso caso: “Não tem lead? Então nem segue.”
2. Early Exit / Short-Circuit Evaluation
Técnica de otimização lógica onde você avalia uma condição mínima necessária antes de executar algo mais custoso. Aplicada ao banco:
SELECT 1 FROM leads WHERE filtros LIMIT 1;
Resultado: se não tiver nem esse 1, pare tudo.
3. Query Optimization Strategy (Early Filtering)
No mundo dos bancos de dados, isso é uma estratégia comum de otimização, especialmente quando combinada com índices:
Minimiza o escopo, evita joins e sorting e reduz carga no banco.
4. Fail Fast Principle
Filosofia de desenvolvimento onde um sistema deve falhar o mais rápido possível se não puder continuar. Usar o pré-check evita gasto de tempo e processamento em algo que já se sabe que não terá retorno útil.
O principio Fast fail é um dos meus favoritos, se você ainda não usa, comece a usar hoje.
No Pcontrol, isso é ainda mais relevante pois:
Trabalhamos com milhões de registros e temos múltiplos filtros customizáveis, o lead pode ter diversas associações (campanhas, atendimentos, atividades e etc).
O método getLeadPreCheck() implementa um mecanismo de defesa que evita custo desnecessário, especialmente em contas com muitos dados.
Evita dezenas ou centenas de milissegundos (ou até segundos) desperdiçados com:
- Join de tabelas
- Cálculo de paginação
- Ordenações complexas
Troca de Eloquent Paginator por DB::table com limit/offset
O Eloquent é excelente, mas para grandes volumes, sua abstração pode custar caro.
O que o Eloquent retorna não é um array puro, e sim uma instância de lluminate\Database\Eloquent\Collection que contém vários objetos com diversos métodos, atributos e etc. O objetivo do Eloquent é facilitar a vida do programador, mas a performance pode ser bem prejudicada com centenas de milhões de registros.
Trocando para para DB::table, passamos a ter controle total da query, eliminando a criação de objetivos desnecessários, usando apenas os campos e relações necessárias para melhorar nossa performance.
O Paginator do Laravel faz uso condicional do count(*). Ele o faz o count(*) de todos os registros em todas as páginas (em cada request) da paginação, isso é extremamente custoso para o banco de dados.
Solução: só fazemos o count quando estamos na página 1.
No Laravel, ao usar o Eloquent::paginate(), a mágica por trás da paginação inclui 2 queries principais:
- A consulta dos registros da página atual (com LIMIT e OFFSET)
- Uma segunda query automática que executa: “SELECT COUNT(*) as aggregate FROM tabela WHERE …”
Essa segunda query é feita em todas as requisições de página, inclusive quando você já está, por exemplo, na página 5 ou 10.
Impacto deste mudança no Pcontrol:
No nosso sistema de CRM de Vendas e geração de leads Pcontrol, temos cenários onde a tabela leads possui mais de 150 milhões de registros e ela não pára de crescer.
Mesmo que o filtro retorne apenas 500 registros, o Eloquent::paginate() faz o COUNT(*) em cima de toda a base filtrada, o que significa:
- Executa uma query pesada
- Pode forçar leitura de muitos índices
- Pode gerar uso intensivo de disco e CPU
Tudo isso para exibir apenas “Página 5 de X”.
A solução adotada: Remoção do Paginate do Laravel, criação de uma classe que gera a paginação usando PHP puro.
Só executamos o count(*) se for a primeira página (página 1), guardamos o total em um sessão para que o valor seja exibido na página HTML para o usuário.
Ao aplicar uma lógica condicional para só executar count(*) na primeira página da paginação, conseguimos:
- Reduzir o tempo de resposta de forma drástica
- Aliviar a carga no banco de dados
- Manter a experiência do usuário fluída
Mais uma pequena mudança com grande impacto no desempenho do Pcontrol.
Criação de índices compostos nas colunas mais utilizadas:
- account_id
- source_id
- status
- created_at
Agora o MySQL faz:
Busca direta pelo account_id e filtra rapidamente por source_id, status e created_at. Já entrega um resultado enxuto para aplicar joins e ordenações.
O Resultado Final?
Após essas mudanças, uma tela que levava 16 segundos para ser montada passou a carregar em 0.2 segundos — com dados reais em produção.
O que aprendemos com isso no Pcontrol:
1 – Laravel é incrível, mas o Eloquent não é mágico. Quando trabalhamos com grandes volumes de dados, às vezes precisamos ir direto ao SQL.
2 – Paginação exige estratégia — evitar count(*) sempre que possível é essencial.
3 – Índices compostos mudam tudo: índices individuais não garantem grande performance em determinados contextos.
4 – EXPLAIN e SHOW FULL PROCESSLIST são seus melhores amigos: sempre analise o plano de execução das queries.
5 – Usar recursos nativos do banco de dados, do framework e da linguagem de programação para identificação, monitoramento e aprimoramento é uma estratégia simples e extremamente eficiente.
6 – O mais importante: pequenas decisões técnicas fazem uma enorme diferença no dia a dia do usuário final.
Como Otimizar Desempenho Laravel com Milhões de Registros?
- Pre-check (getLeadPrecheck())
- Troca de Model:select() por DB::select()
- Troca de Eloquent::paginate() por uma classe de Paginação usando PHP puro
- Count(*) da páginação apenas na página 1
- Criação de indices compostos na tabela do banco de dados
Sobre o Pcontrol:
O Pcontrol é mais do que um CRM de vendas. Ele é um sistema completo de geração e qualificação de leads B2b , pensado para empresas que lidam com volume e performance. Se você quer escalar suas vendas sem abrir mão da velocidade e eficiência, venha conhecer o Pcontrol.
Se você também lida com centenas de milhões de registros no seu sistema Laravel e está enfrentando lentidão, espero que este relato te ajude.





