합의 알고리즘과 리더 선출
무엇에 합의하는가
- 여러 노드가 같은 명령을 같은 순서로 적용하도록 로그의 순서에 합의한다
- 그 결과 모든 노드가 동일한 상태에 도달한다(복제 상태 기계)
Raft의 세 조각
| 요소 | 내용 |
|---|
| 리더 선출 | 하트비트가 끊기면 후보가 term을 올려 투표를 요청하고, 과반 득표로 리더가 된다 |
| 로그 복제 | 쓰기는 리더만 받아 팔로워에 복제하고, 과반에 복제되면 커밋으로 확정한다 |
| 안전성 | 최신 로그를 가진 노드만 리더가 될 수 있어 커밋된 항목이 사라지지 않는다 |
쿼럼과 노드 수
- 과반수는 N/2 + 1. 3노드는 1대, 5노드는 2대 장애까지 견딘다
- 짝수 노드는 이득이 없다. 4노드도 허용 장애는 1대뿐이므로 홀수로 구성한다
- 과반을 모으지 못하면 쓰기를 멈춘다. CAP 관점에서 CP 성향이다
왜 직접 만들지 않나
합의는 정상 경로가 아니라 이상 경로에서 무너집니다. 그 경로가 많습니다.
리더가 살아 있는데 네트워크만 끊겨 둘이 리더가 되는 경우
투표가 매번 갈려 리더가 안 정해지는 경우
옛 리더가 돌아와 옛 결정을 밀어 넣는 경우
셋 다 평소에는 안 보입니다. 그래서 직접 만든 합의는 사고가 난 뒤에 틀렸다는 것을 압니다.
검증된 구현을 쓰는 쪽이 옳습니다.
노드 수를 어떻게 정하나
과반이 필요하므로 홀수가 낭비가 없습니다.
| 노드 | 견딜 수 있는 고장 | 과반 |
|---|
| 3 | 1대 | 2 |
| 4 | 1대 | 3 |
| 5 | 2대 | 3 |
| 7 | 3대 | 4 |
3과 4가 같은 것이 요점입니다. 한 대를 더 넣어도 견디는 고장 수는 그대로인데 과반이
늘어 쓰기가 느려집니다. 그리고 노드를 늘릴수록 결정마다 기다릴 상대가 많아집니다.
실무 포인트
- 직접 구현할 일은 드물다. etcd, ZooKeeper(ZAB), Kafka KRaft가 이미 사용 중이다
- 리더가 교체되는 수백 ms 동안 쓰기가 실패한다. 클라이언트 재시도가 전제다
- 분단이 생기면 소수 쪽은 리더가 될 수 없다. term과 과반 규칙이 split-brain을 막는 장치다
- 멀티 리전 배치에서는 노드 간 왕복 지연이 커밋 지연으로 그대로 드러난다
Q.리더 선출이 필요한 이유와 과정을 설명해주세요
여러 노드가 같은 데이터를 들고 있을 때, 누구 말이 맞는지 정하는 기준이 필요합니다.
쓰기를 한 곳으로 모으면 순서가 하나로 정해집니다. 그 한 곳이 리더입니다.
리더가 없으면 두 노드가 같은 자리에 다른 값을 쓰고, 나중에 어느 것이 맞는지 알 수
없게 됩니다. 시계로 판단하려 해도 서버마다 시계가 다릅니다.
선출 과정은 세 조각입니다.
| 단계 | 내용 |
|---|
| 임기 | 선거마다 번호가 하나씩 오른다. 옛 임기의 말은 무시된다 |
| 후보 | 리더 소식이 일정 시간 없으면 스스로 후보가 되어 표를 청한다 |
| 과반 | 과반의 표를 얻으면 리더가 된다. 못 얻으면 다음 임기로 넘어간다 |
임기 번호가 옛 리더 문제를 해결합니다. 네트워크가 끊겼다 돌아온 옛 리더가 명령을
내려도, 임기가 낮으므로 받는 쪽이 거절합니다.
대기 시간을 노드마다 다르게(무작위로) 두는 것도 중요합니다. 전부 동시에 후보가 되면
표가 갈려 아무도 과반을 못 얻고, 그게 반복되면 리더가 영영 안 정해집니다.
흔한 실수: 리더 선출을 직접 구현하는 것. 정상 경로는 쉬워 보이지만 무너지는 곳은
이상 경로입니다. 옛 리더의 복귀, 표 분산, 반쪽 네트워크. 세 가지 모두 평소에는
안 보이고 사고가 난 뒤에 틀렸다는 것을 알게 됩니다.
Q.노드를 5개로 구성하면 몇 대 장애까지 견디나요? 짝수는 왜 피하나요?
5대면 2대까지 견딥니다. 과반인 3대가 살아 있어야 결정을 내릴 수 있기 때문입니다.
| 노드 | 과반 | 견디는 고장 |
|---|
| 3 | 2 | 1 |
| 4 | 3 | 1 |
| 5 | 3 | 2 |
| 6 | 4 | 2 |
| 7 | 4 | 3 |
짝수를 피하는 이유가 이 표에 있습니다. 3과 4가 똑같이 1대만 견딥니다. 한 대를 더
넣었는데 내결함성은 그대로이고, 과반이 2에서 3으로 늘어 쓰기마다 기다릴 상대만
늘어납니다. 6과 7도 같은 관계입니다. 짝수는 돈과 지연을 더 쓰고 얻는 것이 없습니다.
무한정 늘리지 않는 이유도 같습니다. 노드가 늘수록 견디는 고장은 늘지만 결정마다
더 많은 응답을 기다려야 해서 쓰기가 느려집니다. 그래서 5가 흔한 타협점입니다.
투표에만 참여하고 데이터를 안 들고 있는 노드를 두는 방식도 있습니다. 짝수 구성에서
동수를 깨는 용도인데, 그 노드가 죽으면 다시 짝수가 되므로 근본 해법은 아닙니다.
흔한 실수: 가용성을 올리려고 노드를 짝수로 늘리는 것. 합의 클러스터에서 노드 수는
과반을 만들 수 있는가로 정해집니다. 대수를 늘리는 것과 견디는 고장 수가 비례하지
않는 유일한 구성입니다.
Q.리더 교체 순간 애플리케이션은 무엇을 겪나요?
교체는 순간이 아니라 구간입니다. 그 사이 쓰기가 거절되거나 멈춥니다.
| 시점 | 애플리케이션이 겪는 것 |
|---|
| 리더가 죽음 | 요청이 응답 없이 타임아웃까지 매달린다 |
| 선출 중 | 쓰기가 거절된다. 읽기는 되기도 한다 |
| 새 리더 확정 | 클라이언트가 새 주소를 알기까지 더 걸린다 |
| 직후 | 옛 리더에 붙어 있던 연결이 끊어져 재연결이 몰린다 |
첫 줄이 가장 아픕니다. 죽은 리더는 거절하지 않고 그냥 대답하지 않습니다. 그래서
클라이언트 타임아웃이 길면 그 시간만큼 요청이 쌓입니다. 합의 계층이 몇 초에 복구돼도
애플리케이션은 훨씬 오래 느립니다. 타임아웃을 짧게 잡는 것이 복구 시간을 정합니다.
준비할 것은 셋입니다. 쓰기 실패를 재시도하되 멱등하게 만들 것, 재시도 간격을 벌려
새 리더에 몰리지 않게 할 것, 그리고 이 구간에 사용자에게 무엇을 보여줄지 정해 둘 것.
읽기는 설정에 따라 다릅니다. 복제본에서 읽도록 허용했다면 이 구간에도 읽기는 되지만
조금 옛 데이터입니다. 그 거래를 받아들일지 미리 정해야 합니다.
흔한 실수: 합의 계층의 복구 시간만 보고 "몇 초면 된다" 고 보는 것. 사용자가 겪는
시간은 거기에 클라이언트 타임아웃과 재연결 몰림이 더해진 값입니다.
Q.네트워크 분단에서 split-brain을 어떻게 막나요?
과반 규칙이 그 자체로 방어입니다. 양쪽이 동시에 과반이 될 수 없기 때문입니다.
5대가 3대와 2대로 갈리면, 3대 쪽만 리더를 뽑고 2대 쪽은 아무 결정도 못 합니다.
2대 쪽에 옛 리더가 있어도 과반의 응답을 못 받으므로 쓰기를 확정하지 못합니다.
| 쪽 | 할 수 있는 것 |
|---|
| 과반이 있는 쪽 | 새 리더를 뽑고 쓰기를 계속한다 |
| 과반이 없는 쪽 | 쓰기 불가. 읽기는 옛 데이터일 수 있다 |
그래서 남는 위험은 소수 쪽에서 읽는 것입니다. 옛 리더가 자기가 아직 리더인 줄
알고 읽기를 처리하면 낡은 값을 줍니다. 리더 임대(일정 시간마다 리더 자격을 갱신)로
막는데, 그 시간 동안은 여전히 낡을 수 있습니다.
정말 위험한 구성은 따로 있습니다. 분단된 양쪽이 각자 별개 클러스터로 재구성되는
경우입니다. 사람이 장애 대응 중 소수 쪽 노드 수를 줄여 과반을 만들어 주면 이것이
일어납니다. 양쪽이 각자 리더를 갖고 각자 쓰기를 받습니다.
흔한 실수: 장애 중에 급해서 노드 수 설정을 손으로 바꾸는 것. 과반 규칙이 지켜 주던
유일한 보호막을 사람이 직접 걷어내는 행동이고, 복구된 뒤 두 갈래 데이터를 합칠 방법이
없습니다.
먼저 스스로 답해보고 아래 답변과 견줘보세요. 막히는 부분은 문제로 확인할 수 있어요.
읽었으면 문제로 확인해보세요
분산 시스템 문제를 풀면 틀린 문제가 자동으로 노트에 쌓입니다. 가입 없이 5문제를 먼저 풀어볼 수도 있어요.