---
type: knowledge
domain: backend
status: active
last-reviewed: 2026-07-27
---

# 백그라운드 작업과 스케줄링

> 한 줄 정의
> HTTP 요청-응답 안에서 끝낼 수 없는 일을 "나중에, 반드시, 딱 필요한 만큼" 실행하는 규율. 큐·DLQ의 채택 기준은 [[백엔드 원리]] 신뢰성 표, 분산 전달 보장은 [[분산 신뢰성 패턴]] — 여기는 작업·스케줄러 구현의 함정이다.

## 큐 도입 기준 — 타임아웃이 선을 긋는다

- 기준은 하나: **HTTP 타임아웃(수 초) 안에 못 끝내는 작업**인가. 파일 변환·대량 메일·외부 API 연쇄·리포트 생성이면 큐, 아니면 동기 처리가 정답이다.
- 패턴은 [[백엔드 원리]] 비동기 처리 그대로: 202 + job id → 상태 조회/알림. 작업 상태는 DB의 명시적 상태 필드(queued → running → done/failed)로. 상태 행이 큐가 되려면 최소 규약이 더 붙는다 — **원자적 선점**(조건부 UPDATE로 claim), 시도 횟수 기록, 워커 죽음 대비 타임아웃 회수.
- 전용 큐 인프라(RabbitMQ·SQS류)는 승격 조건이 생길 때 — **DB 테이블 큐로 시작해도 하루 수천 건 규모까지는 대체로 충분하다**(감각치 — 실제 상한은 부하로 검증). 발주사 폐쇄망에서 매니지드 큐를 못 쓰는 경우에도 이게 기본 경로다.

## 작업은 멱등하게 — 재시도가 전제다

큐에 들어간 작업은 **반드시 두 번 실행될 수 있다**(워커 크래시 후 재전달, 재시도, 수동 재처리). 멱등하지 않은 작업을 큐에 넣는 것 자체가 결함이다.

| 기법 | 예 |
|------|-----|
| 자연 키 원자 선점 | "주문 #123 확정 메일"은 발송 이력에 유니크 키 INSERT로 먼저 선점한 뒤 발송 — 확인-후-발송(check-then-act)은 동시 실행에 진다 |
| 상태 전이 조건부 갱신 | `UPDATE ... WHERE status = 'pending'` — 이미 처리됐으면 0행 |
| 멱등 키 | 외부 API 호출에 작업 ID를 멱등 키로 전달 |

리뷰 표준 질문: **"이 작업이 두 번 돌면 어떻게 되나?"** → [[분산 신뢰성 패턴]] 3절.

## 재시도·백오프·DLQ — 3종 세트

- 재시도는 **일시 오류(타임아웃·5xx·락 충돌)만**, 지수 백오프 + 최대 횟수. 영구 오류(검증 실패·404)의 재시도는 같은 실패의 반복이다 → [[외부 연동과 웹훅]] 호출 4계명.
- 최대 횟수 소진 = **DLQ(실패 격리)로 이동 + 알림**. DLQ 없는 재시도는 두 갈래뿐이다: 무한 재시도로 큐를 막거나, 조용히 버려지거나.
- DLQ는 재처리 경로가 세트다 — 원인 수정 후 되돌릴 수단이 없으면 격리가 아니라 무덤이다.

## 스케줄러 함정 — 4대 단골

| 함정 | 전형 | 처방 |
|------|------|------|
| 다중 인스턴스 중복 실행 | 서버 2대 = 같은 cron 2번 실행, 정산 이중 처리 | 실행 슬롯 유니크 키(작업명+주기 INSERT)·멱등 설계가 먼저, 분산 락·리더 선출은 그다음 ([[분산 신뢰성 패턴]] 우선순위) — "인스턴스 1대니까"는 스케일아웃 첫날 깨진다 |
| 이전 실행과 겹침 | 5분 주기 작업이 7분 걸리면 동시 2개 | 실행 중 플래그·락으로 스킵 + 소요 시간 기록 |
| 시간대 | 서버 UTC vs 업무 KST, DST 있는 리전 | 스케줄 기준 시간대를 명시 설정 — 서버 기본값 신뢰 금지 |
| 놓친 실행 | 배포·다운 중 지나간 스케줄 | "놓치면 스킵인가 따라잡기인가"를 작업마다 결정해 둔다 |

## cron 표현식 — 검증 없이 배포하지 않는다

- 5필드/6필드(초 포함) 체계가 도구마다 다르다 — 표준 5필드에서 `0 0 * * *`는 매일 자정이지만, 초-우선 런타임은 같은 문자열을 거부하거나 다르게 읽는다. 채택 런타임의 문법부터 확인한다.
- 요일과 일(day-of-month)을 동시에 쓰면 OR로 동작하는 구현이 있다 — 의도와 다른 대표 사례.
- 배포 전 다음 실행 시각 N개를 계산해 보는 검증(라이브러리·온라인 도구)을 절차에 넣는다. "매월 말일" 같은 요구는 cron으로 안 되는 경우가 많다 — 매일 실행 + 코드 판정이 안전하다.

## 작업 상태의 관측 — 무성 실패 금지

- 최소 지표: **큐 적체(대기 건수·최고 대기 시간) · 실패율 · DLQ 크기 · 스케줄 작업의 마지막 성공 시각**.
- 특히 "마지막 성공 시각" 감시가 핵심이다 — 스케줄러가 죽으면 에러 로그조차 없다. **실행됐다는 신호가 아니라 실행 안 됐다는 침묵을 잡아야 한다.**
- 실패는 상태 필드 + 실패 사유 기록까지가 한 세트. 로그에만 남는 실패는 외주 검수에서 "미발견 결함"으로 돌아온다.

## 안티패턴

- **fire-and-forget 스레드** — 앱 프로세스 안에서 스레드만 띄우면 재시작 시 작업이 증발한다. 큐가 과하면 최소한 DB 상태 기록이라도.
- **재시도 간격 고정 1초** — 장애 난 외부 시스템에 초당 폭격 = 복구 방해. 백오프는 예의가 아니라 생존이다.
- **거대 작업 하나** — 3시간짜리 작업은 실패 시 3시간을 다시 산다. 청크로 쪼개 재개 가능하게 → [[DB 접근과 트랜잭션 실전]] 대량 처리.
- **스케줄 작업 목록이 코드에만** — 어떤 배치가 언제 도는지 운영 문서 없이 이관하면 발주사는 "멈춰도 모르는 시스템"을 인수한다.

## 관련 문서

- [[00_백엔드 허브]] — 06_백엔드 진입점
- [[백엔드 원리]] — Queue·DLQ·Scheduler 채택 기준표, 비동기 처리
- [[분산 신뢰성 패턴]] — at-least-once·멱등 소비자·분산 락 우선순위
- [[외부 연동과 웹훅]] — 재시도 대상 판별, 웹훅 비동기 처리
- [[DB 접근과 트랜잭션 실전]] — 배치 청크·재개 가능 설계
