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

pcontrol - tabelas totalizadoras - totalizador

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

Gostou do nosso conteúdo? Compartihe!

Facebook
LinkedIn
WhatsApp