한양과학기술고등학교

에듀테크 프로젝트 대시보드

Raft 합의 알고리즘

Raft 합의 알고리즘

서버 한 대는 언젠가 죽는다. 그래서 중요한 데이터는 여러 대에 복제해 둔다. 그런데 여러 대가 동시에 값을 바꾸면 서로 내용이 어긋난다. 합의(consensus) 알고리즘은 여러 서버가 마치 한 대인 것처럼 똑같은 기록을 갖도록 맞춰 주는 규칙이다. Raft는 그중 가장 이해하기 쉽게 설계된 방식이다.

한눈에 — 조별 과제를 떠올리자. 먼저 조장(리더)을 한 명 뽑는다. 모든 결정은 조장을 거치고, 조장은 정해진 내용을 조원들 공책에 같은 순서로 받아 적게 한다. 조장이 사라지면(잠수) 남은 조원들이 다시 투표해 새 조장을 뽑는다. 단, "과반수가 찬성해야" 조장이 되고, "과반수가 받아 적어야" 그 결정이 확정된다.

1. 리더 선출 — 직접 지켜보기

서버(노드) 5대가 원으로 놓여 있다. 각 노드에는 선거 타이머(파란 링)가 돌아간다. 리더의 하트비트를 받으면 타이머가 리셋되지만, 리더가 없어 타이머가 다 차면 그 노드는 후보(candidate)가 되어 투표를 요청한다. 과반수(3표)를 얻으면 리더가 된다.

● Follower — 리더 말을 따름 ● Candidate — 표를 모으는 중 ● Leader — 하트비트 전송 ● Down — 정지됨
현재 리더
–
현재 term(임기)
1
치른 선거 횟수
0
term(임기) 번호 — 선거가 열릴 때마다 1씩 오르는 숫자. Raft에서 시간은 term으로 잘게 나뉜다. 각 term에는 리더가 최대 한 명. 더 큰 term 번호를 들고 온 메시지를 보면, 오래된 리더는 즉시 스스로 물러나 follower가 된다. "누가 더 최신인가"를 term 숫자 하나로 판정한다.

2. 왜 타이머를 무작위로 돌릴까 — 스플릿 보트

모든 노드가 똑같은 시간에 타이머가 끝나면, 다 같이 후보가 되어 자기 자신에게 투표한다. 5명이 1표씩 나눠 가지면 아무도 과반수(3표)를 못 얻는다. 이를 표 분산(split vote)이라 한다. Raft는 각 노드의 선거 타이머를 150~300ms처럼 무작위 범위에서 뽑는다. 그러면 누군가 먼저 후보가 되어 남들이 타이머를 다 채우기 전에 표를 쓸어 담는다.

한 번의 선거에서 첫 시도만에 리더가 뽑히면 성공. 방식을 바꿔 가며 비교해 보세요.

3. 로그 복제 — 결정을 같은 순서로 받아 적기

리더가 정해지면, 클라이언트의 명령(x=3, x=5 …)은 전부 리더에게 간다. 리더는 이를 자기 로그 끝에 붙이고 모든 follower에게 복사를 보낸다. 과반수가 로그에 적었다고 응답하면, 리더는 그 항목을 commit(확정) 하고 실제 값에 반영한다. 확정된 항목은 절대 바뀌거나 사라지지 않는다.

■ pending — 복제 중, 아직 미확정 ■ committed — 과반수 도달, 확정
S1이 리더입니다. "명령 추가"를 눌러 로그가 어떻게 퍼지는지 보세요. S3를 끊어도 나머지 3대(과반수)로 commit이 됩니다.

4. 세 가지 상태와 핵심 규칙

상태하는 일전이 조건
Follower아무 요청도 시작하지 않음. 리더/후보의 메시지에 응답만.선거 타이머 만료 → Candidate
Candidateterm+1, 자신에게 투표, 나머지에게 RequestVote 전송과반수 득표 → Leader
다른 리더 발견 → Follower
타임아웃 → 재선거
Leader주기적 AppendEntries(하트비트+로그) 전송, 클라이언트 요청 처리더 큰 term을 보면 → Follower
안전을 지키는 두 개의 "과반수" — ① 리더가 되려면 과반수 표가 필요하다. ② 로그 항목을 확정하려면 과반수가 저장해야 한다. 노드가 2N+1대면 N대까지 죽어도 과반수가 살아 있어 시스템이 돈다(5대 → 2대 장애 허용). 두 과반수 집합은 반드시 겹치므로, 새 리더는 이전에 확정된 항목을 항상 갖고 있다.

5. 의사코드 — follower가 투표를 결정하는 규칙

def on_request_vote(req):            # 후보가 보낸 표 요청
    if req.term < current_term:
        return Vote(current_term, granted=False)   # 낡은 후보 거절

    if req.term > current_term:          # 더 최신 term 발견
        current_term = req.term
        voted_for    = None              # 새 임기이므로 투표권 부활
        state        = FOLLOWER

    already_voted = voted_for not in (None, req.candidate_id)
    log_ok       = req.last_log_term >= my_last_log_term \
                   and req.last_log_index >= my_last_log_index

    if not already_voted and log_ok:
        voted_for = req.candidate_id
        reset_election_timer()          # 표를 줬으니 잠시 기다려 줌
        return Vote(current_term, granted=True)

    return Vote(current_term, granted=False)

log_ok 조건이 핵심이다. 자기 로그가 후보보다 더 최신이면 표를 주지 않는다. 덕분에 확정된 기록이 빠진 노드는 절대 리더가 될 수 없다.

6. 어디에 쓰이나

etcd — 쿠버네티스(Kubernetes)가 클러스터 상태를 저장하는 곳. 내부적으로 Raft로 여러 마스터 노드의 데이터를 일치시킨다.

HashiCorp Consul / Nomad — 서비스 등록·설정 저장소의 복제에 Raft 사용.

Kafka KRaft, TiKV, CockroachDB, MongoDB — 예전에는 ZooKeeper(Paxos 계열)에 의존했지만, 최근에는 Raft를 직접 품는 추세.

관련 개념 — 일관된 해싱으로 데이터를 나누고, 각 조각(shard)을 Raft 그룹으로 복제하는 구조가 흔하다. TCP 위에서 메시지를 주고받는다.

한 줄 요약 — 무작위 타이머로 리더 한 명을 뽑고(과반수 득표), 모든 변경을 리더가 같은 순서의 로그로 퍼뜨려 과반수가 저장하면 확정한다. term 번호로 "누가 최신인지"를 판정해, 서버가 죽고 살아나도 기록이 어긋나지 않는다. 관련: 일관된 해싱, 트랜잭션과 ACID, 경쟁 상태.