TIL - 20260708

juni·2026년 7월 8일

TIL

목록 보기
397/468

0708 백엔드 실무 심화 (15/N): Redis, Queue Worker 분리 운영과 백그라운드 작업 관리


✅ 1. Worker란 무엇인가?

  • Worker는 사용자의 실시간 요청과 분리해서 백그라운드 작업을 처리하는 프로세스입니다.
  • API 서버는 사용자 요청을 받고 빠르게 응답하는 역할을 하고, Worker는 시간이 오래 걸리거나 실패 가능성이 있는 작업을 뒤에서 처리합니다.
  • 알림톡 발송, SMS 발송, 엑셀 파일 생성, 외부 API 재시도, Webhook 후속 처리, 배치 작업 처리 같은 기능에 사용됩니다.
API 서버:
사용자 요청 처리
빠른 응답 반환

Worker:
Queue에 쌓인 작업 처리
외부 API 호출
파일 생성
재시도 처리

➕ 1-1. Worker가 필요한 이유

  • 사용자 응답 속도 개선

    • 오래 걸리는 작업을 기다리지 않고 빠르게 응답할 수 있습니다.
  • 실패 재시도 가능

    • 외부 API 실패, timeout, 네트워크 오류를 재시도할 수 있습니다.
  • 서버 부하 분산

    • 무거운 작업을 API 서버와 분리할 수 있습니다.
  • 운영 추적

    • 어떤 작업이 성공했고 실패했는지 Job 단위로 관리할 수 있습니다.
상담 신청 저장
  ↓
API 서버는 즉시 성공 응답
  ↓
Queue에 알림톡 발송 Job 등록
  ↓
Worker가 알림톡 발송 처리
  ↓
성공/실패 이력 저장

✅ 2. Queue란 무엇인가?

  • Queue는 처리해야 할 작업을 순서대로 쌓아두는 대기열입니다.
  • API 서버는 Queue에 작업을 넣고, Worker는 Queue에서 작업을 꺼내 처리합니다.
  • Node.js/NestJS에서는 Redis 기반의 BullMQ를 많이 사용합니다.
Producer:
작업을 Queue에 넣는 쪽

Queue:
작업 대기열

Worker:
작업을 꺼내 처리하는 쪽

➕ 2-1. Queue 흐름

상담 신청 API
  ↓
notificationQueue.add('send-alimtalk', { consultId: 123 })
  ↓
Redis Queue에 Job 저장
  ↓
Worker가 Job 수신
  ↓
알림톡 발송
  ↓
성공/실패 기록
  • Queue는 API 서버와 Worker 사이의 중간 저장소 역할을 합니다.
  • Worker가 잠시 죽어도 Queue에 작업이 남아 있으면 다시 처리할 수 있습니다.

✅ 3. Redis의 역할

  • Redis는 메모리 기반 데이터 저장소입니다.
  • Queue 시스템에서는 Job 대기열, 실행 상태, 재시도 정보, 실패 이력 등을 관리하는 데 사용됩니다.
  • Redis는 캐싱, Rate Limit, 세션 저장, Lock에도 자주 사용됩니다.

➕ 3-1. Redis를 사용하는 대표 기능

기능설명
Queue백그라운드 Job 관리
Cache상품/배너/FAQ 등 빠른 조회
Rate Limit과도한 요청 제한
Session로그인 세션 저장
Lock배치 중복 실행 방지
Temporary Data인증번호, 임시 토큰 저장

➕ 3-2. Queue에서 Redis가 중요한 이유

API 서버가 Job 등록
  ↓
Redis에 Job 저장
  ↓
Worker가 Redis에서 Job 조회
  ↓
처리 결과와 재시도 상태 저장
  • Redis가 장애 나면 Queue 작업 처리도 영향을 받습니다.
  • 따라서 운영에서는 Redis 상태도 모니터링해야 합니다.

✅ 4. API 서버와 Worker를 분리해야 하는 이유

  • API 서버와 Worker를 같은 프로세스에서 실행하면 처음에는 단순합니다.
  • 하지만 서비스가 커질수록 분리하는 것이 안전합니다.

➕ 4-1. 같은 프로세스에서 처리하는 문제

