3 min read
Part 9. 분산 환경 배치: Leader Election, Kubernetes CronJob, 락 전략 비교
멀티 인스턴스에서 배치 중복 실행을 방지하고 운영 복잡도를 통제하기 위한 실행 주체/락 전략 선택 기준을 제시한다.
Series: Spring Boot 배치 전략 완전 정복
총 12편 구성. 현재 9편을 보고 있습니다.
- 01Part 1. 배치의 본질과 분류: 스케줄, 이벤트, 수동, 대량, Near-real-time
- 02Part 2. @Scheduled 실전 운영: 단순함의 대가와 멀티 인스턴스 함정
- 03Part 3. Quartz 클러스터 아키텍처: JobStore, Misfire, 대규모 스케줄 관리
- 04Part 4. Spring Batch 핵심: Chunk, 트랜잭션 경계, Restart 가능한 Job 설계
- 05Part 5. Spring Batch 확장: Partition과 Multi-threaded Step의 트레이드오프
- 06Part 6. 수동 배치 전략: REST 트리거, Admin UI, 파라미터 재처리, 롤백
- 07Part 7. DB 대량 조회 전략: OFFSET/LIMIT 한계와 Keyset, ID Range, Covering Index
- 08Part 8. OpenSearch/Elasticsearch 배치 전략: Scroll, Search After, PIT, Bulk, Rollover
- 09Part 9. 분산 환경 배치: Leader Election, Kubernetes CronJob, 락 전략 비교CURRENT
- 10Part 10. 성능 최적화: 배치 사이즈, 커밋 간격, JVM 메모리, Backpressure
- 11Part 11. 장애 대응 아키텍처: Partial Failure, Poison Data, DLQ, 재시도, 멱등성
- 12Part 12. 통합 레퍼런스 아키텍처와 최종 선택 가이드

출처: Pexels - Row of blue shipping containers
버전 기준
- Java 21
- Spring Boot 3.3.x
- Spring Batch 5.2.x
- Quartz 2.3.x
- PostgreSQL 15
- OpenSearch 2.x
1) 문제 제기
분산 환경에서 가장 흔한 배치 장애는 "중복 실행"이다. 애플리케이션이 10개 파드로 확장된 순간, 배치 트리거가 10배가 될 수 있다. 이 문제는 기술 선택의 문제가 아니라 "실행 주체"를 명확히 정의하지 않았기 때문에 발생한다.
핵심 질문은 두 가지다.
- 배치를 누가 트리거할 것인가? (Kubernetes vs 애플리케이션 내부)
- 동시에 하나만 실행된다는 보장을 어디서 만들 것인가? (락/리더 선출)
2) 핵심 개념 정리
Kubernetes CronJob vs 앱 내부 스케줄러
| 기준 | Kubernetes CronJob | 앱 내부 스케줄러 (@Scheduled/Quartz) |
|---|---|---|
| 실행 주체 | 플랫폼(K8s) | 애플리케이션 |
| 배포/재시작 독립성 | 높음 | 앱 라이프사이클 영향 큼 |
| 코드 근접성 | 낮음(외부 Job Pod) | 높음(같은 코드베이스) |
| 운영 난이도 | K8s 의존 | 앱 코드/락 설계 의존 |
| 권장 상황 | 단순 독립 배치, 인프라 표준화 | 도메인 로직과 밀접한 배치 |
락/리더 선출 전략 비교
| 전략 | 장점 | 단점 | 권장 상황 |
|---|---|---|---|
| DB 락 | 강한 일관성, 추가 인프라 없음 | DB 경합 증가 | 트리거 빈도 낮고 DB 중심 |
| Redis 락 | 빠르고 확장성 좋음 | TTL/분할 브레인 고려 | 고빈도 작업 |
| Zookeeper/etcd 선출 | 리더 선출에 강함 | 운영 복잡도 높음 | 대규모 플랫폼 팀 |
| K8s Lease | 쿠버네티스 친화적 | K8s 종속 | K8s 표준 환경 |
분산 실행 제어 다이어그램
Mermaid diagram rendering...

