Laravel Microserviços e Crons: Como Implementamos e Escalamos no Pcontrol?

Otimizar a Performance de Microsserviços e Cron Jobs

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:

  1. Paginação com grandes volumes de dados
  2. Evitando o problema N+1
  3. Quando usar Cron Jobs ou Filas?
  4. Insert/Update em Massa
  5. Estratégias de microsserviços e escalabilidade
  6. Evitando uso desnecessário do count()
  7. Uso estratégico de cache
  8. Uso de tabelas de totalizadores para alta performance
  9. Estratégias de banco de dados
  10. 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() por cursorPaginate() 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 de count());
  • Revise índices e analise planos com EXPLAIN;
  • Use chunk() ou cursor() 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 DateTime e 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.

Gostou do nosso conteúdo? Compartihe!

Facebook
LinkedIn
WhatsApp