TIL - 20260622

juni·2026년 6월 22일

TIL

목록 보기
384/468

0622 백엔드 실무 심화 (2/N): 비동기 작업과 Queue 시스템


✅ 1. 비동기 작업이란 무엇인가?

  • 비동기 작업(Asynchronous Job)은 사용자의 요청을 받은 즉시 모든 작업을 끝내지 않고, 시간이 오래 걸리거나 실패 가능성이 있는 작업을 뒤로 미뤄 처리하는 방식입니다.
  • 사용자는 빠르게 응답을 받고, 서버는 오래 걸리는 작업을 별도 흐름에서 처리합니다.
  • 백엔드 실무에서는 문자 발송, 알림톡 발송, 이메일 발송, 엑셀 생성, 이미지 처리, 외부 API 연동, 통계 집계 같은 작업을 비동기로 분리하는 경우가 많습니다.

➕ 1-1. 동기 처리와 비동기 처리 차이

동기 처리:
사용자 요청
  ↓
DB 저장
  ↓
알림톡 발송
  ↓
문자 발송
  ↓
관리자 알림 생성
  ↓
응답 반환
  • 모든 작업이 끝날 때까지 사용자는 기다려야 합니다.
  • 알림톡 API가 느리거나 실패하면 전체 API 응답도 느려지거나 실패할 수 있습니다.
비동기 처리:
사용자 요청
  ↓
DB 저장
  ↓
알림톡 발송 작업을 Queue에 등록
  ↓
응답 반환

Queue Worker:
알림톡 발송 작업을 나중에 처리
  • 사용자는 빠르게 응답을 받습니다.
  • 알림톡 발송은 별도 작업자가 처리합니다.

✅ 2. Queue란 무엇인가?

  • Queue는 처리해야 할 작업을 순서대로 쌓아두는 대기열입니다.
  • 백엔드 서버가 직접 오래 걸리는 작업을 처리하지 않고, Queue에 작업을 넣어두면 Worker가 하나씩 꺼내서 처리합니다.
API Server
  ↓ 작업 등록
Queue
  ↓ 작업 꺼내기
Worker
  ↓ 실제 처리
외부 API / DB / S3

➕ 2-1. Queue가 필요한 이유

  • 사용자 응답 속도를 빠르게 만들 수 있습니다.
  • 실패한 작업을 재시도할 수 있습니다.
  • 서버 부하를 분산할 수 있습니다.
  • 외부 API 장애가 전체 서비스 장애로 번지는 것을 줄일 수 있습니다.
  • 대량 작업을 순차적으로 처리할 수 있습니다.
  • 작업 이력을 남기고 추적하기 좋습니다.

✅ 3. 비동기로 분리하기 좋은 작업

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

  • 카카오 알림톡 발송
  • SMS 발송
  • 이메일 발송
  • 관리자 Slack 알림
  • 고객 신청 완료 알림
  • 사전예약 일정 안내
상담 신청 API:
상담 신청 저장은 즉시 처리
알림톡/SMS 발송은 Queue로 처리

➕ 3-2. 파일 처리

  • 이미지 리사이징
  • WebP 변환
  • 썸네일 생성
  • S3 업로드 후 메타데이터 정리
  • 대량 엑셀 파일 생성
  • 파일 압축
관리자 엑셀 다운로드:
요청 즉시 대용량 엑셀 생성
  ↓
응답 지연 또는 서버 부하

개선:
엑셀 생성 작업 Queue 등록
  ↓
생성 완료 후 다운로드 링크 제공

➕ 3-3. 외부 API 연동

  • 결제 승인/취소 후속 처리

  • 본인인증 결과 확인

  • 통신사 API 연동

  • 광고 전환 API 전송

  • 네이버/당근 EP 파일 생성 및 업로드

  • CRM/상담 시스템 연동

  • 외부 API는 우리 서버보다 느리거나 불안정할 수 있습니다.

  • 이런 작업을 요청 흐름에 직접 넣으면 사용자 API까지 같이 불안정해집니다.


