stack-analysis
CHAPTER 35 / 90
읽기 약 2분
FUNCTION
작업 큐: BullMQ + Redis
핵심 개념
백그라운드 잡·재시도·우선순위·스케줄링 — 이메일·이미지 처리·웹훅.
본문
BullMQ 셋업
pnpm add bullmq ioredis// queues/email.queue.ts
import { Queue } from 'bullmq';
import IORedis from 'ioredis';
const connection = new IORedis(process.env.REDIS_URL!, {
maxRetriesPerRequest: null,
});
export const emailQueue = new Queue('email', { connection });
export async function sendWelcomeEmail(userId: string, email: string) {
await emailQueue.add(
'welcome',
{ userId, email },
{
attempts: 3,
backoff: { type: 'exponential', delay: 5000 },
removeOnComplete: 100, // 완료된 잡 100개만 보관
removeOnFail: 500,
}
);
}Worker — 잡 처리
// workers/email.worker.ts
import { Worker } from 'bullmq';
import { sendEmail } from '@/lib/email';
const worker = new Worker(
'email',
async (job) => {
const { type, userId, email } = job.data;
await job.log(`Processing ${type} for ${email}`);
if (type === 'welcome') {
await sendEmail(email, '환영합니다', `<h1>가입을 환영합니다</h1>`);
} else if (type === 'reset-password') {
await sendResetEmail(job.data);
}
return { sent: true, at: new Date() };
},
{
connection,
concurrency: 5, // 동시 5개 처리
limiter: {
max: 100, // 분당 최대 100개 (외부 API 한도)
duration: 60000,
},
}
);
worker.on('completed', (job) => console.log(`Job ${job.id} done`));
worker.on('failed', (job, err) => console.error(`Job ${job?.id} failed:`, err));우선순위 + 지연 잡
// 즉시 (긴급)
await queue.add('urgent', data, { priority: 1 });
// 5분 후
await queue.add('reminder', data, { delay: 5 * 60 * 1000 });
// 매일 오전 9시 (cron)
import { JobScheduler } from 'bullmq';
const scheduler = new JobScheduler('email');
await scheduler.upsertJobScheduler(
'daily-digest',
{ pattern: '0 9 * * *' },
{ name: 'digest', data: {} }
);
// 1시간 후 (지연 알림)
await queue.add(
'reminder',
{ userId, message: '결제 완료해주세요' },
{ delay: 60 * 60 * 1000 }
);이미지 처리 큐
import sharp from 'sharp';
import { GetObjectCommand, PutObjectCommand } from '@aws-sdk/client-s3';
const imageWorker = new Worker(
'image',
async (job) => {
const { srcKey, sizes } = job.data;
const original = await s3.send(new GetObjectCommand({
Bucket: BUCKET, Key: srcKey,
}));
const buffer = Buffer.from(await original.Body!.transformToByteArray());
const results = [];
for (const size of sizes) {
const resized = await sharp(buffer)
.resize(size.width, size.height, { fit: 'cover' })
.webp({ quality: 80 })
.toBuffer();
const dstKey = srcKey.replace(/\.\w+$/, `_${size.width}x${size.height}.webp`);
await s3.send(new PutObjectCommand({
Bucket: BUCKET, Key: dstKey, Body: resized, ContentType: 'image/webp',
}));
results.push(dstKey);
await job.updateProgress((results.length / sizes.length) * 100);
}
return { resized: results };
},
{ connection, concurrency: 3 }
);
// 사용
await imageQueue.add('resize', {
srcKey: 'uploads/abc.jpg',
sizes: [
{ width: 200, height: 200 },
{ width: 800, height: 600 },
],
});웹훅 재시도
const webhookWorker = new Worker(
'webhook',
async (job) => {
const { url, payload, secret } = job.data;
const signature = crypto.createHmac('sha256', secret)
.update(JSON.stringify(payload))
.digest('hex');
const res = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Signature': signature,
},
body: JSON.stringify(payload),
signal: AbortSignal.timeout(10000),
});
if (!res.ok) {
throw new Error(`Webhook failed: ${res.status}`);
}
return { status: res.status };
},
{
connection,
concurrency: 10,
}
);
// 발송 — 자동 재시도
await webhookQueue.add('order.created', payload, {
attempts: 5,
backoff: { type: 'exponential', delay: 2000 }, // 2s, 4s, 8s, 16s, 32s
});모니터링 (Bull Board)
import { ExpressAdapter } from '@bull-board/express';
import { createBullBoard } from '@bull-board/api';
import { BullMQAdapter } from '@bull-board/api/bullMQAdapter';
const serverAdapter = new ExpressAdapter();
serverAdapter.setBasePath('/admin/queues');
createBullBoard({
queues: [
new BullMQAdapter(emailQueue),
new BullMQAdapter(imageQueue),
new BullMQAdapter(webhookQueue),
],
serverAdapter,
});
app.use('/admin/queues', requireAdmin, serverAdapter.getRouter());다음 챕터
CH.36 "Rate Limiting + CORS + Helmet 실전".
AI 프롬프트
🤖 AI에게 잘 물어보는 법 — 모델·전략별 프롬프트
Claude
무료: Sonnet 4.6 / Pro $20/mo: Opus 4.6
내 코드의 작업 큐 부분을 분석해서 실전 분석 + 개선 우선순위를 알려줘.
ChatGPT
무료: GPT-5.5 / Plus $20/mo: GPT-5.5 Pro
작업 큐 관련 인기 라이브러리/패턴 5개를 비교 분석해서 패턴 추출를 알려줘.
Gemini
무료: 2.5 Flash / Pro $19.99/mo: 3.1 Pro
내 프로젝트 전체에서 작업 큐 최적화 가능 위치를 보고해줘.
Grok
무료: Grok 4.1 / SuperGrok $30/mo
2026년 한국 백엔드 시장의 작업 큐 트렌드를 솔직히 알려줘.
⭐ 이것만 기억하세요
작업 큐: BullMQ + Redis는 이 3가지만 확실히 잡으세요
1.BullMQ = Redis 기반 잡 큐 — 재시도·우선순위·지연·cron 모두
2.Worker는 별도 프로세스 — API 서버와 분리해야 안정
3.Bull Board로 큐 상태 시각화 — 실패 잡 재처리 가능
공유하기
진행도 35 / 90