rcRacoelho
HomeBlogDesafiosProjetosSetupVagasComunidade
rcRacoelho

Conteúdo sobre desenvolvimento, tecnologia e desafios de programação para impulsionar sua carreira em tech.

Links rápidos
  • Blog
  • Desafios
  • Projetos
  • Setup
  • Vagas
  • Comunidade
  • Links
Conecte-se
    © 2024–2026 Racoelho. Todos os direitos reservados.v3.0.27 · build 2026-10-04
    rcRacoelho
    HomeBlogDesafiosProjetosSetupVagasComunidade
    blog / Arquitetura de Sistemas / background-jobs-como-escalar-seu-sistema
    Arquitetura de SistemasDesenvolvimento Web

    Seu Sistema Está Lento? O Problema Não É o Banco de Dados — É Quando Você Processa os Dados

    Rafael Coelho
    17 mar 2026 · 11 min de leitura

    Todo sistema começa pequeno. Mil registros, dez mil, cem mil.

    Tudo responde rápido. O usuário clica e a informação aparece. Você nem pensa em performance porque não precisa.

    Aí o sistema cresce.

    O banco acumula milhões de registros e um belo dia, alguém abre o dashboard, aplica um filtro de janeiro a dezembro e espera.

    E espera. E toma timeout. E dá F5. E abandona.

    A reação natural é culpar o banco de dados, trocar o plano, adicionar índice, jogar mais RAM... Mas o problema real é outro: você está fazendo o processamento pesado no momento em que o usuário pede a informação. E isso não escala.

    Nesse artigo eu vou te mostrar o padrão que sistemas grandes usam pra lidar com milhões (ou bilhões) de registros sem que o usuário perceba. E o melhor: não precisa ser um engenheiro do Google pra aplicar isso.


    O Problema: Processar na Hora Errada

    Pra entender o problema, imagina um cenário simples.

    Você tem uma tabela de transações financeiras. Cada depósito, cada saque, cada transferência gera um registro. Pra saber o saldo, o sistema precisa somar todas as transações desde o início da conta.

    Com mil transações, isso é instantâneo. Com cem milhões? Inviável em tempo real.

    Visualmente, o fluxo problemático é esse:

    O problema não é o banco. É a estratégia. Você está pedindo pro banco fazer um trabalho gigantesco toda vez que alguém clica num botão.


    A Solução: Processar Antes, Servir Rápido

    A ideia é simples: separe o processamento pesado da resposta ao usuário.

    Um processo roda em background — pode ser uma vez por hora, uma vez por dia, depende do caso — e consolida os dados numa tabela de rollup. Quando o usuário pede o saldo, o sistema consulta o rollup e calcula apenas as transações que aconteceram depois do último processamento.

    Ao invés de varrer anos de dados, ele varre horas. Ou minutos.

    Percebe a diferença? O trabalho pesado já foi feito. A resposta pro usuário é quase instantânea.


    Como Funciona uma Tabela de Rollup

    A tabela de rollup é basicamente um checkpoint. Ela guarda o resultado de um cálculo pesado num ponto específico no tempo.

    Estrutura da tabela

    sql
    CREATE TABLE saldo_rollups ( id BIGINT PRIMARY KEY IDENTITY, conta_id BIGINT NOT NULL, saldo_acumulado DECIMAL(18,2) NOT NULL, calculado_em DATETIME2 NOT NULL, transacao_ate_id BIGINT NOT NULL, -- última transação incluída no cálculo INDEX IX_rollup_conta (conta_id, calculado_em DESC) );

    O job que gera o rollup

    csharp
    public class RollupJob { private readonly AppDbContext _db; public RollupJob(AppDbContext db) => _db = db; public async Task ExecutarAsync() { // Pega todas as contas que têm transações novas var contasComMovimentacao = await _db.Transacoes .Where(t => t.Data > _db.SaldoRollups .Where(r => r.ContaId == t.ContaId) .Max(r => (DateTime?)r.CalculadoEm) ?? DateTime.MinValue) .Select(t => t.ContaId) .Distinct() .ToListAsync(); foreach (var contaId in contasComMovimentacao) { // Busca o último rollup dessa conta var ultimoRollup = await _db.SaldoRollups .Where(r => r.ContaId == contaId) .OrderByDescending(r => r.CalculadoEm) .FirstOrDefaultAsync(); var saldoAnterior = ultimoRollup?.SaldoAcumulado ?? 0; var dataCorte = ultimoRollup?.CalculadoEm ?? DateTime.MinValue; // Soma só as transações novas var delta = await _db.Transacoes .Where(t => t.ContaId == contaId && t.Data > dataCorte) .SumAsync(t => t.Valor); // Grava o novo rollup _db.SaldoRollups.Add(new SaldoRollup { ContaId = contaId, SaldoAcumulado = saldoAnterior + delta, CalculadoEm = DateTime.UtcNow }); } await _db.SaveChangesAsync(); } }

    A consulta rápida

    csharp
    public async Task<decimal> ObterSaldoAsync(long contaId) { // Pega o último rollup var rollup = await _db.SaldoRollups .Where(r => r.ContaId == contaId) .OrderByDescending(r => r.CalculadoEm) .FirstOrDefaultAsync(); var saldoBase = rollup?.SaldoAcumulado ?? 0; var dataCorte = rollup?.CalculadoEm ?? DateTime.MinValue; // Soma só o que veio depois var delta = await _db.Transacoes .Where(t => t.ContaId == contaId && t.Data > dataCorte) .SumAsync(t => t.Valor); return saldoBase + delta; }

    O ponto chave: a query do rollup varre milhões de registros, mas roda em background, sem pressa. A query do usuário varre dezenas ou centenas de registros — só o delta desde o último processamento.


    Background Jobs: O Trabalho Pesado Acontece nos Bastidores

    O rollup é um tipo de background job. Mas o padrão vai muito além de saldos financeiros.

    Cenário: Monitoramento Industrial com Milhares de Sensores

    Imagina uma operação industrial com 5 mil sensores: temperatura, pressão, vibração, umidade. Cada sensor manda dados o tempo todo. Se você tentar gravar tudo direto num banco relacional e calcular em tempo real, vai estourar.

    O que sistemas assim fazem:

    Cada camada resolve um problema diferente:

    CamadaO que fazPor que existe
    Message BrokerAbsorve o volume de entrada dos sensoresDesacopla produtores de consumidores. Se um worker cai, as mensagens ficam na fila
    WorkersProcessam dados em paralelo, cada um especializadoDistribui a carga. Se temperatura demora mais, não trava pressão
    Rollup DBConsolida dados históricos em intervalosTransforma bilhões de leituras em milhares de agregações consultáveis
    CacheGuarda os resultados mais acessadosEvita consultas repetidas ao banco. Dashboard abre em milissegundos

    Outros cenários comuns

    O padrão se repete em vários contextos:


    Anatomia de um Sistema com Background Jobs

    Quando você junta essas peças, a arquitetura completa fica assim:

    Dois mundos separados:

    • Camada do usuário: rápida, leve, só consulta dados pré-processados
    • Camada de background: pesada, sem pressa, faz o trabalho bruto quando ninguém está esperando

    Quando Vale a Pena (e Quando Não Vale)

    Essa arquitetura não é pra todo mundo. Se seu sistema tem poucos dados e responde bem, adicionar workers e rollups é over-engineering puro.

    A regra é simples: se o processamento é pesado e o resultado não muda a cada segundo, não faz sentido recalcular tudo toda vez que alguém pede. Processa antes, guarda o resultado, serve rápido.

    Sinais de que você precisa dessa arquitetura

    SinalO que aconteceO que fazer
    Queries lentas que pioram com o tempoDashboard que levava 200ms agora leva 8sRollup + cache
    Timeout em relatóriosUsuário clica em "gerar relatório" e toma 504Background job + notificação quando pronto
    CPU do banco no teto em horário comercialConsultas analíticas competindo com operações transacionaisSeparar leitura pesada em jobs off-peak
    Usuários reclamando de lentidãoO sistema "trava" em horários de picoCache + pré-processamento

    Na Prática: Ferramentas por Stack

    As ferramentas variam conforme a stack, mas o conceito é sempre o mesmo.

    .NET

    csharp
    // Com Hangfire — agendar um job recorrente RecurringJob.AddOrUpdate<RollupJob>( "rollup-saldos", job => job.ExecutarAsync(), Cron.Hourly); // roda a cada hora

    Node.js

    javascript
    // Com BullMQ — adicionar job numa fila import { Queue } from 'bullmq'; const rollupQueue = new Queue('rollup'); // Agendar job recorrente await rollupQueue.add('consolidar-saldos', { tipo: 'saldos' }, { repeat: { every: 3600000 } } // a cada 1 hora );

    Python

    python
    # Com Celery — task periódica from celery import Celery from celery.schedules import crontab app = Celery('tasks') @app.on_after_configure.connect def setup_periodic_tasks(sender, **kwargs): sender.add_periodic_task( crontab(minute=0), # a cada hora cheia gerar_rollup.s() ) @app.task def gerar_rollup(): # lógica de consolidação aqui pass

    Outras opções

    FerramentaStack / ContextoTipo
    Hangfire.NETLibrary (roda dentro da app)
    Quartz.NET.NETLibrary (mais configurável)
    BullMQNode.jsFila + workers com Redis
    CeleryPythonTask queue distribuída
    SidekiqRubyBackground jobs com Redis
    Azure FunctionsCloud (Azure)Serverless com timer trigger
    AWS Lambda + EventBridgeCloud (AWS)Serverless com schedule
    Cron jobLinux / qualquerO clássico. Simples e funciona

    O conceito é o mesmo independente da tecnologia: separar o processamento pesado da resposta ao usuário. Um acontece em background, no tempo dele. O outro acontece na hora, e precisa ser rápido.


    Quem Faz Isso no Mundo Real

    Stack Overflow, Reddit, Instagram, Amazon, Mercado Livre. Todos processam bilhões de dados por dia. Nenhum deles faz o cálculo na hora que o usuário clica.

    • Netflix: pré-calcula recomendações em batch jobs massivos. Quando você abre o app, o catálogo personalizado já está pronto.
    • Mercado Livre: consolida métricas de vendedores (reputação, tempo de envio, taxa de reclamação) em background. Se calculasse em tempo real pra cada busca, a página nunca carregaria.
    • Nubank: saldo, extrato, limites — tudo consolidado em pipelines de dados que rodam continuamente. O app só consulta o resultado.

    Se eles fizessem o processamento na hora do clique, você já teria reclamado.


    Resumo Visual


    Conclusão

    Parece complexo visto de fora. Mas cada peça encaixa na outra.

    O broker absorve o volume. Os workers distribuem o processamento. O rollup consolida. O cache serve rápido. E o usuário nem percebe que por trás de um clique tem uma arquitetura inteira trabalhando nos bastidores.

    Você não precisa implementar tudo de uma vez. Começa pelo rollup. Coloca um job simples que roda de hora em hora. Mede a diferença. Quando sentir necessidade, adiciona cache. Depois workers. Cada camada resolve um problema novo.

    O segredo não é usar a ferramenta mais sofisticada. É entender quando processar. E a resposta quase sempre é: não na hora que o usuário pediu.

    Neste artigo
    • O Problema: Processar na Hora Errada
    • A Solução: Processar Antes, Servir Rápido
    • Como Funciona uma Tabela de Rollup
    • Background Jobs: O Trabalho Pesado Acontece nos Bastidores
    • Anatomia de um Sistema com Background Jobs
    • Quando Vale a Pena
    • Na Prática: Ferramentas por Stack
    • Quem Faz Isso no Mundo Real
    • Resumo Visual
    • Conclusão

    Leia tambémContinue lendo

    9 min

    Como Arquitetar um Sistema de Notificações que Aguenta Escala

    Arquitetura de Sistemas
    8 min

    Você Não Precisa Escolher Entre REST, gRPC e GraphQL

    Arquitetura de Sistemas
    18 min

    Como Funciona um Sistema de Buscas de Verdade (e por que LIKE não é busca)

    Arquitetura de Sistemas
    rcRacoelho

    Conteúdo sobre desenvolvimento, tecnologia e desafios de programação para impulsionar sua carreira em tech.

    Links rápidos
    • Blog
    • Desafios
    • Projetos
    • Setup
    • Vagas
    • Comunidade
    • Links
    Conecte-se
      © 2024–2026 Racoelho. Todos os direitos reservados.v3.0.27 · build 2026-10-04