➕ 3-4. 통계/집계 작업

  • 일별 신청 수 집계

  • 광고 유입별 전환율 계산

  • 상품별 조회 수 집계

  • 관리자 대시보드 통계 생성

  • 월별 정산 데이터 생성

  • 중복 신청/IP 분석

  • 통계 작업은 실시간으로 꼭 처리할 필요가 없는 경우가 많습니다.

  • 주기적으로 Queue나 Cron 작업으로 처리하면 API 응답 부담을 줄일 수 있습니다.


✅ 4. Queue를 쓰지 않아도 되는 작업

  • 모든 작업을 Queue로 빼는 것이 좋은 것은 아닙니다.
  • 즉시 성공/실패가 사용자에게 중요한 작업은 동기 처리해야 합니다.

➕ 4-1. 동기 처리가 필요한 작업

  • 로그인
  • 회원가입 기본 저장
  • 주문 생성 핵심 데이터 저장
  • 결제 승인 결과 확인
  • 상담 신청 핵심 데이터 저장
  • 관리자 상태 변경
  • 권한 확인
  • 중복 신청 검증
상담 신청에서 반드시 동기 처리:
전화번호 중복 검증
신청 데이터 저장
신청 상태 저장

비동기 가능:
신청 완료 알림톡
관리자 알림
광고 전환 이벤트 전송
  • 핵심 데이터 저장은 먼저 확실히 끝내고, 부가 작업을 Queue로 보내는 흐름이 안정적입니다.

✅ 5. Queue 시스템 구성 요소

➕ 5-1. Producer

  • Producer는 Queue에 작업을 등록하는 쪽입니다.
  • 보통 API 서버가 Producer 역할을 합니다.
상담 신청 API
  ↓
sendAlimtalk 작업 Queue 등록

➕ 5-2. Queue

  • Queue는 작업이 쌓이는 대기열입니다.
  • Redis, RabbitMQ, SQS 같은 시스템이 Queue 역할을 할 수 있습니다.

➕ 5-3. Worker

  • Worker는 Queue에서 작업을 꺼내 실제로 처리하는 프로세스입니다.
Worker:
Queue에서 sendAlimtalk job을 꺼냄
  ↓
알림톡 API 호출
  ↓
성공/실패 기록

➕ 5-4. Job

  • Job은 Queue에 등록된 하나의 작업입니다.
  • Job에는 작업 이름과 필요한 데이터가 들어갑니다.
{
  "name": "send-consult-alimtalk",
  "data": {
    "consultId": 123,
    "phone": "01012345678",
    "templateCode": "CONSULT_COMPLETE"
  }
}
  • Job 데이터에는 필요한 최소 정보만 넣는 것이 좋습니다.
  • 개인정보를 그대로 많이 넣기보다 DB id를 넣고 Worker에서 DB를 다시 조회하는 방식도 고려할 수 있습니다.

✅ 6. Redis 기반 Queue

  • Node.js/NestJS 환경에서는 Redis 기반 Queue를 많이 사용합니다.
  • 대표적으로 Bull, BullMQ 같은 라이브러리가 있습니다.
  • Redis는 메모리 기반 저장소이지만, Queue 작업 저장과 재시도 처리에도 많이 활용됩니다.

➕ 6-1. 기본 구조

NestJS API Server
  ↓ add job
Redis Queue
  ↓ process job
NestJS Worker

➕ 6-2. Redis가 필요한 이유

  • Queue 작업을 저장합니다.
  • Worker가 작업을 가져갈 수 있게 합니다.
  • 실패한 작업을 재시도할 수 있습니다.
  • 지연 작업을 예약할 수 있습니다.
  • 여러 Worker가 같은 Queue를 공유할 수 있습니다.

✅ 7. NestJS에서 BullMQ를 사용하는 기본 흐름

  • NestJS에서는 @nestjs/bullmq 또는 Bull 기반 라이브러리를 이용해 Queue를 구성할 수 있습니다.
  • 핵심은 API 서버에서 Job을 등록하고, Processor에서 Job을 처리하는 구조입니다.

