예전에 서비스를 확장하는 방법이라는 포스팅을 통해 scale out시 데이터를 분산하는 방법에 대해 간단히 다루었다. 이 포스팅에서는 데이터를 분산 시스템의 어떤 노드에 저장할지 결정하는 방법, 즉 파티셔닝에 대해서만 다루었는데 이는 분산 시스템이 해결해야 하는 여러 문제 중 일부에 불과하다.
'서비스를 확장하는 방법'에서는 Stateful 응용에서 고가용성을 확보하기 위한 방법을 다루지 않았다. 예를 들어 문자열을 저장하는 데이터베이스를 Ranged sharding 기법을 이용해 두 개의 노드로 분산했다고 가정해보자. A-M으로 시작하는 문자열은 첫 번째 노드, N-Z로 시작하는 문자열은 두 번째 노드에 저장한다. 그런데 모종의 이유로 첫 번째 노드에 장애가 생긴다면 A-M으로 시작하는 문자열은 읽을 수 없게 된다. 결국 안정적인 서비스를 위해선 같은 데이터를 저장하는 DB가 하나 더 있어야 한다. 이렇게 같은 데이터를 두 개 이상의 개체에 저장하는 운영 기법을 다중화(Redundancy)라 하는데, 수를 특정하여 이중화, 삼중화라고도 한다.
본론에 들어가기 전에 용어 정리를 먼저 하겠다. 같은 데이터를 저장하는 개체들을 복제 그룹(Replication group)이라고 하고, 각각의 개체를 멤버, 또는 노드라고 한다. '노드' 는 분산 시스템을 구성하는 개체를 지칭할 때 폭넓게 쓰이는 단어다. 의미의 폭이 지나치게 넓어서 데이터 파티셔닝에도 쓰일 수 있고(예: A-M으로 시작하는 데이터를 저장하는 노드), 복제 그룹의 멤버를 지칭하는 말로도 쓸 수 있다. 분산 시스템에는 부하를 분산하기 위한 데이터 파티셔닝과 고가용성을 위한 다중화가 모두 필요하지만, 이 글의 주제는 다중화로 좁히도록 하겠다. 따라서 '노드'라는 단어도 복제 그룹의 멤버를 지칭할 때만 썼다.
일관성이란 복제 그룹의 멤버들이 언제나 올바른 응답을 한다는 것이 보장되는 속성을 말한다. 예를 들어 게시판의 글을 저장하는 데이터베이스가 두 개의 노드로 분산되어 있다고 가정해보자. 작성자가 글을 수정하였다면 두 개의 노드 중 어떤 곳에 질의하더라도 수정된 내용이 보여야 한다. 만약 한 노드에는 반영이 되지 않았거나, 반영이 늦어서 수정하기 전의 내용이 보인다면 일관성을 완전히 충족하지 못하는 것이다. 뒤늦게라도 최신의 내용으로 업데이트 된다면 최종 일관성(Eventual consistency)을 충족한다고 한다.
가용성이란 장애가 발생한 노드가 있더라도 다른 노드는 정상적으로 동작하는 것이 보장되는 속성을 말한다.
분할 용인이란 노드 간 네트워크에 장애가 생겨서 데이터가 전달되지 못하더라도 정상 동작할 수 있음이 보장되는 속성을 말한다. '분산' 시스템의 의미를 생각해보면 매우 당연히 충족해야 할 요구사항이다. 장애가 전혀 발생하지 않는 네트워크는 없기 때문이다.
위의 세 가지 속성 모두 분산 시스템에는 꼭 필요하다. 하지만 CAP 정리에 의하면 세 가지 속성을 모두 완벽하게 만족하는 것은 불가능하다.
일관성, 가용성, 분할 용인을 모두 만족하는 분산 시스템 S가 있다고 가정하자. S는 두 개의 노드 a, b로 구성되어 있다. 한 노드의 데이터를 생성/변경하는 즉시 다른 노드에도 반영된다. (일관성) 클라이언트가 a에 'v=0' 라는 데이터를 저장하였다면 다음과 같은 과정을 거쳐 b에도 'v=0' 이라는 데이터가 저장된다.
a와 b를 연결하는 네트워크에 잠시 장애가 생긴 시점에 클라이언트가 'v=1'로 데이터를 수정하면 어떻게 될까? 네트워크 장애 때문에 데이터 복제는 실패할 것이다.
엎친데 덮친 격으로 a에 장애가 발생했다고 하자. S는 가용성을 만족해야 하므로, a에 장애가 있더라도 b에서 v의 값을 읽을 수 있어야 한다. 그런데 이 때 클라이언트가 b에 v를 질의하면 예전의 값인 0을 응답할 것이므로 일관성이 보장되지 않는다. 따라서 일관성, 가용성, 분할 용인을 완벽하게 만족하는 분산 시스템은 없다.
증명에서 사용한 예시를 다시 들여다보자. 예시에서의 b 노드는 가용성을 보장하려다 예전의 데이터를 응답하고 말았다. 만약 a, b 노드가 서로의 데이터가 일치한다는 것이 보장될때만 응답을 한다면 어떻게 해야 할까? 네트워크 장애가 생겨 복제가 불가능하므로 v의 값을 1로 수정하는 연산을 거부했어야 한다. 이렇게 되면 가용성을 잃게 된다. 네트워크 단절이 전혀 발생하지 않는다면, 즉 분할 용인 속성을 포기한다면 일관성과 가용성을 모두 만족할 수 있겠지만 그런 환경을 구성하는 것은 물리적으로 불가능하다.
반드시 '완벽한 일관성'과 '완벽한 가용성' 중 하나를 택해야 하는 것은 아니라는 점에 주의하자. 앞서 반영이 늦더라도 언젠가 수정을 반영하는 '최종 일관성'에 대해 설명했다. 가용성을 위해 일관성을 다소 희생했지만 완전히 포기하지는 않았다. 반대로 일관성을 지키기 위해 가용성을 다소 희생하는 시스템도 존재한다. ZooKeeper는 전체 노드 중 과반수 이상이 데이터 쓰기에 성공했을 때에만 업데이트 연산이 성공한 것으로 간주한다. 따라서 장애 노드가 과반이 되는 순간 운영이 불가능한 것으로 보고 동작을 중지한다. 일관성을 지키기 위해 절반 이하의 노드에 장애가 발생했을 때에만 가용성을 보장한 셈이다.
궁금한 내용이었는데 쉽게 설명 잘해주셔서 감사합니다~