Lbluetape4k Leader
1 → N 슬롯 English

시각 자료 · Release 0.4.0 · Redis Lettuce

하나의 락.
하나의 유효한 리더.

blocking LeaderElector가 락 경쟁을 거쳐 소유권 token과 리스 TTL을 얻고, 보호된 작업을 실행한 뒤 연장·만료·token 확인 해제로 끝나는 흐름을 따라갑니다.

결정적인 리스 타임라인

논리 tick은 순서를 설명하며 Redis 지연 시간을 재현하지 않습니다.

tick 0에서 준비되었습니다.
리더 후보
node-a대기
node-b대기
node-c대기
Redis · 개념적 record
invoice-close
TTL —
lockNameinvoice-close
owner
token
expires
보호된 작업
리더 대기

현재 token을 가진 후보만 이 경계에 들어갑니다.

logical tick 0 waitTime 2 minLeaseTime 1 autoExtend false

최근 소유권 event

Tick 후보 동작 Token TTL 결과

Step 01 · 모델

현재 소유권 token이 리더를 증명합니다

lockName → { owner, token, TTL }은 Redis key 직렬화 형식이 아니라 동작을 설명하는 개념 모델입니다.

원자적 획득

락이 없을 때 한 후보만 소유권을 얻습니다.

불투명 token

현재 소유권 token과 일치할 때만 연장하거나 해제합니다.

리스 TTL

유효한 연장이 없으면 leaseTime 뒤에 소유권이 사라집니다.

호출 범위 작업

락 획득이 성공한 경우에만 사용자 작업을 실행합니다.

정상적인 락 경쟁은 throw하지 않습니다.

기다림이 끝난 후보는 작업을 실행하지 않고 건너뜁니다.

Step 02 · 설정

서로 다른 네 가지 시간 경계

위 control에서 작업 시간과 leaseTime을 바꾸면 같은 상태 전이가 새 설정으로 초기화됩니다.

waitTime

후보가 락을 얻으려고 기다릴 수 있는 최대 시간입니다.

leaseTime

획득 또는 연장 뒤 token이 유효한 TTL입니다.

minLeaseTime

작업이 빨리 끝나도 성공한 락을 유지할 최소 시간이며 leaseTime보다 클 수 없습니다.

작업 시간

업무 코드가 실행되는 시간으로 리스보다 길 수 있습니다.

autoExtend는 단일 리더 경로의 기능입니다.

자동 연장 시나리오는 작업 중 리스 TTL을 주기적으로 갱신합니다.

Step 03 · 직접 API

간결한 null 또는 명시적인 result

val value = elector.runIfLeader("invoice-close") {
    closeInvoices()
}
// elected: 작업 반환값
// contention: null
when (val result =
    elector.runIfLeaderResult("invoice-close") { closeInvoices() }) {
    is LeaderRunResult.Elected -> showCompleted(result.value)
    LeaderRunResult.Skipped -> showSkipped()
    is LeaderRunResult.ActionFailed -> showFailure(result.cause)
}
LeaderRunResult.Elected

작업 반환값이 null이어도 락 획득 여부가 분명합니다.

LeaderRunResult.Skipped

정상적인 경쟁 때문에 락을 획득하지 못했습니다.

LeaderRunResult.ActionFailed

락을 얻은 뒤 사용자 작업이 실패했습니다.

Backend 실패

연결이나 설정 실패를 정상 경쟁으로 바꾸지 않습니다.

Step 04 · Spring Boot

annotation이 같은 실행 경계를 보호합니다

@LeaderElection(
    name = "invoice-close",
    waitTime = "PT0.5S",
    leaseTime = "PT10S",
    minLeaseTime = "PT1S",
    autoExtend = true,
)
fun closeInvoices(): CloseSummary? = service.closeInvoices()
AspectJ compile-time weaving

@EnableAspectJAutoProxy는 추가하지 않습니다. Kotlin method는 open일 필요가 없고 private method는 가로채지 않습니다. 동적 이름에는 유효한 SpEL을 사용합니다.

이 자료는 동기 호출만 움직입니다. release는 suspend, Mono, Flux, Kotlin Flow도 지원합니다. 긴 stream은 autoExtend = true가 필요하며 리스 안에 완료될 때만 streamBounded = true를 사용합니다.

Step 05 · 장애와 복구

리스 만료 뒤 업무 작업은 겹칠 수 있습니다

리스 만료 시나리오에서 node-a는 stale owner가 되고 node-b가 새 token으로 락을 얻습니다.

리스는 외부 부수 효과를 막는 fencing token이 아닙니다.

오래된 token은 후속 리더의 락을 해제할 수 없지만, 이미 실행 중인 외부 쓰기를 되돌리지는 못합니다. 작업을 멱등하게 만들고 stale 쓰기를 거부해야 한다면 업무에 맞는 fencing 또는 claim을 사용합니다.

자동 연장 시나리오는 유효한 owner가 만료 전에 TTL을 갱신합니다. 모든 해제는 token을 확인하므로 오래된 token이 새 owner의 락을 삭제하지 못합니다.

Release 근거

0.4.0 commit에 고정

아래 코드와 manual link는 17ab7f8을 가리킵니다.