사본이 어긋나는 경로가 앞에서 여러 개 나왔습니다. 쓰기가 셋 중 둘에만 성공하고, 죽어 있던 노드가 돌아오고, 분단 중 양쪽이 각자 씁니다. 어긋남은 예외가 아니라 상시 상태입니다.
그래서 맞추는 일을 설계에 넣습니다. 방법이 셋이고 비용과 시점이 다릅니다.
세 가지 시점
| 방법 | 언제 | 비용 |
|---|---|---|
| 읽는 김에 고치기 | 그 키를 누가 읽을 때 | 거의 없다. 이미 비교했다 |
| 돌아온 노드에 넘기기 | 잠깐 죽었다 돌아올 때 | 보관해 둔 만큼 |
| 배경에서 전부 맞추기 | 주기적으로 | 크다. 전체를 비교해야 한다 |
앞의 둘로 대부분이 해결됩니다. 그런데 아무도 읽지 않는 키는 앞의 둘로 절대 고쳐지지 않습니다. 조회가 없으면 읽는 김에 고칠 기회가 없고, 대신 받아 둔 것도 상한을 넘겨 버려졌을 수 있습니다.
그 키들이 남아 있는 상태에서 다른 사본이 죽으면 최신 값이 사라집니다. 그래서 배경에서 전부 맞추는 일이 필요합니다.
전부 비교하면 감당할 수 없다
노드당 담는 양이 크므로 두 사본이 키를 전부 주고받으면 네트워크가 감당하지 못합니다. 어긋난 키는 대개 아주 적은데 찾는 비용이 전체 크기에 비례하는 것이 문제입니다.
키 범위를 구간으로 나누고 구간마다 요약값을 만들어 둡니다. 그 요약값들을 다시 묶어 위쪽 요약값을 만듭니다. 그러면 비교를 위에서부터 좁혀 갈 수 있습니다.
맨 위 요약값이 같으면 전부 같다. 끝난다
다르면 아래로 내려가 다른 쪽만 따라간다
같은 쪽은 요약값 하나로 확인하고 넘긴다
어긋난 키가 하나뿐이어도 찾는 데 필요한 비교는 몇 단계로 끝납니다. 전송량이 데이터 크기가 아니라 어긋난 양에 비례하게 됩니다.
요약값을 언제 다시 만드나
쓰기가 들어오면 그 구간의 요약값이 낡습니다. 매 쓰기마다 위쪽까지 다시 계산하면 쓰기 경로가 무거워집니다.
| 방식 | 대가 |
|---|---|
| 쓰기마다 갱신 | 쓰기가 무거워진다 |
| 파일을 합칠 때 갱신 | 그 사이 요약값이 낡아 헛비교가 생긴다 |
합칠 때 함께 만드는 편이 낫습니다. 어차피 그때 파일 전체를 읽으므로 추가 비용이 작습니다. 헛비교가 조금 생기지만 맞추기는 주기 작업이라 급하지 않습니다.
맞추기가 서비스를 밀어내지 않게
배경 맞추기는 네트워크와 디스크를 씁니다. 파일 합치기와 마찬가지로 사용자 요청과 같은 자원을 다투므로 속도에 상한을 둡니다.
그리고 사본 셋이 동시에 맞추기를 시작하지 않게 흩습니다. 이유는 합치기와 같습니다. 셋이 함께 느려지면 읽기 정족수가 느린 사본을 우회할 수 없습니다.
이 설계가 약속하는 것
마지막으로 무엇을 약속하고 무엇을 약속하지 않는지 정리합니다.
| 약속한다 | 약속하지 않는다 |
|---|---|
| 분단 중에도 쓰기를 받는다 | 방금 쓴 값을 바로 읽는 것 |
| 두 대가 죽어도 데이터가 남는다 | 두 값 중 어느 것이 최신인지 서버가 판단하는 것 |
| 언젠가 사본이 같아진다 | 언제까지 같아지는지 |
약속하지 않는 것을 명확히 적는 것이 이 저장소를 쓰는 사람에게 가장 중요한 문서입니다. 모르고 쓰면 저장소 장애로 오해하고, 알고 쓰면 응용에서 감당할 수 있습니다.