LabHub
배우기 러닝패스 코스

리더가 둘이었다 · 로그는 무한히 자란다 · 이론

로그는 무한히 자란다

LabHub 에서 이어서 보기

한 줄 요약

합의된 순서를 전부 보관하면 재시작 시간과 디스크가 로그 길이에 비례해 늘어난다. 스냅샷은
'여기까지 적용한 결과' 를 한 장으로 저장해 그 앞의 로그를 버리게 해 주고, 그 대가로
복구 절차와 백업의 의미가 달라진다.

왜 이게 필요했나

합의 알고리즘은 값을 저장하지 않는다. 명령의 순서를 저장한다. 그 순서를 처음부터
다시 적용하면 언제나 같은 상태가 나온다는 것이 복제 상태 기계의 전제이고,
raft.github.io 는 이것을 "합의 알고리즘은 서버들의 로그에 담긴 명령에 합의하는 데
쓰인다" 고 설명한다([raft.github.io](https://raft.github.io/)).

문제는 그 로그가 절대 줄지 않는다는 점이다. 초당 100건을 받는 클러스터는 하루에
860만 개의 항목을 쌓는다. 디스크가 찬다는 것보다 더 아픈 것은 재시작 시간이다.
로그를 처음부터 다시 적용해야 한다면, 노드 하나를 재시작하는 데 며칠이 걸릴 수 있다.
따라잡기도 마찬가지다. 새 멤버를 넣거나 오래 죽어 있던 노드를 되살릴 때, 리더가
보내야 할 항목이 수백만 개면 그 복구는 실질적으로 불가능하다.

어떻게 동작하나

해법은 단순하다. 어느 지점까지 적용한 결과를 통째로 저장하고, 그 앞의 로그를 버린다.
저장한 것에는 마지막으로 포함한 자리 번호와 그 자리의 임기를 함께 적어 둔다. 그 둘이
있어야 그 뒤의 로그를 이어 붙일 수 있기 때문이다.

  버린다                         남긴다 [1..20000 로그]  →  스냅샷(last_index=20000, last_term=7) + [20001.. 로그]

여기서 새 문제가 하나 생긴다. 리더가 팔로워에게 20001번 항목을 보내려는데 그 팔로워가
19000번까지밖에 없다면, 리더에게는 보낼 로그가 이미 없다. 그래서 실제 구현은 로그
대신 스냅샷 자체를 전송하는 경로를 따로 둔다. 뒤처진 정도가 로그로 메울 수 있는
범위를 넘으면 스냅샷을 통째로 보내고, 그 뒤부터 로그로 이어 간다.

etcd 에서 이 손잡이는 --snapshot-count 다. 메모리에 들고 있는 raft 항목이 이 수에
이르면 압축이 일어나고, 기본값은 v3.2 에서 10,000 에서 100,000 으로 바뀌었다. 키의
과거 리비전을 정리하는 것은 별개의 손잡이인 자동 압축이고
--auto-compaction-mode--auto-compaction-retention 으로 정한다. 조각 모음
(etcdctl defrag)은 또 다른 일인데, 문서는 "살아 있는 멤버에 조각 모음을 하면 상태를
다시 만드는 동안 읽기와 쓰기가 막힌다" 고 경고한다
([etcd 유지보수](https://etcd.io/docs/v3.5/op-guide/maintenance/)).

백업의 의미도 달라진다. etcd 의 스냅샷 저장은 etcdctl snapshot save 이고, 복구는
etcdutl snapshot restore 다. 중요한 것은 복구가 원래 클러스터를 되살리는 것이
아니라는 점이다. 문서는 "복구는 스냅샷 메타데이터의 일부(멤버 ID 와 클러스터 ID)를
덮어쓰며, 그 멤버는 이전의 정체성을 잃는다" 고 적고, 그래서 "스냅샷으로 클러스터를
시작하려면 새로운 논리적 클러스터를 시작해야 한다" 고 말한다
([etcd 재해 복구](https://etcd.io/docs/v3.5/op-guide/recovery/)).

이름이 비슷한 세 가지를 구분해 두면 운영에서 헷갈리지 않는다. 로그 압축은 raft 로그를
줄이는 일이고, 자동 압축은 키의 과거 리비전을 줄이는 일이며, 조각 모음은 그렇게 비운
자리를 디스크에 돌려주는 일이다. 앞의 둘을 아무리 돌려도 파일 크기는 줄지 않고,
조각 모음을 아무리 돌려도 리비전은 줄지 않는다.

현장에서 만나는 모습

가장 흔한 사고는 백업을 떠 놓고 복구를 한 번도 해 보지 않는 것이다. 위의 문장이
뜻하는 바는, 복구가 '어제 상태로 되돌리기' 가 아니라 새 클러스터를 세우는 일이라는
것이다. 멤버 ID 가 바뀌므로 기존 멤버가 섞여 들어오면 안 되고, 모든 멤버가 같은
스냅샷으로 복구해야 한다. 이 절차를 장애 당일에 처음 읽으면 이미 늦다.

두 번째는 스냅샷 시점과 실제 데이터의 간격이다. 문서는 member/snap/db 파일에서
스냅샷을 뜨면 "아직 기록되지 않았지만 wal 폴더에 들어 있는 데이터를 잃을 수 있다" 고
경고한다. 그래서 백업은 파일을 복사하는 방식이 아니라 snapshot save 로 뜬다.

세 번째는 압축과 백업 주기의 관계다. 로그를 자주 압축할수록 재시작은 빨라지지만
스냅샷을 만드는 일 자체가 비용이라 그때마다 응답이 잠깐 느려진다. 반대로 드물게
압축하면 평소에는 조용하다가 재시작할 때 한꺼번에 대가를 치른다. 어느 쪽이 나은지는
'얼마나 자주 재시작하는가' 로 정해지지, 문서의 기본값으로 정해지지 않는다.

다음 퀴즈에서 확인할 것

스냅샷이 무엇을 버리고 무엇을 남기는지, 왜 마지막 자리 번호와 임기를 함께 적어야 하는지,
로그 압축과 리비전 자동 압축과 조각 모음이 서로 다른 일이라는 점, 그리고 복구가 왜
새 클러스터를 만드는 일인지를 확인한다.