Files
obsidian/infra/postgresql-ha.md

9.7 KiB

title, updated, tags
title updated tags
PostgreSQL HA (Patroni + etcd) 2026-04-06
infra
postgresql
patroni
etcd
ha

개요

PostgreSQL 3노드 HA 클러스터. Patroni가 자동 failover를 관리하고, etcd를 DCS(Distributed Consensus Store)로 사용.

K3s의 kine 데이터스토어로 사용 중.

PostgreSQL 클러스터

노드 호스트 IP 역할
postgres-1 incus-hp2 10.100.2.5 Replica 또는 Leader
postgres-2 incus-kr1 10.100.3.185 Replica 또는 Leader
postgres-3 incus-kr2 10.100.1.83 Replica 또는 Leader
  • PostgreSQL 17.9, Patroni 4.1.0
  • Patroni 설정: /etc/patroni.yml
  • Patroni 서비스: /etc/systemd/system/patroni.service (ExecStart: /opt/patroni/bin/patroni /etc/patroni.yml)
  • 클러스터 이름: nocodb-cluster
  • 레플리케이션: async streaming
  • Patroni REST API: 각 노드 8008 포트
  • 컨테이너 메모리 제한: 2GiB (limits.memory)
  • shared_buffers: 512MB, effective_cache_size: 1536MB, work_mem: 8MB, maintenance_work_mem: 128MB, wal_buffers: 16MB

DB 목록

DB 용도
kine K3s 데이터스토어 (kine)
nocodb NocoDB
n8n n8n 워크플로
outline Outline 위키

Patroni 명령어

# 클러스터 상태 확인
incus exec postgres-1 -- /opt/patroni/bin/patronictl -c /etc/patroni.yml list

# 수동 switchover
incus exec postgres-1 -- /opt/patroni/bin/patronictl -c /etc/patroni.yml switchover

# Replica reinitialize
incus exec postgres-1 -- /opt/patroni/bin/patronictl -c /etc/patroni.yml reinit nocodb-cluster postgres-3 --force

etcd 클러스터 (Patroni DCS)

노드 위치 IP 방식
etcd-nas Synology NAS (서울) 192.168.9.100 Docker (quay.io/coreos/etcd:v3.5.21)
etcd-mbp kaffa-macbookpro (서울, Tailscale) 100.115.154.78 Docker (quay.io/coreos/etcd:v3.5.17) via colima, peer/client는 socat으로 Tailscale 노출
etcd-jp1 Incus 컨테이너 jp1 (도쿄) 10.253.101.233 Alpine + apk add etcd (v3.5.16)
  • NAS: 데이터 /volume1/docker/etcd/data, --restart=always
  • jp1: openrc 서비스 (/etc/init.d/etcd), command_background=true
  • mbp: docker container etcd (named volume etcd-data), client/peer 모두 호스트 127.0.0.1만 노출 → socat이 Tailscale IP 100.115.154.782379/2380으로 forward (~/Library/LaunchAgents/com.kaffa.etcd-socat{,-peer}.plist)
  • Patroni etcd namespace: /patroni

etcd 확인 명령어

# 클러스터 멤버 확인
incus exec postgres-1 -- etcdctl --endpoints=http://192.168.9.100:2379 member list -w table

# 엔드포인트 상태
incus exec postgres-1 -- etcdctl --endpoints=http://192.168.9.100:2379,http://100.115.154.78:2379,http://10.253.101.233:2379 endpoint status -w table

etcd에 저장된 데이터

prefix 용도
/patroni Patroni DCS (Leader election, 설정)
/apisix/osaka APISIX 오사카 라우팅 설정
/apisix/tokyo APISIX sandbox-tokyo 라우팅 설정 (미사용, 데이터 보존)
/apisix/seoul APISIX 서울 K3s 라우팅 설정

K3s kine 연결

K3s → HAProxy(OpenWrt 192.168.9.1:5432) → Patroni Leader PostgreSQL

K3s config

# /etc/rancher/k3s/config.yaml (kr1, kr2)
datastore-endpoint: "postgres://kine:kine@192.168.9.1:5432/kine"

HAProxy (OpenWrt)

/etc/haproxy.cfg에 PostgreSQL backend 설정. Patroni REST API(/primary 엔드포인트)로 Leader를 자동 감지.

frontend ft_postgres
    bind :5432
    default_backend bk_postgres_primary

backend bk_postgres_primary
    option httpchk GET /primary
    http-check expect status 200
    default-server inter 3s fall 3 rise 2 on-marked-down shutdown-sessions
    server postgres-1 10.100.2.5:5432 check port 8008
    server postgres-2 10.100.3.185:5432 check port 8008
    server postgres-3 10.100.1.83:5432 check port 8008
  • Patroni failover 시 HAProxy가 자동으로 새 Leader를 감지 (~3초)
  • K3s config 변경 없이 Leader 전환 대응

애플리케이션 접속 경로

NocoDB, n8n 등 K3s 내부 애플리케이션은 pgcat(연결 풀링)을 통해 PostgreSQL에 접속.

nocodb/n8n → pgcat (db.svc.cluster.local:6432) → HAProxy 192.168.9.1:5432 → Patroni Leader

pgcat HA 구성

db/pgcat Deployment는 2 replica, 서로 다른 K3s 노드에 강제 분산.

  • replicaCount: 2 (Helm values values/pgcat.yaml)
  • podAntiAffinity.requiredDuringSchedulingIgnoredDuringExecution (topologyKey kubernetes.io/hostname)
  • PodDisruptionBudget minAvailable: 1 — 최소 1개 pod 유지 보장
  • Service ClusterIP 로 두 endpoint 모두 등록 — 클라이언트 요청은 kube-proxy 라운드로빈
  • 노드 장애 또는 롤아웃 시 나머지 replica가 트래픽 흡수. NocoDB는 일시적 Connection ended unexpectedly 후 자동 재연결 (drop 1회, 재시작 없음)

