ติดตั้ง PgBouncer Connection Pooler สำหรับ PostgreSQL บน VPS
PostgreSQL สร้าง process ใหม่ 1 process ต่อ connection 1 อัน ซึ่งใช้ RAM ประมาณ 5–10 MB ต่อ connection เมื่อ application มี connection จำนวนมากพร้อมกัน เช่น 200–500 connections เซิร์ฟเวอร์จะกินหน่วยความจำหมดและ performance จะตก PgBouncer คือ connection pooler น้ำหนักเบาที่นั่งอยู่ระหว่าง application กับ PostgreSQL รับ connection จาก client จำนวนมาก แล้วส่งต่อไปยัง PostgreSQL ผ่าน connection จริงจำนวนน้อยลงมาก ทำให้ระบบรองรับ traffic สูงได้โดยไม่ต้องอัปเกรด server
PgBouncer ทำงานอย่างไร
PgBouncer มี 3 โหมด ควรเลือกตามลักษณะ application:
| โหมด | วิธีทำงาน | เหมาะกับ |
|---|---|---|
session | 1 server connection ต่อ 1 client session (ตลอดการเชื่อมต่อ) | Application ที่ใช้ session-level features เช่น temp table, advisory lock |
transaction | ยืม server connection เฉพาะช่วง transaction เท่านั้น | แนะนำสำหรับ web app ทั่วไป (Django, Rails, Node.js) |
statement | ยืม server connection ต่อ 1 statement | Application ที่ไม่ใช้ multi-statement transaction (ไม่ค่อยพบ) |
สิ่งที่ต้องเตรียม
- VPS Ubuntu 22.04 LTS หรือ 24.04 LTS
- PostgreSQL ติดตั้งและทำงานอยู่แล้ว (ดู ติดตั้ง PostgreSQL บน VPS)
- สิทธิ์ sudo หรือ root
- RAM อย่างน้อย 512 MB (PgBouncer เองกิน RAM ไม่ถึง 5 MB)
ขั้นตอนที่ 1 — ติดตั้ง PgBouncer
PgBouncer อยู่ใน repository มาตรฐานของ Ubuntu ติดตั้งได้เลย:
sudo apt update
sudo apt install -y pgbouncer
pgbouncer --version
ตรวจสอบสถานะ service:
sudo systemctl status pgbouncer
ขั้นตอนที่ 2 — ตั้งค่าไฟล์ pgbouncer.ini
ไฟล์ config หลักอยู่ที่ /etc/pgbouncer/pgbouncer.ini แก้ไขให้ตรงกับ environment:
sudo nano /etc/pgbouncer/pgbouncer.ini
ตัวอย่าง config สำหรับ production (transaction pooling):
[databases]
; Format: dbname = host=... port=... dbname=... user=...
myapp = host=127.0.0.1 port=5432 dbname=myapp_db
[pgbouncer]
; Listen address and port
listen_addr = 127.0.0.1
listen_port = 6432
; Auth method — md5 หรือ scram-sha-256
auth_type = md5
auth_file = /etc/pgbouncer/userlist.txt
; Pooling mode: session | transaction | statement
pool_mode = transaction
; Connection limits
max_client_conn = 200
default_pool_size = 20
min_pool_size = 5
reserve_pool_size = 5
reserve_pool_timeout = 3
; Timeouts (seconds)
server_idle_timeout = 600
client_idle_timeout = 0
query_timeout = 0
query_wait_timeout = 120
; Logging
logfile = /var/log/pgbouncer/pgbouncer.log
pidfile = /var/run/pgbouncer/pgbouncer.pid
stats_period = 60
; Admin
admin_users = pgbouncer_admin
stats_users = pgbouncer_stats
default_pool_size = 20 หมายความว่า PgBouncer จะเปิด connection จริงกับ PostgreSQL ไม่เกิน 20 connections ต่อ database แต่รับ client connection ได้ถึง 200 — ratio 10:1 ช่วยประหยัด RAM ได้มากขั้นตอนที่ 3 — สร้าง userlist.txt
PgBouncer ต้องการ password ของ user ที่จะใช้ เก็บในรูป MD5 hash:
# สร้าง MD5 hash สำหรับ user "myapp_user" password "mypassword"
echo -n "mypasswordmyapp_user" | md5sum
# ผลลัพธ์เช่น: a1b2c3d4e5f6... -
เพิ่ม user ลงใน /etc/pgbouncer/userlist.txt:
sudo nano /etc/pgbouncer/userlist.txt
รูปแบบไฟล์:
"myapp_user" "md5a1b2c3d4e5f6789abcdef1234567890ab"
"pgbouncer_admin" "md5xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
echo -n "PASSUSERname" | md5sum | awk '{print "\"username\" \"md5"$1"\""}'ขั้นตอนที่ 4 — ตั้งค่า auth_query (วิธีที่ดีกว่า)
แทนที่จะเก็บ password ใน userlist.txt สามารถให้ PgBouncer ดึง password จาก PostgreSQL โดยตรงผ่าน auth_query ซึ่งปลอดภัยกว่า:
สร้าง function ใน PostgreSQL:
sudo -u postgres psql
-- สร้าง schema สำหรับ pgbouncer
CREATE SCHEMA pgbouncer;
-- สร้าง function ดึง password
CREATE OR REPLACE FUNCTION pgbouncer.get_auth(p_usename TEXT)
RETURNS TABLE(username TEXT, password TEXT) AS $$
BEGIN
RETURN QUERY
SELECT usename::TEXT, passwd::TEXT
FROM pg_shadow
WHERE usename = p_usename;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;
-- สร้าง user สำหรับ pgbouncer auth
CREATE USER pgbouncer_auth WITH PASSWORD 'strong_auth_password';
GRANT CONNECT ON DATABASE myapp_db TO pgbouncer_auth;
REVOKE ALL ON SCHEMA public FROM pgbouncer_auth;
GRANT USAGE ON SCHEMA pgbouncer TO pgbouncer_auth;
GRANT EXECUTE ON FUNCTION pgbouncer.get_auth(TEXT) TO pgbouncer_auth;
เพิ่มใน pgbouncer.ini:
auth_user = pgbouncer_auth
auth_query = SELECT username, password FROM pgbouncer.get_auth($1)
ขั้นตอนที่ 5 — สร้าง directory และเริ่ม service
sudo mkdir -p /var/log/pgbouncer /var/run/pgbouncer
sudo chown pgbouncer:pgbouncer /var/log/pgbouncer /var/run/pgbouncer
sudo chmod 750 /var/log/pgbouncer
# reload config
sudo systemctl restart pgbouncer
sudo systemctl enable pgbouncer
sudo systemctl status pgbouncer
ขั้นตอนที่ 6 — ทดสอบ Connection ผ่าน PgBouncer
Application ต้องเปลี่ยน port จาก 5432 (PostgreSQL โดยตรง) เป็น 6432 (PgBouncer):
# ทดสอบ connect ผ่าน PgBouncer (port 6432)
psql -h 127.0.0.1 -p 6432 -U myapp_user -d myapp
# ตรวจดู connection ใน PgBouncer admin console
psql -h 127.0.0.1 -p 6432 -U pgbouncer_admin pgbouncer
pgbouncer=# SHOW POOLS;
pgbouncer=# SHOW CLIENTS;
pgbouncer=# SHOW SERVERS;
pgbouncer=# SHOW STATS;
ขั้นตอนที่ 7 — ปรับแต่ง pool_size ตาม workload
ขนาด pool ที่เหมาะสมขึ้นอยู่กับ server และ workload:
| RAM Server | PostgreSQL max_connections | PgBouncer default_pool_size | max_client_conn |
|---|---|---|---|
| 1 GB | 100 | 10–15 | 100–200 |
| 2 GB | 200 | 20–30 | 200–500 |
| 4 GB | 300 | 30–50 | 500–1000 |
| 8 GB+ | 500 | 50–100 | 1000–2000 |
default_pool_size × จำนวน database ≤ max_connections × 0.8 เผื่อ connection สำหรับ admin และ monitoring ไว้ด้วยขั้นตอนที่ 8 — Reload config โดยไม่ต้อง restart
PgBouncer รองรับการ reload config โดยไม่ต้องตัด connection ที่มีอยู่:
# ผ่าน admin console
psql -h 127.0.0.1 -p 6432 -U pgbouncer_admin pgbouncer
pgbouncer=# RELOAD;
# หรือผ่าน systemctl
sudo systemctl reload pgbouncer
# หรือส่ง signal SIGHUP
sudo kill -SIGHUP $(cat /var/run/pgbouncer/pgbouncer.pid)
ตรวจสอบว่า PgBouncer ทำงานถูกต้อง
# 1. ตรวจสอบ service status
sudo systemctl status pgbouncer
# 2. ตรวจสอบ port ที่ listen
ss -tlnp | grep 6432
# 3. ดู pool stats ผ่าน admin console
psql -h 127.0.0.1 -p 6432 -U pgbouncer_admin pgbouncer -c "SHOW POOLS;"
# 4. ดู active connections
psql -h 127.0.0.1 -p 6432 -U pgbouncer_admin pgbouncer -c "SHOW CLIENTS;"
# 5. ดู stats ย้อนหลัง (queries/second, response time)
psql -h 127.0.0.1 -p 6432 -U pgbouncer_admin pgbouncer -c "SHOW STATS_TOTALS;"
cl_waiting — ถ้ามี client รอนานๆ แสดงว่า pool_size เล็กเกินไป ให้เพิ่ม default_pool_sizeแก้ปัญหาที่พบบ่อย
Error: "no more connections allowed (max_client_conn)"
จำนวน client connection ถึง limit ที่ตั้งไว้ใน max_client_conn PgBouncer ปฏิเสธ connection ใหม่
# เพิ่ม max_client_conn ใน pgbouncer.ini แล้ว reload
max_client_conn = 500
sudo systemctl reload pgbouncer
# ดู client ปัจจุบัน
psql -h 127.0.0.1 -p 6432 -U pgbouncer_admin pgbouncer -c "SHOW CLIENTS;"
Error: "ERROR: prepared statement already exists"
เกิดเมื่อใช้ pool_mode = transaction กับ application ที่ใช้ prepared statements (เช่น Django ORM บางเวอร์ชัน)
# วิธีที่ 1: เปลี่ยน pool_mode เป็น session (แต่ได้ประโยชน์น้อยลง)
pool_mode = session
# วิธีที่ 2 (แนะนำ): ปิด server_reset_query ให้ pgbouncer ล้าง state
server_reset_query = DISCARD ALL
# Django settings.py: ปิด persistent connections เมื่อใช้ pgbouncer
DATABASES = {
'default': {
'CONN_MAX_AGE': 0,
}
}
Error: "could not connect to server: Connection refused" (port 6432)
PgBouncer ไม่ได้ run หรือ config ผิด
# ตรวจสอบ process
ps aux | grep pgbouncer
# ดู error log
sudo tail -50 /var/log/pgbouncer/pgbouncer.log
# ตรวจสอบ syntax ของ config
pgbouncer --check-config /etc/pgbouncer/pgbouncer.ini
# restart และดู journal
sudo systemctl restart pgbouncer
sudo journalctl -u pgbouncer -n 50
Error: "Auth failed" หรือ password authentication failed
userlist.txt มี hash ผิด หรือ auth_type ไม่ตรงกับ PostgreSQL
# ตรวจสอบ auth_type ใน postgresql.conf
sudo grep -i "password_encryption" /etc/postgresql/*/main/postgresql.conf
# สร้าง hash ใหม่ (md5 = md5 + hash ของ password+username ต่อกัน)
echo -n "mypasswordmyapp_user" | md5sum
# ใส่ใน userlist.txt: "myapp_user" "md5HASH_HERE"
# reload pgbouncer หลังแก้ userlist
sudo systemctl reload pgbouncer
Error: "query_wait_timeout" — client รอนานเกินไป
Pool เต็ม server connection หมด client ต้องรอ connection ว่าง
# เพิ่ม reserve_pool_size และ default_pool_size
reserve_pool_size = 10
reserve_pool_timeout = 3
default_pool_size = 30
# หรือเพิ่ม query_wait_timeout ให้นานขึ้น (หน่วยเป็นวินาที)
query_wait_timeout = 180
sudo systemctl reload pgbouncer
คำถามที่พบบ่อย
ควรใช้ transaction pool หรือ session pool?
สำหรับ web application ทั่วไป (Django, Rails, Express, FastAPI) ให้ใช้ pool_mode = transaction เพราะได้ประโยชน์สูงสุด connection จะถูก reuse ระหว่าง request ทำให้ 20 server connections รองรับได้หลายร้อย simultaneous requests ข้อยกเว้นคือถ้า application ใช้ advisory lock, temporary table, SET session variable หรือ LISTEN/NOTIFY ต้องใช้ session โหมดแทน
PgBouncer ปลอดภัยพอสำหรับ Production หรือไม่?
ใช่ PgBouncer ถูกใช้ใน production ของบริษัทใหญ่อย่าง Instagram, GitLab, Heroku มานานกว่า 15 ปี ควรตั้งค่า listen_addr = 127.0.0.1 เพื่อให้ bind เฉพาะ localhost ไม่เปิดออก internet และใช้ TLS หากต้องการ encrypt connection ระหว่าง client กับ PgBouncer เพิ่มเติม
ถ้าใช้ ORM เช่น SQLAlchemy หรือ Prisma ต้องปรับอะไรบ้าง?
ต้องปิด built-in connection pool ของ ORM เพราะมี pooling ซ้ำซ้อน สำหรับ SQLAlchemy ตั้ง pool_size=1, max_overflow=0, pool_pre_ping=True หรือใช้ NullPool สำหรับ Prisma ตั้ง connection_limit=1 ใน database URL แล้วให้ PgBouncer จัดการ pooling ทั้งหมดแทน