이 글에서 얻는 것
- 배치 시스템을 설계할 때 **정확성(중복/누락 최소화)**과 **처리량(시간 내 완료)**을 동시에 맞추는 기준을 가져갑니다.
- “실패하면 재실행” 수준이 아니라, 어디서부터 어떻게 재처리할지를 데이터 단위로 정의하는 방법을 익힙니다.
- 운영 중 의사결정을 감(느낌)으로 하지 않도록, 지표·임계치·우선순위를 숫자로 정리합니다.
배치는 서비스 트래픽의 전면에 있지 않다는 이유로 설계가 뒤로 밀리는 경우가 많습니다. 하지만 정산, 통계, 추천 피처 생성, 로그 집계처럼 배치가 틀어지면 비즈니스 신뢰도가 직접 흔들리는 영역이 실제로 더 많습니다. 특히 규모가 커질수록 문제는 비슷하게 반복됩니다. “중복으로 두 번 반영됨”, “한 구간이 누락됨”, “실패 복구하는데 사람이 SQL을 수작업으로 만짐” 같은 유형입니다.
핵심은 화려한 프레임워크보다 재실행 가능한 구조를 먼저 만드는 것입니다.
핵심 개념/이슈
1) 정확히 한 번 처리(Exactly-once)는 목표가 아니라 비용이 큰 환상에 가깝다
분산 환경에서 end-to-end exactly-once를 완전히 보장하려고 하면 시스템 복잡도와 비용이 급격히 증가합니다. 실무에서는 보통 다음 방식이 더 현실적입니다.
- 전달은 at-least-once를 받아들임
- 처리 단계에서 멱등성으로 중복 영향 제거
- 결과 저장 시 유니크 키/버전으로 중복 반영 차단
즉 “중복 전송은 허용하지만 중복 반영은 허용하지 않는다”가 운영 가능한 정답에 가깝습니다.
2) 재처리는 ‘잡 전체 재실행’이 아니라 ‘데이터 경계 기반 재실행’이어야 한다
전체 잡을 다시 돌리면 간단해 보이지만, 데이터량이 커질수록 비용과 리스크가 커집니다. 재처리의 기본 경계는 보통 아래 3개를 조합합니다.
- 시간 경계:
2026-03-04 12:00~13:00구간만 재처리 - 엔터티 경계: 특정 고객/상점/주문 ID 범위만 재처리
- 상태 경계: 실패 레코드(FAILED)만 재처리
경계 정의가 없는 배치는 장애 때마다 수동 스크립트가 늘어나고, 결국 “복구가 불가능한 시스템”이 됩니다.
3) 체크포인트와 워터마크는 성능 기능이 아니라 신뢰성 기능이다
체크포인트를 단순히 “중간 저장”으로 생각하면 설계가 약해집니다. 체크포인트는 다음 질문에 답해야 합니다.
- 어디까지 성공으로 커밋됐는가?
- 다음 실행은 어디서 시작해야 하는가?
- 동일 레코드가 다시 들어오면 어떻게 판별할 것인가?
권장 실무 기준:
- 체크포인트 저장 주기: 1,000~10,000건 단위(처리 비용과 롤백 비용 균형)
- 단일 트랜잭션 처리 시간: 1~5초 내 유지(락/장기 트랜잭션 위험 감소)
- 재시도 상한: 동일 레코드 최대 3회, 이후 DLQ 이동
4) 실패를 숨기지 말고 분류해야 운영이 빨라진다
실패를 단일 FAIL 상태로만 저장하면 복구 난이도가 높아집니다. 최소한 아래처럼 분류하세요.
- RETRYABLE: 네트워크 타임아웃, 일시적 락 경합
- NON_RETRYABLE: 스키마 불일치, 필수 필드 누락, 비즈니스 불변식 위반
- POISON: 재시도해도 동일하게 깨지는 데이터
분류 기준이 있으면 알람 우선순위도 정해집니다. RETRYABLE 증가는 인프라/의존성 이슈 가능성이 높고, NON_RETRYABLE 증가는 데이터 계약(API/스키마) 문제일 확률이 높습니다.
실무 적용
1) 배치 멱등성 설계 템플릿
아래 4가지를 문서로 먼저 고정하면 구현이 훨씬 안정됩니다.
- 멱등 키:
source_system + business_id + event_date같은 조합 - 중복 판정 저장소: 결과 테이블 유니크 인덱스 또는 processed_event 테이블
- 재실행 정책: 동일 멱등 키 재수신 시 skip/update/merge 중 하나로 고정
- TTL 정책: 멱등 키 보관 기간(예: 30일, 90일)
의사결정 기준:
- 정산/결제/포인트: skip보다 dedupe + 감사로그 유지 우선
- 집계/리포트: 최신값 overwrite 허용 가능
- 추천/랭킹 피처: 중복 허용 범위가 상대적으로 큼(정확성보다 지연 민감)
2) 배치 SLA/SLO를 숫자로 정의하기
“새벽 배치가 가끔 늦다” 같은 표현은 개선을 못 만듭니다. 아래처럼 숫자로 고정하세요.
- 완료 SLO: 06:00 이전 99% 완료
- 데이터 정확성 SLO: 중복 반영률 0.01% 미만
- 복구 SLO: 단일 구간 실패 시 30분 내 재처리 완료
- 지연 경보: 예상 완료시간 대비 +20% 초과 시 경고
운영 우선순위(권장):
- 정확성(중복/누락)
- 복구 가능성(재처리 시간)
- 처리량(총 소요 시간)
- 비용(컴퓨트/스토리지)
정확성이 깨진 배치는 빠르게 끝나도 의미가 없습니다.
3) DLQ와 재처리 큐 운영 규칙
DLQ를 “버리는 공간”으로 쓰면 안 됩니다. DLQ는 지연된 업무 큐입니다.
- DLQ 적체 임계치: 전체 처리량의 0.5% 초과 시 즉시 점검
- 동일 에러 코드 반복: 50건 이상이면 자동 재시도 중단 + 알람 승격
- 재처리 배치 윈도우: 본 배치와 리소스 분리(예: 06:30~07:00)
또한 재처리에도 멱등 키를 그대로 써야 합니다. 재처리 경로에서 키를 새로 만들면 중복 반영이 다시 발생합니다.
4) 트랜잭션 경계와 청크 사이즈 설정
청크를 너무 크게 잡으면 롤백 비용이 커지고, 너무 작게 잡으면 오버헤드가 증가합니다.
실무 시작값 예시:
- DB 중심 배치: 청크 500~2,000건
- 외부 API 포함 배치: 청크 50~200건 + 강한 타임아웃
- 워커 동시성: CPU 코어 수의 1~2배에서 시작, DB 커넥션 풀 상한과 함께 조정
조정 기준:
- p95 처리시간이 목표 대비 30% 초과면 청크 축소 또는 동시성 제한
- DB lock wait 비율이 5% 넘으면 청크 축소 우선
- GC/메모리 압박이 높으면 청크 축소 + 스트리밍 처리 검토
트레이드오프/주의점
강한 멱등성 vs 저장 비용
멱등 키를 오래 보관할수록 안전하지만 저장 비용이 증가합니다. 정산 도메인은 보관 기간을 길게, 통계 도메인은 상대적으로 짧게 두는 식으로 도메인별 차등이 필요합니다.작은 청크 vs 처리량
작은 청크는 복구에 유리하지만 처리량이 떨어질 수 있습니다. 배치 창(마감 시간)이 촉박한 시스템은 청크를 키우되, 실패 시 부분 재처리 경계를 더 촘촘히 잡아야 합니다.재시도 적극 사용 vs 장애 증폭
무조건 재시도는 다운스트림 장애를 확대합니다. 재시도는 RETRYABLE 에러에만, 최대 횟수와 지수 백오프를 명확히 둬야 합니다.자동 복구 vs 사람 검토
비즈니스 불변식이 깨질 수 있는 데이터(정산 차감, 법적 보고)는 자동 재처리보다 수동 승인 단계를 두는 편이 안전합니다.
체크리스트 또는 연습
- 멱등 키가 문서화되어 있고 코드/DB 스키마에 반영되어 있다.
- 실패 레코드가 RETRYABLE/NON_RETRYABLE/POISON으로 분류된다.
- 재처리 경계(시간/엔터티/상태)가 정의되어 있다.
- 완료 SLO, 정확성 SLO, 복구 SLO가 숫자로 정의되어 있다.
- DLQ 적체 임계치와 알람 승격 규칙이 있다.
연습 과제:
- 최근 배치 장애 1건을 골라, “전체 재실행” 대신 “구간 재처리”로 복구하는 절차를 10단계 내로 작성해보세요.
- 멱등 키 충돌 가능성을 점검하기 위해 1주 데이터 샘플에서 키 중복률을 측정해보세요.
- 청크 크기를 500→1000→2000으로 바꿔 처리시간, lock wait, 실패 복구시간을 비교해 운영 기준을 확정해보세요.
💬 댓글