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 모드 |
| pgcat | Rust로 재작성. 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 SET | SET search_path 등이 다음 트랜잭션에 안 따라옴 |
LISTEN / NOTIFY | 다른 backend가 받음 |
SET LOCAL | OK — 트랜잭션 안 |
| 임시 테이블 | 트랜잭션 내만 |
| 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_mode | session / transaction / statement |
default_pool_size | DB+user 조합 1개의 server 연결 수 |
max_client_conn | 클라이언트 동시 연결 상한 |
reserve_pool_size | 한도 초과 시 추가 가용 풀 |
server_idle_timeout | 유휴 server 연결 자동 종료 |
PostgreSQL max_connections은 pool_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=$1auth_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 pgbouncerSHOW 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 vCPU | 10 |
| 8 vCPU | 20 |
| 16 vCPU | 35 |
| 32 vCPU | 70 |
너무 크면 PostgreSQL 컨텍스트 스위칭이 손해. 너무 작으면 cl_waiting 폭주.
pgBouncer 한계
| 한계 | 메모 |
|---|---|
| single-thread | C로 작성, libevent 기반. 매우 빠르지만 한 호스트당 1 thread 한계 |
| TPS 한계 | 호스트당 ~50k QPS 정도 (HW에 따라) |
| HA 자체 부족 | 여러 pgBouncer 인스턴스 + HAProxy로 |
| Prepared statement | 1.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 = 25read·write split + sharding 같은 기능 통합합니다. 신규 도입에 검토 가치.
운영 시 주의
| 주의 | 메모 |
|---|---|
| transaction mode에서 session SET 안 됨 | 코드 점검 필요 |
LISTEN/NOTIFY 안 됨 | 별도 session pool 필요 |
SHOW POOLS 정기 모니터링 | cl_waiting 알람 |
| 인증서 회전 | pgBouncer reload (자동 fallback 아님) |
| restart 시 기존 client 끊김 | 운영 시간 외에 |
정리
- 풀러 = 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 등의 튜닝을 봅니다.