Raft Consensus 알고리즘 완벽 가이드 – 분산 시스템에서 리더를 뽑는 가장 쉬운 방법 (50~60자)
# 서론 – “왜 우리는 리더 선출 문제에 고민해야 할까?”
현대의 클라우드 서비스, 데이터베이스, 캐시 시스템 등 분산 시스템은 수많은 서버가 협업해 하나의 서비스를 제공하도록 설계됩니다.
그런데 서버 중 하나가 장애가 나면 어떻게 할까요?
데이터 일관성을 유지해야 한다.
클라이언트 요청을 지속적으로 처리해야 한다.
시스템 전체가 멈추지 않아야 한다.
이 모든 요구를 만족하려면 ‘누가 현재 시스템을 이끌어야 하는가(리더)’를 명확히 정하고, 그 리더가 장애가 발생했을 때 빠르게 새로운 리더를 선출할 메커니즘이 필요합니다.
분산 시스템에서 리더를 선출하는 대표적인 방법이 바로 Raft Consensus 알고리즘입니다. 이 글에서는 Raft가 어떻게 동작하는지, 왜 이해하기 쉬운지, 실제 프로젝트에 어떻게 적용되는지를 초보자도 쉽게 따라올 수 있도록 2,000 단어 안팎으로 정리합니다.
핵심 키워드: Raft 알고리즘, 분산 시스템, 리더 선출
# 1. Raft란 무엇인가? – “쉽게 말해 ‘팀장 뽑기’ 게임”
Raft는 2013년 Diego Ongaro와 John Ousterhout가 발표한 분산 합의(Consensus) 알고리즘입니다. 기존에 복잡하고 이해하기 어려운 Paxos와 달리, Raft는 다음 세 가지 기본 목표를 내세워 설계되었습니다.
| 목표 | 설명 |
|------|------|
| 리더 선출 | 시스템에 하나의 리더만 존재하도록 보장 |
| 로그 복제 | 리더가 클라이언트 요청을 받아 로그에 기록하고, 이를 팔로워에게 복제 |
| 안전성 보장 | 어떤 상황에서도 일관된 상태(state)를 유지 |
즉, Raft는 “팀장이 정해지면 팀장이 모든 결정을 내리고, 팀원은 팀장의 명령을 그대로 따라한다”는 단순한 메커니즘을 구현합니다. 이 단순함이 바로 ‘초보자도 이해하기 쉬운’ 이유입니다.
# 2. Raft의 기본 구성 요소
# 2.1 서버 역할 – Leader, Follower, Candidate
Follower
기본 상태이며, 리더의 하트비트(heartbeat) 신호를 기다립니다.
일정 시간 동안 하트비트를 받지 못하면 Candidate가 됩니다.
Candidate
선거를 시작하고, 다른 서버에게 투표를 요청합니다.
과반수(> 50 %)의 표를 얻으면 Leader가 됩니다.
Leader
클라이언트 요청을 받아 로그(entry) 를 생성하고, 이를 모든 Follower에게 복제합니다.
일정 주기로 하트비트를 전송해 자신이 살아 있음을 알립니다.
# 2.2 로그와 상태 머신
로그(entry) : 클라이언트가 보낸 명령(예: “키‑값 저장”)을 순서대로 저장한 리스트.
상태 머신 : 로그를 적용해 실제 데이터베이스나 파일 시스템을 업데이트하는 역할.
Raft는 ‘로그가 동일한 순서대로 적용될 때만 상태 머신이 동일해진다’는 원칙을 따릅니다. 따라서 로그 복제와 커밋(commit) 과정이 핵심입니다.
# 3. Raft 동작 흐름 – 단계별 설명
# 3.1 초기화와 하트비트
시스템이 시작되면 모든 서버는 Follower 상태이며, 임의의 타임아웃(election timeout)을 설정합니다. Leader가 존재하면 일정 주기(heartbeat interval)마다 AppendEntries RPC를 통해 하트비트를 전송합니다.
하트비트 수신 → 타임아웃 재설정 → 그대로 Follower 유지
하트비트 미수신 → 타임아웃 초과 → Candidate 전환
# 3.2 선거(Election) 과정
Candidate 전환
타임아웃이 끝나면 현재 term(선거 번호)를 증가시키고, 자신을 후보로 선언합니다.
RequestVoteRPC를 모든 다른 서버에 전송합니다.
투표 규칙
각 서버는 아직 투표하지 않았고, 후보의 로그가 자신의 로그보다 최신이면 투표합니다.
최신성 판단은
(lastLogTerm, lastLogIndex)쌍으로 비교합니다.
과반수 확보
Candidate가 과반수 표를 받으면 Leader가 됩니다.
새로운 Leader는 즉시
AppendEntries(하트비트)와 함께 빈 로그를 전송해 자신이 Leader임을 알립니다.
Tip: 선거는 분산 시스템에서 가장 빠르게 수렴하도록 설계되었습니다. 대부분의 경우 1~2 라운드만에 Leader가 선출됩니다.
# 3.3 로그 복제와 커밋
클라이언트 요청 → Leader가 로그에 새로운 entry 추가
AppendEntries RPC → Leader가 모든 Follower에게 새 entry 전송
Follower 응답 → 성공 시
matchIndex업데이트, 실패 시nextIndex감소 후 재시도커밋
Leader는 대다수(majority) 서버가 해당 entry를 저장했는지 확인합니다.
확인되면 해당 entry를 커밋하고, 상태 머신에 적용합니다.
커밋된 entry는
CommitIndex에 기록됩니다.
# 3.4 장애 복구와 로그 일관성 보장
Follower가 뒤처졌을 때: Leader는
prevLogIndex와prevLogTerm을 검증해 일치하지 않으면 log truncation(잘라내기) 후 재전송합니다.Leader 장애: 하트비트가 사라지면 새로운 선거가 시작되고, 기존 로그를 보존한 최신 서버가 새로운 Leader가 됩니다.
이러한 메커니즘 덕분에 ‘어떤 서버가 죽어도 일관된 상태를 유지한다’는 안전성을 확보합니다.
# 4. Raft를 실제 서비스에 적용한 사례
| 프로젝트/툴 | 적용된 Raft 모듈 | 주요 활용 |
|------------|----------------|----------|
| etcd | Core Raft 구현 | 키‑값 저장소의 메타데이터 복제 |
| Consul | HashiCorp Raft | 서비스 디스커버리와 KV 스토어 |
| RethinkDB | 자체 Raft | 샤드 복제와 장애 복구 |
| TiKV | Rust‑based Raft | 분산 트랜잭션 스토리지 |
| CockroachDB | Go‑Raft | SQL 레이어 위에 일관된 트랜잭션 제공 |
이처럼 Raft는 데이터베이스, 키‑값 스토어, 서비스 레지스트리 등 다양한 분야에서 핵심 합의 메커니즘으로 활용됩니다. 특히 Go 언어와 Rust 같은 현대 언어에 최적화된 구현체가 풍부해, 개발자는 직접 Raft를 내 서비스에 끼워 넣는 것이 비교적 쉬워졌습니다.
# 5. Raft 구현 시 흔히 마주치는 고민과 해결책
# 5.1 타임아웃 설정
Election timeout은 보통 150 ms ~ 300 ms 사이로 랜덤하게 설정합니다.
Heartbeat interval은 50 ms 이하가 일반적이며, 네트워크 상황에 맞게 조정합니다.
팁: 타임아웃을 너무 짧게 하면 불필요한 선거가 빈번히 발생하고, 너무 길게 하면 장애 복구가 지연됩니다.
# 5.2 로그 압축(Snapshot)
로그가 무한히 커지는 것을 방지하기 위해 스냅샷을 주기적으로 생성합니다.
Leader가
InstallSnapshotRPC를 통해 최신 상태를 전송Follower는 스냅샷을 적용하고, 이후부터는 로그 복제만 진행
스냅샷은 디스크 I/O와 네트워크 대역폭을 절약해 대규모 클러스터에서도 효율적으로 동작합니다.
# 5.3 멤버십 변화(Cluster Reconfiguration)
Raft는 동적 멤버십을 지원합니다.
Joint consensus 방식을 사용해 기존 구성과 새 구성을 동시에 유지하면서 안전하게 서버를 추가/제거합니다.
이 과정에서도 다수결 원칙을 유지해 일관성을 보장합니다.
# 6. Raft와 다른 합의 알고리즘 비교
| 항목 | Raft | Paxos | ZAB (ZooKeeper) |
|------|------|-------|-----------------|
| 이해 난이도 | ★★★★★ (가장 쉬움) | ★★☆☆☆ | ★★★☆☆ |
| 리더 선출 | 명시적 선거 과정 | 암묵적(프로포저) | ZAB 자체 선출 |
| 로그 복제 | 단순 AppendEntries | 복잡한 프로포저/아코머 | ZAB 프로토콜 |
| 장애 복구 | 빠른 선거 | 복구 속도 느림 | 빠른 복구 |
| 구현 난이도 | 중간 | 높음 | 중간 |
Raft는 ‘쉽게 배우고 구현할 수 있는’ 장점 덕분에 초보자부터 대규모 서비스까지 폭넓게 채택되고 있습니다.
# 7. 직접 Raft를 구현해 보는 작은 프로젝트
# 7.1 목표
3대 노드(서버)로 구성된 클러스터 만들기
Go 언어와
etcd/raft패키지를 활용해 Leader 선출, 로그 복제 구현
# 7.2 주요 단계
- 프로젝트 초기화
bash
go mod init raft-demo
go get go.etcd.io/etcd/raft/v3
- Node 구조 정의
go
type Node struct {
id uint64
raftNode raft.Node
storage *raft.MemoryStorage
transport Transport // 네트워크 전송 추상화
}
- Raft 초기화
go
cfg := &raft.Config{
ID: nodeID,
ElectionTick: 10,
HeartbeatTick: 1,
Storage: storage,
MaxSizePerMsg: 1024 * 1024,
MaxInflightMsgs: 256,
}
node.raftNode = raft.StartNode(cfg, peers) // peers는 초기 클러스터 멤버
- 메시지 처리 루프
go
go func() {
for {
select {
case rd := <-node.raftNode.Ready():
// 로그 저장, 스냅샷 적용, 메시지 전송 등
node.storage.Append(rd.Entries)
node.transport.Send(rd.Messages)
node.raftNode.Advance()
}
}
}()
클라이언트 요청
- Leader에게
Propose호출 → 로그에 entry 추가 → 복제 진행
- Leader에게
위와 같은 최소 구현으로 리더 선출과 로그 복제를 체험할 수 있습니다. 실제 서비스에 적용하려면 스냅샷, 영속성, 보안 등을 추가해야 하지만, 초보자 입장에서는 충분히 학습용 프로젝트가 됩니다.
# 결론 – “Raft, 이제 직접 써볼 차례입니다”
Raft Consensus 알고리즘은 복잡한 분산 합의를 ‘리더 선출 → 로그 복제 → 커밋’이라는 세 단계로 단순화했습니다.
이해하기 쉬운 구조와 풍부한 구현체 덕분에 초보자도 금방 적용 가능합니다.
실제 서비스(예: etcd, Consul, CockroachDB)에서도 핵심 역할을 수행하고 있어, 분산 시스템을 설계하거나 운영하는 모든 개발자에게 필수 지식입니다.
다음 행동 권장
위에서 소개한 간단 Raft 프로젝트를 직접 구현해 보세요.
GitHub에서
etcd/raft혹은rust-raft같은 오픈소스 구현을 살펴보며 코드를 분석해 보세요.현재 운영 중인 서비스에 Raft 기반 키‑값 스토어(예: etcd)를 도입해 고가용성을 검증해 보세요.
분산 시스템의 핵심은 ‘일관성과 가용성을 동시에 보장하는 메커니즘’에 있습니다. Raft는 그 메커니즘을 가장 직관적으로 제공하므로, 이제 여러분 차례입니다. Raft를 직접 다뤄보고, 여러분만의 견고한 분산 서비스를 구축해 보세요!
관련 링크: Raft Consensus in 2000 words (Reddit)
SEO 키워드: Raft 알고리즘, 분산 시스템, 리더 선출.
Comments
Join the discussionNo comments yet. Be the first to comment.