DEV Community

Yuri Peixinho
Yuri Peixinho

Posted on • Edited on

O que são Jobs, Worker, Storage e Servidor

Introdução

Esses conceitos formam a espinha dorsal de qualquer sistema de processamento assíncrono. Vale entender cada um de forma independente.

Job

No contexto da programação, um job é simplesmente uma tarefa que precisa ser executada. Um boco de trabalho, com início, meio e fim. A palavra vem do inglês mesmo: job = trabalho, tarefa.

// Isso é um job — qualquer método .NET
public async Task ProcessarTransacao(int tenantId, string transacaoId)
{
    // lógica aqui
}
Enter fullscreen mode Exit fullscreen mode

Job vs. Código Comum

A diferença fundamental é que código comum executa junto com quem pediu. Um job executa separado, no seu próprio tempo:

Código comum:
Cliente  Requisição  Servidor processa tudo  Resposta  Cliente

Com jobs:
Cliente  Requisição  Servidor responde imediatamente
                              
                         Job 1: envia email    (depois)
                         Job 2: atualiza estoque (depois)
                         Job 3: gera nota fiscal  (depois)
Enter fullscreen mode Exit fullscreen mode

Ideia Central

Pense no seu computador. Quando você abre o Outlook às 8h da manhã e ele automaticamente verifica novos e-mails, sincroniza o calendário e baixa anexos — cada uma dessas operações é um job. Alguém programou: "toda vez que o programa abrir, execute essas tarefas".

Em software, um job é qualquer operação que você quer executar de forma controlada, separada do fluxo principal da aplicação.

Jobs no dia a dia de sum sistema

Pense em um e-commerce. Quando um cliente finaliza uma compra, várias coisas precisam acontecer:

  • Enviar e-mail de confirmação
  • Atualizar o estoque
  • Notificar o vendedor
  • Gerar nota fiscal
  • Registrar no sistema financeiro

Você poderia fazer tudo isso dentro da requisição HTTP — o cliente clica em "Comprar" e fica esperando enquanto o servidor faz tudo. Mas isso é lento e frágil. Se qualquer etapa falhar, o cliente vê um erro.

A solução é transformar cada uma dessas tarefas em um job: o servidor responde imediatamente "compra realizada!" e delega o restante para ser executado em background, de forma assíncrona.

Anatomia de um Job

Todo job tem três características:

O que fazer — o código que será executado, normalmente um método. Ex: public void EnviarEmailConfirmacao(int pedidoId)

Quando fazer — agora, daqui a 30 minutos, todo dia às 9h:

O que fazer se falhar — tentar de novo? Quantas vezes? Notificar alguém?

Por que persistir Jobs?

Se você simplesmente usar Task.Run(() => EnviarEmail()), o job existe só na memória. Se o servidor reiniciar — por um deploy, uma falha, qualquer motivo — o job desaparece e o e-mail nunca é enviado.

Por isso sistemas de jobs sérios gravam a tarefa em um banco de dados antes de executar. Assim, mesmo que o servidor caia no meio da execução, o job pode ser retomado depois.

É exatamente isso que o Hangfire faz, e agora que você entende o conceito de job, o Hangfire vai fazer muito mais sentido.

Worker

Um worker é uma thread que fica olhando a fila de jobs e os executa. O número de workers é configurável por servidor.

Um worker é uma unidade de execução — uma thread (ou processo) que fica em loop contínuo fazendo basicamente:

  1. "Tem job disponível na fila?"
  2. Se sim, pega e executa
  3. Se não, dorme um tempo e volta ao passo 1

O número de workers define o paralelismo real do sistema. Com 5 workers, você processa até 5 jobs simultaneamente. Mais workers significa mais throughput, mas também mais pressão sobre recursos compartilhados (banco, memória, CPU).

Workers podem ser thread-based (dentro do mesmo processo da aplicação) ou process-based (processos separados, como no modelo do Celery com Python). Cada modelo tem tradeoffs de isolamento, overhead e simplicidade operacional.

Storage

O storage é a camada de persistência que conecta quem enfileira jobs com quem os executa. Funciona como um contrato durável: mesmo que a aplicação caia, os jobs não se perdem.

O storage precisa resolver alguns problemas não triviais:

  • Mutual exclusion — garantir que dois workers não peguem o mesmo job
  • Visibilidade — jobs precisam ficar "invisíveis" enquanto estão sendo processados
  • Reentrada — se um worker morrer no meio, o job volta à fila
  • Scheduling — suporte a jobs agendados para o futuro

Bancos relacionais, Redis, SQS, Kafka — qualquer coisa pode ser storage, desde que resolva esses problemas. A escolha impacta diretamente latência, throughput e confiabilidade.

Servidor

O "servidor" nesse contexto é uma instância da aplicação que tem workers ativos. É o processo que efetivamente consome a fila.

A separação entre quem enfileira e quem processa é intencional e poderosa. Você pode ter:

  • Aplicação web que só enfileira (sem workers)
  • Processo dedicado que só processa (sem receber requisições HTTP)
  • Ou os dois juntos, no mesmo processo

Múltiplos servidores apontando pro mesmo storage formam um pool distribuído de workers — é assim que você escala horizontalmente o processamento.

Como os três se relacionam

[Produtor]    [Storage]  ←→  [Servidor A: worker 1, worker 2]
                              [Servidor B: worker 1, worker 2]
Enter fullscreen mode Exit fullscreen mode

Top comments (0)