bullmq / ioredis
BullMQ executa filas de jobs no Redis; ioredis é o cliente Node que filas e camadas de cache compartilham. Emparelhe-os com namespaces de chave claros e pooling de conexão.
Busque em todas as páginas da documentação
BullMQ executa filas de jobs no Redis; ioredis é o cliente Node que filas e camadas de cache compartilham. Emparelhe-os com namespaces de chave claros e pooling de conexão.
Cartão de receita de referência rápida - pronto para copiar e colar.
// src/redis.ts - factory de conexão única
import IORedis from "ioredis";
export function createRedis() {
return new IORedis(process.env.REDIS_URL!, {
maxRetriesPerRequest: null, // necessário para workers BullMQ
enableReadyCheck: false,
});
}// src/queues/email.queue.ts
import { Queue } from "bullmq";
import { createRedis } from "../redis.js";
const connection = createRedis();
export const emailQueue = new Queue("email", {
connection,
defaultJobOptions: {
attempts: 5,
backoff: { type: "exponential", delay: 2000 },
removeOnComplete: 1000,
removeOnFail: false, // mantém para inspeção de DLQ
},
});
await emailQueue.add("send-welcome", { userId: "abc", template: "welcome" });// src/workers/email.worker.ts
import { Worker } from "bullmq";
import { createRedis } from "../redis.js";
const worker = new Worker(
"email",
async (job) => {
await sendEmail(job.data);
},
{ connection: createRedis(), concurrency: 10 }
);
worker.on("failed", (job, err) => {
console.error({ jobId: job?.id, err }, "email_job_failed");
});Quando usar isso:
// src/cache.ts
import type IORedis from "ioredis";
const CACHE_PREFIX = "cache:";
export async function cacheGet<T>(
redis: IORedis,
key: string
): Promise<T | null> {
const raw = await redis.get(`${CACHE_PREFIX}${key}`);
return raw ? (JSON.parse(raw) as T) : null;
}
export async function cacheSet(
redis: IORedis,
key: string,
value: unknown,
ttlSeconds: number
): Promise<void> {
await redis.set(
`${CACHE_PREFIX}${key}`,
JSON.stringify(value),
"EX",
ttlSeconds
);
}// Handler HTTP: padrão cache-aside
import { createRedis } from "./redis.js";
import { cacheGet, cacheSet } from "./cache.js";
const redis = createRedis();
app.get("/products/:id", async (req, reply) => {
const { id } = req.params;
const cached = await cacheGet<Product>(redis, `product:${id}`);
if (cached) return cached;
const product = await db.findProduct(id);
await cacheSet(redis, `product:${id}`, product, 300);
return product;
});Notas de arquitetura:
bull: por padrãocache: - sem colisões de chavenode dist/workers/email.js)maxRetriesPerRequest para cache; workers usam null| Sinal | Ferramenta | Ação |
|---|---|---|
| Backlog da fila | BullMQ getJobCounts() | Escalar workers |
| Jobs travados | Eventos BullMQ | Investigar tarefas de CPU longas |
| Memória Redis | INFO memory | Reduzir removeOnComplete, TTL do cache |
| Jobs falhados | Bull Board / Redis CLI | Reproduzir ou mover para DLQ |
// Dead letter após tentativas máximas
import { Queue } from "bullmq";
export const dlq = new Queue("email-dlq", { connection: createRedis() });
worker.on("failed", async (job, err) => {
if (job && job.attemptsMade >= (job.opts.attempts ?? 1)) {
await dlq.add("failed", { original: job.data, error: err.message });
}
});| Abordagem | Escolha quando | Pule quando |
|---|---|---|
| BullMQ + ioredis | Redis já está na stack, jobs atrasados | Apenas SQS na AWS sem Redis |
| SQS + Lambda | Cargas de trabalho de burst serverless | Jobs de CPU de longa duração no processo |
| pg-boss | Lojas apenas com Postgres, sem Redis | Necessidade de latência de job sub-milissegundo |
| Listas Redis brutas | Minimalismo extremo | Necessidade de retentativas, agendamento, UI |
Sim em stacks pequenas/médias com prefixos de chave e limites de memória. Em escala, separe instâncias Redis de cache (evicção OK) de fila (sem evicção).
Um ioredis compartilhado para cache por pod HTTP; workers precisam de conexões dedicadas por instância de Worker. Monitore connected_clients no Redis.
Use jobs repetíveis do BullMQ quando várias réplicas não devem disparar cron duas vezes. Use node-cron apenas com eleição de líder ou um único pod agendador.
Versões da Stack: Esta página foi escrita para Node.js 24.18.0 (LTS Ativo), npm 10+, TypeScript 5.6+, Express 5, Fastify 5 e NestJS 11.
Revisado por Chris St. John·Última atualização: 16 de jul. de 2026