➕ 7-1. Queue 등록 개념

await this.notificationQueue.add('send-consult-alimtalk', {
  consultId: consult.id,
});
  • API 요청 중에는 알림톡을 직접 보내지 않고 Queue에 작업만 등록합니다.
  • 응답은 빠르게 반환할 수 있습니다.

➕ 7-2. Worker 처리 개념

@Processor('notification')
export class NotificationProcessor {
  @Process('send-consult-alimtalk')
  async handleSendConsultAlimtalk(job: Job) {
    const { consultId } = job.data;

    // 1. 상담 신청 정보 조회
    // 2. 알림톡 API 호출
    // 3. 발송 결과 저장
  }
}
  • Worker는 Queue에 쌓인 작업을 별도로 처리합니다.
  • 실패하면 재시도하거나 실패 이력을 남길 수 있습니다.

✅ 8. 상담 신청 + 알림톡 Queue 예시

➕ 8-1. 동기 처리와 비동기 처리 분리

동기 처리:
1. 전화번호 중복 검증
2. 상담 신청 저장
3. 신청 이력 저장
4. 알림톡 발송 Job 등록
5. 사용자에게 신청 완료 응답

비동기 처리:
1. Queue에서 Job 꺼냄
2. 상담 신청 정보 조회
3. 알림톡 API 호출
4. 발송 성공/실패 이력 저장
5. 실패 시 재시도 또는 관리자 확인 대상 등록

➕ 8-2. Service 예시

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

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

      await tx.consultHistory.create({
        data: {
          consultId: created.id,
          action: 'CREATED',
          memo: '상담 신청 생성',
        },
      });

      return created;
    });

    await this.notificationQueue.add(
      'send-consult-alimtalk',
      {
        consultId: consult.id,
      },
      {
        attempts: 3,
        backoff: {
          type: 'exponential',
          delay: 5000,
        },
      },
    );

    return {
      success: true,
      consultId: consult.id,
    };
  }
}
  • 핵심 DB 저장은 트랜잭션 안에서 처리합니다.
  • 알림톡 발송 작업은 트랜잭션이 끝난 뒤 Queue에 등록합니다.
  • 알림톡 실패가 상담 신청 저장 자체를 실패시키지 않게 분리했습니다.

✅ 9. 재시도 전략

  • Queue를 쓰는 큰 이유 중 하나가 실패한 작업을 재시도할 수 있다는 점입니다.
  • 외부 API는 일시적으로 실패할 수 있습니다.
  • 네트워크 문제, API 서버 장애, timeout, rate limit 때문에 한 번 실패했다고 바로 포기하면 안 되는 경우가 있습니다.

➕ 9-1. 재시도가 필요한 작업

  • 알림톡 발송
  • SMS 발송
  • 이메일 발송
  • 광고 전환 API 전송
  • 외부 CRM 연동
  • S3 일시 오류
  • 결제 후속 상태 확인

➕ 9-2. 재시도 예시

await this.notificationQueue.add(
  'send-sms',
  {
    consultId: consult.id,
  },
  {
    attempts: 3,
    backoff: {
      type: 'exponential',
      delay: 3000,
    },
  },
);
  • attempts: 3은 최대 3번 시도한다는 뜻입니다.
  • exponential backoff는 실패할수록 재시도 간격을 늘리는 방식입니다.
  • 외부 API에 부담을 주지 않기 위해 즉시 무한 재시도는 피해야 합니다.

✅ 10. 실패 작업 관리

  • Queue 작업은 실패할 수 있다는 전제로 설계해야 합니다.
  • 실패한 작업이 조용히 사라지면 운영자가 문제를 알 수 없습니다.

➕ 10-1. 실패 시 남겨야 할 정보

  • Job 이름
  • 대상 데이터 id
  • 실패 원인
  • 외부 API 응답 코드
  • 재시도 횟수
  • 최종 실패 여부
  • 발생 시간
  • 관리자 확인 여부
send-consult-alimtalk 실패
consultId=123
reason=KAKAO_TIMEOUT
attempts=3
status=FAILED

