隔离、两边都写、再恢复
한국어 원문으로 표시합니다.
목표
세 노드 중 하나를 나머지에서 떼어 냈다가 되돌리면서, 같은 순간에 리더가 둘이 되는 상태와 그때 각 쪽에 쓴 값의 운명을 직접 봅니다.
왜 중요한가
분산 시스템의 사고는 대개 '장애' 가 아니라 불완전한 정보에서 옵니다. 떼어진 리더는 자기가 떼어졌다는 것을 모릅니다. 그가 아는 것은 '요즘 응답이 안 온다' 뿐이고, Raft 의 리더에게는 스스로 물러나는 장치가 없습니다. 그래서 그는 계속 리더처럼 행동하고, 클라이언트의 값을 받고, ok 를 돌려줍니다. 같은 순간 반대편에서는 과반이 모여 더 높은 임기의 새 리더를 뽑습니다.
이때 리더는 정말로 둘입니다. Raft 가 막는 것은 리더가 둘인 상태가 아니라 같은 임기에 리더가 둘인 상태이고, 소수 쪽 리더는 과반의 확인을 받을 수 없으므로 아무것도 확정하지 못합니다. 회복되는 순간 그의 미확정 항목은 조용히 사라집니다. 이 실습이 남기는 질문은 하나입니다 — 그 사이에 ok 를 받아 간 클라이언트는 어떻게 되는가.
단계
- 세 파일을
/root/raft/에 놓고 세 노드를 띄워/root/raft/leader.json에 적습니다. /root/raft/net.py에should_deliver(src, dst, cut)를 씁니다.- 값
alpha를 써서 확정하고/root/raft/before.json에 적습니다. - 리더와 나머지 둘이 서로를 끊게 하고
/root/raft/isolated.json에 적습니다. - 새 리더가 뽑히기를 기다려
/root/raft/two-leaders.json에 적습니다. - 옛 리더에게
ghost를 쓰고/root/raft/minority.json에 적습니다. - 새 리더에게
real을 쓰고/root/raft/majority.json에 적습니다. - 끊기를 풀고 결과를
/root/raft/verdict.json에 적습니다.
참고
- 끊기:
curl -s -XPOST -d '{"peers":[2,3]}' 127.0.0.1:5001/cut - 풀기:
curl -s -XPOST -d '{"peers":[]}' 127.0.0.1:5001/cut - 로그 보기:
curl -s 127.0.0.1:5001/log - 흔한 실수 1: 한쪽에만 끊기를 거는 것. 반대 방향이 살아 있으면 분할이 아닙니다.
- 흔한 실수 2: 새 리더가 뽑히기 전에 서둘러 읽는 것. 선거에는 타임아웃만큼의 시간이 필요합니다.
세 노드를 세운다
/opt/lab/raft/ 의 node.py, rules-for-partition.py, log-for-partition.py 를 /root/raft/ 의 node.py, raftrules.py, raftlog.py 로 놓고 세 노드를 띄운 뒤, 뽑힌 리더를 /root/raft/leader.json 에 적으세요.
앞 두 실습에서 쓴 규칙을 완성본으로 드립니다. 이번 실습에서 여러분이 쓰는 코드는 분할 스위치 하나뿐이고, 나머지는 손으로 실험하는 시간입니다. leader.json 에는 leader 와 term 을 적으세요.
선을 끊는 대신 메시지를 거부한다
/root/raft/net.py 에 should_deliver(src, dst, cut) 를 쓰세요. cut 에 들어 있는 상대의 메시지는 주고받지 않습니다.
이 파드에는 방화벽을 걸 권한이 없습니다. 그래서 '선을 끊는' 대신 노드가 특정 상대의 요청을 거부하게 만듭니다 — 상대에게는 응답이 오지 않는 것과 구별되지 않으므로, 합의 알고리즘 입장에서는 진짜 분할과 같습니다. dst 는 나 자신이고 cut 은 내가 끊어 버린 상대의 목록입니다. curl -s -XPOST -d '{"peers":[2,3]}' 127.0.0.1:5001/cut 으로 걸고, /status 의 cut 으로 확인하세요.
분할 전에 값 하나를 확정한다
리더에게 값 alpha 를 쓰고 세 노드 모두 commit 이 1 이 되는 것을 확인한 뒤, /root/raft/before.json 에 value, commit, committed_on 을 적으세요.
뒤에서 무엇이 버려지고 무엇이 남는지를 가리려면, 분할 전에 확정된 값이 하나 있어야 합니다. committed_on 은 그 값을 커밋한 노드의 수입니다. 쓰기는 curl -s -XPOST -d '{"value":"alpha"}' 127.0.0.1:<리더포트>/client 입니다.
리더를 떼어 놓는다
리더와 나머지 둘이 서로를 끊게 만들고, 끊긴 직후 옛 리더의 상태를 /root/raft/isolated.json 에 old_leader, state_after_cut, cut 으로 적으세요.
양쪽 모두에 걸어야 진짜 분할입니다 — 한쪽만 걸면 한 방향은 여전히 닿습니다. 끊긴 리더가 스스로 물러나는지 보세요. Raft 의 리더는 자기를 강등하는 장치가 없습니다. 더 큰 임기를 보기 전까지는 자기가 리더라고 계속 믿습니다.
같은 순간에 리더가 둘이다
과반 쪽에서 새 리더가 뽑히기를 기다렸다가, 두 리더의 번호와 임기를 /root/raft/two-leaders.json 에 old_leader, old_term, new_leader, new_term 으로 적으세요.
여기가 이 코스의 제목입니다. 세 노드의 /status 를 나란히 놓고 보면 두 노드가 동시에 leader 라고 답합니다. 그런데 임기가 다릅니다. Raft 가 막는 것은 '리더가 둘인 상태' 가 아니라 '같은 임기에 리더가 둘인 상태' 이고, 그 구분이 이 알고리즘의 안전성이 서 있는 자리입니다.
소수 쪽에 쓴 값
끊긴 옛 리더에게 값 ghost 를 쓰고, /root/raft/minority.json 에 accepted, committed, log_len, commit 을 적으세요.
끊긴 리더도 값을 받기는 합니다. 자기 로그에 적고 클라이언트에게 ok 를 돌려줍니다. 그런데 과반의 확인이 오지 않으니 commit 은 1 에서 멈춰 있습니다. 받은 것과 확정한 것의 거리가 여기서 눈에 보입니다 — 이 거리를 모르는 클라이언트가 ok 를 성공으로 읽으면 사고가 납니다.
과반 쪽에 쓴 값
새 리더에게 값 real 을 쓰고, /root/raft/majority.json 에 index, committed, commit, same_index_as_ghost 를 적으세요.
두 값이 같은 자리 번호를 받는다는 것이 핵심입니다. 로그의 2번 자리를 두고 ghost 와 real 이 다투고 있고, 한 자리에는 하나만 남을 수 있습니다. 어느 쪽이 남을지는 이미 정해져 있습니다 — 과반의 확인을 받은 쪽입니다.
회복 - 무엇이 버려지는가
세 노드의 끊기를 모두 풀고 옛 리더의 로그가 정리되는 것을 확인한 뒤, /root/raft/verdict.json 에 survived, discarded, old_leader_state_after, ghost_was_committed, acknowledged_to_client 를 적으세요.
끊기를 푸는 것은 curl -s -XPOST -d '{"peers":[]}' 127.0.0.1:<포트>/cut 입니다. 옛 리더는 더 큰 임기의 심장박동을 보는 순간 물러나고, 그 뒤 자기 로그의 2번 자리가 새 리더의 것으로 덮어써집니다. 마지막 두 항목이 이 실습의 교훈입니다 — 그 값은 한 번도 커밋된 적이 없지만, 클라이언트는 ok 를 받았습니다.