출처: Pexels - Container ships at cargo port
3) 코드 예시
예시 A: Kubernetes CronJob
apiVersion: batch/v1
kind: CronJob
metadata:
name: settlement-job
spec:
schedule: "*/5 * * * *"
concurrencyPolicy: Forbid
successfulJobsHistoryLimit: 3
failedJobsHistoryLimit: 5
jobTemplate:
spec:
template:
spec:
restartPolicy: Never
containers:
- name: settlement
image: my-registry/batch:1.0.0
args: ["--job=settlement"]
예시 B: DB 기반 리더 락
CREATE TABLE batch_leader_lock (
lock_name VARCHAR(100) PRIMARY KEY,
holder_id VARCHAR(100) NOT NULL,
expires_at TIMESTAMP NOT NULL
);
-- 리더 선점 시도
INSERT INTO batch_leader_lock (lock_name, holder_id, expires_at)
VALUES ('settlement', :holder_id, NOW() + INTERVAL '30 second')
ON CONFLICT (lock_name)
DO UPDATE SET holder_id = EXCLUDED.holder_id,
expires_at = EXCLUDED.expires_at
WHERE batch_leader_lock.expires_at < NOW();
예시 C: Spring 서비스에서 락 확인 후 실행
public void runIfLeader() {
boolean acquired = leaderLockRepository.tryAcquire("settlement", instanceId, Duration.ofSeconds(30));
if (!acquired) {
return;
}
try {
settlementService.execute();
} finally {
leaderLockRepository.release("settlement", instanceId);
}
}
예시 D: 실행 이력 키셋 조회
SELECT id, job_name, instance_id, status, started_at
FROM batch_job_execution
WHERE job_name = 'settlement'
AND id > :last_id
ORDER BY id ASC
LIMIT 200;
4) 실제 장애/운영 시나리오
상황: 네트워크 분할로 Redis 락 갱신이 실패했고, 두 인스턴스가 모두 자신이 리더라고 판단해 배치를 동시에 실행했다(Split-brain).
원인:
- 락 획득만 있고 펜싱 토큰(fencing token)이 없었다.
- 하위 Writer가 "최신 토큰만 유효" 규칙을 검증하지 않았다.
- 락 TTL이 짧아 일시 지연 시 재선출이 빈번했다.
개선:
- 리더 락 발급 시 단조 증가 토큰 저장.
- Writer에서 토큰 검증 후 낮은 토큰 작업 거부.
- 락 TTL과 heartbeat 간격을 네트워크 지연 P99 기준으로 재설정.
5) 설계 체크리스트
- 실행 주체를 K8s CronJob 또는 앱 내부 중 하나로 명확히 정의했는가?
-
concurrencyPolicy또는 분산락으로 동시 실행을 차단하는가? - 리더 선출에 펜싱 토큰을 적용했는가?
- 락 만료/갱신 실패 시 중단 및 복구 절차가 있는가?
- 실행 이력과 리더 변경 이력을 추적하는가?
- 플랫폼 팀 역량에 맞는 락 기술(DB/Redis/Zookeeper)을 선택했는가?
6) 요약
분산 배치 설계의 핵심은 "한 번만 실행"이 아니라 "중복 실행이 발생해도 안전"한 구조다. 실행 주체, 락, 멱등성, 펜싱 토큰을 함께 설계해야 운영 리스크를 통제할 수 있다.
7) 다음 편 예고
다음 편에서는 성능 최적화를 다룬다. 배치 사이즈, 커밋 간격, JVM 메모리 계산, Backpressure 설계를 실제 수치 모델로 설명한다.
참고 링크
- Spring Batch Reference
- Quartz Scheduler Documentation
- PostgreSQL Transaction Isolation
- 블로그: Idempotency Key API 설계