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 연동
➕ 3-4. 통계/집계 작업
✅ 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;
}
}
- 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 도입 체크리스트
- 이 작업이 사용자 응답을 늦추는가?
- 외부 API 실패 가능성이 있는가?
- 실패 시 재시도가 필요한가?
- 즉시 결과가 꼭 필요한 작업은 아닌가?
- 작업 처리 이력을 남길 수 있는가?
- Worker가 죽었을 때 감지할 수 있는가?
- 같은 Job이 여러 번 실행되어도 안전한가?
- Job data에 민감정보나 대용량 데이터가 들어가지 않는가?
➕ 17-2. 알림톡/SMS Queue 체크리스트
- 신청 데이터 저장과 발송 작업이 분리되어 있는가?
- 발송 성공/실패 이력을 남기는가?
- 실패 시 재시도 횟수가 제한되어 있는가?
- 이미 성공한 발송을 중복으로 보내지 않는가?
- 외부 API 응답 코드를 저장하는가?
- 관리자 페이지에서 실패 건을 확인할 수 있는가?
➕ 17-3. 엑셀/파일 Queue 체크리스트
- 대량 조회가 API 응답을 막고 있지 않은가?
- 생성 요청 상태를 DB에 저장하는가?
- 파일 생성 완료 후 S3 key를 저장하는가?
- 다운로드 URL은 만료 시간이 있는가?
- 오래된 생성 파일을 Lifecycle로 정리하는가?
- 실패한 생성 작업을 재시도할 수 있는가?
✅ 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 답변 검증 기준
- 핵심 DB 저장과 외부 알림 발송을 분리하는가?
- Queue 실패와 재시도를 고려하는가?
- 이미 성공한 Job의 중복 실행 방지, 즉 멱등성을 설명하는가?
- Job data에 개인정보를 과하게 넣지 말라고 하는가?
- 실패 이력 테이블이나 관리자 확인 흐름을 제안하는가?
- Worker 프로세스 모니터링을 언급하는가?
- Queue가 무조건 정답이 아니라 복잡도도 증가한다고 설명하는가?
📌 요약
- 비동기 작업은 시간이 오래 걸리거나 실패 가능성이 있는 작업을 사용자 요청 흐름에서 분리해 나중에 처리하는 방식입니다.
- Queue는 처리할 작업을 쌓아두는 대기열이고, Worker는 Queue에서 작업을 꺼내 실제로 처리합니다.
- 알림톡, SMS, 이메일, 엑셀 생성, 이미지 처리, 외부 API 연동, 통계 집계는 Queue로 분리하기 좋은 작업입니다.
- 상담 신청 저장, 주문 생성, 중복 검증, 권한 확인처럼 즉시 결과가 필요한 핵심 작업은 동기 처리해야 합니다.
- Queue 작업은 실패와 재시도를 전제로 설계해야 하며, 성공/실패 이력과 관리자 확인 흐름이 필요합니다.
- 같은 Job이 여러 번 실행되어도 문제가 없도록 멱등성을 고려해야 합니다.
- 대량 엑셀 생성이나 EP 파일 생성 같은 무거운 작업은 Queue와 S3를 함께 사용하면 운영 안정성이 좋아집니다.
- Queue를 도입하면 Redis, Worker, Job 상태, 실패 관리가 추가되므로 서비스 규모와 필요성을 보고 신중하게 적용해야 합니다.