9.4 클라이언트 라이브러리 라우팅
로드밸런서도, VIP도, pooler도 없이 라우팅하는 방법이 하나 남아 있다. 클라이언트가 후보 노드를 전부 알고, 접속할 때마다 조건에 맞는 노드를 고르는 방식이다. libpq(그리고 libpq를 감싸는 대부분의 드라이버)와 PgJDBC가 이를 지원한다. 이 절의 내용은 Patroni 문서가 아니라 PostgreSQL과 PgJDBC 공식 문서에 근거하며, Patroni 클러스터가 아니어도 동작하는 범용 기능이다.
libpq multi-host 접속 문자열
접속 문자열에 호스트를 쉼표로 나열하면 libpq가 순서대로 시도해서, 조건을 만족하는 첫 호스트에 연결한다.
postgresql://10.0.0.11:5432,10.0.0.12:5432,10.0.0.13:5432/mydb?target_session_attrs=read-write&connect_timeout=5첫 호스트가 죽어 있으면 다음으로, 연결은 되지만 조건(여기서는 read-write)에 맞지 않으면 또 다음으로 넘어간다. failover로 primary가 두 번째 노드로 옮겨 갔다면, 재연결 시점에 첫 노드는 조건 불일치로 걸러지고 두 번째 노드에 붙는다.
target_session_attrs의 여섯 값
어떤 세션이면 연결을 수락할지는 target_session_attrs가 정한다. 판정 기준이 값마다 미묘하게 다르다.
| 값 | 수락 조건 |
|---|---|
any (기본) | 접속만 되면 아무 서버나 |
read-write | hot standby가 아니고 default_transaction_read_only가 off |
read-only | read-write의 반대 (기본 읽기 전용 세션) |
primary | hot standby가 아님 (default_transaction_read_only는 무관) |
standby | hot standby |
prefer-standby | standby 우선, 하나도 없으면 any로 전체 재시도 |
read-write와 primary의 차이가 실무에서 갈리는 지점이다. primary라도 default_transaction_read_only가 on이면 read-write는 거부하고 primary는 수락한다. “쓰기가 실제로 되는 노드"를 원하면 read-write, “복제 계층상 primary"를 원하면 primary를 쓴다.
부하 분산과 connect_timeout
기본 동작은 나열 순서대로의 시도라서 첫 호스트에 부하가 쏠린다. load_balance_hosts=random을 주면 후보를 무작위 순서로 시도한다. target_session_attrs=standby와 조합하면 읽기 전용 연결을 standby들에 랜덤 분산하는 구성이 된다.
postgresql://n1,n2,n3/mydb?target_session_attrs=standby&load_balance_hosts=randomconnect_timeout도 함께 설정하는 편이 좋다. 노드 하나가 TCP 응답 없이 죽어 있을 때 timeout이 없으면 다음 후보로 넘어가기까지 OS 기본 타임아웃만큼 기다리게 되어, multi-host의 장점이 무색해진다.
인증 실패는 다음 호스트로 안 넘어간다
주의할 함정이 있다. 호스트에 연결이 닿지 않거나 세션 조건이 안 맞으면 다음 후보를 시도하지만, 연결은 됐는데 인증에 실패하면 거기서 멈추고 나머지 호스트를 시도하지 않는다.
pg_hba.conf나 비밀번호가 어긋나 있으면 multi-host 나열이 기대한 fallback으로 동작하지 않는다. 인증 실패는 “이 노드만 건너뛰고 다음"이 아니라 전체 접속 실패다. 클러스터 전 노드의 인증 설정을 동일하게 유지하는 일이 이 방식의 전제 조건이다.PgJDBC
JDBC 드라이버는 libpq와 별개 구현이지만 같은 아이디어를 제공한다.
jdbc:postgresql://node1,node2,node3/accounting?targetServerType=preferSecondary&loadBalanceHosts=truetargetServerType이 libpq의 target_session_attrs에 해당한다. 값은 any(기본), primary(구 표기 master), secondary(구 표기 slave), preferSecondary, preferPrimary이고, primary/secondary 판별은 서버가 쓰기 가능한지 관찰하는 방식이다. loadBalanceHosts(기본 false)를 켜면 후보 중 무작위로 고른다.
libpq에 없는 요소가 hostRecheckSeconds(기본 10초)다. 호스트별 판별 결과를 JVM 전역으로 캐시하는 시간으로, 캐시가 유효한 동안은 직전 판정을 재사용한다. 연결 시도 횟수를 줄여 주는 대신, failover 직후에는 캐시가 만료될 때까지 구 역할 기준으로 후보를 고르는 지연이 생긴다.
한계: leader lock을 보지 않는다
이 방식의 판별은 전부 PostgreSQL 수준의 관찰이다. hot standby인가, 쓰기가 되는가. Patroni가 DCS에 유지하는 leader lock은 어디에서도 확인하지 않으며, 판별 시점도 접속 순간 한 번뿐이다.
target_session_attrs=read-write는 두 노드 모두 통과시킨다. 접속이 어느 쪽으로 붙느냐에 따라 쓰기가 갈라진다. leader lock을 직접 확인하는 REST API 기반 라우팅이 이런 상황에서 더 안전하다고 보는 이유다. 클라이언트 라우팅을 주 경로로 쓴다면 pause 상태의 수동 작업 절차를 그만큼 더 엄격하게 관리해야 한다.정리
클라이언트 라이브러리 라우팅은 인프라 추가가 없다는 점 하나로 충분히 매력적이고, 소규모 구성이나 라우팅 계층의 이중화 부담을 피하고 싶은 환경에서 실용적인 선택이다. 다만 판별이 접속 시점의 PostgreSQL 관찰에 그치고 leader lock을 보지 않는다는 성격, 그리고 인증 실패가 fallback을 끊는다는 함정을 알고 써야 한다. connect_timeout(libpq)과 hostRecheckSeconds(PgJDBC)가 장애 시 체감 지연을 좌우하는 조절점이다.