➕ 10-2. 실패 이력 테이블 예시

model NotificationLog {
  id          Int      @id @default(autoincrement())
  consultId   Int?
  type        String
  status      String
  provider    String?
  errorCode   String?
  errorMessage String?
  attempts    Int      @default(0)
  createdAt   DateTime @default(now())
}
  • 알림 발송이 실패해도 관리자 페이지에서 확인할 수 있어야 합니다.
  • 실패 이력을 남기면 나중에 재발송 기능도 만들 수 있습니다.

✅ 11. Idempotency, 멱등성

  • 멱등성(Idempotency)은 같은 작업이 여러 번 실행되어도 결과가 한 번 실행한 것과 같게 유지되는 성질입니다.
  • Queue 작업은 실패 후 재시도될 수 있기 때문에 멱등성이 중요합니다.

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

알림톡 발송 Job 실행
  ↓
외부 API 발송 성공
  ↓
DB에 성공 기록 저장 전 서버 장애
  ↓
Queue가 실패로 판단하고 재시도
  ↓
알림톡이 두 번 발송될 수 있음
  • Queue는 작업을 다시 실행할 수 있습니다.
  • 그래서 “이미 처리한 작업인지” 확인하는 로직이 필요합니다.

➕ 11-2. 멱등성 처리 예시

const existingLog = await this.prisma.notificationLog.findFirst({
  where: {
    consultId,
    type: 'CONSULT_COMPLETE',
    status: 'SUCCESS',
  },
});

if (existingLog) {
  return;
}
  • 이미 성공한 발송 이력이 있으면 다시 보내지 않습니다.
  • 알림톡, 결제, 포인트, 쿠폰 지급처럼 중복 실행이 위험한 작업에는 멱등성 처리가 중요합니다.

✅ 12. Dead Letter Queue

  • Dead Letter Queue(DLQ)는 여러 번 재시도해도 계속 실패한 작업을 따로 보관하는 Queue입니다.
  • 실패 작업을 바로 버리지 않고, 운영자가 나중에 확인하거나 수동 재처리할 수 있게 합니다.

➕ 12-1. DLQ가 필요한 상황

  • 알림톡 API가 계속 실패
  • 특정 고객 데이터 형식 오류
  • 외부 API 인증키 만료
  • 파일이 이미 삭제됨
  • DB 데이터 정합성 문제
  • 재시도해도 해결되지 않는 작업
일반 Queue
  ↓ 3번 실패
Dead Letter Queue
  ↓
관리자 확인 또는 수동 재처리
  • 작은 서비스에서는 별도 DLQ를 복잡하게 만들기보다 실패 이력 테이블과 관리자 확인 기능으로 시작해도 됩니다.

✅ 13. Cron과 Queue

  • Cron은 정해진 시간에 반복 작업을 실행하는 방식입니다.
  • Queue는 작업을 대기열에 넣고 Worker가 처리하는 방식입니다.
  • 둘은 함께 쓰는 경우가 많습니다.

➕ 13-1. Cron이 필요한 작업

  • 매일 신청 통계 집계
  • 매일 EP 파일 생성
  • 오래된 임시 파일 삭제
  • 실패 알림톡 재처리
  • RDS dump 백업 실행
  • 오래된 로그 정리
  • 사전예약 상태 자동 변경

➕ 13-2. Cron + Queue 구조

Cron Scheduler
  ↓
오늘 처리할 작업 조회
  ↓
Queue에 Job 등록
  ↓
Worker가 순차 처리
  • Cron이 직접 모든 작업을 처리하면 한 번에 부하가 몰릴 수 있습니다.
  • Cron은 작업을 등록하고, Queue가 처리 속도를 조절하는 구조가 더 안정적입니다.

✅ 14. 대량 엑셀 다운로드와 Queue

  • 관리자 페이지에서 수만 건의 주문/상담 데이터를 엑셀로 다운로드할 때 서버 부하가 커질 수 있습니다.
  • 요청 즉시 생성해서 바로 응답하면 timeout이 발생할 수 있습니다.

