6장. 분산 키-값 저장소 설계
어떤걸 고려해야 할까? 어떤 기술을 쓰면 좋을까?
파티셔닝은 하나의 큰 데이터를 조각내어 분산시켜 저장하는 기법이다. 아래 2가지 문제를 잘 따져봐야 한다.
이 문제에 대한 해답은 안정 해시 설계에서 찾을 수 있다. 위 2가지 요구사항을 정확히 잘 수행하는 기법이다. 추가로 이런 장점도 갖는다.
다중화는 (주로) 비동기적으로 복사본을 저장, 즉 백업하는 것이다. 높은 가용성과 안정성을 확보하기 위해서 다중화가 필요하다.
어떤 데이터를 어디에 저장할 것인지 결정 할 때도 안정 해시 기법을 사용할 수 있다. 키 하나를 해시 링 위에 배치한 후, 그 지점으로부터 시계 방향으로 링을 순회하면서 만나는 첫 N개 서버에 사본을 보관하면 된다. 위 그림은 사본을 3개 저장하려고 할 때, Key1의 사본이 S1, S2, S3에 저장되는 모습을 보여준다. 이 때 주의할 점은 다음과 같다.
여기서 말하는 일관성은 ‘여러 노드에 다중화된 데이터를 동기화하여 유지되는 일관성, 즉 원본 데이터의 사본이 서로 같은 값을 가진 상태로 잘 분산되어 저장되어있는 성질’을 의미한다. 동기화가 제대로 되지 않으면 일관성이 깨진다.
이를 막기 위한 여러가지 방법이 있는데, 책에서는 정족수 합의(Quorum Consensus) 프로토콜을 소개한다. 한마디로 요약하자면 다수결의 원칙을 따르는 것이다. 정족수는 그 다수결을 위한 기준이 되는 노드의 개수다.
정족수 값을 정할 때에는 요구사항에 따라 적절히 값을 정해야 한다. 아래는 몇가지 시나리오에 대한 정족수 선택 케이스다. 원하는 일관성의 수준에 따라 W, R, N 값을 조절하면 된다.
그렇다면 여기서 나온 ‘강한 일관성’은 무엇일까? 일관성의 수준에 따라 다음과 같이 말할 수 있다.
쓰기 연산으로 인한 일관성이 깨지는 문제를 해소하기 위해서는 버저닝 versioning과 벡터 시계 vector clock 기법을 사용할 수 있다. 간단하게 말하자면 데이터 사본의 각 버전을 함께 저장하고, 클라이언트쪽에서 벡터 시계라는 특별한 구조의 데이터를 관리하면서 데이터 충돌 문제를 해소하는 것이다. 클라이언트에서 처리하는 것이기 때문에 클라이언트 구현이 복잡해지고 벡터 시계 용량이 빨리 늘어날 수 있다는 단점이 생기지만, 임계값을 설정하는 등의 방법으로 단점을 극복할 수 있다.
단일 서버만 쓴다면 “서버 A가 죽었습니다. 장애 발생!” 이라고 하면 되지만 서버가 여러개일 때는 좀 다르다. 보통 2대 이상의 서버가 똑같이 서버 A의 장애를 보고해야 해당 서버에 장애가 발생했다고 간주한다. 이를 위한 가장 쉬운 방법은 모든 노드 사이에 멀티캐스팅 채널을 구축하는 것이다. 하지만 서버가 많아질수록 이 방법은 비효율적이게 된다.
가십 프로토콜 gossip protocol 같은 분산형 장애 감지 솔루션을 채택하는게 좋다. 가십 프로토콜은 자신이 살아있음을 박동 카운터 heartbeat counter의 값을 증가시킴으로써 알리고, 다른 노드가 그걸 지켜보면서 누가 죽었는지 판단하는 방법이다. 스스로가 자신의 생존신고를 하고 나머지는 그걸 보다가 일정 시간동안 반응이 없으면 서버 장애가 일어났다고 판단하는 것이다.
장애는 크게 두 종류로 나눌 수 있다. 일시적 장애와 영구적 장애.
일시적 장애로 인해 서버 A가 죽은 경우, 나머지 서버가 해당 서버로 향하는 트래픽을 임시로 처리하면 된다. 장애가 복구되기 전까지의 변경사항을 임시로 기록해두었다가, 장애 복구 이후에 그 기록을 보고 서버 A의 데이터를 보정하는 것이다. 이를 단서 후 임시 위탁 hinted handoff 기법이라고 부른다.
서버 A의 영구적인 장애 상태를 처리하기 위해서 다른 서버의 데이터와 서버 A의 데이터를 서로 비교한 다음 동기화하는 작업이 필요하다. 이를 위해서는 반-엔트로피 프로토콜 anti-entropy protocol을 쓸 수 있다. 각 노드에 보관된 값이나 레이블로부터 계산된 해시값을 사용하여 어떤 부분의 데이터가 다른지 알아내는 기법이다. 이 때 사용하는 해시값을 모아둔 트리를 해시 트리 hash tree, 혹은 머클 트리 Merkle tree 라고 부른다.
머클 트리를 사용하면 좋은 점은 동기화해야 하는 데이터의 양이 두 서버에 저장된 전체 데이터 양에 관계 없이 서로 다른 데이터의 크기에만 비례한다는 점이다. 따라서 두 서버에 저장된 데이터 양이 크다고해서 걱정할 필요가 없다.
분산 시스템 설계를 위해서 신경쓸게 정말 정말 많다. 이런걸 고려해야 한다.
목표/문제 | 사용 가능한 기술 |
---|---|
대규모 데이터 저장 | 안정 해시 |
읽기 연산에 대한 높은 가용성 | 데이터 다중화 |
쓰기 연산에 대한 높은 가용성 | 버저닝 && 백터 시계 |
데이터 파티션 | 안정 해시 |
점진적 규모 확장성 | 안정 해시 |
다양성 | 안정 해시 |
조절 가능한 데이터 일관성 | 정족수 합의 |
일시적 장애 처리 | 느슨한 정족수 프로토콜 |
영구적 장애 처리 | 머클 트리 |
데이터 센터 장애 대응 | 여러 데이터센터에 데이터 다중화 |
여담으로, 이번 장 이름은 "분산 키-값 저장소 설계"였지만 사실 저장소 유형에 관계없이 분산 시스템이라면 모두 통용되는 이야기인 것 같단 생각도 든다. 좀 더 배우다보면 알게 될 것 같다. 어쩜 이렇게 배워야 할게 많은지 😇