Como Otimizar Desempenho Laravel com Milhões de Registros no Pcontrol?

otimizar-desempenho-laravel-com-milhoes-de-registros

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:

 
    1. Lia toda a tabela leads (mais de 150 milhões de registros)
    2. Filtrava account_id (eliminando aproximadamente 99%)
    3. Depois source_id, status e etc
    4. 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:

  1. Pré-filtragem lógica em PHP
  2. Passamos a usar uma método auxiliar chamado getLeadPreCheck();
  3. 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:

  1. A consulta dos registros da página atual (com LIMIT e OFFSET)
  2. 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?

  1. Pre-check (getLeadPrecheck())
  2. Troca de Model:select() por DB::select()
  3. Troca de Eloquent::paginate() por uma classe de Paginação usando PHP puro
  4. Count(*) da páginação apenas na página 1
  5. 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.

Gostou do nosso conteúdo? Compartihe!

Facebook
LinkedIn
WhatsApp