Raft 리더 리스와 선형화 가능한 읽기
리더가 매 읽기마다 과반수를 확인하지 않고 로컬에서 응답하려면 시간 기반 리스가 필요합니다. 리스가 성립하는 조건과 조용히 깨지는 경로, LeaseGuard의 로그 기반 리스, 그리고 리더 교체 직후에 치르는 가용성 대가를 정리합니다.
읽기 왕복을 없애는 리스는 정확성의 근거를 로그에서 시계로 옮기고, 그 값은 리더가 바뀌는 순간의 가용성으로 치릅니다.
읽기마다 붙는 왕복
Raft는 로그 복제로 쓰기의 선형화 가능성(linearizability)을 보장하지만, 읽기는 별도 장치 없이 같은 보장을 받지 못합니다. 이미 팔로워로 밀려난 옛 리더가 자신을 여전히 리더라고 믿는 상태를 stale leader라고 부릅니다. 이 노드가 로컬 상태만 보고 응답하면, 새 리더가 이미 커밋한 쓰기를 반영하지 못한 값이 클라이언트로 나갑니다.
표준 해법은 읽기마다 과반수(quorum)에 자신이 여전히 유효한 리더인지 확인하는 quorum check입니다. Raft 구현체에서는 대개 ReadIndex라는 이름으로 제공됩니다. 정확하지만 읽기 한 번마다 네트워크 왕복이 하나 붙습니다.
비용은 왕복 한 번으로 끝나지 않습니다. Davis, Demirbas, Deng의 LeaseGuard 논문(arXiv:2512.15659, SIGMOD 2026)이 이 비용을 실측했습니다. 대상은 Raft 참조 구현인 LogCabin입니다. 네트워크 지연이 10ms인 조건에서 quorum read의 읽기 지연은 대기열 효과 탓에 16초까지 늘어났습니다. 왕복 지연이 요청 적체를 부르고, 적체가 다시 지연을 키운 결과입니다.
| 읽기 방식 | 추가 왕복 | 일관성 | 대가 |
|---|---|---|---|
Quorum read (ReadIndex) | 1 RTT | 선형화 가능 | 지연, I/O 경합, 클라우드 통신 비용 |
| Follower stale read | 없음 | 최종 일관성 | 낡은 값을 허용해야 함 |
| Leader lease read | 없음 | 선형화 가능 | 시계 정확도 의존, 교체 직후 가용성 |
세 번째 줄이 이 글의 주제입니다. 리더가 있는 구조를 그대로 두고 읽기 경로만 왕복 0회로 만드는 방법이며, 이 글은 그 대가가 어디로 옮겨 가는지를 따라갑니다.
리스가 성립하는 조건
리더 리스(leader lease)는 시간을 근거로 quorum check를 생략하는 장치입니다. 불변식 하나로 요약됩니다. 같은 시각에 두 노드가 스스로를 리더라고 믿고 읽기에 응답하는 일이 없어야 합니다.
리더는 "지금부터 Δ 시간 안에는 다른 리더가 선출될 수 없다"는 구간을 확보한 뒤에만 로컬 읽기를 처리합니다. Ongaro가 2014년에 제시한 원형은 과반수 RPC가 election timeout 안에 성공하면 그 시점부터 리스를 보유한 것으로 봅니다. 이 정의가 뒤에서 문제가 됩니다.
리스 기간 Δ를 얼마로 잡을지는 election timeout(ET)과 묶여 있습니다. Δ가 ET보다 짧으면 리스가 자주 끊겨 로컬 읽기 구간이 줄고, ET보다 길면 신규 리더가 리스 없이 기다리는 구간이 늘어납니다. LeaseGuard는 두 값을 맞춘 Δ = ET를 권장합니다.
만료 판정은 단일 시각으로 하지 않습니다. 각 노드는 오차 범위를 가진 구간 시각 [earliest, latest]를 다루는 유계 불확실성 시계(bounded-uncertainty clock)를 전제로 합니다. 어떤 항목의 기록 시각을 e라고 하면, e.latest + Δ가 현재 시각의 earliest보다 작을 때에만 그 리스가 확실히 만료된 것입니다. 오차 상한을 모르는 환경에서는 이 비교 자체가 성립하지 않습니다.
리스가 깨지는 경로
리스는 요란하게 실패하지 않습니다. 두 리더의 유효 구간이 잠깐 겹치는 동안 낡은 값이 정상 응답처럼 나갈 뿐입니다.
첫 경로는 노드 간 시계 오차입니다. 아래는 오차 e만큼 시계가 빠른 노드가 리스를 먼저 만료로 판단할 때 벌어지는 일입니다.
리더 A의 리스 [T0, T0+Δ] (A의 시계 기준)
노드 B의 시계 A보다 e만큼 빠름
B: T0+Δ-e 시점에 만료로 판단 -> 선거 시작 -> 새 리더가 됨
A: T0+Δ까지 자신을 리더로 믿고 로컬 읽기에 응답
겹치는 e 구간 동안 두 리더가 동시에 읽기를 처리두 번째 경로는 메시지 지연입니다. 과반수 응답이 도착한 시각만 보고 리스 시작점을 잡으면, 요청을 보낸 시각과 응답을 받은 시각 사이의 지연이 리스 구간에 빠집니다. HashiCorp Raft가 이 계산을 빠뜨린 채로 남아 있고, 2016년에 보고된 일관성 위반이 2025년 기준으로도 고쳐지지 않았습니다.
세 번째 경로는 시계와 무관합니다. 리스 유효 판정과 실제 응답 사이에 GC 정지나 스케줄링 지연이 끼면, 노드는 이미 만료된 리스를 근거로 응답합니다. 판정 시점에 남은 리스 기간이 정지 시간보다 짧으면 그대로 위반이 됩니다.
| 시스템 | 리스 방식 | 알려진 문제 |
|---|---|---|
| HashiCorp Raft | 리더 리스 | 만료 계산이 메시지 지연을 반영하지 않음. 2016년 보고, 2025년 기준 미수정 |
| etcd | 리더 리스 | 리스 타임아웃 계산 오류로 낡은 읽기 가능. 2024년 보고 |
| TiDB | Placement Driver가 리스 관리 | 별도 인프라 필요, 10초 리스 탓에 선출 후 10초간 불가용 |
| CockroachDB | 데이터 범위 단위 리스 | 리스 획득이 추가 조율 단계, 최대 클럭 오프셋(기본 500ms)만큼 딜레이 삽입 |
| YugabyteDB | 선거 중 리스 겹침 허용 | 후보자가 이전 리스 정보를 명시적으로 학습해야 함 |
| MongoDB | 리스 미사용 | 읽기마다 빈 로그 항목을 복제해 우회, quorum check와 같은 통신 비용 |
LeaseGuard 논문은 공통 원인을 사양의 모호함으로 지목합니다. 원본 리스 설명이 서술로만 되어 있고 형식 명세가 없어서, 구현체마다 만료 시점 계산과 리더 교체 시 리스 전이를 다르게 해석했다는 것입니다.
로그가 곧 리스다
LeaseGuard는 리스 전용 메시지나 자료구조를 추가하지 않습니다. Raft가 이미 보장하는 Leader Completeness, 즉 새로 선출된 리더는 과반수가 복제한 이전의 모든 로그 항목을 보유한다는 속성을 리스의 근거로 씁니다.
리더는 로그 항목에 구간 타임스탬프를 함께 기록합니다. 마지막 커밋 항목의 시각이 Δ 이내라면, 그 항목이 과반수에 복제됐다는 사실 자체가 다른 리더가 존재할 수 없다는 증명이 됩니다. 팔로워는 평소의 로그 복제만으로 리스 상태를 학습하므로 추가 통신이 없습니다.
이 설계의 전제는 여전히 시계입니다. 논문의 실측은 AWS TimeSync와 PTP로 평균 50µs 미만의 오차 상한을 확보한 환경에서 이뤄졌습니다. CockroachDB가 비협력적 리스 획득 시 최대 클럭 오프셋만큼 딜레이를 넣는 것도 같은 문제를 다른 방식으로 다룬 사례입니다.
차이는 검증 방식에도 있습니다. LeaseGuard는 프로토콜을 TLA+로 형식 명세하고 정확성 정리를 함께 확인했습니다. 정리에는 모든 읽기가 선행 쓰기의 효과를 관찰한다는 Read-Your-Writes가 들어갑니다. 읽기가 복제 집합의 최신 상태를 반영한다는 Read At Highest commitIndex도 함께 담겼습니다.
리더 교체 직후의 공백
리스 기반 읽기는 정상 구간에서는 비용이 거의 들지 않습니다. 비용은 리더가 바뀌는 순간에 몰려 있습니다. 신규 리더는 옛 리더의 리스가 만료될 때까지 기다려야 안전한데, 그 구간에서 아무것도 하지 못하면 쓰기와 읽기가 함께 멈춥니다. TiDB가 선출 후 10초간 불가용해지는 것이 이 공백의 극단적인 사례입니다.
LeaseGuard는 이 공백을 두 가지 방식으로 메웁니다. Deferred Commit Writes는 신규 리더가 대기 중에도 쓰기를 받아 팔로워에 복제해 둡니다. commitIndex 갱신만 옛 리더의 마지막 항목이 Δ를 넘길 때까지 미룹니다. 리스가 열리는 순간 추가 통신 없이 곧바로 커밋을 확정할 수 있습니다.
Inherited Lease Reads는 옛 리더의 마지막 커밋 항목이 아직 Δ를 넘지 않았다면 그 리스를 상속받아 읽기를 즉시 제공합니다. 다만 선출 시점의 commitIndex보다 크고 선출 인덱스보다 작은 구간, 즉 limbo region의 항목들은 옛 리더가 어디까지 커밋했는지 알 수 없습니다. 이 구간에 걸린 키는 다중 버전 동시성 제어(MVCC)로 여러 버전을 읽어 비교하는 방식으로 안전성을 확보합니다.
두 최적화 모두 TLA+ 명세를 탐색하는 과정에서 나왔습니다. 논문은 limbo region 문제 자체를 형식 명세가 드러냈다고 서술하며, 이 점에서 명세를 사후 검증이 아니라 설계 도구로 씁니다.
실측은 AWS EC2 m7g.xlarge 3노드(us-east-1, 평균 핑 155µs)에서 LogCabin 약 27,000줄 중 1,600줄을 고쳐 진행했습니다.
| 지표 | Quorum check | Ongaro 2014 리스 | LeaseGuard |
|---|---|---|---|
| 일관성 읽기의 추가 왕복 | 1 RTT | 없음 | 없음 |
| 교체 직후 쓰기 처리량 | - | 급격히 저하 | 약 1,000 → 약 10,000 writes/sec |
| 교체 직후 읽기 가용성 | - | 리스 만료까지 0% | 약 99% |
| 쓰기 50% 혼합에서 100ms 미만 유지 | 5,000 ops/sec에서 지연 급증 | 25,000 ops/sec 부근에서 지연 증가 | 25,000 ops/sec까지 유지 |
가용성 회복은 워크로드에 따라 갈립니다. 읽기 분포를 Zipfian으로 바꾼 실험에서 균등 분포(a=0)일 때는 초당 3,000건이 성공했지만, 소수 키에 몰린 분포(a=2)에서는 초당 500건 미만으로 떨어졌습니다. limbo region에 걸린 키로 읽기가 집중될수록 상속 리스의 효과가 줄어들기 때문입니다.
어디까지 리스로 덮을 것인가
첫 판단 기준은 시계 인프라입니다. 오차 상한을 측정할 수 없는 환경이라면 리스를 쓰지 않고 quorum check를 쓰는 편이 맞습니다. 상한을 모르는 상태에서 리스를 켜면 위반이 발생해도 감지되지 않습니다.
다음은 워크로드 구성입니다. 읽기 비중이 높을수록 왕복 하나를 걷어낸 이득이 커지고, 쓰기 위주라면 quorum check가 전체 비용에서 차지하는 몫이 작아 리스의 이득도 줄어듭니다. 여기에 리더 교체 직후 Δ만큼의 읽기 공백을 감당할 수 있는지를 함께 봅니다.
리스를 리더 한 대에만 줄지도 선택지입니다. Moraru, Andersen, Kaminsky는 2014년 ACM SoCC에서 Quorum Leases로 리스를 과반수 노드 집합에 나눠 줬습니다. 리스를 가진 노드는 어디서든 로컬로 선형화 가능한 읽기를 제공하지만, 쓰기는 해당 데이터의 리스 보유 노드 전원에게 확인을 받아야 합니다. 그중 하나가 느리면 쓰기 전체가 그만큼 늦어집니다.
Bodega는 리스 대상을 임의로 지정한 응답자(responder) 집합으로 일반화합니다. Hu, Arpaci-Dusseau, Arpaci-Dusseau가 2025년 9월 arXiv에 공개한 설계입니다(arXiv:2509.07158). 로스터(roster)라는 클러스터 메타데이터가 리더가 누구인지와 로컬 읽기를 담당할 노드 집합을 함께 추적합니다. 이 로스터에 대한 합의를 유지하는 장치가 로스터 리스입니다. 이 관점에서 리더 리스는 리스 보유 노드가 하나로 고정된 특수 사례입니다.
| 리스 보유 집합 | 대표 사례 | 로컬 읽기 지점 | 대가 |
|---|---|---|---|
| 리더 1개 | Raft leader lease, sofa-jraft LeaseRead | 리더뿐 | 구현이 단순한 대신 원거리 클라이언트는 여전히 원격 |
| 과반수 고정 집합 | Paxos Quorum Leases (SoCC 2014) | 리스 보유 노드마다 | 쓰기가 리스 보유 노드 전원의 확인을 요구 |
| 임의 응답자 집합 | Bodega roster lease (2025) | 지정한 응답자 어디서나 | 로스터 재구성 자체가 새로운 합의 대상 |
실무에서 마주치는 것은 대개 첫 줄입니다. sofa-jraft가 ReadIndex와 LeaseRead를 나란히 제공하듯, 많은 구현이 quorum check와 리스 읽기를 옵션으로 두고 운영자가 고르게 합니다. 아래로 내려갈수록 읽기는 가까워지지만 쓰기 경로와 재구성 로직이 함께 무거워집니다.
정리
리더 리스는 읽기 경로에서 왕복 하나를 걷어내는 대신, 정확성의 근거를 로그에서 시계로 옮깁니다. 그래서 오차 상한을 모르는 환경에서는 켜면 안 되고, 시계가 정확해도 메시지 지연이나 프로세스 정지로 조용히 깨질 수 있습니다. LeaseGuard는 리스를 별도 자료구조가 아니라 로그 항목의 타임스탬프로 정의해 이 모호함을 없앴습니다. TLA+ 명세에서 끌어낸 두 최적화로 교체 직후 쓰기 처리량은 약 1,000에서 약 10,000 writes/sec로, 읽기 가용성은 0%에서 약 99%로 올라갔습니다. 도입 여부는 시계 오차 상한을 아는지, 읽기 비중이 얼마나 큰지, 교체 직후의 짧은 읽기 공백을 감당할 수 있는지로 판단하면 됩니다.