Connection Pool이 필요한 이유
데이터베이스 연결 생성은 TCP 핸드셰이크, 인증, 세션 초기화 등 비용이 큰 작업입니다. PostgreSQL은 연결당 프로세스를 포크하므로 연결 수가 많아지면 메모리와 CPU 오버헤드가 급증합니다. Connection Pool은 미리 생성한 연결을 재사용하여 이 비용을 제거합니다.
풀 크기 공식
// 최적 풀 크기 계산 (HikariCP 권장)
// connections = (core_count * 2) + effective_spindle_count
// SSD 서버 (spindle=1): 8코어 -> (8 * 2) + 1 = 17
// 일반적인 가이드라인
// - 웹 서버: 10-20 connections
// - 배치 처리: 5-10 connections
// - 마이크로서비스: 5-10 per instance
// 과다 설정의 문제
// 100개 연결 = PostgreSQL에서 100개 프로세스
// 컨텍스트 스위칭 비용 -> 20개가 100개보다 빠를 수 있음
HikariCP 설정 (Java/Spring)
# application.yml - Spring Boot HikariCP 설정
spring:
datasource:
url: jdbc:postgresql://db:5432/myapp
username: app_user
password: secret
hikari:
# 풀 크기 설정
maximum-pool-size: 15 # 최대 연결 수
minimum-idle: 5 # 최소 유휴 연결
# 시간 설정
connection-timeout: 30000 # 연결 획득 대기 (30초)
idle-timeout: 600000 # 유휴 연결 유지 (10분)
max-lifetime: 1800000 # 연결 최대 수명 (30분)
keepalive-time: 300000 # 연결 유효성 확인 (5분)
# 검증
validation-timeout: 5000
connection-test-query: "SELECT 1"
# 모니터링
pool-name: "MyApp-Pool"
register-mbeans: true
# 누수 감지
leak-detection-threshold: 60000 # 60초 이상 반환 안 하면 경고
HikariCP 모니터링
// Micrometer를 통한 메트릭 수집
// 자동으로 수집되는 메트릭:
// - hikaricp.connections.active : 사용 중인 연결
// - hikaricp.connections.idle : 유휴 연결
// - hikaricp.connections.pending : 대기 중인 요청
// - hikaricp.connections.timeout : 타임아웃 횟수
// - hikaricp.connections.acquire : 연결 획득 시간
// - hikaricp.connections.creation : 연결 생성 시간
// Prometheus + Grafana 대시보드에서 모니터링
// 경고 기준:
// - pending > 0 이 지속되면 풀 크기 부족
// - active == maximum-pool-size 이면 포화 상태
// - timeout 발생 시 즉시 알림
// 프로그래밍 방식 확인
HikariPoolMXBean poolProxy = ((HikariDataSource) dataSource).getHikariPoolMXBean();
log.info("Active: {}, Idle: {}, Waiting: {}",
poolProxy.getActiveConnections(),
poolProxy.getIdleConnections(),
poolProxy.getThreadsAwaitingConnection());
PgBouncer 설정 (PostgreSQL 전용)
PgBouncer는 PostgreSQL 앞에서 동작하는 경량 연결 풀러입니다. 수천 개의 클라이언트 연결을 소수의 PostgreSQL 연결로 다중화하여, 마이크로서비스 환경에서 특히 효과적입니다.
# /etc/pgbouncer/pgbouncer.ini
[databases]
myapp = host=127.0.0.1 port=5432 dbname=myapp
auth_user=pgbouncer pool_size=20
[pgbouncer]
listen_addr = 0.0.0.0
listen_port = 6432
auth_type = scram-sha-256
auth_file = /etc/pgbouncer/userlist.txt
# 풀링 모드
pool_mode = transaction # transaction | session | statement
# 풀 크기
default_pool_size = 20 # DB당 기본 풀 크기
min_pool_size = 5 # 최소 유지 연결
max_client_conn = 1000 # 최대 클라이언트 연결
max_db_connections = 50 # DB당 최대 연결
# 시간 설정
server_idle_timeout = 600 # 유휴 서버 연결 종료 (10분)
client_idle_timeout = 0 # 클라이언트 유휴 타임아웃 없음
server_lifetime = 3600 # 서버 연결 최대 수명 (1시간)
server_login_retry = 1 # 재연결 대기 (초)
# 로깅
log_connections = 1
log_disconnections = 1
log_pooler_errors = 1
stats_period = 60
풀링 모드 비교
| 모드 | 연결 반환 시점 | 장점 | 제약사항 |
|---|---|---|---|
| session | 클라이언트 연결 종료 | 완전 호환 | 풀링 효과 낮음 |
| transaction | 트랜잭션 종료 | 높은 다중화 | 세션 변수, PREPARE 제한 |
| statement | 쿼리 완료 | 가장 높은 다중화 | 멀티스테이트먼트 트랜잭션 불가 |
PgBouncer 관리 명령어
# PgBouncer 관리 콘솔 접속
psql -h 127.0.0.1 -p 6432 -U pgbouncer pgbouncer
-- 연결 풀 상태 확인
SHOW POOLS;
-- database | user | cl_active | cl_waiting | sv_active | sv_idle | pool_mode
-- 전체 통계
SHOW STATS;
-- total_xact_count, total_query_count, avg_xact_time, avg_query_time
-- 서버 연결 상태
SHOW SERVERS;
-- 클라이언트 연결 상태
SHOW CLIENTS;
-- 설정 리로드
RELOAD;
-- 데이터베이스 일시 중지 (유지보수)
PAUSE myapp;
RESUME myapp;
HikariCP vs PgBouncer 선택
| 항목 | HikariCP | PgBouncer |
|---|---|---|
| 위치 | 애플리케이션 내장 | 독립 프록시 |
| 언어 | Java/JVM | 모든 언어 |
| 다중화 | 인스턴스별 풀 | 전체 인스턴스 연결 통합 |
| 적합한 경우 | 단일 앱, JVM 환경 | 마이크로서비스, 다중 앱 |
- 마이크로서비스에서 인스턴스 수 x 풀 크기가 PostgreSQL max_connections를 초과하면 PgBouncer가 필수입니다
- PgBouncer transaction 모드에서는 PREPARED STATEMENT를 사용할 수 없으므로 ORM 설정을 확인하세요
- HikariCP의 max-lifetime은 PostgreSQL의 idle_in_transaction_session_timeout보다 짧게 설정하세요
- 두 가지를 함께 사용할 수도 있습니다: HikariCP(앱 측 풀) -> PgBouncer(서버 측 다중화) -> PostgreSQL
댓글 0