본문으로 건너뛰기
13.4 커넥션 풀링

13.4 커넥션 풀링

PostgreSQL의 프로세스 기반 모델(1.3)은 한 연결마다 OS 프로세스 1개. 동시 연결 1000+이면 메모리·컨텍스트 스위칭 비용 폭증합니다. 커넥션 풀러(connection pooler)가 클라이언트와 PostgreSQL 사이에서 적은 수의 실제 연결을 다수의 클라이언트가 공유하게 만듭니다. OLTP 운영에서 사실상 필수입니다.

왜 풀링이 필요한가

    flowchart LR
  subgraph NO["풀러 없음 — 1:1"]
    C1["client × 1000"]
    PG1["PostgreSQL<br/>backend × 1000"]
    C1 --- PG1
  end

  subgraph WITH["풀러 있음 — N:M"]
    C2["client × 1000"]
    POOL["pgBouncer"]
    PG2["PostgreSQL<br/>backend × 50"]
    C2 --- POOL
    POOL --- PG2
  end

  classDef bad fill:#fee2e2,stroke:#b91c1c,color:#7f1d1d
  classDef ok fill:#d1fae5,stroke:#047857,color:#064e3b
  class NO bad
  class WITH ok
  
풀러 없음풀러 있음
1000 backend × work_mem × … = 메모리 폭증50 backend로 충분
컨텍스트 스위칭 비용작음
max_connections = 1000 필요max_connections = 200 OK
postmaster fork 비용없음 (재사용)

풀러 종류

도구특징
pgBouncer표준. 가볍고 빠름. C 작성. transaction/session/statement 모드
pgcatRust로 재작성. sharding·load balancing 추가 기능
pgpool-II부하 분산·query rewriting 등 종합 도구. 더 무거움
HAProxy + 단순 풀라우팅만. 풀링은 별도
클라우드 매니지드RDS Proxy, Azure pgBouncer 등

운영 표준 = pgBouncer.

pgBouncer 모드

모드동작가능
session pooling클라이언트가 backend를 연결 종료까지 점유거의 native — 모든 PG 기능
transaction pooling트랜잭션 끝나면 backend 반납 — 권장session-level state 제한
statement pooling매 statement마다 backend 반납트랜잭션 사용 불가

운영 권장 = transaction pooling. 가장 효율적이고 거의 모든 기능 작동.

transaction pooling의 제약

미지원메모
session-level SETSET search_path 등이 다음 트랜잭션에 안 따라옴
LISTEN / NOTIFY다른 backend가 받음
SET LOCALOK — 트랜잭션 안
임시 테이블트랜잭션 내만
Advisory lock (session)session 단위 lock 안 됨 — pg_advisory_xact_lock
Prepared statement옛 PG는 안 됨, pgBouncer 1.21+의 server-side prepared statement 지원으로 가능

pgBouncer 설정

/etc/pgbouncer/pgbouncer.ini:

[databases]
app_main = host=primary.example.com port=5432 dbname=app_main
* = host=primary.example.com port=5432

[pgbouncer]
listen_addr = 0.0.0.0
listen_port = 6432
auth_type = scram-sha-256
auth_file = /etc/pgbouncer/userlist.txt

pool_mode = transaction
default_pool_size = 25
min_pool_size = 5
reserve_pool_size = 5
max_client_conn = 2000

server_idle_timeout = 60
server_lifetime = 3600

stats_period = 60
admin_users = pgbouncer_admin
파라미터의미
pool_modesession / transaction / statement
default_pool_sizeDB+user 조합 1개의 server 연결 수
max_client_conn클라이언트 동시 연결 상한
reserve_pool_size한도 초과 시 추가 가용 풀
server_idle_timeout유휴 server 연결 자동 종료

PostgreSQL max_connectionspool_size × DB·user 조합 + 여유로 잡으면 됩니다.

인증 — userlist.txt

"app_user" "SCRAM-SHA-256$..."
"app_readonly" "SCRAM-SHA-256$..."

PostgreSQL pg_authid.rolpassword의 SCRAM 해시를 그대로 복사. 또는 auth_query 옵션으로 PostgreSQL에서 동적 조회.