API 서버가 사용자 요청 처리
동시에 Worker가 대량 엑셀 생성
  ↓
CPU/메모리 사용량 증가
  ↓
API 응답 느려짐
  ↓
사용자 경험 악화

➕ 4-2. 분리 운영 구조

API Process:
HTTP 요청 처리

Worker Process:
Queue Job 처리

Redis:
API와 Worker 사이의 Job 저장소
  • API 서버는 빠른 응답에 집중합니다.
  • Worker는 시간이 오래 걸리는 작업을 처리합니다.
  • 장애가 나도 영향 범위를 줄일 수 있습니다.

✅ 5. Worker로 분리하기 좋은 작업

➕ 5-1. 알림/메시지 발송

  • 알림톡
  • SMS/LMS
  • 이메일
  • 관리자 알림
  • 푸시 알림
상담 신청 완료
  ↓
Queue에 알림톡 Job 등록
  ↓
Worker가 발송
  ↓
NotificationLog 저장

➕ 5-2. 파일 생성

  • 엑셀 다운로드
  • CSV Export
  • 정산 파일 생성
  • EP 파일 생성
  • 리포트 PDF 생성
관리자 엑셀 다운로드 요청
  ↓
ExportJob 생성
  ↓
Queue에 Export Job 등록
  ↓
Worker가 파일 생성
  ↓
S3 업로드
  ↓
ExportJob COMPLETED

➕ 5-3. 외부 API 연동

  • CRM 동기화
  • 광고 전환 API
  • 통신사/제휴사 API
  • 결제 후속 처리
  • Webhook 후속 작업
Webhook 수신
  ↓
핵심 상태 변경
  ↓
Queue에 후속 CRM 연동 Job 등록
  ↓
Worker가 CRM API 호출

➕ 5-4. 재처리 작업

  • 실패 알림 재발송

  • 실패 Webhook 재처리

  • 실패 Export 재생성

  • 외부 API timeout 재시도

  • EP 파일 재생성

  • Worker는 실패한 작업을 재시도하고 이력을 관리하는 데 적합합니다.


✅ 6. Worker로 분리하면 안 되는 작업

  • 모든 작업을 무조건 Worker로 빼면 안 됩니다.
  • 사용자가 즉시 결과를 알아야 하는 작업은 API 요청 안에서 처리해야 합니다.

➕ 6-1. Worker로 빼면 안 되는 대표 작업

작업이유
로그인 검증즉시 성공/실패가 필요
주문 생성 핵심 저장저장 성공 여부가 바로 필요
결제 승인 결과 확인사용자가 결과를 즉시 알아야 함
중복 신청 검증저장 전에 막아야 함
권한 검사API 처리 전에 즉시 필요
필수 DB 트랜잭션데이터 정합성에 직접 영향
상담 신청 저장 자체:
API에서 동기 처리

신청 완료 알림톡:
Worker에서 비동기 처리
  • 핵심 데이터 저장은 동기 처리하고, 부가 작업을 Worker로 분리하는 것이 기본입니다.

✅ 7. BullMQ 기본 구성

  • BullMQ는 Redis 기반 Queue 라이브러리입니다.
  • NestJS에서는 @nestjs/bullmq를 사용해 Queue와 Worker를 구성할 수 있습니다.

➕ 7-1. 설치 예시

npm install @nestjs/bullmq bullmq ioredis

➕ 7-2. AppModule 설정 예시

import { BullModule } from '@nestjs/bullmq';

@Module({
  imports: [
    BullModule.forRoot({
      connection: {
        host: process.env.REDIS_HOST,
        port: Number(process.env.REDIS_PORT),
      },
    }),
  ],
})
export class AppModule {}

➕ 7-3. Queue 등록 예시

@Module({
  imports: [
    BullModule.registerQueue({
      name: 'notification',
    }),
  ],
  providers: [NotificationProducer, NotificationWorker],
})
export class NotificationQueueModule {}
  • Queue 이름은 기능별로 명확히 나누는 것이 좋습니다.
  • 예: notification, export, webhook, external-api, batch

✅ 8. Producer 설계

  • Producer는 Queue에 Job을 등록하는 코드입니다.
  • API Service에서 직접 Queue를 다뤄도 되지만, Producer 클래스로 분리하면 유지보수가 쉬워집니다.