➕ 14-1. 동기 방식 문제

관리자 엑셀 다운로드 클릭
  ↓
DB 수만 건 조회
  ↓
엑셀 파일 생성
  ↓
응답까지 30초 이상
  ↓
timeout 또는 서버 부하

➕ 14-2. Queue 방식

관리자 엑셀 생성 요청
  ↓
ExportJob 생성
  ↓
Queue에 엑셀 생성 작업 등록
  ↓
사용자에게 "생성 중" 응답
  ↓
Worker가 엑셀 생성
  ↓
S3 업로드
  ↓
관리자에게 다운로드 링크 제공

➕ 14-3. ExportJob 테이블 예시

model ExportJob {
  id          Int      @id @default(autoincrement())
  adminId     Int
  type        String
  status      String
  fileKey     String?
  errorMessage String?
  createdAt   DateTime @default(now())
  completedAt DateTime?
}
  • 관리자 페이지에서 생성 상태를 보여줄 수 있습니다.
  • 실패하면 다시 생성 버튼을 제공할 수도 있습니다.

✅ 15. Queue Worker 운영

  • Queue를 도입하면 API 서버뿐 아니라 Worker도 운영해야 합니다.
  • Worker가 죽으면 Queue에 작업은 쌓이지만 처리되지 않습니다.

➕ 15-1. Worker 실행 방식

pm2 start dist/worker.js --name togethermall-worker

또는 API 서버 안에서 Processor를 함께 실행할 수도 있습니다.

작은 서비스:
API 서버와 Worker를 같은 서버에서 실행

트래픽 증가:
API 서버와 Worker를 분리

➕ 15-2. Worker 모니터링

  • Worker 프로세스가 실행 중인가?
  • Queue에 작업이 과도하게 쌓이지 않는가?
  • 실패 Job이 증가하지 않는가?
  • 재시도 횟수가 비정상적으로 많지 않은가?
  • 외부 API 장애로 Worker가 막히지 않았는가?

✅ 16. Queue 도입 시 주의점

➕ 16-1. 복잡도가 증가한다

  • Queue를 도입하면 Redis, Worker, Job 상태, 재시도, 실패 관리가 추가됩니다.
  • 단순 CRUD 서비스에는 오히려 과한 구조일 수 있습니다.

➕ 16-2. 즉시 결과가 필요한 작업에는 부적합하다

  • 사용자가 바로 결과를 알아야 하는 작업을 Queue로 빼면 UX가 나빠질 수 있습니다.
부적합 예시:
로그인 성공 여부
결제 승인 여부
중복 신청 여부
관리자 권한 확인

➕ 16-3. 중복 실행 가능성을 고려해야 한다

  • Queue 작업은 재시도될 수 있습니다.
  • Worker 장애나 네트워크 문제로 같은 Job이 다시 실행될 수 있습니다.
  • 따라서 멱등성 설계가 필요합니다.

➕ 16-4. 작업 데이터가 너무 커지면 안 된다

  • Job data에 대용량 데이터를 넣기보다 DB id나 S3 key를 넣는 것이 좋습니다.
좋지 않은 예시:
엑셀 전체 데이터 5만 건을 Job data에 넣음

좋은 예시:
exportJobId만 넣고 Worker에서 DB 조회

✅ 17. 실무 체크리스트

➕ 17-1. Queue 도입 체크리스트

  1. 이 작업이 사용자 응답을 늦추는가?
  2. 외부 API 실패 가능성이 있는가?
  3. 실패 시 재시도가 필요한가?
  4. 즉시 결과가 꼭 필요한 작업은 아닌가?
  5. 작업 처리 이력을 남길 수 있는가?
  6. Worker가 죽었을 때 감지할 수 있는가?
  7. 같은 Job이 여러 번 실행되어도 안전한가?
  8. Job data에 민감정보나 대용량 데이터가 들어가지 않는가?

