LabHub
배우기 러닝패스 코스

There Were Two Leaders

It is committed only when a majority has it

LabHub 에서 이어서 보기

한국어 원문으로 표시합니다.

목표

리더가 받은 값이 어떻게 세 노드로 퍼지고, 어느 순간 '확정' 되는지를 규칙으로 씁니다. 끝나면 로그 일치성, 덮어쓰기, 커밋 조건, 그리고 뒤처진 노드가 따라잡는 과정을 손으로 겪은 상태가 됩니다.

왜 중요한가

복제는 '값을 여러 곳에 보낸다' 가 아닙니다. 보낸 것과 확정한 것을 구분하는 일입니다. 리더가 값을 받아 자기 로그에 적었다는 사실은 아무것도 보장하지 않습니다 — 그 리더가 다음 순간 죽으면 그 값은 없던 일이 될 수 있습니다. 과반이 같은 자리에 같은 항목을 갖고 있어야 비로소 '어떤 미래의 리더도 이 자리를 바꿀 수 없다' 가 성립하고, 그때서야 클라이언트에게 성공이라고 말할 수 있습니다.

커밋 조건에는 사람들이 거의 항상 빠뜨리는 단서가 하나 더 붙습니다 — 리더는 자기 임기의 항목이 과반에 닿았을 때만 커밋 번호를 올립니다. 앞 임기의 항목이 과반에 닿았다는 이유로 커밋하면, 그 뒤에 뽑힌 다른 리더가 같은 자리를 덮어써서 이미 확정했다고 말한 값이 사라질 수 있습니다.

단계

  1. node.pyrules-for-replication.py 를 제자리에 놓고 리더를 뽑아 /root/raft/leader.json 에 적습니다.
  2. raftrules.pyup_to_date 를 더하고 on_request_vote 가 그 조건을 함께 보게 합니다.
  3. /root/raft/raftlog.pyappend_entry(log, term, value) 를 씁니다.
  4. 같은 파일에 log_ok(log, prev_index, prev_term) 을 더합니다.
  5. 같은 파일에 apply_entries(log, prev_index, entries) 를 더합니다.
  6. 같은 파일에 commit_index(counts, total, log, term) 을 더합니다.
  7. 값 세 개를 써서 세 노드가 모두 커밋하는 것을 보고 /root/raft/replicated.json 에 적습니다.
  8. 팔로워 하나를 죽였다 되살려 따라잡는 것을 보고 /root/raft/catchup.json 에 적습니다.

참고

앞 실습의 결과를 다시 세운다

/opt/lab/raft/node.py/opt/lab/raft/rules-for-replication.py/root/raft/node.py/root/raft/raftrules.py 로 놓고, 세 노드를 띄워 뽑힌 리더를 /root/raft/leader.json 에 적으세요.

실습은 매번 새 파드에서 시작하므로 앞 실습에서 만든 것이 남아 있지 않습니다. 선거 규칙은 앞 실습의 답을 그대로 드립니다. leader.json 에는 leader 와 term 두 열쇠를 적으세요.

뒤처진 후보에게는 표를 주지 않는다

raftrules.pyup_to_date(log, last_index, last_term) 를 더하고, on_request_vote 가 표를 주기 전에 그 조건도 함께 보게 고치세요.

로그가 생기면 선거에 조건이 하나 붙습니다 — 후보의 로그가 내 것만큼은 최신이어야 표를 줍니다. 비교는 길이가 먼저가 아닙니다. 마지막 항목의 임기를 먼저 보고, 그것이 같을 때만 길이로 가릅니다. 이 조건이 없으면 뒤처진 노드가 리더가 되어 이미 커밋된 항목을 덮어쓸 수 있습니다.

클라이언트 값이 로그에 붙는다

/root/raft/raftlog.pyappend_entry(log, term, value) 를 쓰세요. term 과 value 를 가진 항목을 끝에 붙인 새 목록을 돌려줍니다.

항목에는 값만이 아니라 그때의 임기가 함께 붙습니다. 그 임기가 나중에 두 로그를 비교하는 유일한 근거가 됩니다. 넘겨받은 목록을 그 자리에서 고치지 말고 새 목록을 만들어 돌려주세요 — 배선이 원본을 따로 들고 있습니다.

로그 일치성

raftlog.pylog_ok(log, prev_index, prev_term) 을 더하세요. prev_index 자리의 임기가 prev_term 과 같은지 판정합니다.

Raft 는 로그 전체를 비교하지 않습니다. 한 자리만 맞으면 그 앞은 전부 같다고 봅니다 — 그 성질을 로그 일치성(Log Matching)이라고 합니다. prev_index 는 1부터 세는 자리 번호이고, 0 은 '앞자리가 없다' 는 뜻입니다. 내 로그가 prev_index 보다 짧으면 비교할 것 자체가 없습니다.

어긋나는 자리부터 잘라 낸다

raftlog.pyapply_entries(log, prev_index, entries) 를 더하세요. 어긋나는 자리를 만나면 그 자리부터 잘라 내고 새 항목을 붙인 목록을 돌려줍니다.

핵심은 '언제 자르지 않는가' 입니다. 같은 임기의 같은 항목이 다시 온 것뿐이라면 뒤를 건드리면 안 됩니다 — 재전송이 잦은 환경에서 매번 자르면 팔로워의 로그가 계속 짧아졌다 길어집니다. 항목이 하나도 없는 심장박동도 마찬가지입니다.

커밋 조건

raftlog.pycommit_index(counts, total, log, term) 을 더하세요. counts 는 각 서버가 가진 항목 수이고, 과반이 가졌으면서 그 자리가 현재 임기의 항목인 가장 높은 번호를 돌려줍니다.

'과반이 복제했으면 커밋' 만으로는 부족합니다. 앞 임기의 항목이 과반에 닿았다는 이유로 커밋하면, 그 뒤에 뽑힌 리더가 그 자리를 다른 항목으로 덮어쓸 수 있습니다 — 논문 그림 8 의 사고입니다. 그래서 리더는 자기 임기의 항목이 과반에 닿을 때만 커밋 번호를 올리고, 그때 앞의 것들이 함께 따라옵니다.

세 노드에 값을 쓴다

세 노드를 띄우고 리더에게 값 세 개를 써서 세 노드 모두 commit 이 3 이 되는 것을 확인한 뒤, /root/raft/replicated.json 에 values, commit, log_len 을 적으세요.

값은 리더에게만 씁니다: curl -s -XPOST -d '{"value":"x"}' 127.0.0.1:5001/client. 팔로워에게 보내면 거절하면서 리더가 누구인지 알려 줍니다. 쓴 직후에는 commit 이 아직 오르지 않은 것이 정상입니다 — 팔로워의 응답이 돌아오고 다음 심장박동이 나가야 반영됩니다.

되살아난 노드가 따라잡는다

팔로워 하나를 죽였다 되살려 빈 로그에서 세 칸을 따라잡는 것을 확인하고, /root/raft/catchup.json 에 node, log_len, commit 을 적으세요.

이 습작은 로그를 디스크에 남기지 않습니다. 그래서 되살아난 노드는 아무것도 모르는 상태로 돌아오고, 리더가 보내는 자리 번호를 한 칸씩 뒤로 물리며 맞는 지점을 찾아 거기서부터 채워 줍니다. 죽이는 법은 pkill -f 'node.py --id 2 ' 처럼 번호까지 지정하면 됩니다. 되살린 뒤 log_len 이 올라가는 것을 눈으로 보세요.