➕ 8-1. NotificationProducer 예시

@Injectable()
export class NotificationProducer {
  constructor(
    @InjectQueue('notification')
    private readonly notificationQueue: Queue,
  ) {}

  async addConsultCreatedJob(consultId: number) {
    await this.notificationQueue.add(
      'send-consult-created-alimtalk',
      {
        consultId,
      },
      {
        attempts: 3,
        backoff: {
          type: 'exponential',
          delay: 3000,
        },
        removeOnComplete: true,
        removeOnFail: false,
      },
    );
  }
}

➕ 8-2. Producer 사용 예시

async createConsult(dto: CreateConsultDto) {
  const consult = await this.prisma.consult.create({
    data: {
      name: dto.name,
      phone: dto.phone,
      status: 'PENDING',
    },
  });

  await this.notificationProducer.addConsultCreatedJob(consult.id);

  return consult;
}
  • 상담 신청 저장은 API에서 처리합니다.
  • 알림톡 발송은 Queue에 등록합니다.

✅ 9. Worker 설계

  • Worker는 Queue에 쌓인 Job을 실제로 처리합니다.
  • BullMQ에서는 Processor를 사용해 작업을 처리합니다.

➕ 9-1. Worker 예시

@Processor('notification')
export class NotificationWorker extends WorkerHost {
  constructor(
    private readonly notificationService: NotificationService,
  ) {
    super();
  }

  async process(job: Job) {
    switch (job.name) {
      case 'send-consult-created-alimtalk':
        return this.notificationService.sendConsultCreatedAlimtalk(
          job.data.consultId,
        );

      default:
        throw new Error(`Unknown job name: ${job.name}`);
    }
  }
}

➕ 9-2. Service 처리 예시

async sendConsultCreatedAlimtalk(consultId: number) {
  const consult = await this.prisma.consult.findUnique({
    where: {
      id: consultId,
    },
  });

  if (!consult) {
    throw new Error('상담 신청 정보를 찾을 수 없습니다.');
  }

  const alreadySent = await this.prisma.notificationLog.findFirst({
    where: {
      relatedType: 'CONSULT',
      relatedId: consultId,
      templateCode: 'CONSULT_CREATED',
      status: 'SUCCESS',
    },
  });

  if (alreadySent) {
    return {
      skipped: true,
      reason: 'already_sent',
    };
  }

  const result = await this.alimtalkClient.send({
    phone: consult.phone,
    templateCode: 'CONSULT_CREATED',
  });

  await this.prisma.notificationLog.create({
    data: {
      relatedType: 'CONSULT',
      relatedId: consultId,
      templateCode: 'CONSULT_CREATED',
      status: result.success ? 'SUCCESS' : 'FAILED',
      providerMessageId: result.providerMessageId,
      errorCode: result.errorCode,
      errorMessage: result.errorMessage,
    },
  });

  if (!result.success) {
    throw new Error(result.errorMessage ?? '알림톡 발송 실패');
  }

  return result;
}
  • 이미 성공한 알림은 중복 발송하지 않도록 확인합니다.
  • 실패 시 에러를 throw하면 BullMQ가 재시도 정책에 따라 다시 실행할 수 있습니다.

✅ 10. Job 상태 관리

  • Queue Job은 처리 상태를 가집니다.
  • 상태를 이해해야 실패 원인과 재처리 상황을 추적할 수 있습니다.
상태의미
waiting처리 대기
active처리 중
completed처리 완료
failed처리 실패
delayed지연 후 실행 대기
pausedQueue 일시 정지

➕ 10-1. 상태 흐름

waiting
  ↓
active
  ↓
completed

또는

waiting
  ↓
active
  ↓
failed
  ↓
delayed
  ↓
active
  ↓
completed
  • 실패 후 재시도 설정이 있으면 delayed 상태로 들어갈 수 있습니다.
  • 최종 실패한 Job은 관리자 확인이나 수동 재처리 대상이 될 수 있습니다.

✅ 11. 재시도 전략

  • Worker 작업은 외부 API 실패나 네트워크 오류 때문에 실패할 수 있습니다.
  • 재시도는 필요하지만 무조건 많이 하면 안 됩니다.