auth_user = pgbouncer_lookup
auth_query = SELECT usename, passwd FROM pg_shadow WHERE usename=$1

auth_user는 pg_shadow를 읽을 수 있는 권한 필요합니다.

TLS

pgBouncer ↔ client + pgBouncer ↔ PostgreSQL 둘 다 TLS 권장합니다.

client_tls_sslmode = require
client_tls_cert_file = /etc/pgbouncer/cert.pem
client_tls_key_file = /etc/pgbouncer/key.pem

server_tls_sslmode = verify-full
server_tls_ca_file = /etc/pgbouncer/server-ca.crt

모니터링 — admin console

psql -h 127.0.0.1 -p 6432 -U pgbouncer_admin pgbouncer
SHOW POOLS;
SHOW CLIENTS;
SHOW SERVERS;
SHOW STATS;
SHOW CONFIG;
SHOW POOLS 컬럼의미
cl_active현재 사용 중 클라이언트
cl_waiting백엔드 대기 중 — pool 부족 신호
sv_active사용 중 server
sv_idle유휴 server
maxwait가장 오래된 클라이언트 대기 시간

cl_waiting이 자주 > 0이면 pool size 부족. maxwait가 초 단위면 사용자 대기 시간 큰 상태.

부하 분산 — read replica로

pgBouncer 자체는 라우팅 안 합니다. 일반적 패턴:

client → HAProxy → pgBouncer (primary) → PostgreSQL primary
client → HAProxy → pgBouncer (replica) → PostgreSQL standby (read)

또는 pgcat이 자체 read split 기능 제공합니다.

풀 크기 결정

optimal_pool_size = ((core_count × 2) + effective_spindle_count)

전통 공식 (HikariCP가 유명). PostgreSQL은 디스크 의존이 크니 core × 2 + 약간.

호스트pool_size
4 vCPU10
8 vCPU20
16 vCPU35
32 vCPU70

너무 크면 PostgreSQL 컨텍스트 스위칭이 손해. 너무 작으면 cl_waiting 폭주.

pgBouncer 한계

한계메모
single-threadC로 작성, libevent 기반. 매우 빠르지만 한 호스트당 1 thread 한계
TPS 한계호스트당 ~50k QPS 정도 (HW에 따라)
HA 자체 부족여러 pgBouncer 인스턴스 + HAProxy로
Prepared statement1.21+의 protocol-level prepared statement로 해결

대안 — pgcat

Rust 기반, multi-threaded. 부하 큰 클러스터에 검토합니다.

# pgcat.toml
[general]
host = "0.0.0.0"
port = 6432
admin_username = "admin"
admin_password = "..."

[pools.main]
pool_mode = "transaction"

[pools.main.users.0]
username = "app_user"
password = "..."
pool_size = 25

read·write split + sharding 같은 기능 통합합니다. 신규 도입에 검토 가치.

운영 시 주의

주의메모
transaction mode에서 session SET 안 됨코드 점검 필요
LISTEN/NOTIFY 안 됨별도 session pool 필요
SHOW POOLS 정기 모니터링cl_waiting 알람
인증서 회전pgBouncer reload (자동 fallback 아님)
restart 시 기존 client 끊김운영 시간 외에
대부분의 운영 PostgreSQL은 pgBouncer + transaction mode. 새 시스템 도입 시 전제하고 설계. 코드가 session state에 의존하면 pgBouncer가 어려워진다 — 처음부터 stateless 패턴입니다.

정리

  • 풀러 = 1000+ client를 50 backend로 흡수. 운영 필수
  • pgBouncer가 표준, transaction pooling이 운영 모드
  • 세션 상태(SET, LISTEN, session lock)는 transaction 모드에서 제약
  • 풀 크기는 core × 2 + 약간
  • SHOW POOLS의 cl_waiting 모니터링
  • pgcat·RDS Proxy 등 대안 — 신규 도입 검토

다음 절(13.5)에서는 OS·HW 레벨 — HugePages, NUMA, NVMe 등의 튜닝을 봅니다.