콘텐츠로 이동
로봇 작업자들이 리더 선출 저장소 작업대에 Redis, etcd, SQL, Kubernetes, 관측성 도구를 배치하는 일러스트
백엔드 선택에 따라 리스, TTL, 갱신, 관측 방식이 달라집니다.

Part 4에서는 Spring Boot 애너테이션/AOP와 Ktor 애플리케이션 생명주기 경계를 살펴봤습니다. Part 5에서는 리더십을 저장하는 백엔드를 다룹니다. 같은 runIfLeader를 호출하더라도 Redis, etcd 리스, SQL 행, Kubernetes Lease 중 어디에 상태를 저장하는지에 따라 장애 대응과 관측 방법이 달라집니다.

백엔드는 클라이언트 API의 형태보다 운영 조건을 기준으로 선택해야 합니다. 정상적인 잠금 경합은 조용히 건너뛰되, 장애가 발생하면 어떤 노드가 작업을 실행하거나 건너뛰었는지, 리스를 갱신했는지, 어느 백엔드에서 실패했는지 설명할 수 있어야 합니다.

먼저 운영 중인 인프라의 재사용성을 검토한다

섹션 제목: “먼저 운영 중인 인프라의 재사용성을 검토한다”
작업 형태와 운영 기반에 따라 리더 선출 저장소를 고르는 결정 지도
백엔드 선택 지도는 성능 순위표가 아니라 현재 운영 중인 인프라에서 출발하는 선택 기준입니다.

처음 선택은 대부분 이 질문으로 충분합니다.

현재 운영 중인 인프라우선 검토할 백엔드이유
Redis를 공통 캐시·잠금 저장소로 사용함Lettuce 또는 RedissonRedis TTL과 소유자 토큰으로 단일 리더 잠금을 구성할 수 있습니다. Lettuce는 경량 클라이언트 경로에, Redisson은 잠금 중심의 운영 경험을 활용할 때 적합합니다.
etcd가 제어 평면의 기준 저장소임etcd v3리스와 조건부 쓰기가 조정기와 제어 평면 작업에 잘 맞습니다.
DB 운영팀이 장애 대응 범위를 관리함Exposed JDBC/R2DBC잠금 행, 토큰, 만료 시각을 SQL로 확인하고 마이그레이션·감사 체계와 연계할 수 있습니다.
워크로드가 Kubernetes에서 실행됨Kubernetes Leasecoordination.k8s.io/v1 Lease를 Pod·오퍼레이터 생명주기와 함께 관리할 수 있습니다.
MongoDB, DynamoDB, Consul, Hazelcast, ZooKeeper를 이미 운영함해당 백엔드새 인프라를 추가하지 않고 기존 운영 지식으로 리더 잠금을 관리할 수 있습니다.

리더 선출 백엔드는 성능 수치만으로 선택하기 어렵습니다. 장애가 발생했을 때 리스를 갱신할 수 있는 주체, 소유자 토큰 검증 방식, 만료된 소유권의 정리 방법, 운영자가 상태를 확인할 위치가 백엔드마다 다릅니다.

Redis: Lettuce와 Redisson을 우선 검토한다

섹션 제목: “Redis: Lettuce와 Redisson을 우선 검토한다”

이미 Redis를 캐시, 호출률 제한, 단기 조정에 사용한다면 리더 잠금도 같은 운영 환경에서 관리할 수 있습니다.

bluetape4k-leader는 Redis 계열을 두 가지로 제공합니다.

백엔드적합한 상황주의점
leader-redis-lettuceLettuce를 직접 운영하면서 블로킹·코루틴 경로를 가볍게 구성할 때리스 TTL, 토큰 검증, 자동 갱신을 애플리케이션 계약으로 이해해야 합니다.
leader-redis-redissonRedisson의 잠금 API와 운영 방식에 익숙할 때선출자가 명시적인 leaseTime을 전달하므로 벤치마크의 autoExtend는 Redisson 기본 watchdog이 아니라 bluetape4k 공통 갱신 경로입니다.

긴 작업에서는 autoExtend = true로 리스를 갱신할 수 있습니다. 그룹 선출의 자동 갱신은 아직 지원하지 않습니다. 각 슬롯에 별도의 리스와 완료 경계가 필요하기 때문입니다.

val options = LeaderElectionOptions(
leaseTime = 30.seconds,
autoExtend = true,
)
val election = RedissonLeaderElector(client, options)
election.runIfLeader("nightly-maintenance") {
LockAssert.assertLocked("nightly-maintenance")
LockExtender.extendActiveLock("nightly-maintenance", 60.seconds)
runLongJob()
}

LockAssert는 현재 코드가 실제 잠금 범위에서 실행 중인지 검증하는 보호 장치입니다. fail-open 센티널 범위나 잠금 밖에서는 통과하지 않습니다. LockExtender는 현재 잠금의 만료 시간을 연장합니다. 기존 Boolean 반환값만으로는 실패 원인을 구분하기 어려우므로 상세 API는 ExtendOutcome.NotHeld, WrongThread, BackendError를 구분합니다.