➕ 11-1. 재시도 가능한 실패

TIMEOUT
NETWORK_ERROR
PROVIDER_500
PROVIDER_502
PROVIDER_503
RATE_LIMIT

➕ 11-2. 재시도하면 안 되는 실패

INVALID_PHONE
INVALID_TEMPLATE
AUTH_FAILED
PERMISSION_DENIED
INVALID_PAYLOAD
NOT_FOUND_REQUIRED_DATA

➕ 11-3. BullMQ 재시도 예시

await queue.add(
  'send-alimtalk',
  { consultId },
  {
    attempts: 3,
    backoff: {
      type: 'exponential',
      delay: 5000,
    },
  },
);
  • 재시도 횟수는 제한해야 합니다.
  • 영구 실패는 재시도보다 원인 수정이 먼저입니다.

✅ 12. 멱등성 처리

  • Worker 작업은 같은 Job이 두 번 실행될 수 있다고 가정해야 합니다.
  • 재시도, 서버 재시작, 네트워크 문제, 중복 등록 때문에 같은 작업이 반복될 수 있습니다.

➕ 12-1. 멱등성이 필요한 이유

알림톡 발송 성공
  ↓
성공 로그 저장 전 Worker 종료
  ↓
Job 재시도
  ↓
알림톡 중복 발송 가능

➕ 12-2. 멱등성 키 예시

notification:consult:123:CONSULT_CREATED
export:consults:job:55
webhook:event:payment:evt_1234

➕ 12-3. DB unique 제약 예시

model NotificationLog {
  id           Int      @id @default(autoincrement())
  idempotencyKey String @unique
  relatedType  String
  relatedId    Int
  templateCode String
  status       String
  createdAt    DateTime @default(now())
}
  • 중요한 작업은 DB unique 제약으로 중복 성공을 막는 것이 안전합니다.
  • 코드에서만 체크하면 동시성 상황에서 중복이 발생할 수 있습니다.

✅ 13. Job ID로 중복 등록 방지

  • BullMQ는 Job ID를 지정할 수 있습니다.
  • 같은 Job ID를 사용하면 중복 등록을 줄일 수 있습니다.
await this.notificationQueue.add(
  'send-consult-created-alimtalk',
  { consultId },
  {
    jobId: `notification:consult:${consultId}:created`,
    attempts: 3,
    backoff: {
      type: 'exponential',
      delay: 3000,
    },
  },
);
  • 같은 상담 신청에 같은 알림 Job이 여러 번 등록되는 것을 막을 수 있습니다.
  • 단, Job이 이미 완료 후 제거되면 같은 ID로 다시 등록될 수 있으므로 removeOnComplete 정책도 함께 고려해야 합니다.

✅ 14. Worker Concurrency

  • Concurrency는 Worker가 동시에 몇 개의 Job을 처리할지 정하는 값입니다.
  • 너무 낮으면 처리가 느리고, 너무 높으면 외부 API 제한이나 서버 부하가 발생할 수 있습니다.

➕ 14-1. 예시

@Processor('notification', {
  concurrency: 5,
})
export class NotificationWorker extends WorkerHost {
  async process(job: Job) {
    // Job 처리
  }
}

➕ 14-2. 기준

작업 종류추천 방향
알림톡/SMS외부 API rate limit 고려
엑셀 생성낮은 concurrency 권장
S3 업로드중간 수준 가능
Webhook 후속 처리중요도에 따라 조절
광고 전환 API외부 API 제한 고려
엑셀 생성 Worker concurrency=10
  ↓
대용량 파일 10개 동시 생성
  ↓
메모리 급증
  ↓
서버 불안정
  • 작업 성격에 따라 Queue를 나누고 concurrency를 다르게 설정하는 것이 좋습니다.

✅ 15. Worker와 API 서버 배포 순서

  • API 서버와 Worker가 같은 코드를 공유하더라도 역할은 다릅니다.
  • 배포 순서를 잘못 잡으면 Queue에 등록된 Job을 새 Worker가 처리하지 못하거나, 구 Worker가 새 Job 구조를 이해하지 못할 수 있습니다.

