Zookeeper가 Paxos를 기용한 이유
이 글에서 paxos의 수학적 한계와 livelock 같은 치명적인 단점들 시스템 중단 및 가용성 포기등이 있음에도 주키퍼를 비롯한 분산 코디네이터 시스템들이 이러한 동작 방식을 채택하는 이유와 내부 구현에 대해 명확한 사실을 바탕으로 알아보겠다.
분산시스템의 CAP(Consistency, Availability, Partition Toleration) 측면에서, 주키퍼는 철저하게 CP(Consistency & Partitation Tolerance) 시스템으로 설계되어 있다.
즉, 네트워크 장애가 발생했을 때 잘못된 데이터를 주느니 차라리 시스템을 멈추는 것(가용성 포기)가 분산 코디네이션의 목적에 부합하기 때문이다.
치명적인 split-brain 방지: 주키퍼는 주로 distributed lock이나 동적 설정 관리에 사용된다. 만약 네트워크가 단절되어 과반수 quorum를 잃은 노드 그룹이 계속 쓰기 요청을 허용한다면 두 개의 서로 다른 서버가 동일한 락을 획득하는 대참사가 발생하며 이는 결제 시스템이나 재고 관리에서 데이터 무결성이 완전히 붕괴되는 결과를 초래한다.
안정성 최우선: FLP 불가능성 정리에서 보았듯, 시스템은 안정성과 생존성중에 하나를 타협한다. 주키퍼는 모든 합의 과정에서 노드의 과반수 확인을 강제함으로써 무한 대기(가용성 저하)가 발생하더라도 데이터와 순서와 일관성(안정성)을 100% 보장하는 쪽을 선택한 것이다.
즉 CAP에서 C vs A를 조절하는 것 중에 C를 택한것이 주키퍼인거다.
FLP 불가능성은 비동기 네트워크 환경에서는 단 1개의 노드만 다운되더라도 Safety, Liveness 즉 안정성과 생존성을 동시에 100% 만족하는 결정론적 합의 알고리즘은 존재하지 않는다. 라는 개념
Zookeeper #
주키퍼는 분산 애플리케이션을 위한 강력한 동기화 및 상태 관리 서비스다.
핵심적인 개념과 실제 동작 명령어를 요약해보면
핵심 데이터 모델 ZNode #
주키퍼는 데이터는 리눅스 파일 시스템과 유사한 계층적 트리구조 ZNode로 저장되어 메모리상에 유지되어 매우 빠른 읽기 속도를 제공한다.
- Persistent Node(영구 노드): 명시적으로 삭제하기 전까지 영구적으로 유지되는 기본 노드다.
- Ephemeral Node (임시 노드): 노드를 생성한 클라이언트의 세션이 끊어지면(네트워크 단절) 자동으로 삭제된다. 분산 환경의 서비스 디스커버리와 노드 헬스 체크에 핵심적으로 사용된다.
- Sequenctial Node (순차 노드): 생성시 이름이 순차적으로 증가하는 번호가 부여되며 분산 락 구현시 대기열을 만드는데 사용된다.
Watcher 메커니즘 #
클라이언트가 특정 ZNode에 Watcher를 설정하면 해당 노드의 데이터가 변경되거나 자식 노드가 생성/삭제될 때 주키퍼가 클라이언트에게 이벤트를 푸시한다. 클라이언트가 주기적으로 변경 사항을 묻는 Polling 부하를 없애준다.
Zookeeper CLI 명령어 예시 #
기술적 이해를 돕기 위해 쉘에서 주로 사용하는 명령어들을 더 보겠다.
# 1. 영구 노드 생성 (환경 변수 저장용)
[zk: localhost:2181] create /app_config "db_port=3306"
# 2. 임시 노드 생성 (현재 살아있는 웹 서버 1번 등록)
# -e 옵션을 주면 클라이언트 세션 종료 시 /live_servers/web_01 노드는 자동 삭제됨
[zk: localhost:2181] create -e /live_servers/web_01 "192.168.1.10"
# 3. 데이터 조회 및 Watcher 설정
# -w 옵션을 통해 이후 /app_config 데이터가 바뀌면 알림을 받음
[zk: localhost:2181] get -w /app_config
Paxos의 실제 구현과 주키퍼에서의 활용 상세 #
이론적인 Basic Paxos 단일 값 합의는 지속적인 데이터가 불가능하므로 실제 산업에서는 이를 확장한 Multi-Paxos 형태나 그 변형 알고리즘이 구현되어 사용된다.
산업계의 대표적인 paxos 구현체는 Google Chubby나 Spanner가 있다
- Google Chubby: Paxos 알고리즘을 실전에 성공적으로 구현한 구글의 분산 락 서비스다 주키퍼의 직접적인 모티브가 된 시스템이다. Chubby는 셀이라는 5대의 복제 서버로 구성되며, 그안에서 Multi Paxos를 통해 리더를 선출하고 데이터를 복제한다.
- Google Spanner: 구글의 글로벌 분산 데이터베이스로 수천 킬로미터 떨어져있는 데이터센터간의 트랜잭션 동기화를 위해 각 샤드별로 paxos 합의 그룹을 구성하여 사용한다.
주키퍼의 구현체 ZAB (Zookeeper Atomic Broadcast) #
주키퍼는 순수 Paxos 를 그대로 사욯아지 않고 논리적 한계를 보완한 데이터베이스 트랜잭션 처리에 특화된 ZAB이라는 프로토콜을 독자적으로 구현해 사용한다.
그 이유는 다음과 같다.
- 상태 기계의 복제 State Machine Replication 순서 보장이다. 멀티 팍소스는 여러개의 제안이 동시에 합의될 때, 최종적으로 합의만 이루어진다면 그 순서가 뒤바뀌는 것을 허용할 수 있는 구조적 맹점이 있다.
- ZAB의 해결책 (FIFO 보장): 주키퍼는 이전에 처리된 트랜잭션이 반드시 다음 트랜잭션보다 먼저 복제됨을 보장해야한다. ZAB은 오직 선출된 리더만이 트랜잭션 id(zxid)를 순차적으로 발급하고, tcp의 특성을 이용해 브로드캐스트 단계에서 메시지 FIFO 순서를 엄격하게 강제한다.
livelock 문제에 대해 어떻게 되었는지를 좀 더 알아보자면. 기본 paxos에서 livelock이 발생하는 이유는 누구나 동시에 제안자가 될 수 있기 때문이다. 두 명의 제안자가 서로 더 높은 번호로 prepare 요청을 보내며 상대방의 진행을 방해하는 Dueling Propesers 현상이 핵심이다.
- ZAB, Raft는 한번에 오직 한명의 제안자만 존재한다는 규칙을 세웠다.
- 리더가 선출된 동안 다른 노드들은 제안을 하지 않고 팔로워로서 리더의 명령만 따른다. 제안자가 한 명뿐이니 서로 방해할 일이 없어지고 시스템은 중단 없이 합의를 완료 liveness 확보할 수 있게 된다.
- 이러한 구현은 운에 맡기지않고. (제안하는 시점) 투표를 통해 과반수를 넘은 제안만 받아주는 교차 검증을 통해 달성한것이고 그만큼 성능은 떨어지지만 정확성이 올라간다
ZAB 구현절차 #
- Discovery: 발견 및 리더선출 하는 작업, 여러대의 서버가 투표를 통해 가장 높은
zxid(최신 데이터)를 가진 서버를 리더로 선출한다. (이 과정은 paxos의 1단계 prepare과 유사하다) - Synchronization (동기화): 새로 선출된 리더는 팔로워들과 데이터를 동기화한다 동기화를 마치면 리더 체제가 확립된다.
- Broadcast (방송/합의): paxos의 2단계 accept와 유사하다 클라이언트의 쓰기 요청이 오면 리더가 트랜잭션으로 만들고 팔로워들에게 전송한다. 과반수의 팔로워가 받았다고 응답하면 리더는 commit 명령을 내려 데이터를 최종 저장한다.