Worker
워커
백그라운드 작업 처리 프로세스. Kubernetes 워커 노드.
워커
백그라운드 작업 처리 프로세스. Kubernetes 워커 노드.
Worker는 백그라운드에서 작업을 처리하는 프로세스입니다. 웹 요청-응답 사이클과 분리되어, 이메일 발송, 이미지 처리, 데이터 집계 같은 시간이 오래 걸리거나 즉각적인 응답이 필요 없는 작업을 비동기로 처리합니다.
메시지 큐 기반 아키텍처에서 Producer가 작업을 큐에 넣으면, Worker가 이를 소비(consume)해 처리합니다. RabbitMQ, Redis Queue, Amazon SQS, Kafka가 대표적인 메시지 브로커입니다. Celery(Python), BullMQ(Node.js), Sidekiq(Ruby)가 Worker 프레임워크입니다.
Kubernetes에서 Worker는 Deployment로 배포되며 Horizontal Pod Autoscaler로 큐 길이에 따라 자동 스케일됩니다. 컨트롤 플레인의 명령을 수행하는 노드도 Worker Node라고 부르며, 이는 Pod를 실행하는 서버를 의미합니다.
실무에서는 작업 실패 시 재시도 로직(exponential backoff), Dead Letter Queue(처리 실패 작업 격리), 멱등성(idempotency, 중복 실행해도 결과 동일) 보장이 중요합니다. 모니터링으로 큐 길이, 처리 시간, 실패율을 추적합니다.
// BullMQ를 사용한 Node.js Worker 예제
// queue.js - 큐 설정
import { Queue } from 'bullmq';
const connection = {
host: process.env.REDIS_HOST || 'localhost',
port: 6379,
};
// 이메일 발송 큐
export const emailQueue = new Queue('email', { connection });
// 이미지 처리 큐
export const imageQueue = new Queue('image-processing', {
connection,
defaultJobOptions: {
attempts: 3, // 최대 3번 재시도
backoff: {
type: 'exponential', // 지수 백오프
delay: 1000, // 1초부터 시작
},
removeOnComplete: 100, // 완료된 작업 100개만 유지
removeOnFail: 500, // 실패한 작업 500개만 유지
},
});
// producer.js - 작업 추가
export async function sendWelcomeEmail(userId, email) {
const job = await emailQueue.add('welcome', {
userId,
email,
template: 'welcome',
}, {
priority: 1, // 높은 우선순위
delay: 0, // 즉시 실행
jobId: `welcome-${userId}`, // 중복 방지용 ID
});
console.log(`Job ${job.id} added to queue`);
return job;
}
export async function processImage(imageId, operations) {
return imageQueue.add('resize', {
imageId,
operations, // [{ type: 'resize', width: 800 }, { type: 'compress' }]
});
}
// worker.js - Worker 정의
import { Worker } from 'bullmq';
import { sendEmail } from './services/email';
import { resizeImage, compressImage } from './services/image';
// 이메일 Worker
const emailWorker = new Worker('email', async (job) => {
console.log(`Processing email job ${job.id}`);
const { userId, email, template } = job.data;
// 멱등성: 이미 발송된 이메일인지 확인
if (await isEmailAlreadySent(userId, template)) {
console.log(`Email already sent to ${email}`);
return { status: 'skipped', reason: 'already_sent' };
}
await sendEmail({
to: email,
template,
data: { userId },
});
await markEmailAsSent(userId, template);
return { status: 'sent', email };
}, {
connection,
concurrency: 5, // 동시 처리 수
});
// 이벤트 핸들링
emailWorker.on('completed', (job, result) => {
console.log(`Job ${job.id} completed:`, result);
});
emailWorker.on('failed', (job, err) => {
console.error(`Job ${job.id} failed:`, err.message);
// 알림 발송, 메트릭 기록 등
});
// 이미지 처리 Worker
const imageWorker = new Worker('image-processing', async (job) => {
const { imageId, operations } = job.data;
let image = await loadImage(imageId);
for (const op of operations) {
// 진행률 업데이트
await job.updateProgress(operations.indexOf(op) / operations.length * 100);
if (op.type === 'resize') {
image = await resizeImage(image, op.width, op.height);
} else if (op.type === 'compress') {
image = await compressImage(image, op.quality);
}
}
const outputPath = await saveImage(image);
return { outputPath };
}, {
connection,
concurrency: 2, // 리소스 많이 쓰므로 제한
});
// Kubernetes Deployment (worker.yaml)
/*
apiVersion: apps/v1
kind: Deployment
metadata:
name: email-worker
spec:
replicas: 3
selector:
matchLabels:
app: email-worker
template:
spec:
containers:
- name: worker
image: myapp/worker:v1.0.0
command: ["node", "worker.js"]
env:
- name: REDIS_HOST
valueFrom:
configMapKeyRef:
name: redis-config
key: host
resources:
requests:
cpu: 100m
memory: 256Mi
*/
시니어: "주문 완료 이메일이 API 응답 시간에 영향주고 있어요. Worker로 분리해서 비동기 처리해야 해요."
주니어: "큐에 넣었는데 Worker가 죽으면 이메일이 안 가지 않나요?"
시니어: "Redis persistence 설정하고, 작업 실패 시 재시도 로직 넣으면 돼요. Dead Letter Queue로 계속 실패하는 건 따로 모아서 확인하고요."
면접관: "메시지 큐 기반 시스템에서 멱등성을 어떻게 보장하나요?"
지원자: "작업마다 고유 ID를 부여하고, 처리 완료된 ID는 Redis나 DB에 기록합니다. Worker가 작업을 받으면 먼저 이 ID로 이미 처리됐는지 확인하고, 중복이면 스킵합니다. 네트워크 문제로 재전송되거나 재시도되어도 같은 결과를 보장합니다."
리뷰어: "외부 API 호출하는 Worker인데 타임아웃이나 재시도 설정이 없네요."
개발자: "axios timeout 30초로 설정하고, BullMQ에서 attempts: 3, exponential backoff 추가하겠습니다. 3번 실패하면 DLQ로 가게요."