➕ 15-1. 문제 예시

새 API 서버 배포
  ↓
새로운 jobName으로 Queue 등록
  ↓
Worker는 아직 구버전
  ↓
Unknown job name 에러 발생

➕ 15-2. 안전한 배포 기준

  1. 새 Worker가 기존 Job과 새 Job을 모두 처리할 수 있게 만든다.
  2. Worker를 먼저 배포한다.
  3. API 서버를 배포해 새 Job을 등록한다.
  4. Queue 실패 로그를 확인한다.
  5. 구 Job 처리 완료 후 오래된 코드 제거를 검토한다.
1차 배포:
Worker가 oldJobName + newJobName 모두 처리

2차 배포:
API가 newJobName 등록

3차 배포:
oldJobName 제거
  • Queue 구조 변경은 프론트/백 API 변경보다 더 조심해야 합니다.
  • 이미 Redis에 쌓인 Job이 어떤 구조인지 확인해야 합니다.

✅ 16. Worker 장애 대응

  • Worker가 죽어도 API 서버는 정상처럼 보일 수 있습니다.
  • 하지만 알림톡, Export, 외부 API 연동이 처리되지 않고 쌓일 수 있습니다.

➕ 16-1. Worker 장애 증상

상담 신청은 되는데 알림톡이 안 감
엑셀 다운로드가 계속 처리 중
Webhook 후속 처리가 안 됨
외부 API 재시도가 멈춤
Queue waiting 수가 계속 증가

➕ 16-2. 확인할 것

pm2 list
pm2 logs worker --lines 100
docker compose logs -f worker

➕ 16-3. 운영 체크

  • Worker 프로세스가 online인지 확인합니다.
  • Redis 연결이 정상인지 확인합니다.
  • failed Job이 급증했는지 확인합니다.
  • waiting Job이 계속 쌓이는지 확인합니다.
  • 외부 API 인증키나 rate limit 문제가 있는지 확인합니다.

✅ 17. Worker 프로세스 분리 실행

➕ 17-1. PM2 구조 예시

module.exports = {
  apps: [
    {
      name: 'togethermall-api',
      script: 'dist/main.js',
      instances: 2,
      exec_mode: 'cluster',
    },
    {
      name: 'togethermall-worker',
      script: 'dist/worker.js',
      instances: 1,
    },
  ],
};
  • API와 Worker를 서로 다른 PM2 프로세스로 분리할 수 있습니다.
  • Worker는 HTTP 서버를 띄우지 않고 Queue 처리만 담당하도록 구성할 수 있습니다.

➕ 17-2. Docker Compose 구조 예시

services:
  api:
    image: togethermall-api:20260708
    command: node dist/main.js
    ports:
      - "3000:3000"
    env_file:
      - .env.production
    depends_on:
      - redis

  worker:
    image: togethermall-api:20260708
    command: node dist/worker.js
    env_file:
      - .env.production
    depends_on:
      - redis

  redis:
    image: redis:7-alpine
    restart: always
  • 같은 이미지를 사용하되 실행 command만 다르게 할 수 있습니다.
  • API 컨테이너와 Worker 컨테이너를 독립적으로 재시작할 수 있습니다.

✅ 18. Worker 전용 엔트리포인트

  • NestJS 앱에서 API 서버와 Worker를 완전히 분리하려면 Worker 전용 진입 파일을 둘 수 있습니다.

➕ 18-1. main.ts

async function bootstrap() {
  const app = await NestFactory.create(AppModule);

  app.enableCors();

  await app.listen(process.env.PORT ?? 3000);
}

bootstrap();

➕ 18-2. worker.ts

async function bootstrap() {
  await NestFactory.createApplicationContext(WorkerModule);
}

bootstrap();
  • main.ts는 HTTP 서버를 띄웁니다.
  • worker.ts는 HTTP 서버 없이 Queue Worker만 실행합니다.
  • 이렇게 분리하면 API와 Worker 역할이 명확해집니다.

✅ 19. Queue 모니터링

  • Queue는 운영 중 반드시 모니터링해야 합니다.
  • Worker가 죽거나 외부 API가 장애 나면 Job이 쌓이기 때문입니다.