etcd: 제어 평면 작업에 잘 맞는다

섹션 제목: “etcd: 제어 평면 작업에 잘 맞는다”

etcd는 제어 평면 상태를 이미 etcd에 저장하는 팀에 적합합니다. Kubernetes 밖에서 조정기를 실행하거나 목표 상태를 한 노드만 적용해야 한다면 etcd 리스를 사용할 수 있습니다.

Redis가 애플리케이션의 공통 인프라라면 etcd는 제어 평면의 기준 저장소에 가깝습니다. 서비스가 Redis를 사용한다는 이유만으로 리더십도 Redis에 저장할 필요는 없습니다. 작업의 기준 상태가 etcd에 있다면 etcd를 우선 검토하는 편이 운영 경계를 단순하게 유지합니다.

examples/etcd-reconciler는 한 노드만 목표 상태를 적용하는 제어 평면 조정기 흐름을 보여줍니다.

나머지 잠금 저장소는 운영 환경에 맞게 선택한다

섹션 제목: “나머지 잠금 저장소는 운영 환경에 맞게 선택한다”

Redis와 etcd 외에도 선택지가 있습니다. 백엔드의 성능 순위를 정하기보다 현재 운영 환경과 저장 의미에 맞는 구현을 고르는 것이 이 표의 목적입니다.

계열저장 의미선택 기준
Exposed JDBC/R2DBC잠금 행, 토큰, 만료 시각, 선택적 이력 행마이그레이션 관문, 테넌트 집계, 감사 비중이 큰 배치처럼 SQL에서 상태를 직접 확인해야 할 때 적합합니다.
MongoDBfindOneAndUpdate와 TTL 인덱스 기반 소유권MongoDB를 운영 저장소로 사용하면서 단기 소유권을 문서로 관리할 때 적합합니다.
DynamoDB조건부 항목 쓰기와 논리적 TTLAWS 중심 운영 환경에서 검토할 수 있습니다. 벤치마크 측정값의 오차가 크므로 반복 측정한 뒤 판단해야 합니다.
ConsulSession + KV서비스 유지 보수와 트래픽 배제처럼 Consul 운영 모델에 맞는 작업에 적합합니다.
Kubernetes Leasecoordination.k8s.io/v1 Lease 객체Pod·오퍼레이터 생명주기와 관측을 Kubernetes API로 통일할 때 선택합니다.
HazelcastIMap 기반 백엔드Hazelcast 클러스터를 이미 런타임 인프라로 사용할 때 의미가 있습니다. README는 CP Subsystem이 아니라 IMap 기반이라고 명시합니다.
ZooKeeperCurator 잠금·세마포어ZooKeeper·Curator 운영 지식을 활용해 기존 조정 인프라를 유지해야 할 때 검토합니다.

운영 기능: 갱신, 검증, 기록, 메트릭

섹션 제목: “운영 기능: 갱신, 검증, 기록, 메트릭”

백엔드를 선택한 뒤에는 다음 운영 조건을 확인해야 합니다.

질문도구
작업이 최초 리스보다 오래 실행될 수 있는가autoExtend, LeaderLeaseAutoExtender, LockExtender
현재 코드가 실제 잠금 범위에서 실행 중인가LockAssert.assertLocked() / assertLockedSuspend()
어떤 노드가 리더였고 작업이 완료되거나 실패했는가리더 이력 기록기와 백엔드별 이력 저장소
AOP 경계에서 시도·획득·건너뛰기·실패가 몇 번 발생했는가leader-micrometerMicrometerLeaderAopMetricsRecorder

SafeLeaderHistoryRecorder는 획득·완료·실패 이력을 남기되, 이력 저장소의 일반 오류가 보호 대상 작업을 실패시키지 않도록 최선 노력 방식으로 처리합니다. CancellationExceptionInterruptedException은 다시 던집니다. 취소 신호와 기록 가능한 일반 실패를 구분해야 애플리케이션 종료 과정이 지연되지 않습니다.

Micrometer 연동은 AOP 경계에서 시도, 획득, 미획득 사유, 실행 시간, 작업 실패, 활성 작업 게이지를 기록합니다. 운영 대시보드에서 잠금 경합과 백엔드 실패를 같은 건너뛰기로 집계하면 안 됩니다. 잠금 경합은 예상된 결과지만 백엔드 실패는 신뢰성 문제입니다.

벤치마크: 수치보다 측정 조건을 먼저 읽는다

섹션 제목: “벤치마크: 수치보다 측정 조건을 먼저 읽는다”

벤치마크 README는 이 결과를 동일한 머신에서 변경 전후를 비교하는 kotlinx-benchmark 모음으로 한정합니다. 릴리스 수준의 성능을 보장하는 자료가 아닙니다. 2026-05-21 기준선은 포크 1개, 스레드 1개, 예열 2회, 1초 측정 3회 조건입니다. 이 글의 크로스백엔드 표는 2026-05-29에 추가된 PostgreSQL·MySQL 측정값을 포함하며, 리스 갱신 설명은 2026-06-01 Redis 전용 측정값을 근거로 합니다.

