9.1 라우팅 개요
failover가 끝나면 클러스터 내부는 정리된다. 새 primary가 leader lock을 쥐고, 나머지 노드는 새 leader를 따라 복제를 재개한다. 그런데 애플리케이션은 이 사실을 모른다. 접속 문자열에 적힌 주소는 여전히 구 primary를 가리키고, 그 노드는 지금 죽어 있거나 read-only replica로 재합류해 있다. 연결이 거부되거나, 더 나쁜 경우 연결은 되는데 쓰기 쿼리가 전부 실패한다. Patroni는 데이터베이스 계층의 failover만 책임지며, 애플리케이션을 새 primary로 이끄는 일은 연결 라우팅 계층이 맡는다.
문제: primary가 옮겨 다닌다
단일 인스턴스 시절에는 접속 정보가 바뀔 일이 없었다. Patroni 클러스터에서는 failover와 switchover가 일어날 때마다 “쓰기를 받는 노드"가 바뀐다. 매번 사람이 애플리케이션 설정을 고쳐 재배포하는 방식은 자동 failover의 의미를 없앤다. 결국 필요한 것은 “지금 누가 primary인가"를 스스로 판별해 트래픽을 보내 주는 무언가다.
판별 기준이 중요하다. PostgreSQL 프로세스가 떠 있는지만 보는 TCP 체크로는 부족하다. demote된 구 primary도, replica도 5432 포트는 열려 있기 때문이다. 쓰기 가능 여부를 SQL로 확인하는 방법도 완전하지 않다. 클러스터의 정본은 DCS(Distributed Configuration Store)의 leader key이고, 라우팅도 가능하면 그 기준을 따라야 한다.
공통 원리: REST API health check
서버 측 라우팅 도구 대부분은 Patroni REST API의 health check 엔드포인트를 폴링하는 구조다. 각 노드의 Patroni는 REST API(기본 8008 포트)로 자기 역할을 HTTP 상태 코드로 답한다. 조건을 만족하면 200, 아니면 503이다.
| 엔드포인트 | 200 조건 |
|---|---|
GET /, GET /primary, GET /read-write | leader lock을 가진 primary로 동작 중 |
GET /leader | leader lock 보유 (PostgreSQL 실제 role은 확인하지 않음) |
GET /replica | state가 running, role이 replica, noloadbalance tag 미설정 |
GET /replica?lag=10MB | 위 조건에 더해 lag이 지정값 이내 |
GET /read-only | replica 조건에 primary도 포함 |
본문(JSON)이 필요 없으면 GET 대신 HEAD를 사용해도 된다. 이 판정의 근거는 PostgreSQL의 쓰기 가능 여부가 아니라 DCS의 leader lock이다. 즉 “지금 이 노드가 클러스터가 인정한 leader인가"를 묻는 것이고, 이 점이 뒤에서 볼 클라이언트 라이브러리 방식과의 결정적 차이다. 엔드포인트 전체 목록과 ?lag=, tag 필터의 세부는 Part VIII에서 다뤘다.
네 가지 방식
flowchart TD
RT["연결 라우팅"]
RT --> LB["L4 로드밸런서"]
RT --> VIP["VIP 이동"]
RT --> POOL["pooler 재설정"]
RT --> CLI["클라이언트 라우팅"]
LB --> HAP["HAProxy"]
VIP --> VM["vip-manager"]
VIP --> CB["callbacks 스크립트"]
POOL --> PGB["PgBouncer + confd"]
CLI --> LP["libpq multi-host"]
CLI --> JD["PgJDBC"]
같은 목표를 이루는 경로가 넷이다. 무엇을 보고 primary를 판별하는지, 그 판별을 어떻게 반영하는지가 서로 다르다.
| 방식 | 판별 근거 | 반영 방법 |
|---|---|---|
| HAProxy | REST API 폴링 | 백엔드 서버 up/down 전환 |
| vip-manager | DCS leader key watch | VIP 등록/해제 |
| PgBouncer + confd | DCS leader key watch | 설정 재작성 후 reload |
| libpq/PgJDBC | 접속 시점 서버 상태 관찰 | 다음 후보 호스트 시도 |
방식별 장단
HAProxy는 공식 저장소에 데모 설정이 포함될 만큼 표준에 가까운 조합이다. primary용과 replica용 포트를 나눠 쓰기 트래픽 라우팅과 읽기 부하 분산을 한 계층에서 해결하고, 노드가 늘어도 백엔드 목록만 갱신하면 된다. 대신 애플리케이션과 데이터베이스 사이에 hop이 하나 늘고, HAProxy 자체가 단일 장애점이 되지 않도록 이중화를 따로 설계해야 한다. 폴링 주기와 실패 임계값만큼 감지 지연도 존재한다. 상세는 9.2에서 해부한다.
VIP 이동은 애플리케이션 입장에서 가장 투명하다. 접속 문자열은 고정 IP 하나이고 중간 hop도 없다. 다만 VIP는 일반적으로 같은 네트워크 세그먼트 안에서만 유효하며, 클라우드 환경에서는 인터페이스에 IP를 얹는 기본 방식이 통하지 않아 제공자 API를 호출하는 별도 처리가 필요해진다. 9.3에서 vip-manager를 다룬다.
pooler 재설정은 이미 PgBouncer를 쓰는 환경이라면 매력적이다. confd가 DCS를 watch하다가 leader가 바뀌면 PgBouncer의 대상 주소를 다시 써 주는 구조로, 연결 풀링과 라우팅을 한 컴포넌트가 겸한다.
클라이언트 라이브러리 방식은 인프라 추가가 전혀 없다. 접속 문자열에 후보 노드를 모두 나열하면 라이브러리가 접속 시점에 골라 준다. 대신 판별이 접속 시점에만 일어나고 leader lock을 보지 않는다는 한계가 있다. 9.4에서 함정과 함께 살펴본다.
정리
라우팅 계층의 본질은 “누가 primary인가"라는 질문에 무엇을 근거로 답하느냐다. REST API와 DCS leader key를 보는 서버 측 방식은 클러스터의 정본을 따르고, 접속 시점에 서버 상태를 관찰하는 클라이언트 방식은 인프라가 없는 대신 판별이 얕다.