➕ 19-1. 모니터링 지표

지표의미
waiting count처리 대기 Job 수
active count처리 중 Job 수
completed count완료 Job 수
failed count실패 Job 수
delayed count재시도 대기 Job 수
처리 시간Job 하나 처리에 걸리는 시간
실패율전체 대비 실패 비율

➕ 19-2. 위험 신호

waiting count가 계속 증가
failed count가 급증
active Job이 오래 멈춤
delayed Job이 계속 쌓임
Worker 로그에 Redis 연결 오류
외부 API timeout 증가
  • 이런 신호가 있으면 Worker, Redis, 외부 API, 서버 리소스를 확인해야 합니다.

✅ 20. 실패 Job 관리자 화면

  • 실패한 Job을 관리자 페이지에서 확인할 수 있으면 운영이 훨씬 쉬워집니다.
  • BullMQ 내부 상태만 보는 것보다, 서비스 DB에 실패 이력을 남기는 것이 실무적으로 더 유용합니다.

➕ 20-1. 관리자 화면에서 보여줄 항목

작업 종류
연관 데이터 ID
상태
실패 사유
시도 횟수
마지막 시도 시간
재시도 가능 여부
수동 재처리 버튼

➕ 20-2. 실패 이력 모델 예시

model JobFailureLog {
  id           Int      @id @default(autoincrement())
  queueName    String
  jobName      String
  jobId        String?
  relatedType  String?
  relatedId    Int?
  status       String
  attempts     Int      @default(0)
  errorCode    String?
  errorMessage String?
  retryable    Boolean  @default(false)
  createdAt    DateTime @default(now())
  updatedAt    DateTime @updatedAt

  @@index([queueName, status])
  @@index([relatedType, relatedId])
}
  • 실패 이력을 DB에 남기면 관리자 화면, 알림, 재처리 기능과 연결하기 쉽습니다.

✅ 21. Queue와 개인정보

  • Queue Job data에는 개인정보를 최소한만 넣어야 합니다.
  • Redis에 저장된 Job 데이터도 운영 데이터입니다.

➕ 21-1. 좋지 않은 Job data

{
  "name": "홍길동",
  "phone": "01012345678",
  "memo": "상담 메모 전체",
  "address": "서울시 ..."
}

➕ 21-2. 좋은 Job data

{
  "consultId": 123
}
  • Job에는 ID만 넣고, Worker가 DB에서 필요한 데이터를 조회하는 방식이 안전합니다.
  • 개인정보가 Redis, 로그, 실패 이력에 중복 저장되는 것을 줄일 수 있습니다.

✅ 22. 실무 체크리스트

➕ 22-1. Worker 설계 체크리스트

  1. API 서버와 Worker 역할이 분리되어 있는가?
  2. 시간이 오래 걸리는 작업만 Worker로 보내는가?
  3. 핵심 데이터 저장은 API에서 동기 처리하는가?
  4. Queue 이름과 Job 이름이 명확한가?
  5. Producer와 Worker 코드가 분리되어 있는가?
  6. 재시도 횟수와 backoff가 설정되어 있는가?
  7. 멱등성 처리가 되어 있는가?
  8. Job data에 개인정보를 과하게 넣지 않는가?

➕ 22-2. 운영 체크리스트

  1. Redis 연결 상태를 확인할 수 있는가?
  2. Worker 프로세스가 별도로 모니터링되는가?
  3. waiting/failed Job 수를 확인할 수 있는가?
  4. Worker 장애 시 알림을 받을 수 있는가?
  5. 실패 Job을 관리자에서 확인할 수 있는가?
  6. 수동 재처리 기준이 있는가?
  7. Queue 구조 변경 시 배포 순서를 고려하는가?
  8. 오래된 completed/failed Job 정리 정책이 있는가?

➕ 22-3. 배포 체크리스트

  1. 새 Job 이름이 기존 Worker와 호환되는가?
  2. Worker를 먼저 배포해야 하는 변경인가?
  3. Redis에 구버전 Job이 남아 있는가?
  4. Worker 배포 후 로그에 Unknown job name이 없는가?
  5. API 배포 후 Queue 등록이 정상인가?
  6. failed Job이 급증하지 않는가?
  7. Worker와 API의 환경변수가 모두 반영되어 있는가?
  8. rollback 시 Job 처리 호환성이 유지되는가?

