야간 정산 배치가 OutOfMemory로 죽습니다. "데이터를 한 번에 다 올렸나 봐요." 한편 주문 이벤트를 처리하는 Consumer는 lag이 쌓이고, DLQ엔 실패 메시지가 수천 건. "같은 알림이 두 번 갔다"는 클레임도 들어옵니다. PM·인프라인 당신은 배치·비동기 용어를 알아야 이 문제들의 방향을 잡을 수 있습니다. 이 사전은 대량 처리·배치·비동기 용어를 빠르게 해독합니다. 설계 원리는 동기 vs 비동기에서 다룹니다.
- 1배치(Job·Step·Chunk·Reader/Processor/Writer) 구조를 설명할 수 있다
- 2메시지 큐(Producer/Consumer·Topic/Partition/Offset)의 구성을 이해할 수 있다
- 3전달 보장(at-least/most/exactly-once)과 멱등·DLQ의 관계를 설명할 수 있다
- 4백프레셔·재시도큐 등 안정성 용어로 비동기 장애를 진단할 수 있다
배치 처리
확대
위 그림처럼 Chunk 처리는 Reader가 N건씩 읽고 Processor가 가공하고 Writer가 한 번에 쓰는 묶음을 반복합니다. 전체 데이터를 한 번에 메모리에 올리지 않아 OutOfMemory를 방지하며, Chunk 크기는 메모리와 성능의 균형점입니다.
대량 데이터를 안정적으로 처리하는 구조
| 용어 | 한 줄 뜻 | 비고 | 중요도 |
|---|---|---|---|
| Batch / Spring Batch | 대량 일괄 처리 / 자바 배치 프레임워크 | 정산·집계 → Cron/Quartz 장애 분석과 배치 재처리 실무 | ★★ |
| Job / Step / Chunk | 배치 작업 / 단계 / 처리 묶음 | Chunk로 메모리 관리 | ★★ |
| Reader / Processor / Writer | 읽기 / 가공 / 쓰기 | 청크 처리 3요소 | ★★ |
| Scheduler / Cron | 예약 실행 / 크론 표현식 | → cron으로 리눅스 주기적 반복 작업(배치) 예약 및 백업 자동화 | ★★ |
| Bulk Insert / Update / API | 대량 삽입·수정 | 성능 → 대량 데이터 고성능 처리 패턴 Bulk Insert·Update·Delete | ★★ |
핵심: 배치 OutOfMemory의 단골 원인은 '전체를 한 번에 메모리 적재'입니다. Chunk 처리(N건씩 읽고-쓰기 반복)로 메모리 부담 없이 대용량을 처리합니다(대량 데이터 고성능 처리 패턴 Bulk Insert·Update·Delete).
메시지 큐 · 이벤트
확대
위 그림처럼 Producer가 Topic에 발행하면 Partition으로 나뉘고 Consumer Group이 병렬로 소비합니다. Consumer Lag이 우상향이면 소비 속도가 유입을 따라가지 못하는 신호이며, 소비자 증설로 처리량을 확장합니다.
비동기 통신의 부품들
| 용어 | 한 줄 뜻 | 비고 | 중요도 |
|---|---|---|---|
| Queue / Message Queue | 큐 / 메시지 큐 | 분리·완충 → 동기 vs 비동기 | ★★ |
| Kafka / RabbitMQ / Redis Queue / SQS / Pub/Sub | 대표 메시징 시스템 | 스택 → 기술스택 읽기 | ★★ |
| Producer / Consumer / Consumer Group | 발행자 / 소비자 / 소비 그룹 | 그룹=병렬 확장 | ★★ |
| Topic / Partition / Offset / Broker | 채널 / 분할 / 읽은위치 / 큐서버 | Kafka 핵심 | ★★ |
| Event / Event Driven / Async Processing | 이벤트 / 이벤트기반 / 비동기처리 | → 동기 vs 비동기 | ★★ |
핵심: Consumer Group에 소비자를 늘려 처리량을 확장합니다. Consumer lag(소비 지연)이 쌓이면 소비 속도 < 유입 — 소비자 증설이나 처리 최적화 신호입니다.
전달 보장 · 안정성
확대
위 그림처럼 대부분 시스템은 at-least-once(중복 가능)를 사용하므로 멱등 처리가 필수입니다. 멱등 키 없이 중복 메시지를 받으면 중복 결제·중복 알림이 발생하고, DLQ 적체는 특정 메시지가 조용히 반복 실패 중이라는 경고 신호입니다.
중복·실패·역압을 다루는 용어
| 용어 | 한 줄 뜻 | 비고 | 중요도 |
|---|---|---|---|
| Retry Queue / Dead Letter Queue (DLQ) | 재시도 큐 / 실패 격리 큐 | 적체=경고 신호 | ★★★ |
| Backpressure | 소비 못 따라갈 때 유입 조절 | 과부하 방지 | ★★ |
| At-least-once / At-most-once / Exactly-once | 최소1번(중복가능) / 최대1번(유실가능) / 정확1번 | 대부분 at-least-once | ★★★ |
| Idempotent Processing / Duplicate Handling | 멱등 처리 / 중복 처리 | at-least-once 필수 짝 → 요구사항 정의 | ★★★ |
핵심: 대부분 at-least-once(중복 가능)이므로 멱등 처리가 필수입니다 — 안 하면 중복 결제·중복 알림. DLQ 적체는 특정 메시지가 반복 실패 중이라는 '조용한 사고' 신호입니다(동기 vs 비동기).
비동기 장애 진단 — 직접 확인
비동기 시스템은 lag(소비 지연)과 DLQ(실패 격리)로 건강을 봅니다.
점검:
1) Consumer lag 추세: 계속 증가? → 소비 < 유입(소비자 증설/최적화)
2) DLQ 적체: 쌓이고 있나? → 특정 메시지 반복 실패(형식·버그)
3) 중복 처리: 같은 이벤트 2회 처리 흔적? → 멱등 미보장
4) 배치 메모리: OOM? → Chunk 미사용(전체 적재)
Consumer lag: 12,000 → 45,000 (증가 중) → 소비 못 따라감, 소비자 증설
DLQ: 3,200건 (동일 형식 에러) → 잘못된 메시지 형식, 소비자 버그
중복 알림 클레임 발생 → 멱등 키 미적용([[glossary-api-auth]])
야간배치 OOM → 전체 select 후 메모리 적재, Chunk로 전환([[bulk-operations]])
echo 'lag 추세 + DLQ 적체 + 중복 처리 여부 확인'- Consumer lag이 우상향이면 소비 속도 < 유입 → 소비자(그룹 내) 증설 또는 처리 최적화. 방치하면 지연 폭증·메모리 압박
- DLQ가 쌓이면 특정 메시지가 반복 실패 중 → 그 메시지 형식·소비자 버그 조사. DLQ 적체는 "조용히 누적되는 실패"라 알람 필수
- 중복 결제·알림 클레임이 있으면 at-least-once + 멱등 미보장 → Idempotency Key·중복 체크 적용(요구사항 정의)
- 배치 OOM이면 전체 데이터 메모리 적재 의심 → Chunk(N건씩 읽고-쓰기)로 전환(대량 데이터 고성능 처리 패턴 Bulk Insert·Update·Delete). 청크 크기는 메모리·성능 균형
상황: 주문 이벤트를 비동기로 정산 처리하는데, 일부 메시지가 처리 실패해 DLQ로 갔지만 아무도 DLQ를 보지 않아 며칠간 정산 누락이 누적된 뒤에야 발견됩니다.
원인: DLQ에 대한 모니터링·알람 부재입니다. 비동기는 실패가 즉시 사용자에게 안 보여 '조용히' 누적됩니다. DLQ가 안전망 역할을 했지만, 그것을 감시하지 않으면 데이터 유실로 이어집니다.
진단:
□ DLQ 크기에 대한 알람이 있나? (>0이면 경고)
□ 실패 메시지의 공통 원인은? (형식 오류 vs 일시 장애)
□ 재처리 절차가 있나? (고친 뒤 DLQ→원큐 재투입)
해결: (1) DLQ 크기 알람을 걸어 적체를 즉시 감지(용어사전). (2) 실패 원인 분류 — 형식 오류는 생산자/스키마 수정, 일시 장애는 재시도. (3) 원인 수정 후 DLQ 메시지를 재처리하는 절차 마련. 비동기의 실패는 '안 보이는 것'이 가장 위험하므로, DLQ·lag를 관측성에 반드시 포함시킵니다(SLO·에러버짓·포스트모템).
심화 — 전달 보장은 설정이 아니라 소비자 코드가 정한다
심화: ack 시점·리밸런스·멱등이 한 사건에서 만나는 지점
"우리 큐는 at-least-once니까 유실은 없다"는 말은 절반만 맞습니다. 실제 보장 수준은 브로커 설정이 아니라 소비자가 언제 ack(오프셋 커밋)하느냐가 정하고, 운영에서는 리밸런스가 여기에 끼어듭니다.
- ack 시점이 곧 보장 수준입니다: 처리 '전'에 ack하면(자동 커밋의 함정) 처리 중 죽었을 때 그 메시지는 사라집니다 — at-least-once 시스템에서 유실이 나는 전형적 경로입니다. 처리 '후'에 ack하면 죽었을 때 재전달돼 중복이 됩니다. 유실이냐 중복이냐를 소비자 코드의 커밋 위치 한 줄이 가릅니다. 면접 후속 질문 "at-least-once인데 유실이 났다면 어디를 보나요?"의 답이 바로 이 커밋 시점입니다.
- lag 해소책이 중복을 만듭니다: lag이 쌓여 소비자를 늘리면 리밸런스가 일어나고, 처리 중이던 메시지들이 다른 소비자에게 재할당돼 다시 전달됩니다. 즉 '소비자 증설'이라는 표준 대응 자체가 중복 처리를 전제합니다 — 멱등 처리가 안 돼 있으면 lag을 풀려는 조치가 중복 알림·중복 정산 사고를 만듭니다.
- 한 건의 느린 처리가 그룹 전체를 세웁니다: 소비자가 폴링 후 제한 시간(max.poll.interval 류) 안에 처리를 못 마치면 브로커는 소비자가 죽었다고 판단해 리밸런스를 겁니다. 느린 처리가 반복되면 '리밸런스 → 재전달 → 또 느림 → 또 리밸런스' 루프에 빠져, lag은 늘어나는데 실제 소비량은 0에 수렴합니다. lag 그래프가 계단이 아니라 수직으로 치솟으면 이 루프를 의심합니다.
그래서 비동기 시스템의 점검 질문은 "at-least-once인가?"가 아니라 "ack은 처리 전인가 후인가, 재전달을 멱등으로 받는가, 리밸런스가 반복되고 있는가"입니다(동기 vs 비동기).
상황: 트래픽 증가로 앱 인스턴스를 1대에서 2대로 늘렸습니다. 다음 달 정산에서 일부 금액이 정확히 2배로 집계돼, 처음엔 정산 로직 버그를 의심하고 계산 코드만 뒤졌습니다.
원인: 정산 배치가 앱에 내장된 스케줄러(@Scheduled 류)로 돌고 있었습니다. 내장 스케줄은 인스턴스마다 각자 발화하므로, 2대가 된 순간 같은 Job이 같은 시각에 두 번 실행된 것입니다. 코드는 한 줄도 바뀌지 않았고 달라진 것은 인스턴스 수뿐이라, 아무도 배치를 의심하지 않아 발견이 한 달 늦었습니다.
진단:
□ 같은 Job의 시작 로그가 같은 시각, 서로 다른 호스트명으로 두 줄 찍히는가?
□ 배치가 앱 내장 스케줄러인가, 외부 스케줄러(전용 배치 실행 환경)인가?
□ Job에 실행 이력 체크(이번 달 정산 이미 완료)가 있는가?
해결: (1) 스케줄 실행에 분산 락을 걸어 여러 인스턴스 중 하나만 실행하게 하거나(ShedLock 류), 배치를 스케일 대상이 아닌 전용 실행 환경으로 분리합니다(Cron/Quartz 장애 분석과 배치 재처리 실무). (2) Job 자체를 멱등하게 — '기간+대상' 실행 이력을 남겨 중복 기동 시 건너뛰게 합니다. (3) 이중 실행은 에러 없이 조용히 진행되므로 '같은 Job 동시 실행 감지' 알람을 둡니다(용어사전). 스케일아웃 계획에는 "내장 스케줄 배치가 있는가"를 체크리스트로 넣어야 합니다 — 이 사고는 코드 변경 없이 인프라 변경만으로 발생합니다.
인프라/SRE로서 메시징·배치 도입은 새로운 운영 책임을 동반합니다 — 브로커 클러스터 이중화·디스크/보존기간 산정·Consumer lag·DLQ 알람을 설계합니다(용어사전). "비동기로 빼자"는 결정에 이 운영 비용을 함께 견적해야 합니다(모놀리식 vs 마이크로서비스). PM은 비동기 요구사항에 멱등·DLQ·재처리 절차를 인수 기준으로 명시하고(요구사항 정의), '조용한 실패(DLQ 적체)'가 데이터 사고로 번지지 않도록 모니터링을 요구합니다. 비동기는 응답을 빠르게 하지만, 보이지 않는 실패를 보이게 만드는 설계가 함께 가야 합니다.
다음 용어사전에서는 성능을 끌어올리는 캐시·세션 용어를 정리합니다.
용어 식별 실습으로 굳히기: 용어 식별 — 대량 처리 / 배치 / 비동기 — 증상·문구를 보고 배치·큐·멱등성 용어를 가려냅니다.