고트래픽 애플리케이션을 위한 PgBouncer로 PostgreSQL 연결 풀링 구성하기
PostgreSQL에 대한 PgBouncer 연결 풀링을 구성하여 수천 개의 동시 연결을 처리하고, 리소스 오버헤드를 줄이며, 애플리케이션 성능을 극적으로 향상시키는 방법을 알아보세요.
PgBouncer를 활용한 PostgreSQL 연결 풀링 구성: 고트래픽 애플리케이션을 위한 가이드
PostgreSQL 데이터베이스가 많은 연결 요청을 처리할 때 성능이 급격히 저하될 수 있습니다. 각 클라이언트 연결은 PostgreSQL 백엔드 프로세스에 매핑되므로, 트래픽이 많은 웹 앱은 세션 유지에 과도한 메모리와 CPU를 소비하게 됩니다. PgBouncer 연결 풀링은 여러 클라이언트 연결이 더 적은 수의 서버 연결을 재사용할 수 있도록 하여 이러한 부담을 줄여줍니다.
연결 풀링이 중요한 이유
연결 문제
- 리소스 오버헤드: 모든 PostgreSQL 연결은 백엔드 프로세스와 메모리 오버헤드를 발생시킵니다.
- 연결 제한:
max_connections는 유한하며, 이를 너무 높게 설정하면 데이터베이스가 더 빨라지기보다 불안정해질 수 있습니다. - 시작 비용: 새로운 데이터베이스 연결을 생성하면 지연 시간이 추가됩니다.
- 컨텍스트 스위칭: 너무 많은 활성 백엔드 프로세스는 CPU를 낭비할 수 있습니다.
PgBouncer의 장점
- 많은 애플리케이션 클라이언트가 더 적은 수의 PostgreSQL 서버 연결을 공유할 수 있습니다.
- 풀이 사용 중일 때 데이터베이스에 과부하를 주는 대신 클라이언트를 대기시킵니다.
- 세션, 트랜잭션 및 명령문 풀링 모드를 지원합니다.
SHOW POOLS,SHOW CLIENTS,RELOAD와 같은 운영 명령을 제공합니다.
설치 및 기본 설정
PgBouncer 설치
Ubuntu/Debian:
sudo apt update
sudo apt install pgbouncer
CentOS/RHEL:
sudo yum install pgbouncer
macOS:
brew install pgbouncer
일반적인 파일 위치
/etc/pgbouncer/
├── pgbouncer.ini # 기본 구성 파일
└── userlist.txt # 인증 자격 증명
구성 파일 설정
기본 pgbouncer.ini 구성
[databases]
; database_name = host=호스트명 port=5432 dbname=실제_데이터베이스
myapp = host=localhost port=5432 dbname=production_db
[pgbouncer]
; 연결 풀링 모드
pool_mode = transaction
; 최대 연결 수
max_client_conn = 1000
default_pool_size = 25
reserve_pool_size = 5
reserve_pool_timeout = 3
; 네트워킹
listen_addr = 0.0.0.0
listen_port = 6432
; 인증
auth_type = md5
auth_file = /etc/pgbouncer/userlist.txt
; 로깅
log_connections = 1
log_disconnections = 1
log_pooler_errors = 1
; 성능
max_prepared_statements = 0
패키지 기본값은 배포판에 따라 다르므로 설치 후 서비스 파일과 구성 경로를 확인하세요. 많은 Linux 패키지에서 /etc/pgbouncer/pgbouncer.ini가 기본 파일입니다.
풀링 모드 이해하기
1. 세션 풀링 (pool_mode = session)
- 동작: 전체 세션 동안 클라이언트에 연결 할당
- 사용 사례: 임시 테이블, 준비된 명령문을 사용하는 애플리케이션
- 효율성: 낮음 (1:1 연결 비율)
pool_mode = session
2. 트랜잭션 풀링 (pool_mode = transaction) - 권장
- 동작: 각 트랜잭션 후 풀에 연결 반환
- 사용 사례: 짧은 트랜잭션을 사용하는 대부분의 웹 애플리케이션
- 효율성: 높음 (앱이 세션 상태에 의존하지 않는 경우)
pool_mode = transaction
default_pool_size = 25
max_client_conn = 1000
트랜잭션 풀링은 요청/응답 웹 앱에 강력한 기본값이지만, 세션 수준 기능에 대한 가정을 깨뜨릴 수 있습니다. 임시 테이블, 세션 변수, 트랜잭션 외부의 조언 잠금, LISTEN/NOTIFY, 드라이버 수준의 준비된 명령문은 PgBouncer 버전 및 설정과 함께 테스트하지 않은 경우 주의하세요.
3. 명령문 풀링 (pool_mode = statement)
- 동작: 각 명령문 후 연결 반환
- 사용 사례: 트랜잭션이 없는 간단한 읽기 전용 쿼리
- 효율성: 최대 (그러나 매우 제한적)
pool_mode = statement
; 주의해서 사용하세요 - 다중 명령문 트랜잭션을 중단시킵니다.
인증 설정
userlist.txt 생성
PgBouncer는 별도의 인증 파일이 필요합니다. MD5 해시를 생성하여 userlist.txt에 추가하세요.
userlist.txt 예시:
"app_user" "md5d8578edf8458ce06fbc5bb76a58c5ca4"
"readonly_user" "md5a3c7f5e89d24e7c8b1f9d2e4a6c8b0d2"
PostgreSQL 스타일 MD5 비밀번호의 경우 값은 md5와 비밀번호 + 사용자명의 MD5 해시를 연결한 것입니다. 프로덕션에 가짜 해시를 붙여넣지 말고 실제 사용자 이름과 비밀번호로 항목을 생성하거나 환경에서 지원하는 더 안전한 인증 방법을 사용하세요.
PostgreSQL auth_query 사용 (고급)
PgBouncer는 auth_file에서 사용자를 찾을 수 없는 경우 PostgreSQL에 사용자 자격 증명을 쿼리할 수 있지만, 이를 위해서는 PgBouncer가 로그인할 수 있는 auth_user가 필요합니다. 최소 예시는 다음과 같습니다:
auth_type = md5
auth_user = pgbouncer_auth
auth_query = SELECT usename, passwd FROM pg_shadow WHERE usename=$1
인증 사용자의 권한을 제한하고 PostgreSQL 버전의 지침을 따르세요. 많은 팀이 카탈로그 비밀번호 데이터에 대한 직접 액세스 권한을 부여하는 대신 SECURITY DEFINER 함수를 사용합니다.
고트래픽을 위한 최적 구성
연결 풀 크기 조정
보편적인 풀 크기 공식은 없습니다. 데이터베이스가 잘 실행할 수 있는 활성 쿼리 수를 기준으로 보수적인 값으로 시작한 다음 실제 메트릭을 기반으로 조정하세요.
일반적인 웹 앱의 경우 다음과 같이 시작하고 조정할 수 있습니다:
default_pool_size = 25
reserve_pool_size = 5
max_client_conn = 1000
PostgreSQL CPU, 쿼리 지연 시간, 잠금 대기 및 PgBouncer의 cl_waiting 수를 모니터링하세요. 데이터베이스가 유휴 상태인데 클라이언트가 대기 중이면 풀이 너무 작을 수 있습니다. 데이터베이스가 포화 상태이면 풀을 늘리면 상황이 악화될 수 있습니다.
완전한 프로덕션 구성
[databases]
production = host=db.example.com port=5432 dbname=prod_db pool_size=30
analytics = host=db-replica.example.com port=5432 dbname=prod_db pool_size=15
[pgbouncer]
pool_mode = transaction
; 연결 제한
max_client_conn = 2000
default_pool_size = 25
min_pool_size = 10
reserve_pool_size = 8
reserve_pool_timeout = 3
server_lifetime = 3600
server_idle_timeout = 600
; 네트워킹
listen_addr = 0.0.0.0
listen_port = 6432
so_reuseport = 1
pkt_buf = 8192
; 보안
auth_type = md5
auth_file = /etc/pgbouncer/userlist.txt
ignore_startup_parameters = extra_float_digits,options
; 로깅
log_connections = 1
log_disconnections = 1
log_pooler_errors = 1
stats_period = 60
; 성능
max_prepared_statements = 0
query_timeout = 30
query_wait_timeout = 120
max_prepared_statements = 0은 PgBouncer의 준비된 명령문 추적을 비활성화합니다. 최근 PgBouncer 버전은 이 값을 0보다 크게 설정하면 트랜잭션 풀링에서 프로토콜 수준의 준비된 명령문을 지원할 수 있지만, 활성화하기 전에 드라이버와 워크로드를 테스트해야 합니다.
애플리케이션 연결 문자열
PgBouncer 사용 전
# 직접 PostgreSQL 연결
DATABASE_URL = "postgresql://user:[email protected]:5432/mydb"
PgBouncer 사용 후
# PgBouncer를 통한 연결
DATABASE_URL = "postgresql://user:[email protected]:6432/mydb"
모니터링 및 관리
관리 콘솔 명령
PgBouncer 관리 콘솔에 연결:
psql -h localhost -p 6432 -U pgbouncer pgbouncer
필수 명령:
-- 풀 통계 표시
SHOW POOLS;
-- 활성 연결 표시
SHOW CLIENTS;
SHOW SERVERS;
-- 구성 표시
SHOW CONFIG;
-- 구성 다시 로드
RELOAD;
일반적인 문제 해결
문제 1: "더 이상 연결을 허용하지 않음"
이는 PgBouncer가 클라이언트 연결을 거부하거나 PostgreSQL이 서버 연결을 거부함을 의미할 수 있습니다. 오류가 나타나는 위치를 확인하세요.
PgBouncer 측 변경 가능 사항:
max_client_conn = 5000
default_pool_size = 50
둘 다 늘리기 전에 OS 파일 디스크립터 제한과 PostgreSQL max_connections가 새로운 총계를 지원할 수 있는지 확인하세요. PgBouncer 자체도 클라이언트 및 서버 소켓에 충분한 파일 디스크립터가 필요합니다.
문제 2: 높은 cl_waiting 수
해결 방법:
- 풀 크기 증가
- 느린 쿼리 최적화
- 예비 풀 추가
문제 3: 준비된 명령문 오류
앱이나 드라이버가 준비된 명령문을 사용하고 트랜잭션 풀링 모드에 있는 경우, 이후 명령문이 다른 서버 연결에 도달할 때 오류가 발생할 수 있습니다. 옵션은 다음과 같습니다:
- 드라이버 측 준비된 명령문을 비활성화합니다.
- 해당 워크로드에 대해 세션 풀링을 사용합니다.
- 최신 PgBouncer 버전에서 양수
max_prepared_statements값으로 PgBouncer 준비된 명령문 지원을 테스트합니다.
보수적인 설정:
max_prepared_statements = 0
실용적인 배포 예시
앱 서버가 수백 개의 동시 HTTP 요청을 열 수 있지만 데이터베이스는 수십 개의 활성 쿼리로 가장 잘 실행된다고 가정해 보겠습니다. 앱을 포트 6432의 PgBouncer로 지정하고, 클라이언트 버스트를 처리할 수 있을 만큼 max_client_conn을 높게 설정하고, default_pool_size를 해당 데이터베이스/사용자 쌍에 대해 실제로 활성화하려는 데이터베이스 연결 수에 가깝게 유지하세요.
그런 다음 다음으로 확인하세요:
SHOW POOLS;
SHOW STATS;
정상 트래픽 중에 cl_waiting이 0보다 높게 유지되면 단순히 풀 크기를 늘리기 전에 느린 쿼리를 조사하세요. PgBouncer는 PostgreSQL을 연결 폭주로부터 보호하지만 느린 SQL을 저렴하게 만들지는 않습니다.
실용적인 요점
상태 비저장 웹 워크로드에는 트랜잭션 풀링으로 시작하고, 풀 크기를 의도적으로 작게 유지하며, PgBouncer 및 PostgreSQL 메트릭을 기반으로 조정하세요. 앱이 세션 동작이나 준비된 명령문에 의존하는 경우 프로덕션 트래픽 앞에 PgBouncer를 배치하기 전에 해당 경로를 테스트하세요.