✅ 23. AI를 활용해 Worker/Queue 구조를 설계할 때 질문법

  • Worker/Queue 설계는 단순히 BullMQ 코드 작성이 아니라, 어떤 작업을 비동기로 분리할지, 실패를 어떻게 재시도할지, 중복 실행을 어떻게 막을지까지 함께 봐야 합니다.

➕ 23-1. 좋은 질문 예시

NestJS + Prisma + Redis + BullMQ로 Queue Worker 구조를 설계하고 싶어.

상황:
1. 상담 신청 저장 후 알림톡을 발송해야 함
2. 알림톡 실패해도 상담 신청 저장은 성공이어야 함
3. 엑셀 다운로드는 대용량이라 Worker에서 생성 후 S3에 업로드하고 싶음
4. Webhook 수신 후 CRM 연동도 Worker로 분리하고 싶음
5. 같은 알림톡이 중복 발송되면 안 됨
6. 실패한 Job은 최대 3번 재시도하고 싶음
7. 실패 Job은 관리자 페이지에서 확인하고 수동 재처리하고 싶음
8. API 서버와 Worker를 Docker Compose에서 분리 실행할 예정
9. Job data에는 개인정보를 최소화하고 싶음

요청:
- Queue 종류 설계
- Producer/Worker 구조
- BullMQ 설정
- 재시도/backoff 기준
- 멱등성 처리
- NotificationLog/ExportJob/JobFailureLog 모델
- API 서버와 Worker 분리 실행 방식
- 배포 순서와 운영 체크리스트
를 실무 기준으로 정리해줘.

➕ 23-2. AI 답변 검증 기준

  1. 핵심 데이터 저장과 부가 작업을 분리하는가?
  2. API 서버와 Worker를 별도 프로세스로 나누는가?
  3. Redis가 Queue 저장소라는 점을 설명하는가?
  4. 재시도 횟수와 exponential backoff를 포함하는가?
  5. 중복 발송 방지를 위한 멱등성을 설명하는가?
  6. Job data에 개인정보를 넣지 말라고 하는가?
  7. Worker 장애와 Queue 적체 모니터링을 설명하는가?
  8. API/Worker 배포 순서와 Job 호환성을 고려하는가?

📌 요약

  • Worker는 알림톡 발송, 엑셀 생성, 외부 API 연동, Webhook 후속 처리처럼 시간이 오래 걸리거나 실패 가능성이 있는 작업을 백그라운드에서 처리하는 프로세스입니다.
  • Queue는 API 서버와 Worker 사이에서 처리할 작업을 저장하는 대기열이고, Redis는 BullMQ 같은 Queue 시스템의 저장소 역할을 합니다.
  • 핵심 데이터 저장은 API에서 동기 처리하고, 알림/파일 생성/외부 연동 같은 부가 작업은 Worker로 분리하는 것이 안정적입니다.
  • API 서버와 Worker를 같은 프로세스에서 처리하면 무거운 작업이 API 응답 속도에 영향을 줄 수 있으므로, 운영에서는 별도 프로세스나 별도 컨테이너로 분리하는 것이 좋습니다.
  • Worker 작업은 실패와 재시도를 전제로 설계해야 하며, 재시도 횟수 제한과 Exponential Backoff를 적용해야 합니다.
  • 같은 Job이 여러 번 실행될 수 있으므로 알림 발송, 결제 후속 처리, Export 생성 같은 작업에는 멱등성 처리가 필요합니다.
  • Queue Job data에는 개인정보를 직접 넣기보다 consultId, exportJobId처럼 식별자만 넣고 Worker가 DB에서 필요한 정보를 조회하는 방식이 안전합니다.
  • Worker는 죽어도 API 서버가 정상처럼 보일 수 있으므로 waiting/failed Job 수, Worker 로그, Redis 연결 상태를 별도로 모니터링해야 합니다.
  • Queue 구조가 바뀌는 배포에서는 새 Worker가 기존 Job과 새 Job을 모두 처리할 수 있도록 호환성을 고려해야 합니다.

0개의 댓글