본문으로 건너뛰기
3.1 DCS 요구사항과 선택

3.1 DCS 요구사항과 선택

Patroni 클러스터에서 “누가 leader인가"라는 질문의 답은 어느 PostgreSQL 노드에도 저장되지 않는다. 답은 DCS(Distributed Configuration Store)라는 외부 분산 저장소에 있고, 모든 Patroni 데몬은 이 저장소에 기록된 상태를 근거로 promote와 demote를 결정한다. 그래서 DCS를 무엇으로 고르고 어떻게 구성하는지는 클러스터 전체의 가용성을 좌우하는 결정이 된다.

DCS가 맡는 역할

DCS는 클러스터의 합의된 상태를 저장한다. 현재 leader의 이름을 담은 leader lock, 각 노드의 상태를 담은 member key, 전 노드 공통의 동적 설정을 담은 config 키가 대표적이다. 키 구조의 상세는 Part II에서 다루었다.

leader lock에는 TTL(기본 30초)이 걸려 있다. leader 노드의 Patroni는 매 HA 루프마다 이 lock의 lease를 갱신하고, 갱신이 제때 이루어지지 않으면 키가 만료되어 leader race가 시작된다. 즉 “leader가 죽었다"는 판정은 어떤 노드의 주관적 판단이 아니라 DCS 안에서 키가 만료되었다는 객관적 사실로 내려진다.

    flowchart TD
  subgraph DCS["DCS 클러스터"]
    LK["leader lock<br/>(TTL 30초)"]
    MK["member key"]
    CF["config"]
  end
  P1["Patroni A<br/>(primary)"] -->|lease 갱신| LK
  P1 -->|상태 보고| MK
  P2["Patroni B<br/>(replica)"] -->|상태 보고| MK
  P3["Patroni C<br/>(replica)"] -->|상태 보고| MK
  CF -->|동적 설정 배포| P1
  CF -->|동적 설정 배포| P2
  

DCS 자체에도 quorum이 필요하다

etcd, Consul, ZooKeeper는 모두 합의(consensus) 프로토콜 위에서 동작하는 저장소다. 합의 프로토콜은 과반수 노드가 살아 있어야 쓰기를 처리하므로, DCS 클러스터는 통상 3노드 또는 5노드 같은 홀수 구성으로 운영한다. 3노드면 1대 장애까지, 5노드면 2대 장애까지 견딘다.

DCS가 응답하지 않으면 어떻게 되는지는 공식 FAQ가 분명하게 답한다. Patroni는 현재 primary를 강등한다. 단일 노드 관점에서는 DCS 장애와 네트워크 분리를 구분할 방법이 없어 최악의 경우를 가정하기 때문이다. 결국 DCS의 가용성이 곧 데이터베이스 쓰기 가용성이 되고, DCS를 1노드로 두면 그 한 대가 전체의 단일 장애점이 된다. DCS 장애 시의 강등 동작은 2.4 split brain 방지에서, 이를 완화하는 failsafe_mode는 2.5 DCS 장애와 failsafe mode에서 정리했다.

DCS 노드 수 요구와 달리 Patroni/PostgreSQL 노드 수에는 제약이 없다. 공식 FAQ는 “어떤 수의 Postgres 노드로도 Patroni를 실행할 수 있다"고 답한다. PostgreSQL 2노드에 etcd 3노드를 붙이는 비대칭 구성이 자연스럽게 성립한다.

지원 백엔드 개관

Patroni 4.1.4가 지원하는 백엔드는 다음과 같다. patroni.yml에서 해당 이름의 섹션을 정의하면 그 백엔드가 선택된다.

섹션저장소비고
etcdetcd (API v2)etcd v3.4부터 v2 API 기본 비활성
etcd3etcd (API v3)공식 FAQ 권장
consulHashiCorp Consul로컬 agent 경유 통신
zookeeperApache ZooKeeperkazoo 클라이언트 사용
exhibitorExhibitor 관리 ZooKeeper노드 목록 자동 갱신
kubernetesKubernetes API 오브젝트별도 저장소 배포 불필요
raft내장 Raft (pysyncobj)deprecated

raft는 외부 저장소 없이 Patroni 프로세스끼리 Raft 합의를 수행하는 방식이었으나 공식 문서가 deprecated로 표기한다. 상세는 3.3에서 다룬다.

공통 설정: scope, namespace, name

어느 백엔드를 고르든 최상위 설정 세 개가 DCS 안의 위치와 노드 식별을 결정한다.

scope: pg-ha          # 클러스터 이름
namespace: /service   # DCS 경로 프리픽스 (기본값)
name: node1           # 노드 이름, 클러스터 안에서 유일해야 한다

모든 키는 /<namespace>/<scope>/ 경로 아래 저장된다. 위 예시라면 leader lock은 /service/pg-ha/leader가 된다. namespace의 기본값은 /service이고, name은 호스트마다 달라야 한다.

이 구조 덕분에 하나의 DCS 클러스터를 여러 Patroni 클러스터가 공유한다. 공식 FAQ도 같은 etcd 클러스터에 둘 이상의 Patroni 클러스터 데이터를 저장해도 되느냐는 질문에 그렇다고 답하며, namespacescope 조합만 겹치지 않으면 충돌이 없다. Patroni를 위해 DCS를 전용으로 새로 세울 필요가 없다는 뜻이다.

무엇을 고르는가

백엔드 선택은 기술적 우열보다 이미 운영 중인 인프라가 무엇인지에 달린 문제에 가깝다. Kubernetes 위에서 PostgreSQL을 돌린다면 kubernetes 백엔드로 별도 저장소 배포를 생략하는 구성이 자연스럽고, 서비스 디스커버리로 Consul을 이미 쓰는 조직이라면 그 Consul을 재사용하는 편이 운영 부담이 적다. ZooKeeper는 Kafka 등으로 이미 ZooKeeper를 운영하는 환경에서 의미가 있다.

재사용할 인프라가 없다면 etcd3가 무난한 출발점이다. 공식 저장소의 docker-compose 데모도 etcd 3노드에 Patroni 3노드를 붙이는 구성을 기본으로 제공한다. 어떤 경우든 deprecated 상태인 raft는 신규 구성에서 제외하는 편이 안전하다.

정리

  • leader 판정의 근거는 DCS에 저장된 leader lock이며, TTL 만료가 곧 leader race의 트리거다.
  • DCS는 합의 프로토콜 기반이라 과반수 생존이 필요하고, 통상 3노드 또는 5노드로 운영한다. DCS가 불능이면 Patroni는 primary를 강등하므로 DCS 가용성이 쓰기 가용성을 결정한다.
  • Patroni/PostgreSQL 노드 수에는 제약이 없다.
  • 모든 키는 /<namespace>/<scope>/ 아래 저장되며, scope만 다르면 하나의 DCS를 여러 클러스터가 공유한다. 백엔드 선택은 이미 있는 인프라를 재사용하는 관점이 우선이다.