Files
document-haness/.agents/skills/rewriting-technical-prose-naturally/examples/23625-rdb-task-queue.md
T
DongHyeonkaandClaude Opus 5 b2963105a8 docs(keycloak-session-store): import the session-storage lab as a new project
The keycloak project ended with four open questions that design could not
settle. A two-VM lab was built to answer them by measurement, and this is
that material: 26 experiments, 125 raw command outputs, 22 browser captures.

Follows the import procedure in README.md.

  source/     the originating repository verbatim — 78 documents, 28 SVGs,
              8 manifests, plus .source-revision recording the commit
  final/      the SSOT
    document.md   729 lines written from the 29 experiment documents, not
                  concatenated: what was predicted, what was measured, and
                  where the measurement itself was wrong
    evidence/raw    125 outputs, flattened to <experiment>__<file> because
                    the originals collided (01-baseline.txt appeared three
                    times) and the audit only globs the top level
    evidence/meta   one per raw file; command and exitCode are null and the
                    README says why rather than inventing them
    evidence/browser  22 captures
    assets/       three diagrams through techviz
    .techviz/     their VizSpecs

A separate project rather than an addition to keycloak: the B-layer answers
that project's four questions, but the A, C and D layers are about cluster
failure, SSO and operations, and one document.md should hold one subject.
The four question records there can point here through 관계.

Recorded rather than papered over: only three of the 28 diagrams were
remade. The repository forbids hand-drawn SVG and forbids titles inside the
canvas; all 28 originals carry both, so converting them is redrawing, not
reformatting. They stay in source/ and the gap is written into the document.

verify-pipeline.py passes. audit-records.py reports no issues.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-04 22:51:59 +09:00

4.6 KiB

장시간 비동기 작업, Kafka 대신 RDB 기반 Task Queue로 해결하기

박민규 · Backend · 2025년 11월 25일

문제 상황

전자계약서 시스템에서 대용량 엑셀 파일 생성 작업은 Kafka를 통해 비동기로 처리되고 있었습니다. 초기에는 대부분의 작업이 10분 이내에 완료되었지만, 신규 엑셀 타입 추가로 인해 각 행마다 여러 외부 API를 호출해야 하면서 처리 시간이 30분 이상으로 증가했습니다.

심각한 문제는 사용자가 "같은 엑셀 파일을 여러 번 받았다"는 문의였습니다. 조사 결과, Kafka의 5분 타임아웃(max.poll.interval.ms)을 초과하면서 리밸런싱이 발생하여 Worker가 비동기 처리한 뒤 즉시 ACK하도록 변경했지만, 이는 작업 유실 위험을 초래했습니다.

근본 원인

"5분 동안 poll()이 호출되지 않으면 Consumer가 죽은 것으로 판단하고" 리밸런싱이 일어납니다. 장시간 작업 중 리밸런싱이 발생하면서 동일 메시지가 다른 Consumer에게 재할당되었습니다. 타임아웃을 증가시키는 임시 해결책은 실제 Worker 장애 감지를 지연시키는 부작용을 초래했습니다.

기존 Kafka 방식의 한계

팀은 엑셀 생성에 Kafka가 정말 필요한지 검토했습니다. Kafka는 대량 트래픽과 다중 consumer group에 유리하지만, 실제로는 트래픽이 일정하고 생산자/소비자가 모두 내부 서비스였습니다. 장시간 작업 특성상 타임아웃 문제가 지속적으로 발생했고, 타임아웃을 늘리면 실제 장애 감지가 1시간 이상 지연되는 딜레마가 생겼습니다.

RDB 기반 Task Queue 아키텍처로 전환

새로운 구조는 다음과 같은 요구사항을 반영했습니다:

  • 시간 제한 없음: 1~2시간 걸리는 작업도 안정적 완료
  • 배포 영향 없음: 서버 배포 시 즉시 중단되어야 함
  • 작업 유실 방지: 서버 다운 시에도 작업 재처리
  • 자동 재시도: 일시적 오류 시 최대 3회 재시도
  • 병렬 처리: 여러 Worker의 분산 처리
  • 중복 방지: 동일 작업의 중복 처리 차단

핵심 구조

테이블 설계:

CREATE TABLE excel_download_request (
    id BIGINT PRIMARY KEY,
    status VARCHAR(20),           -- PENDING, IN_PROGRESS, DONE, FAILED
    last_heartbeat_at DATETIME,   -- Worker 생존 신호 마지막 수신 시각
    retry_count INT DEFAULT 0,    -- 재시도 횟수 (최대 3회)
    created_at DATETIME,
    updated_at DATETIME
);

작업 선점 및 처리: Worker는 3초마다 PENDING 작업을 조회하고 Redis 분산 락으로 선점합니다. 동시에 최대 2개 작업을 병렬 처리합니다(10대 서버 × 2 = 20개의 시스템 처리 용량).

Heartbeat 메커니즘: 작업 중인 Worker는 1분마다 마지막 활동 시각을 갱신합니다. 2분 이상 갱신이 없으면 Fallback 스케줄러가 작업을 PENDING으로 되돌려 다른 Worker가 복구하도록 합니다. 복구 스케줄러는 ShedLock 으로 한 인스턴스만 수행합니다.

재시도 처리: 실패한 작업은 retryCount를 증가시킨 뒤 PENDING으로 되돌립니다. 3회 이상 실패하면 최종적으로 FAILED 상태로 처리됩니다.

개선 효과

강점:

  • Kafka 메시지 플로우 제거로 디버깅 용이
  • 단일 데이터 소스(RDB)로 상태 관리 단순화
  • 메시지 재발행/유실 문제 원천 차단
  • Worker 수평 확장이 단순함
  • 단순 쿼리로 실시간 모니터링 가능

트레이드오프:

  • 지속적인 폴링 쿼리로 DB 부하 증가(커버링 인덱스로 최적화, 분당 20~30회 정도)
  • 폴링 주기(3초)만큼 처리 시작 지연 발생(엑셀 다운로드에서는 허용 가능)

핵심 인사이트

"작업 트랜잭션 특성에 따라 메시징 시스템 선택이 달라져야 한다"는 결론에 도달했습니다. 짧고 빠른 작업은 Kafka 같은 이벤트 스트리밍에 적합하지만, 수십 분 소요되는 복잡한 작업은 상태 관리와 재시도가 용이한 RDB 기반 Task Queue가 더 안정적입니다.