➕ 17-2. 알림톡/SMS Queue 체크리스트

  1. 신청 데이터 저장과 발송 작업이 분리되어 있는가?
  2. 발송 성공/실패 이력을 남기는가?
  3. 실패 시 재시도 횟수가 제한되어 있는가?
  4. 이미 성공한 발송을 중복으로 보내지 않는가?
  5. 외부 API 응답 코드를 저장하는가?
  6. 관리자 페이지에서 실패 건을 확인할 수 있는가?

➕ 17-3. 엑셀/파일 Queue 체크리스트

  1. 대량 조회가 API 응답을 막고 있지 않은가?
  2. 생성 요청 상태를 DB에 저장하는가?
  3. 파일 생성 완료 후 S3 key를 저장하는가?
  4. 다운로드 URL은 만료 시간이 있는가?
  5. 오래된 생성 파일을 Lifecycle로 정리하는가?
  6. 실패한 생성 작업을 재시도할 수 있는가?

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

  • Queue 설계는 “무엇을 비동기로 뺄지”가 핵심입니다.
  • AI에게 질문할 때는 작업 흐름, 실패 가능성, 중복 실행 위험, 즉시 응답 필요 여부를 같이 알려줘야 합니다.

➕ 18-1. 좋은 질문 예시

NestJS + Prisma 서비스에서 상담 신청 후 알림톡 발송을 Queue로 분리하고 싶어.

상황:
1. 상담 신청 데이터는 즉시 DB에 저장해야 함
2. 신청 이력도 함께 저장해야 함
3. 알림톡 발송은 외부 API를 호출함
4. 외부 API가 가끔 timeout 발생
5. 알림톡은 실패 시 최대 3번 재시도하고 싶음
6. 이미 성공한 알림톡은 중복 발송되면 안 됨
7. 실패한 발송은 관리자 페이지에서 확인하고 싶음
8. Redis + BullMQ 사용을 고려 중

요청:
- 어떤 작업을 동기 처리하고 어떤 작업을 Queue로 보낼지
- Job data에는 무엇을 넣을지
- 실패 이력 테이블 구조
- 멱등성 처리 방법
- Worker 운영 시 체크리스트
를 NestJS 기준으로 설명해줘.

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

  1. 핵심 DB 저장과 외부 알림 발송을 분리하는가?
  2. Queue 실패와 재시도를 고려하는가?
  3. 이미 성공한 Job의 중복 실행 방지, 즉 멱등성을 설명하는가?
  4. Job data에 개인정보를 과하게 넣지 말라고 하는가?
  5. 실패 이력 테이블이나 관리자 확인 흐름을 제안하는가?
  6. Worker 프로세스 모니터링을 언급하는가?
  7. Queue가 무조건 정답이 아니라 복잡도도 증가한다고 설명하는가?

📌 요약

  • 비동기 작업은 시간이 오래 걸리거나 실패 가능성이 있는 작업을 사용자 요청 흐름에서 분리해 나중에 처리하는 방식입니다.
  • Queue는 처리할 작업을 쌓아두는 대기열이고, Worker는 Queue에서 작업을 꺼내 실제로 처리합니다.
  • 알림톡, SMS, 이메일, 엑셀 생성, 이미지 처리, 외부 API 연동, 통계 집계는 Queue로 분리하기 좋은 작업입니다.
  • 상담 신청 저장, 주문 생성, 중복 검증, 권한 확인처럼 즉시 결과가 필요한 핵심 작업은 동기 처리해야 합니다.
  • Queue 작업은 실패와 재시도를 전제로 설계해야 하며, 성공/실패 이력과 관리자 확인 흐름이 필요합니다.
  • 같은 Job이 여러 번 실행되어도 문제가 없도록 멱등성을 고려해야 합니다.
  • 대량 엑셀 생성이나 EP 파일 생성 같은 무거운 작업은 Queue와 S3를 함께 사용하면 운영 안정성이 좋아집니다.
  • Queue를 도입하면 Redis, Worker, Job 상태, 실패 관리가 추가되므로 서비스 규모와 필요성을 보고 신중하게 적용해야 합니다.

0개의 댓글