ghcr.io/postgresml/pgcat:latest. app chart 템플릿은 affinity / topologySpreadConstraints / podDisruptionBudget 옵션 지원 (v0.5.0+).

db/pgcat-config ConfigMap의 각 풀의 shards.0.serversHAProxy 단일 백엔드만 가리켜야 함:

[pools.nocodb.shards.0]
database = "nocodb"
servers = [["192.168.9.1", 5432, "primary"]]

[pools.n8n.shards.0]
database = "n8n"
servers = [["192.168.9.1", 5432, "primary"]]

⚠️ 하지 말 것: pgcat에 Patroni 노드 IP(10.100.2.5/3.185/1.83)를 직접 박지 말 것. Patroni failover가 발생하면 pgcat는 옛 primary를 계속 가리키게 되어 nocodb/n8n이 read-only 에러 발생.

pgcat는 풀링 전용으로만 쓰고, leader 탐지는 OpenWrt HAProxy에 위임. query_parser_enabled = false 설정 (read/write splitting 비활성).

좀비 소켓 방지 — TCP keepalive

Patroni failover 시 pgcat↔backend 및 client↔pgcat TCP 소켓이 반쪽만 끊어진 채 애플리케이션 풀이 계속 붙잡는 현상을 방지하기 위해 양쪽 모두 TCP keepalive 활성화.

pgcat (db/pgcat-config [general]):

tcp_keepalives_idle = 60
tcp_keepalives_interval = 10
tcp_keepalives_count = 3

Patroni (postgresql.parameters via patronictl edit-config -p):

tcp_keepalives_idle = 60
tcp_keepalives_interval = 10
tcp_keepalives_count = 3

동작: 60s idle 후 keepalive probe 전송, 10s 간격으로 3회 재전송 실패 시 커넥션 종료. 최대 감지 시간 90초. pgcat 는 서버 측에, Patroni 는 클라이언트(= pgcat 소켓) 측에 각각 설정 — 어느 쪽에서 먼저 죽음을 감지해도 소켓이 정리됨.

검증: 2026-04-16 switchover 테스트(postgres-3→postgres-2, TL6→7) 기준 n8n 에러 윈도우 7초·9건으로 한정 (../history/2026-04-16-pgcat-patroni-tcp-keepalive). 이전 2026-04-15 19:53 UTC failover 시 1038건과 극적 대비.

Patroni failover 인시던트 이력: ../history/2026-04-08-patroni-failover-incident · ../history/2026-04-15-pgcat-ha-promotion

pgpool-II PoC (n8n 전용)

2026-04-16 기준 n8n 만 pgpool 경유 (NocoDB·Outline 은 pgcat 유지). 1주 관측 후 확대 여부 판단.

구조

n8n → pgpool.db.svc.cluster.local:9999 → Patroni 3노드 직결 (HAProxy 미경유)
  • 이미지: pgpool/pgpool:4.4.3 (공식). pgpool/pgpool 태그 최신. 4.5/4.6 은 Bitnami 쪽 bitnamilegacy/pgpool 에만 있으나 env 래퍼 복잡도로 포기
  • replicas=2 on kr1 + kr2 (podAntiAffinity topologyKey hostname), PDB minAvailable: 1
  • 인증: allow_clear_text_frontend_auth=on + pool_hba.conf 메소드 password. 클라이언트가 plaintext 전송 → pgpool 이 그대로 backend scram-sha-256 challenge-response 에 사용. pool_passwd 미사용 (pgpool 이 plaintext 엔트리를 자동으로 md5 로 해시하는데 postgres 가 md5 거절 → 인증 실패)
  • sr_check/health_check: sr_check_user=sr_check (REPLICATION + pg_monitor), plaintext password 를 entrypoint sed 로 주입
  • backend_clustering_mode = streaming_replication (Patroni 연동). failover_on_backend_error = off — Patroni 가 promotion 수행, pgpool 은 role 변경 탐지 후 라우팅만 갱신
  • load_balance_mode = off — 모든 쿼리 primary 로 (n8n read-your-write 일관성)

검증 결과 (2026-04-16)

  • Patroni switchover (postgres-3→postgres-2, TL9→10): n8n 에러 창 ~2초 / 1건 failed to create a backend 2 connection → 즉시 Database connection recovered. pgpool SHOW POOL_NODES 에서 last_status_change 가 switchover 시점에 갱신되어 primary 자동 재탐지 확인
  • mbp etcd 60s down: n8n 에러 0건, n8n.inouter.com 200 유지. pgcat 에서는 동일 시나리오에서 n8n 풀 좀비 → 503 / pod restart 필요했음

자세한 구성·검증 로그: ../history/2026-04-16-pgpool-n8n-poc

APISIX etcd 사용 현황

사이트 etcd prefix 비고
osaka 통합 클러스터 (192.168.9.100, ...) /apisix/osaka Docker APISIX (waf-apisix), apisix#오사카-apisix-osaka
sandbox-tokyo (미가동) /apisix/tokyo 2026-04-08 NixOS 전환으로 APISIX 자체 폐기, etcd 데이터만 보존
서울 K3s K3s 내부 apisix-etcd StatefulSet (apisix.apisix.svc:2379) /apisix 2026-04-08 외부 통합에서 K3s 내부로 복귀

APISIX etcd 통합/분리 이력: ../history/2026-04-06-apisix-etcd-consolidation. 현재 외부 통합 etcd는 Patroni DCS + osaka APISIX 전용.

관련 문서