Raft는 Reliable(신뢰성), Replicated(복제성), Redundant(중복성), And Fault-Tolerant(내장애성)에서 명명 되었으며, 분산 시스템에서 전체 노드의 동기화, 내결함성(False Tolerance)을 동시에 구현하기 위해 만들어진 합의 알고리즘의 한 종류이다. 분산 시스템을 이해하려면 알아야할 중요한 포인트이기도 하다. 여기에서는 Raft 알고리즘을 이해하는데 도움이 되는 “In Search of an Understandable Consensus Algorithm”이라는 논문에 소개한 Raft 합의 알고리즘을 번역해서 이해를 돕고자 한다.
소개
합의 알고리즘은 여러 머신이 구성원 중 일부의 실패에도 살아남을 수 있는 일관된 그룹으로 작동할 수 있도록 한다. 이 때문에 신뢰성이 높은 대규모 소프트웨어 시스템을 구축하는데 핵심적인 역할을 한다. Paxos는 지난 10년간의 합의 알고리즘 논의를 지배해 왔으며, 대부분의 합의 구현은 Paxos를 기반으로하거나 Paxos에 영향을 받았으며 Paxos는 학생들에게 합의 알고리즘을 가르치는 주요 수단이 되었다.
그러나 불행히도 Paxos는 접근성을 높이기 위한 수많은 시도에도 불구하고 이해하기가 너무 어렵다. 또한, 아키텍처는 실제 시스템을 지원하기 위해서는 복잡한 변경이 필요하다. 그 결과 시스템 구축자와 학생 모두Paxos를 사용하는 데 어려움을 겪었다.
Paxos를 직접 사용해본 후 우리는 시스템 구축과 교육을 위한 더 나은 기반을 제공할 수 있는 새로운 합의 알고리즘을 모색하기 시작했다. 주요 목적이 이해도를 높인다는 점에서 우리의 접근법은 독특했다. 실용적인 시스템을 위해 합의 알고리즘을 정의하고 Paxos보다 훨씬 쉽게 이해할 수 있는 방법으로 설명 할 수 있나요? 더 나아가, 우리는 이 알고리즘이 시스템 구축자에게 필수로 직관적 개발을 촉진하기를 원했다. 알고리즘이 작동하는 것뿐만 아니라 그것이 작동하는 이유를 명확히 보여주는 것도 중요했다.
이 연구의 결과는 Raft라는 합의 알고리즘이다. Raft의 설계에서는 이해도 를 높이기 위해 분해(Raft는 리더 선출, 로그 복제 및 안전성을 분리함)나 공간 상태의 축소(Raft는 Paxos에 비해 비결정성의 정도와 서버간 불일치를 줄이고 있음) 등을 적용했다. 두 대학의 43명의 학생을 대상으로 한 사용자 조사는 Paxos보다 Raft가 훨씬 이해하기 쉽다는 것을 발견했다.
Raft는 많은면에서 기존의 합의 알고리즘(특히 Oki, Liskov의 Viewstamped Replication)과 비슷하지만 몇가지 새로운 기능이 있다.
- 강력한 리더 : Raft는 다른 합의 알고리즘보다 강력한 리더십을 사용한다. 예를 들어 로그 항목은 리더에서 다른 서버로만 흐른다. 이렇게하면 복제된 로그를 쉽게 관리할 수 있으며 Raft를 쉽게 이해할 수 있도록 한다.
- 리더 선츨 : Raft은 리더를 선출하기 위하여 무작위 타이머를 이용한다. 이것은 기존의 컨센서스 알고리즘에서도 필요한 heartbeat에 극히 작은 메커니즘을 추가하는 것만으로 충돌을 쉽고 빠르게 해결한다.
- 멤버십 변경 : Raft 메커니즘은 클러스터의 서버 세트를 변경하기 위해 마이그레이션 중에 두개의 서로 다른 구성의 대부분이 중복되는 새로운 연결 합의(joint consensus) 접근법을 사용한다. 이렇게 하면 클러스터가 구성 변경중에도 정상적으로 계속 작동 할 수 있다.
Raft는 교육 목적과 구현의 기초로서 Paxos 및 기타 합의 알고리즘보다 우수하다고 생각한다. 다른 알고리즘보다 간단하고 이해하기 쉽고 실제 시스템의 요구를 충족할 만큼 충분히 설명되어 있다. 오픈 소스 구현이 몇가지 존재하고 일부 기업에서 사용되고 있으며, 그 안전성은 공식적으로 확인되고 입증되었다. 그리고 효율성은 다른 알고리즘과 유사하다.
Replicated state machines(복제 상태 머신)
복제 상태 머신(Replicated state machines)은 분산 시스템에서 다양한 내결함성 문제를 해결하는데 사용된다. 예를 들어 GFS, HDFS, RAMCloud와 같은 단일 클러스터 리더가 있는 대규모 시스템은 일반적으로 리더 선출을 관리하고 리더 충돌후에도 지속되어야 하는 구성 정보를 저장하기 위해 다른 복제 상태 시스템을 사용한다. 복제 상태 머신의 예로는 Chubby와 ZooKeeper가 있다.
복제 상태 머신은 복제된 로그를 사용하여 구현된다. 각 서버는 상태 머신이 순서대로 실행하는 일련의 명령을 포함하는 로그를 저장한다. 각 로그에는 동일한 명령이 동일한 순서로 포함되므로 각 상태 머신은 동일한 명령 시퀀스를 처리한다. 상태 머신은 결정론적이기 때문에 각각이 동일한 상태와 함께 출력 시퀀스를 계산한다.
복제된 로그의 일관성을 유지하는 것이 합의 알고리즘의 작업이다. 서버의 consensus modules은 클라이언트로부터 명령을 수신하여 로그에 추가한다. 다른 서버의 컨센서스 모듈과 통신하여 일부 서버에서 장애가 발생하더라도 결국 모든 로그에 동일한 요청이 동일한 순서로 포함되도록 한다. 명령이 올바르게 복제되면 각 서버의 상태 머신은 로그 순서대로 처리하고 그 출력이 클라이언트로 반환된다. 결과적으로 여러 서버는 하나의 서버처럼 동작하고, 높은 신뢰성을 가진 상태 머신이 된다.
현실적인 시스템을 위한 Consensus algorithm은 다음과 같은 특징을 갖는다:
- 네트워크 지연, 파티션 분할, 패킷 손실, 중복, 정렬 등 모든 비 비잔틴 상황에서 **(안전성)**을 보장한다(잘못된 결과가 반환되지 않음).
- 대부분의 서버가 구동중이며 클라이언트와 상호 작용할 수 있는 한 이들은 완전히 작동한다**(가용성)**. 즉, 5대의 서버로 구성된 일반적인 클러스터 구성에서 2대의 서버 장애를 견딜 수 있다. 서버가 중지되면 실패한다고 가정한다. 이들은 나중에 안정된 저장 장치의 상태로부터 복구되고 다시 클러스터에 참여할 수 있다.
- 로그 일관성을 보장하기 위해 타이밍에 의존하 지 않는다: 불완전한 시계와 극단적인 메시지 지연은 최악의 경우 가용성 문제를 일으킬 수 있다.
- 일반적인 경우에는 대부분의 클러스터가 한번의 원격 프로시저 호출에 응답하자마자 명령이 완료된다. 소수의 느린 서버가 전체 시스템 성능에 영향을 미치지 않는다.
이해하기 쉬운 설계
Raft의 설계에는 몇가지 목표가 있다: 시스템 구축을 위한 완전하고 실용적인 기반(Paxos 알고리즘은 그렇지 못함)을 제공해야 하기 때문에 개발자에게 요구되는 설계 작업을 대폭 줄여야 한다; 그러나 우리의 가장 중요한 목표, 그리고 가장 어려운 과제는 이해하기 쉬움(understandability) 이었다. 많은 사람들이 알고리즘을 편안하게 이해할 수 있어야 한다. 또한, 시스템 구축자가 실제 구현에서 불가피한 확장을 할 수 있도록 알고리즘을 직관적으로 개발을 할 수 있어야 한다.
Raft의 설계에는 대안적인 접근방법들 중에서 선택해야 하는 점이 많았다. 이러한 상황에서 우리는 이해의 용이성을 바탕으로 선택 사항을 평 가했다. 각 옵션을 설명하는 것은 얼마나 어려운가?
이러한 분석에는 높은 주관성이 존재한다는 것을 우리는 인식하고 있다; 그럼에도 불구하고, 우리는 일반적으로 적용가능한 두가지 기법을 사용하였다. 첫번째 접근법은 잘 알려진 문제 분해 접근법이다. 가능한 한 개별적으로 해결, 설명 및 이해할 수 있도록 우리는 문제를 가능한 한 별도의 부분으로 나누었다. 예를 들어 Raft에서는 리더 선출(leader election), 로그 복제(log replication), 안전성(safety), 멤버십 변경(membership change)을 분할했다. 두번째 접근법은 고려해야 할 상태의 수를 줄이고 시스템을 더 일관성 있게 만들고 가능한 경우 비결정성을 제거하여 상태 공간을 단순화하는 것이다. 구체적으로는, 일련의 로그에는 구멍이 생기는 것이 허용되지 않고, Raft는 로그가 서로 모순될 가능성이 있는 방법을 제한했다. 대부분의 상황에서 우리는 비결정성을 제거하려고 시도했지만, 그 비결정성이 실제로 이해 가능성을 개선하는 몇가지 상황이 있었다. 특히, 랜덤화된 접근법에서는 비결정성이 도입되고 있지만, 이들은 모든 가능한 선택사항을 똑같이 취급함으로써(어떤 것을 선택해도 변함없다) 상태 공간을 단순화할 수 있다. Raft의 리더 선출 알고리즘을 단순화하기 위해 우리는 무작위화를 사용한다.
Raft consensus algorithm
Raft는 먼저 리더를 선출하고 복제된 로그 관리에 대한 완전한 책임을 그 리더에게 부여하는 식으로 합의 알고리즘을 구현한다. 리더는 클라이언트로부터 로그 엔트리를 수신하고, 이들을 다른 서버로 복제하고, 각 서버의 상태 머신에 로그 엔트리를 적용해도 안전한 시점에 서버에 통지한다. 리더를 가지는 것은 복제된 로그의 관리가 단순해 진다. 예를 들어 리더는 다른 서버에 상의하지도 않고 로그의 새 엔트리를 어디에 배치할지 결정할 수 있으며 데이터는 리더에서 다른 서버로 간단한 방식으로 전송된다. 리더에 장애가 나거나, 다른 서버와 연결이 끊어질 경우 새로운 리더가 선출된다.
리더 접근법을 고려하면 Raft는 합의 문제를 세가지 비교적 독립적인 하위 문제로 분해할 수 있다. 이들은 아래의 서브 섹션에서 설명한다.
- Leader election(리더 선출) : 기존 리더가 실패하면 새로운 리더가 선출되어야 한다.
- Log replication(로그 복제) : 리더는 클라이언트로부터 로그 엔트리를 받고 클러스터 전체에 복제하고 다른 로그를 자신의 로그 엔트리와 일치시켜야 한다.
- Safety(안전) : Raft의 중요한 안전은 State Machine Safety Property이다. 특정 로그 엔트리가 그 상태 머신에 적용되는 서버가 존재하는 경우, 다른 서버는 동일한 로그 인덱스에 대해 다른 명령을 적용할 수 없다.
Raft consensus algorithm의 중요한 특성은 아래와 같다.
- Election Safety : 주어진 기간 내에서 선출되는 리더는 한 명까지.
- Leader Append-Only : 리더는 로그의 엔트리을 덮어 쓰거나 삭제하지 않는다. 새 엔트리를 덮어쓴다.
- Log Matching : 2개의 로그에 같은 인덱스와 기간의 엔트리가 포함되어 있는 경우, 로그는 지정된 인덱스까지의 모든 엔트리에서 동일하다.
- Leader Completeness : 주어진 기간안에 로그 엔트리가 커밋되면 모든 엔트리 중 더 높은 등급의 기간을 가진 리더로 로그에 표시된다.
- State Machine Safety : 서버가 그 상태 머신에 특정 인덱스의 로그 엔트리를 적용했을 경우, 다른 서버가 같은 인덱스에 대해서 다른 로그 엔트리를 적용하는 것은 결코 없다.
1. Raft 기초
Raft 클러스터에는 여러대의 서버가 포함되어 있으며, 5대는 일반적인 수이며, 시스템은 2대까지의 장애를 허용할 수 있다. 각 서버는 항상 leader(리더), follower(팔로워), candidate(후보자)의 세가지 상태 중 하나이다. 보통의 운영에서는 리더는 1개만으로, 다른 모든 서버는 팔로워이다. 팔로워는 수동적이다: 그들은 스스로 요청을 발행하지 않으며 단지 리더와 후보자의 요청에 응답한다. 리더는 모든 클라이언트 요청을 처리한다(팔로어가 클라이언트 요청을 받으면 팔로어는 리더로 리디렉션한다). 세번째 상태인 후보자는 새로운 지도자를 선출하는 데 사용된다.
Raft는 위와 같이 시간을 임의의 길이의 기간으로 나눈다. 기간은 연속적인 정수로 번호가 매겨진다. 각 기간은 선거에서 시작하여 선거에서는 하나 이상의 후보자가 지도자가 되려고 시도한다. 상황에 따라 이 선거는 분할 투표(split vote)가 이루어질 수 있다. 이 경우 기간은 리더없이 끝나고 새로운 기간(새 선거)이 신속하게 시작된다. Raft는 주어진 기간에 최대 1명의 지도자가 있다는 것을 보증한다.
다른 서버에서 다른 타이밍에서 기간 간 전환을 관찰할 수 있으며 경우에 따라 서버는 선거 또는 전체 기간을 관찰을 못할 수도 있다. 기간은 Raft에서 논리 시간 역할을 하며 이전 리더와 같은 지연된 정보를 서버가 감지를 할 수 있다. 각 서버에는 현재 기간 번호가 저장되어 있으며 시간이 지남에 따라 순차적으로 번호가 증가한다. 현재 기간은 서버가 통신할 때마다 교환된다. 한 서버의 현재 기간이 다른 서버의 기간보다 작으면 현재 기간이 큰 값으로 업데이트 된다. 후보자 또는 리더가 만약 기간이 만료되었음을 발견하면 즉시 팔로워로 돌아간다. 서버가 만료된 기간 번호를 포함하는 요청을 수신하면 요청이 거부된다.
Raft 서버는 RPC를 사용하여 통신을 수행하고 기본 합의 알고리즘은 두가지 RPC만 필요로 한다. RequestVote RPC는 선거 중에 후보자가 초기화학고, AppendEntries RPC는 리더가 시작하여 로그 엔트리를 복제하고 heartbeat 형식을 제공하기 위해 초기화한다. Log compaction 섹션에서는 서버 간 스냅샷을 전송하기 위한 세번째 RPC를 추가한다. 서버는 시간내에 응답을받지 못하면 RPC를 다시 실행하고 최상의 성능을 얻기 위해 RPC를 병렬로 발행한다.
2. Leader election(리더 선출)
Raft는 heartbeat를 사용하여 리더 선출을 트리거 한다. 서버가 시작되면 팔로워로 시작한다. 서버는 리더 또는 후보자로부터 유효한 RPC를 수신하는 동안 팔로어의 상태를 유지한다. 리더는 권한을 유지하기 위해 정기적인 heartbeat(로그 엔트리가 없는 AppendEntries RPC)를 모든 팔로워에게 보낸다. 팔로워가 선거 타임아웃(election timeout)이라고 불리는 시간내에 메세지를 받지 못한 경우, 실행 가능한 리더가 존재하지 않는다고 판단하여 새로운 리더를 선출하기 위한 선거를 개시한다.
선거를 시작하기 위해 팔로어는 현재 기간을 늘려 후보자의 상태로 전환한다. 그런 다음 자신에게 투표하고 클러스터와 다른 서버에 RequestVote RPC를 발행한다. 후보자는 다음 3가지 중 하나가 일어날 때까지 상태를 계속 유지한다: (a)선거에서 승리, (b)다른 서버가 리더가 되었거나, (c)일정 시간 동안 승자가 없는 경우. 이러한 결과는 아래 단락에서 개별적으로 설명한다.
후보자는 같은 기간에 클러스터 전체의 과반수의 서버로부터 투표를 받았을 경우 선거에서 승리한다. 각 서버는 주어진 기간 내에 선착순으로 최대 한명의 후보자에게 투표를 수행한다. 이 다수결 규칙을 통해 최대 1명의 후보자가 특정 기간의 선거에서 승리할 수 있다. 후보자가 선거에 승리하면 리더가 된다. 그런 다음 다른 모든 서버에 heartbeat 메시지를 보내 권한을 설정하여 새로운 선거의 발생을 방지한다.
투표를 기다리는 동안 후보자는 리더라고 주장하는 다른 서버에서 AppendEntries RPC를 받을 수 있다. (그 RPC에 포함되는) 리더의 기간이 적어도 후보자의 현재의 기간 과 같은 크기인 경우, 후보자는 리더를 정당한 것으로 인식하고 팔로워 상태로 돌아온다. RPC의 기간이 후보자의 현재 기간보다 작으면 후보자는 RPC를 거부하고 후보자 상태를 계속한다.
세번째로 고려되는 결과는 후보자가 선거를 이기거나 잃지 않는 상황이다: 다수의 팔로워가 동시에 후보자가 되는 경우, 투표가 분산되어 후보자가 과반수를 획득할 수 없게 된다. 이것이 발생하면 각 후보자는 타임 아웃하고 그 기간을 하나 증가시키고 다음 라운드의 RequestVote RPC를 시작하여 새로운 선거를 시작한다. 그러나 추가 조치가 없으면 분할표는 무제한으로 반복 될 수 있다.
Raft는 분할 투표가 드물고 발생하더라도 신속하게 해결할 수 있도록 무작위화 된 투표 시간 초과 기능을 사용한다. 첫번째 단계에서 투표가 나뉘지 않도록 선거 시간 초과는 일정 기간(예 : 150-300ms)에서 무작위로 선택된다. 이것은 서버간에 서로 다르기 때문에 대부분의 경우 단일 서버만 타임 아웃 된다. 분할 투표를 처리하기 위해 동일한 메커니즘이 사용된다. 각 후보자는 선거가 시작될 때 랜덤화된 선거 타임아웃을 재개하고 다음 선거를 시작하기 전에 타임아웃이 경과할 때까지 기다린다. 이로 인해 새로운 선거에서 다른 투표가 발생할 가능성이 낮아지게 된다. Performance 섹션에서 이 접근법이 리더를 신속하게 선출한다는 것을 보여준다.
선거는 설계 옵션 중에서 이해 가능성이 우리의 선택을 어떻게 이끌었는지의 예이다. 처음에는 순위 시스템을 사용할 계획이었다 : 각 후보자는 경쟁하는 후보 중에서 선택에 사용되는 고유한 순위가 할당되었다. 후보자가 더 높은 수준의 다른 후보자를 발견하면, 더 높은 수준의 후보자가 선거에서 이길 수 있도록 자신은 팔로어로 돌아간다. 이 접근법은 가용성과 관련된 섬세한 문제가 발생하는 것으로 나타났다(상위 서버에 장애가 발생하면 하위 서버가 시간 초과되어 다시 후보자가 되어야 한다). 알고리즘을 여러번 조정했지만 각 조정 후에 새로운 특이 케이스가 발생했다. 결국, 우리는 무작위화된 재시도 접근법이 더 분명하고 이해하기 쉽다는 결론을 내렸다.
3. Log replication(로그 복제)
리더가 선출되면 리더는 클라이언트의 요청을 처리하기 시작한다. 각 클라이언트 요청에는 복제 상태 머신(Replicated state machine)에서 실행되는 명령이 포함된다. 리더는 새로운 엔트리로서 커멘드를 로그에 추가한 다음, 엔트리를 복제하기 위해서 다른 각 서버에 병렬로 AppendEntries RPC를 실행한다. 엔트리가 안전하게 복제되면 리더가 그 엔트리를 스테이트 머신에 적용해, 그 실행 결과를 클라이언트에 돌려준다. 팔로워가 충돌하거나, 동작이 느려지거나, 네트워크 패킷이 손실된 경우, 리더는 모든 팔로워가 최종적으로 모든 로그 엔트리를 저장할 때까지 (클라이언트에 응답한 후에도) AppendEntries RPC를 무제한으로 재시도한다.
로그는 위와 같이 구성된다. 각 로그 엔트리는 리더에 의해 엔트리가 수신될 때 상태 머신 커맨드와 기간 번호를 저장한다. 로그 엔트리의 기간 번호는 로그 간의 불일치를 감지하고 “Raft consensus algorithm의 중요한 특성”의 일부 속성을 확인하는 데 사용된다. 각 로그 엔트리에는 로그 내의 위치를 식별하는 정수 인덱스도 포함된다.
리더는 상태 머신에 로그 엔트리를 적용해도 안전하다고 판단하는 시점을 결정한다; 이러한 엔트리는 커밋이라고 불린다. Raft는 커밋된 엔트리는 영속적이며, 최종적으로는 이용 가능한 모든 스테이트 머신에 의해 실행되는 것을 보증한다. 로그 엔트리는 그 엔트리를 작성한 리더로부터 과반수의 서버에 복제되면 커밋된다. 또한 이것은 이전 리더가 만든 엔트리를 포함하여 리더가 가진 로그의 이전 엔트리르도 모두 커밋한다. Safety 섹션은 리더를 교체한 후에 이 규칙을 적용할 때의 미묘한 차이점을 설명하고, 이러한 커밋의 정의가 안전하다는 것을 보여준다. 리더는 커밋되고 있는 것을 알고 있는 최대의 인덱스를 추적해, 이후에 AppendEntries RPC(heartbeat를 포함)에 그 인덱스를 포함해, 다른 서버가 최종적으로 찾아낼 수 있도록 한다. 팔로워는 로그 엔트리가 커밋되었음을 알면 해당 엔트리를 로컬 상태 시스템에(로그 순서대로) 적용한다.
Raft 로그 메커니즘은 서로 다른 서버간에 높은 수준의 로그 일관성을 유지하도록 설계되어 있다. 이것은 시스템의 동작을 단순화하고, 보다 예측 가능하게 할 뿐만 아니라 안전성을 확보하기 위한 중요한 요소가 된다. Raft는 Log Matching Property를 구성하는 다음과 같은 특성을 보유하고 있다.
- 다른 로그의 2개의 엔트리에 같은 인덱스와 기간이 있는 경우, 그들은 같은 커멘드를 보관 유지하고 있다.
- 다른 로그의 2개의 엔트리에 같은 인덱스와 기간이 있는 경우, 그 이전의 모든 엔트리에 대한 로그는 동일하다.
첫번째 속성은 리더가 특정 기간 내에 특정 로그 인덱스를 가진 최대 하나의 엔트리를 만들고 로그 엔트리는 로그에서 위치를 변경하지 않는다는 사실에 기반한다. 두번째 속성은 AppendEntries가 수행하는 간단한 일관성 검사를 통해 보장된다. AppendEntries RPC를 송신할 때, 리더는 새로운 엔트리의 직전의 엔트리의 인덱스와 기간을 로그에 포함한다. 팔로어가 해당 로그에서 동일한 인덱스와 기간을 가진 엔트리를 찾을 수 없는 경우 새 엔트리를 거부한다. 일관성 검사는 유도 단계 역할을 한다. 로그 초기의 빈 상태는 Log Matching Property를 충족시키고 일관성 검사는 로그가 확장될 때마다 Log Matching Property를 유지한다. 결과적으로 AppendEntries가 성공적으로 반환되면 리더는 팔로워의 로그가 새 엔트리까지 자신의 로그와 동일하다는 것을 알 수 있다.
보통 운영시에는 리더와 팔로워의 로그는 일관된 채로 있기 때문에 AppendEntries의 일관성 체크가 실패하는 일은 없다. 그러나, 리더가 충돌하면 로그가 일관성이 깨질 수 있다(이전 리더는 로그의 모든 엔트리를 완전히 복제하지 않을 수 있다). 이러한 불일치는 일련의 리더와 팔로워의 충돌을 악화시킬 수도 있다.
위 그림은 팔로워의 로그가 새로운 리더의 로그와 어떻게 다른지를 보여준다. 팔로워는 리더에 존재하는 엔트리가 누락되었거나 리더에 존재하지 않는 추가 엔트리가 있거나 둘 다일 수 있다. 로그에 누락되거나, 관련없는 엔트리가 다수 기간에 걸쳐 있을지도 모른다. Raft에서 리더는 팔로워의 로그에 자신의 복제를 강제하여 모순을 처리한다. 즉, 팔로워 로그의 충돌하는 엔트리는 리더의 로그 엔트리로 덮어쓴다. Safety(안전) 섹션은 한가지 제한 사항과 함께 사용하면 이런 방식이 안전하다는 것을 보여준다.
팔로어의 로그를 자신의 로그와 일치시키려면 리더는 두개의 로그가 일치하는 최신의 로그 엔트리를 찾아, 그 이후는 팔로워의 로그내의 모든 엔트리를 삭제해, 그 후의 모든 리더 엔트리를 팔로워에 송신할 필요가 있다. 이러한 모든 동작은 AppendEntries RPC가 수행하는 일관성 검사에 대한 응답으로 수행한다. 리더는 각 팔로워의 nextIndex를 관리한다. 이것은 리더가 해당 팔로어에게 보내는 다음 로그 엔트리의 인덱스이다. 리더가 최초로 권한을 얻으면, 모든 nextIndex 값을, 자신의 로그내의 마지막 값의 직후에 있는 인덱스로 초기화한다. 팔로워의 로그가 리더의 로그와 일치하지 않으면 다음 AppendEntries RPC에서 AppendEntries 일관성 검사가 실패한다. 거부 후 리더는 nextIndex를 감소시키고 AppendEntries RPC를 다시 실행한다. 이윽고 nextIndex는 리더와 팔로워의 로그가 일치하는 포인트에 도달한다. 이것이 일어나면 AppendEntries가 성공해, 팔로워의 로그내의 경합하는 엔트리가 삭제되어(존재하는 경우) 리더의 로그로부터 엔트리가 추가된다. AppendEntries가 성공하면 팔로워의 로그는 리더의 로그와 일치하고 이 상태는 해당 기간동안 그대로 유지된다.
필요한 경우, 거부되는 AppendEntries RPC를 줄이기 위해 프로토콜을 최적화 할 수 있다. 예를 들어 AppendEntries 요청을 거부하면 팔로어는 충돌하는 엔트리의 기간과 해당 기간에 저장된 첫 번째 인덱스를 포함 할 수 있습니다. 이 정보에 의해 리더는 nextIndex를 감소시켜 그 기간내의 경합하는 엔트리 모두를 회피할 수 있다; 엔트리 마다 1개의 RPC는 아니고, 경합하는 엔트리를 가지는 각 기간에 대해 1개의 AppendEntries RPC가 필요하게 될 것이다. 실제로, 장애가 거의 발생하지 않으며, 많은 모순되는 엔트리가 존재할 가능성이 없기 때문에, 이 최적화가 필요하지 않을 수 있다.
이 메커니즘은 리더가 권한을 얻었을때 로그 무결성을 회복하기 위한 특별한 조치를 취할 필요가 없다. 일반 작업을 시작하기만하면 AppendEntries 일관성 검사 실패에 따라 로그가 자동으로 수렴된다. 리더 자체가 로그의 엔트리를 덮어쓰거나 삭제하지 않는다.
이 로그 복제 메커니즘은 Replicated state machines 섹션에서 설명한 바람직한 합의 특성을 보여준다. Raft는 과반수 서버가 구동하는 한 새로운 로그 엔트리를 수락, 복제, 적용할 수 있다. 일반적인 경우 새로운 엔트리는 클러스터 대부분에 대한 단일 RPC 라운드로 복제될 수 있으며, 단일의 느린 팔로워는 성능에 영향을 미치지 않는다.
4. Safety(안전)
이전 섹션에서는 Raft가 리더를 선택하고 로그 엔트리를 복제하는 방법에 대해 설명했다. 그러나 지금까지 설명한 메커니즘은 각 상태 머신이 동일한 명령을 동일한 순서로 실행하는 것을 보장하기에는 충분하지 않다. 예를 들면, 리더가 복수의 로그 엔트리를 커밋하고 있는 동안 팔로워를 사용할 수 없는 경우가 있다. 그 팔로워가 리더에 선출되어 버리면, 그 엔트리를 새로운 엔트리로 덮어쓰는 것이 가능해, 그 결과로 다른 스테이트 머신이 다른 명령 순서를 실행할 가능성이 있다.
이 섹션에서는 어느 서버가 리더에 선출되는지에 제한을 가함으로써 Raft 알고리즘을 완성시킨다. 이 제한으로 인해 특정 기간의 리더가 이전 기간에 커밋된 모든 엔트리를 포함하게 된다(Leader Completeness Property). 선거의 제한을 부여하고 커밋 규칙을 더 정확하게 만들었다. 마지막으로 Leader Completeness Property에 대한 증명을 제시하고, 복제 상태 머신의 올바른 동작으로 어떻게 연결되는지를 보여준다.
4.1 Election restriction(선거 제한)
리더 기반 합의 알고리즘에서 리더는 결국 모든 커밋된 로그 엔트리를 저장해야 한다. Viewstamped Replication과 같은 일부 합의 알고리즘은 초기에 커밋된 모든 로그 엔트리를 보유하지 않은 서버조차도 리더로 선택할 수 있다. 이러한 알고리즘은 선거 과정 동안 또는 그 직후에 누락된 엔트리를 식별하고 새로운 리더에게 전송하기 위한 추가적인 메커니즘을 포함되어 있다. 불행히도 이로 인해 상당한 추가 메커니즘과 복잡성이 발생한다. Raft는 보다 단순한 접근법을 채택하고, 각 새로운 리더가 선출된 순간부터 이전의 기간에 커밋 된 모든 엔트리가 존재하는 것을 보증한다. 이러한 엔트리를 리더에게 전송할 필요는 없다. 즉, 로그 엔트리는 리더에서 팔로워로의 한 방향으로만 흐르고 리더는 로그의 기존 엔트리를 덮어 쓰지 않는다.
Raft는 로그에 모든 커밋 된 엔트리를 포함하지 않는 후보자가 선거에서 이길 수 없도록 하는 투표 프로세스를 사용한다. 후보자가 선출되기 위해서는 클러스터의 과반수와 접촉할 필요가 있다. 즉, 커밋 된 모든 엔트리는 이러한 서버 중 적어도 하나에 존재해야 함을 의미한다. 후보자의 로그가 이들 과반수 서버의 다른 로그와 적어도 같은 최신의 상태로, 모든 커밋이 끝난 엔트리를 보관 유지하고 있을 것이다. RequestVote RPC는 이 제한을 구현한다. PRC에는 후보자의 로그에 대한 정보가 포함되어 있으며 자신의 로그가 후보자의 로그보다 최신인 경우 투표자는 투표를 거부한다.
Raft는 로그내의 마지막 엔트리의 인덱스와 기간을 비교하여 2개의 로그의 어느 쪽이 최신인지를 판단한다. 로그가 다른 기간의 최종 엔트리를 갖는 경우 새 기간의 로그가 최신인 것이 된다. 로그가 같은 기간으로 끝나는 경우, 어느 쪽이든 긴쪽의 로그가 최신의 것이다.
4.2 Committing entries from previous terms(직전 기간의 커밋 엔트리)
로그 복제 섹션에서 설명한 바와 같이 리더는 현재 기간의 엔트리가 과반수의 서버에 저장한 시점에서 커밋이 되었음을 인식한다. 엔트리를 커밋하기 전에 리더가 충돌하면, 이후 리더는 엔트리의 복제를 완료하려고 시도한다. 그러나 리더는 과반수의 서버에 저장되었다고 해서 즉시 이전 기간의 엔트리가 커밋 되었다고 결론을 내릴 수 없다. 아래 그림은 이전 로그 엔트리가 과반수의 서버에 저장되어 있음에도 불구하고 미래의 리더가 항목을 덮어 쓰는 상황을 보여준다.
위 그림과 같은 문제를 없애기 위해 Raft는 복제본을 계산하여 이전 기간의 로그 엔트리를 커밋하지 않는다. 복제본을 계산하면 리더의 현재 기간의 로그 엔트리만 커밋한다. 현재 기간의 엔트리가 커밋되면 Log Matching Property는 이전의 모든 항목을 간접적으로 커밋한다. 리더는 오래된 로그 엔트리가 커밋 끝났다고 안전하게 결론할 수 있는 상황(예를 들어 그 엔트리가 모든 서버에 저장되어 있는 경우)도 있지만, Raft는 단순화를 위해서 보다 보수적인 어프로치를 채용하고 있다.
Raft에서는 리더가 이전의 기간으로부터 엔트리를 복제할 때에 로그 엔트리가 원래의 기간 번호를 보관 유지하기 때문에, 커밋 룰에 불필요한 복잡성을 발생시킨다. 다른 컨센서스 알고리즘에서는 새로운 리더가 이전의 기간으로부터 엔트리를 재복제하는 경우, 새로운 기간 번호를 사용해 재복제할 필요가 있다. Raft 접근법은 로그 엔트리가 시간이 지남에 따라 로그 전체에 동일한 기간 번호를 유지하기 때문에 로그 엔트리를 쉽게 결정할 수 있다. 게다가 Raft의 새로운 리더는 그외의 알고리즘보다 이전의 기간으로부터 보내지는 로그 엔트리가 적다(다른 알고리즘에서는 커밋하기 전에 중복한 로그 엔트리를 송신해 재번호 붙여야 한다)
4.3. Safety argument(Safety 토론)
완전한 Raft 알고리즘을 제공함으로써, 우리는 Leader Completeness Property가 성립하는 것을 보다 정확하게 논의할 수 있다(이 논의는 안전성의 증명에 근거하고 있다). 우리는 Leader Completeness Property가 성립하지 않는다고 가정하고 그 모순을 증명한다. 기간 T의 리더(leader T)가 그 기간의 로그 엔트리 를 커밋하지만, 그 로그 엔트리가 어느 미래 기간의 리더에 저장되지 않았다고 가정한다. 리더(leader U)가 엔트리를 저장하지 않는 최소 기간 U > T 생각하자.
-
- 커밋된 엔트리는 리더 U가 선출된 시점의 로그에는 아직 존재하지 않아야 한다(리더는 결코 엔트리를 삭제하거나 덮어쓰지 않는다).
-
- 리더 T는 클러스터의 과반수에 엔트리를 복제하고, 리더 U는 클러스터의 과반수에서 득표를 얻었다. 따라서 아래 그림에서 볼 수 있듯이 적어도 하나의 서버(“the voter”)가 리더 T의 항목을 수락하고 리더 U에 투표했다. the voter는 모순에 도달하는 열쇠다.
-
- the voter는 leader U에 투표하기 전에 leader T로부터 커밋된 엔트리를 받아야 한다; 그렇지 않으면 leader T로부터의 AppendEntries 요청은 거절되었을 것이다(그 시점의 기간은 T보다 더 커질 것이다).
-
- leader U에 투표했을 때에도 the voter는 엔트리를 보존하고 있다. 이는 잠재적인 모든 리더가(가정에 따라) 엔트리를 포함하며 리더는 엔트리를 삭제하지 않고 팔로워가 리더와 충돌하는 엔트리만 삭제하기 때문이다.
-
- the voter는 leader U에게 표를 주었기 때문에, leader U의 로그는 the voter의 로그와 친숙한 정도로 최신이어야 한다. 이것은 두가지 모순 중 하나를 이끌어 낸다.
-

