No Pcontrol, lidamos diariamente com milhões de registros em diferentes microserviços, com isso aprender como otimizar o desempenho do Laravel se tornou uma obrigação e aprendizado diário.
Ao longo do tempo, aprendemos diversas estratégias de otimização no Laravel e no PHP que nos permitiram reduzir servidores e aumentar significativamente a velocidade de processamento.
Aqui estão algumas das regras e boas práticas que adotamos:
- Paginação com grandes volumes de dados
- Evitando o problema N+1
- Quando usar Cron Jobs ou Filas?
- Insert/Update em Massa
- Estratégias de microsserviços e escalabilidade
- Evitando uso desnecessário do
count() - Uso estratégico de cache
- Uso de tabelas de totalizadores para alta performance
- Estratégias de banco de dados
- Evite o uso da classe Carbon: prefira funções nativas de data do PHP
1 – Paginação com grandes volumes de dados
Quando precisamos manipular milhares ou milhões de registros, carregar tudo de uma vez é inviável. Algumas abordagens (principalmente no contexto de microsserviços/crons) que adotamos incluem:
- Não utilizar Eloquent em processos pesados;
- Sempre utilizar o método cursorPaginate para grandes volumes;
- Processamento em blocos para evitar sobrecarga de memória;
- Paginação tradicional funciona bem para listas pequenas; para grandes volumes, implementamos paginação personalizada que evita contagens pesadas no banco.
2 – Evitando o problema N+1
Ao trabalhar com relacionamentos no Laravel, é comum que cada registro adicional gere uma nova query, o que impacta severamente a performance.
Solução: carregar relacionamentos necessários de forma antecipada (eager loading), garantindo que todas as informações sejam recuperadas em poucas queries. Na prática, prefira usar joins ou with() quando fizer sentido; em jobs, prefira consultas diretas com DB::table() quando o Eloquent acrescenta overhead.
3 – Quando usar Cron Jobs ou Filas?
Para tarefas em lote ou cron jobs, seguimos algumas regras:
- Evitamos Eloquent quando o desempenho é crítico, utilizando consultas diretas ao banco;
- Usamos transações para garantir consistência de dados (ex.:
DB::transaction()); - Para tarefas que não precisam ser em tempo real, processamos um número controlado de registros por minuto;
- Para tarefas em tempo real, adotamos filas e jobs para escalabilidade e paralelismo.
4 – Insert/Update em Massa
Quando é necessário inserir ou atualizar grandes quantidades de registros:
- Evitar operações individuais que causam múltiplas queries;
- Fazer operações em massa com
insert(),upsert()ou bulk operations para reduzir overhead.
// Exemplo de insert em massa
DB::table('logs')->insert([
['event' => 'job_start', 'created_at' => now()],
['event' => 'job_end', 'created_at' => now()],
]);
// Exemplo de upsert
DB::table('users')->upsert($data, ['email']);
5 – Estratégias de microsserviços e escalabilidade
Ao trabalhar com milhões de dados em microsserviços, adotamos:
- Cada microsserviço é responsável por um domínio específico de dados;
- Otimizamos queries com índices estratégicos e joins eficientes;
- Limitamos a quantidade de registros processados por minuto para evitar sobrecarga no banco.
6 – Evitando uso desnecessário do count()
O uso de count() pode forçar o banco a percorrer todos os registros que atendem ao filtro — operação custosa em tabelas grandes.
Abordagens corretas:
DB::table()->first(): retorna apenas o primeiro registro encontrado; ideal quando precisamos saber se existe algo;DB::table()->exists(): retorna booleano e para a execução ao encontrar o primeiro registro.
Evite Eloquent para checagens de existência quando a performance é prioridade; o Query Builder com DB::table() é mais enxuto e rápido.
7 – Uso estratégico de cache
O cache reduz custos de processamento e acelera respostas quando informações são frequentemente consultadas e raramente alteradas.
Exemplo prático do Pcontrol: previsões e contagens baseadas em dados da Receita Federal (atualizados mensalmente) são ideais para cache. Estratégia típica:
- Gerar cache por filtro aplicado;
- Definir expiração adequada (ex.: 1 mês quando a fonte oficial atualiza mensalmente);
- Invalidar caches quando a base oficial é atualizada.
Benefícios: performance imediata, escalabilidade e custo-benefício.
8 – Uso de tabelas de totalizadores para alta performance
Relatórios que exigem totais costumam ser gargalos. Em vez de executar contagens em tabelas grandes, mantemos tabelas de totalizadores pré-calculadas.
Exemplo: tabela company_total_contacts com colunas como emails, phones e socials. Em vez de count() em company_emails, lemos um único registro na tabela de totalizadores.
Ganho: leitura rápida, escalabilidade e estabilidade em dashboards e relatórios.
9 – Estratégias de banco de dados
Em microsserviços e cron jobs, o banco de dados costuma ser o gargalo principal. Cada query mal otimizada pode custar segundos ou travar filas.
Práticas recomendadas:
- Evite Eloquent em processos pesados — prefira
DB::table()para reduzir overhead de objetos; - Substitua
paginate()porcursorPaginate()para processar grandes volumes sem esgotar memória; - Use transações (
DB::transaction()) para agrupar operações e reduzir round-trips; - Evite múltiplas queries (ex.:
exists()/first()em vez decount()); - Revise índices e analise planos com
EXPLAIN; - Use
chunk()oucursor()para processar em blocos; - Faça selects explícitos em vez de
SELECT *; - Cacheie resultados pesados quando aplicável;
- Prefira inserts/updates em lote em vez de operações item-a-item.
10 – Evite o uso da classe Carbon: prefira funções nativas de data do PHP
O Laravel usa Carbon por conveniência, mas em microsserviços, filas e cron jobs o overhead de objetos pode ser significativo. Em laços e operações em massa, Carbon pode ser várias vezes mais lento que funções nativas.
Por que Carbon é mais lento
- Instanciar Carbon cria um objeto
DateTimee aplica timezone/locale; - Carbon adiciona métodos e manipulações adicionais, aumentando alocação de memória;
- Em loops, esse overhead se acumula rapidamente.
Comparação prática
// Carbon
$start = microtime(true);
for ($i = 0; $i < 100000; $i++) {
$now = Carbon\Carbon::now();
$yesterday = $now->subDay()->format('Y-m-d');
}
$carbonTime = microtime(true) - $start;
// PHP nativo
$start = microtime(true);
for ($i = 0; $i < 100000; $i++) {
$now = date('Y-m-d H:i:s');
$yesterday = date('Y-m-d', strtotime('-1 day'));
}
$nativeTime = microtime(true) - $start;
Em muitos ambientes o PHP nativo é anatomicamente mais rápido (ordem de grandeza menor) do que usar Carbon em loops de alto volume. Use funções nativas (date(), time(), strtotime()) em jobs e microsserviços sem interface.
Quando Carbon ainda é útil?
Use Carbon em contextos onde legibilidade, timezone por usuário ou formatações humanizadas são necessárias (ex.: views, relatórios). Em processamento em massa ou rotinas assistidas por cron, prefira PHP nativo.
Conclusão
Com essas práticas, reduzimos drasticamente a necessidade de servidores, melhoramos a performance de processamento, garantimos consistência de dados e escalabilidade para grandes volumes. Evitamos problemas comuns do Laravel como N+1 e sobrecarga de memória, tornando o sistema mais ágil e confiável mesmo lidando com milhões de registros em tempo real.