bluetape4k-leader 분산 백엔드의 초당 처리량을 비교한 차트
분산 백엔드 처리량은 높을수록 좋습니다. 로컬과 H2 구조 확인용 측정값은 비교에서 제외했습니다.
bluetape4k-leader 분산 백엔드의 평균 실행 시간을 비교한 차트
분산 백엔드 평균 실행 시간은 낮을수록 좋습니다. 동일 머신 측정에서는 PostgreSQL·MySQL 기반 Exposed 측정값이 상대적으로 느렸습니다.

2026-05-29 크로스백엔드 측정값 중 대표 항목은 다음과 같습니다.

측정 항목처리량(ops/s)평균 시간(μs/op)해석
Lettuce Redis 블로킹1,454.7699.4Redis 계열은 분산 백엔드 차트에서 상위권입니다.
Redisson Redis 블로킹1,415.8699.7Lettuce와 비슷한 범위이므로 기존 클라이언트 운영 경험도 선택 기준이 됩니다.
Hazelcast 블로킹1,460.9766.3이 측정에서는 Redis 계열과 같은 상위권입니다.
MongoDB 블로킹843.71,131.0문서 저장소 기반 백엔드의 대표 측정값입니다. 오차가 커서 미세한 순위 비교에는 적합하지 않습니다.
ZooKeeper 블로킹804.31,372.2Curator 기반 조정 인프라의 대표 측정값입니다.
Consul 블로킹593.61,900.6Consul Session·KV 운영 환경에서 해석해야 합니다.
etcd 코루틴467.52,239.4원시 성능 순위보다 제어 평면과의 운영 정합성이 선택 이유가 될 수 있습니다.
PostgreSQL·MySQL 기반 Exposed50~80대13,000~17,000대이 동일 머신 측정에서는 분산 SQL 측정값이 느렸습니다. 로컬·H2 구조 확인용 측정값과 직접 비교하면 안 됩니다.

Kubernetes Lease는 Fabric8 클라이언트의 Vert.x 4·Netty 4.1 의존성 때문에 기본 etcd·Vert.x 5 대상과 분리된 소스 세트에서 측정합니다. 현재 README의 2026-07-02 K3s 시나리오 측정에서 새 리스를 획득하고 해제하는 공개 API 경로는 블로킹 82.297 ops/s, 코루틴 90.055 ops/s였습니다. 활성 소유자를 확인하고 쓰기 없이 건너뛰는 경로는 각각 661.149 ops/s와 465.583 ops/s였습니다. 완료 경로가 다른 측정값을 하나의 처리량 순위로 비교하면 안 됩니다.

Redis 리스 갱신 벤치마크는 일반 runIfLeaderautoExtend 활성화 경로를 비교합니다. 두 측정값의 차이는 넓은 JMH 오차 범위 안에 있으므로 최적화 근거로 사용할 수 없습니다. runIfLeaderWithRenewalWindow는 90ms 리스와 45ms 작업 대기 시간을 사용해 갱신 구간을 의도적으로 만듭니다. 이 행의 약 50,000 μs/op는 약 50 ms/op이며, 작업 대기 시간이 지배하므로 일반적인 리더 선출 비용으로 해석하면 안 됩니다.

구현을 확인할 때는 운영 시나리오와 백엔드가 함께 드러나는 예제부터 살펴보는 편이 좋습니다.

예제백엔드확인할 내용
examples/batch-schedulerLettuce Redis·Spring클러스터 전체에서 예약 배치를 한 번만 실행
examples/migration-gateExposed JDBC스키마·데이터 마이그레이션을 한 노드만 실행
examples/webhook-pollerMongoDB웹훅 폴러의 중복 실행 방지
examples/cache-warmerHazelcast파티션별 캐시 예열 작업 조정
examples/tenant-aggregatorExposed R2DBC테넌트별로 독립된 리더 운용
examples/ktor-appKtor + LettuceLeaderElectionPluginleaderScheduled
examples/prometheus-dashboardSpring + LettuceAOP 메트릭과 Prometheus·Grafana 대시보드
examples/etcd-reconcileretcd목표 상태 조정기
examples/consul-maintenanceConsul유지 보수·트래픽 배제 처리 흐름
examples/k8s-leaseKubernetes Lease저수준 Lease 획득·해제·재획득
examples/k8s-operatorKubernetes Lease + Spring3개 레플리카 중 하나만 조정 루프를 실행하는 오퍼레이터
examples/rate-limiterLettuce + Bucket4j리더가 분배하는 외부 API 점검과 공유 호출률 제한
examples/redisson-watchdogRedisson장시간 작업과 공통 리스 갱신기

댓글

GitHub 계정으로 의견을 남기거나 reaction을 남길 수 있습니다.