Categoria: Técnicos

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

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

    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.

  • Laravel API Boas Práticas: Como Criar APIs Seguras e Performáticas?

    Laravel API Boas Práticas: Como Criar APIs Seguras e Performáticas?

    Por que seguir boas praticas na hora de construir uma API com Laravel e PHP? Quando você cria uma API exclusivamente backend, exposta na web, qualquer erro de configuração pode gerar brechas de segurança ou lentidão. Um pequeno detalhe pode abrir portas para ataques ou travar processos de integração.

    Este artigo traz um guia prático e atualizado para quem quer usar o Laravel e PHP como API — com foco em segurança e performance.

    1. Segurança: configuração, headers e limites

    1.1 Variáveis de ambiente

    Configure corretamente seu arquivo .env em produção:

    APP_ENV=production
    APP_DEBUG=false
    LOG_LEVEL=error
    

    Motivo: Evita vazamento de informações sensíveis e garante que erros não sejam exibidos em tela.

    1.2 SecurityHeadersMiddleware

    Crie um middleware para adicionar headers de segurança a todas as respostas HTTP:

    $response->headers->set('Content-Security-Policy', $csp);
    $response->headers->set('Strict-Transport-Security', 'max-age=31536000; includeSubDomains; preload');
    $response->headers->set('X-Frame-Options', 'DENY');
    $response->headers->set('X-Content-Type-Options', 'nosniff');
    $response->headers->set('Referrer-Policy', 'no-referrer');
    $response->headers->set('X-Powered-By', '');
    $response->headers->set('X-XSS-Protection', '1; mode=block');
    

    1.3 Cookies e Same Site

    Ao lidar com cookies, defina configurações seguras:

    'secure' => true,
    'http_only' => true,
    'same_site' => 'strict',
    

    Secure: True

    • Diz ao navegador que o cookie só pode ser transmitido por conexões HTTPS
    • Isso impede que o cookie (por exemplo, de sessão ou autenticação) seja interceptado em uma conexão HTTP comum

    HTTP Only: True

    • Impede que o cookie seja acessado por JavaScript (via document.cookie)
    • Ele só pode ser enviado automaticamente pelo navegador em requisições HTTP

    Same Site: Strict

    • Controla quando o cookie é enviado em requisições de outros sites (cross-site)
    • 'strict' é o modo mais seguro: o cookie só é enviado se o usuário estiver navegando diretamente no mesmo domínio.

    1.4 Rate Limit e limite de Payload

    • Rate limit: limite de 30 requisições por minuto por API — se ultrapassado, retorne 429 Too Many Requests.
    • Payload máximo: 1KB (+/-1000 linhas de JSON). Ultrapassou? Retorne 413 Content Too Large.

    Essas medidas reduzem abusos e mantêm o servidor saudável.

    2. Autenticação, validação e sanitização

    2.1 Autenticação com Bearer Token

    Use tokens do tipo Bearer em cabeçalhos HTTP:

    Authorization: Bearer token eyJhbGciOi...
    

    Valide o token via middleware e retorne 401 Unauthorized se for inválido, ou 403 Forbidden se o usuário não tiver permissão.

    2.2 Middleware de Sanitização de Payload

    Evite injeções e ataques XSS limpando a entrada de dados:

    public function handle($request, Closure $next) {
      $input = $request->all();
      array_walk_recursive($input, function (&$v) {
        $v = strip_tags($v);
        $v = preg_replace('/javascript:\s*/i', '', $v);
      });
      $request->merge($input);
      return $next($request);
    }
    

    2.3 Requests Validados

    Use FormRequest para validar e remover campos não permitidos:

    • Valide tipos, tamanhos de string e formatos recebidos
    • Converta dados (ex: datas para timestamps).
    • Retorne 422 Unprocessable Entity com erros em JSON.

    3. Performance: filas, consultas e paginação

    3.1 Use filas para operações pesadas

    Evite processar cadastros e updates em tempo real. Em vez disso:

    1. Receba o payload.
    2. Valide e retorne 202 Accepted.
    3. Envie a tarefa para uma fila (Redis, SQS, Beanstalkd) e mande um JOB ou uma CRON processar depois.

    Isso melhora o tempo de resposta e evita bloqueios em endpoints críticos.

    Eu defendo que são poucas as situações que um cadastro tem que ser feito em tempo real, então TUDO que você pude jogar em fila para sempre processa daqui 1 minuto que seja, faça.

    3.2 Paginação em Consultas

    Nunca retorne grandes coleções sem paginação:

    {
      "data": [...],
      "pagination": { "page": 1, "per_page": 20, "total": 50 }
    }
    

    3.3 Prefira DB Query Builder

    Para endpoints com alta carga, use DB::table ao invés do Eloquent:

    $rows = DB::table('users')
      ->select('id','name','email','deleted_at')
      ->where('active', 1)
      ->paginate(20);
    

    Se você usa SoftDeleles, não esqueça adicionar ->whereNull(‘deleted_at’)

    Isso reduz overhead e garante performance em escala.

    Eu escrevi um artigo sobre Como Otimizar Desempenho Laravel com Milhões de Registros no Pcontrol? onde há um tópico especifico como eu resolvi alguns problemas de paginação com milhões de registros.

    As estratégias sugeridas (usando PHP puro), podem e devem ser usadas também nestes casos de API.

    4. Design de API e boas práticas de resposta

    4.1 Sempre retornar JSON com status correto

    // Sucesso
    HTTP/1.1 200 OK
    { "success": true, "data": {...} }
    
    // Processamento assíncrono
    HTTP/1.1 202 Accepted
    { "success": true, "message": "Registro será processado em breve." }
    
    // Erros de validação
    HTTP/1.1 422 Unprocessable Entity
    { "success": false, "errors": { "email": ["Formato inválido"] } }
    

    4.2 Logs e monitoramento

    Use ferramentas como Sentry, Datadog ou Laravel Telescope para:

    • Monitorar erros 4xx e 5xx;
    • Acompanhar tempo médio de resposta;
    • Auditar filas e jobs pendentes.

    4.3 Documentação e versionamento

    Use OpenAPI/Swagger para documentar endpoints e mantenha versionamento (/v1/, /v2/) para evitar quebra de contratos entre clientes e servidores.

    Para saber mais sobre: Swagger

    Conclusão

    Seguir as laravel API boas praticas garante que sua API seja segura, rápida e previsível. Aplique as recomendações de segurança, implemente autenticação por Bearer Token, sanitize entradas e use filas para tarefas pesadas.

    Como criar um API com Laravel em menos de 15 minutos?

    Essas práticas fortalecem a experiência dos desenvolvedores que consomem sua API e reduzem riscos de falhas em produção.

  • Tabelas Totalizadoras: Quando Usar para Aumentar o Desempenho do Domínio?

    Tabelas Totalizadoras: Quando Usar para Aumentar o Desempenho do Domínio?

    Em aplicações que crescem em complexidade, um dos maiores gargalos de performance está em consultas repetitivas: COUNT, SUM e JOINs pesados em tabelas volumosas.

    Esse problema é ainda mais crítico quando lidamos com relatórios, dashboards e telas de overview, onde o mesmo cálculo precisa ser exibido diversas vezes.

    No Pcontrol, esse cenário se aplica ao domínio de empresas e seus contatos. Para enfrentar esse desafio, adotamos uma estratégia simples, mas extremamente eficiente: tabelas totalizadoras ou totalizadores.

    Todo programador precisa saber de uma coisa: Performance não é mágica, é design.

    O que são Totalizadores ou Tabelas Totalizadoras?

    Uma tabela totalizadora é uma estrutura de dados persistida que mantém apenas os totais agregados de um determinado domínio.

    Em vez de calcular a cada consulta, o valor já está disponível, pronto para leitura.

    Exemplo:

    total_company_contacts (
      company_id BIGINT PRIMARY KEY,
      total_emails INT NOT NULL,
      total_phones INT NOT NULL,
      total_founders INT NOT NULL
    )
    

    Dessa forma, em vez de executar múltiplos SELECT COUNT(*) sobre as tabelas de contatos, basta acessar o read model já consolidado.

    Conexão com arquitetura e DDD

    Essa prática conversa diretamente com conceitos bem estabelecidos:

    • Domain Driven Design (DDD):
      • Os totalizadores representam uma visão específica do domínio (empresa → seus contatos).
      • Eles podem ser vistos como um Aggregate Root simplificado para consultas rápidas.
    • CQRS (Command Query Responsibility Segregation):
      • No momento de Command (criação/alteração de um contato), o totalizador é atualizado.
      • No momento de Query, acessamos diretamente o totalizador, sem sobrecarregar a tabela de origem.
    • Read Models (Martin Fowler, Greg Young):
      • Tabelas totalizadoras são essencialmente read models otimizados, desenhados para consulta e não para escrita.
      • Isso segue o princípio de que não é necessário ler do mesmo modelo que se escreve.

    Autores como Eric Evans (DDD) e Martin Fowler (Patterns of Enterprise Application Architecture) já abordam a importância de separar modelos de escrita e leitura, e totalizadores entram exatamente nesse ponto.

    No Pcontrol: ganhos reais em Produção

    No caso do Pcontrol, que trabalhando com um volume imenso de dados (temos um artigo sobre Como Otimizar Desempenho Laravel com Milhões de Registros no Pcontrol), nós implementamos diversas tabelas totalizadoras, mas uma destas muitas que posso usar como exemplo foi, a tabela total_company_contacts.

    A tabela contém as colunas: company_id, total_emails, total_phones, total_founders

    • Cada vez que um contato (e-mail, phone e/ou founder) é adicionado ou removido, o total de registros de cada coluna respectiva é atualizado.
    • Consultas em dashboards e relatórios acessam apenas essa coluna totalizadora.

    Benefícios práticos:

    • Redução drástica de N+1 queries em relatórios
    • Consultas de milissegundos mesmo em bases volumosas
    • Maior previsibilidade de performance em telas críticas

    Boas práticas

    1. Atualização transacional: o totalizador deve ser atualizado na mesma transação da escrita original.
    2. Validação periódica: crie jobs de verificação para detectar divergências entre o totalizador e a tabela de origem.
    3. Evite overdesign: totalize apenas métricas realmente usadas.
    4. Monitore impacto: totalizadores resolvem leitura, mas adicionam custo de escrita — avalie o trade-off.

    Como implementar tabelas totalizadoras?

    Existem diferentes abordagens para manter tabelas totalizadoras sempre consistentes.

    A escolha depende da arquitetura da aplicação e do nível de consistência que você precisa (forte vs eventual).

    1. Atualização via eventos de domínio (Domain Events)

    • Após uma ação no domínio (ex.: criação de contato, exclusão de sócio), um evento é disparado.
    • Um handler ou listener desse evento atualiza a tabela totalizadora.

    Prós: se integra bem com DDD e CQRS, permite evoluir para filas e processamento assíncrono.
    Contras: exige infraestrutura de eventos bem desenhada.

    No Pcontrol é assim que fazemos após determinada ação, um evento é disparado para atualizar a tabela totalizadora.

    2. Atualização direta em triggers do banco de dados

    Usar triggers para manter o totalizador em sincronia sempre que há um INSERT, UPDATE ou DELETE na tabela original.

    Prós: garante consistência no nível do banco, sem depender da aplicação.
    Contras: difícil manter em sistemas grandes, acopla lógica de negócio ao banco.

    3. Atualização na camada de aplicação (transação)

    Dentro da mesma transação em que insere ou remove registros, também atualiza o totalizador.

    Prós: simples de implementar, consistente.
    Contras: a lógica fica repetida em vários pontos se não for centralizada.

    4. Processamento assíncrono (fila de jobs / event sourcing)

    • Em vez de atualizar imediatamente, os eventos de criação/remoção vão para uma fila.
    • Um job de background processa e atualiza os totalizadores.

    Prós: reduz carga em operações críticas, ideal para grandes volumes.
    Contras: a consistência é eventual, não imediata.

    5. Reprocessamento periódico (batch)

    • Em alguns cenários, é aceitável reconstruir os totalizadores em batch (diário, horário).
    • Usado quando consistência em tempo real não é necessária.

    Prós: simples, evita erros de sincronização.
    Contras: relatórios podem ficar desatualizados entre as execuções.

    Conclusão

    Tabelas totalizadoras podem parecer apenas uma otimização de banco de dados, mas na prática são uma implementação aplicada de princípios de arquitetura.

    Ao alinhar conceitos de DDD, CQRS e read models, conseguimos transformar relatórios custosos em consultas instantâneas.

    No Pcontrol, isso significou não só ganho de performance, mas também uma base sólida para escalar o sistema de forma saudável.

    Greg Young — A Decade of DDD, CQRS, Event Sourcing

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

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

    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.