BullMQ no Node.js implementa filas de jobs sobre Redis com workers, retries, delays, prioridades, eventos e agendamento. A biblioteca é útil para mover tarefas lentas para processamento assíncrono, como envio de e-mails, geração de arquivos, importações e integrações externas.
Uma fila não elimina falhas. Jobs podem ser entregues novamente, workers podem morrer no meio da execução e Redis pode ficar indisponível. O código precisa ser idempotente, limitado e observável.
Conexão Redis
import { Queue, Worker } from 'bullmq';
const connection = {
host: process.env.REDIS_HOST,
port: Number(process.env.REDIS_PORT),
password: process.env.REDIS_PASSWORD,
tls: process.env.REDIS_TLS === 'true' ? {} : undefined
};
const emailQueue = new Queue('email', { connection });Use TLS e autenticação quando Redis estiver fora de uma rede confiável. Não compartilhe um banco Redis sem namespace e políticas de capacidade.
Adicionando um job
await emailQueue.add(
'send-welcome',
{
userId,
template: 'welcome-v2'
},
{
jobId: `welcome:${userId}:v2`,
attempts: 5,
backoff: {
type: 'exponential',
delay: 5000
},
removeOnComplete: 1000,
removeOnFail: 5000
}
);Evite colocar dados pessoais ou segredos no payload. Prefira IDs e busque o estado atualizado durante a execução.
Criando um worker
const worker = new Worker(
'email',
async job => {
switch (job.name) {
case 'send-welcome':
return sendWelcome(job.data);
default:
throw new Error(`Job desconhecido: ${job.name}`);
}
},
{
connection,
concurrency: 10
}
);Valide job.name, versão e payload. Jobs antigos podem permanecer na fila durante um deploy.
Idempotência
Um worker pode executar o mesmo job mais de uma vez. A aplicação precisa garantir que efeitos não sejam duplicados.
await db.query(
`INSERT INTO sent_notifications (notification_key, sent_at)
VALUES ($1, now())
ON CONFLICT DO NOTHING`,
[notificationKey]
);A constraint deve envolver a intenção de negócio, não apenas o ID interno do BullMQ.
jobId
Um jobId estável ajuda a impedir duplicações enquanto o job correspondente ainda existe. Se jobs concluídos são removidos, o mesmo ID pode ser adicionado novamente no futuro. Por isso, mantenha idempotência também no banco ou serviço de destino.
Retries
Classifique erros:
- transitórios: timeout, 429 e falhas de rede;
- permanentes: payload inválido, recurso inexistente e credencial revogada;
- desconhecidos: exigem observabilidade e limite.
Não faça retry de erro permanente. Veja Retry com Backoff no Node.js.
Timeout e cancelamento
Defina deadline dentro do processor.
async function sendWelcome(data) {
const signal = AbortSignal.timeout(15_000);
return mailer.send({ ...data, signal });
}Uma Promise rejeitada não cancela automaticamente a operação subjacente. Propague AbortSignal quando a biblioteca aceitar.
Concorrência
Concorrência alta aumenta throughput de I/O, mas pode esgotar banco, APIs e CPU. Defina limites por recurso.
- pool do banco;
- conexões HTTP;
- rate limit do provedor;
- memória por job;
- CPU disponível.
Use filas separadas ou workers com concorrências diferentes para workloads distintos.
Prioridades
await queue.add('generate-report', data, { priority: 10 });Prioridade pode causar starvation de jobs comuns. Monitore idade dos itens e não crie dezenas de níveis sem necessidade.
Jobs atrasados
await queue.add('send-reminder', data, {
delay: 60 * 60 * 1000
});Delay não é garantia de execução no milissegundo exato. Ele indica quando o job se torna elegível, dependendo de workers e capacidade.
Repeatable e schedulers
Tarefas recorrentes devem ter chave e timezone documentados. Mesmo com scheduler da biblioteca, a execução precisa ser idempotente.
Consulte Cron Jobs no Node.js.
Flows
Fluxos podem representar dependências entre jobs. Use com moderação: workflows complexos exigem compensação, estados claros e ferramentas operacionais.
Progresso
await job.updateProgress({ processed: 500, total: 1000 });Atualizações frequentes geram carga em Redis. Atualize por lote ou percentual significativo.
Eventos
Eventos de conclusão e falha ajudam na observabilidade, mas não devem ser a única fonte de estado de negócio. Persista resultados importantes.
Falhas e retenção
Mantenha jobs falhos por tempo suficiente para diagnóstico, com limite para não consumir memória indefinidamente. Para erros permanentes, use fila ou estado dead letter.
Veja Dead Letter Queue no Node.js.
Redis e durabilidade
Configure persistência e replicação conforme o impacto de perder jobs. Redis usado apenas como cache pode ter políticas inadequadas para fila. Evite eviction das chaves do BullMQ.
Consulte Redis com Node.js.
Payload versionado
{
"schemaVersion": 2,
"userId": "u-1",
"template": "welcome-v2"
}Durante deploy, workers antigos e novos podem coexistir. Aceite versões suportadas ou encaminhe job incompatível para análise.
Multi-tenancy
Inclua tenant no payload e nas chaves de idempotência, mas valide o tenant a partir de contexto confiável. Aplique limites por tenant para impedir noisy neighbor.
Observabilidade
Monitore:
- jobs waiting, active, completed e failed;
- idade do job mais antigo;
- duração;
- tentativas;
- throughput;
- event loop lag;
- conexões Redis;
- falhas por tipo.
Graceful shutdown
async function shutdown() {
await worker.close();
await emailQueue.close();
}
process.once('SIGTERM', shutdown);
process.once('SIGINT', shutdown);worker.close() para de buscar novos jobs e aguarda os ativos conforme o comportamento da biblioteca. Configure também timeout externo do processo.
Testes
- job válido;
- payload inválido;
- retry transitório;
- erro permanente;
- job duplicado;
- worker encerrado no meio;
- Redis indisponível;
- concorrência;
- delay;
- versão antiga do payload.
Erros comuns
- payload com dados sensíveis;
- achar que jobId garante idempotência permanente;
- retry de qualquer erro;
- concorrência maior que dependências;
- eviction no Redis;
- remover falhas cedo demais;
- não versionar payload;
- não fechar worker no deploy;
- usar eventos como estado durável.
Checklist
- Redis protegido e persistente;
- payload pequeno;
- schema versionado;
- idempotência de negócio;
- retry classificado;
- timeout;
- concorrência medida;
- retenção limitada;
- métricas e alertas;
- shutdown gracioso.
Conclusão
BullMQ no Node.js oferece uma base completa para filas sobre Redis. Ele resolve agendamento e coordenação operacional, mas a confiabilidade final depende do processor.
Use payloads pequenos, idempotência persistida, retries seletivos e limites de concorrência. Com Redis configurado para durabilidade e workers encerrados corretamente, BullMQ suporta workloads assíncronos previsíveis.
Consulte a documentação oficial do BullMQ e a documentação do Redis.




