[G1-Sync] Manual knowledge update
This commit is contained in:
@@ -0,0 +1,117 @@
|
||||
---
|
||||
id: backend-job-queue-patterns
|
||||
title: Job Queue 패턴 — 비동기 작업 영속화
|
||||
category: Coding
|
||||
status: draft
|
||||
source_trust_level: B
|
||||
verification_status: conceptual
|
||||
created_at: 2026-05-09
|
||||
updated_at: 2026-05-09
|
||||
tags: [backend, queue, worker, async, vibe-coding]
|
||||
tech_stack: { language: "TypeScript / BullMQ / SQS / RabbitMQ", applicable_to: ["Backend"] }
|
||||
applied_in: []
|
||||
aliases: [worker, dead letter queue, delayed job, scheduled job]
|
||||
---
|
||||
|
||||
# Job Queue 패턴
|
||||
|
||||
> "이 작업이 결국 실행되어야 한다 — 실패해도 재시도, 사용자 응답은 즉시" 가 답. **producer / queue / worker / DLQ** 4단 구조 + 멱등성 + 모니터링.
|
||||
|
||||
## 📖 핵심 개념
|
||||
- Producer: HTTP handler 가 요청 받자마자 enqueue, 사용자에게 202.
|
||||
- Queue (broker): Redis (BullMQ), AWS SQS, RabbitMQ, Kafka 등.
|
||||
- Worker: 별도 프로세스. 동시성 / 백프레셔 제어.
|
||||
- DLQ (Dead Letter Queue): 최대 재시도 후에도 실패한 메시지 격리.
|
||||
|
||||
## 💻 코드 패턴
|
||||
|
||||
### BullMQ (Redis 기반, Node)
|
||||
```ts
|
||||
import { Queue, Worker, QueueEvents } from 'bullmq';
|
||||
const conn = { connection: { host: 'redis', port: 6379 } };
|
||||
|
||||
// Producer
|
||||
const emailQ = new Queue('email', conn);
|
||||
|
||||
app.post('/api/welcome-email', async (req, res) => {
|
||||
await emailQ.add('welcome', { userId: req.body.userId }, {
|
||||
attempts: 5,
|
||||
backoff: { type: 'exponential', delay: 1000 },
|
||||
removeOnComplete: 1000, // 마지막 1000개만 보관
|
||||
removeOnFail: false, // 실패는 보관
|
||||
});
|
||||
res.status(202).end();
|
||||
});
|
||||
|
||||
// Worker (별도 프로세스)
|
||||
const worker = new Worker('email', async (job) => {
|
||||
if (job.name === 'welcome') {
|
||||
await sendWelcomeEmail(job.data.userId);
|
||||
}
|
||||
}, {
|
||||
...conn,
|
||||
concurrency: 10,
|
||||
limiter: { max: 100, duration: 1000 }, // 초당 100건 제한
|
||||
});
|
||||
|
||||
worker.on('failed', (job, err) => {
|
||||
log.error('job failed', { id: job?.id, attempts: job?.attemptsMade, err });
|
||||
});
|
||||
```
|
||||
|
||||
### Idempotent worker
|
||||
```ts
|
||||
async function sendWelcomeEmail(userId: string) {
|
||||
const already = await db.emails.exists({ userId, kind: 'welcome' });
|
||||
if (already) return; // 멱등
|
||||
await mailer.send(...);
|
||||
await db.emails.insert({ userId, kind: 'welcome', sentAt: new Date() });
|
||||
}
|
||||
```
|
||||
|
||||
### Delayed / scheduled
|
||||
```ts
|
||||
// 1시간 뒤
|
||||
await emailQ.add('reminder', data, { delay: 60 * 60 * 1000 });
|
||||
|
||||
// Cron — 매일 오전 9시
|
||||
await emailQ.upsertJobScheduler('daily-digest', { pattern: '0 9 * * *' }, { name: 'digest', data: {} });
|
||||
```
|
||||
|
||||
### DLQ
|
||||
```ts
|
||||
worker.on('failed', async (job, err) => {
|
||||
if (job?.attemptsMade === job?.opts.attempts) {
|
||||
await dlq.add('email-dead', { original: job.data, error: err.message, jobId: job.id });
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
## 🤔 의사결정 기준
|
||||
| 작업 | 도구 |
|
||||
|---|---|
|
||||
| 짧은 (<100ms) + 사용자 응답에 결과 필요 | 동기 처리 |
|
||||
| 외부 API 호출 (이메일, 결제, 푸시) | 큐 |
|
||||
| 파일 처리 / PDF 생성 / 이미지 변환 | 큐 |
|
||||
| 이벤트 fan-out (한 이벤트 → 여러 핸들러) | Pub/Sub (Kafka, NATS) |
|
||||
| 정확한 시각 실행 | 스케줄러 (BullMQ scheduler / Temporal) |
|
||||
| 복잡 워크플로우 (sagas) | Temporal / Inngest |
|
||||
|
||||
## ❌ 안티패턴
|
||||
- **HTTP 요청 안에서 큰 작업 동기 처리**: 사용자 30초 대기 + timeout.
|
||||
- **worker 가 멱등 X**: at-least-once delivery → 중복 효과.
|
||||
- **DLQ 없음**: 영구 실패 잡 무한 retry 또는 silently drop.
|
||||
- **monitoring 없음**: queue 길이 / 실패율 모름. 백로그 폭발.
|
||||
- **worker concurrency 제한 없음**: DB / 외부 API 부하 폭증.
|
||||
- **메시지 안에 큰 payload**: 큐 메모리 폭발. ID 만 + DB/blob 보조.
|
||||
- **graceful shutdown 미흡**: deploy 시 진행 중 잡 손실. SIGTERM 받으면 현재 잡 끝나고 종료.
|
||||
- **순서 보장 가정 (대부분 큐는 FIFO 아님)**: 명시적 순서 필요시 partition key.
|
||||
|
||||
## 🤖 LLM 활용 힌트
|
||||
- "큐 설정 + worker 멱등 + DLQ + monitoring 4종 세트" 강제.
|
||||
- BullMQ / SQS / Kafka 중 어떤지 명시.
|
||||
|
||||
## 🔗 관련 문서
|
||||
- [[Idempotent_Operations]]
|
||||
- [[Backend_Retry_Strategy]]
|
||||
- [[Backpressure_Patterns]]
|
||||
Reference in New Issue
Block a user