คำตอบสั้น ๆ: ติดตั้ง PgBouncer บน Ubuntu ทำ Connection Pooling ให้ PostgreSQL เลือก pool_mode ให้เหมาะกับแอป ตั้งค่า SCRAM เปลี่ยน connection string และเฝ้าดูด้วย SHOW POOLS
คู่มือนี้อธิบาย PgBouncer ทีละขั้นตอน พร้อมคำสั่งที่ใช้ได้จริงและปัญหาที่พบบ่อย
PgBouncer คือ Connection Pooler ขนาดเล็กที่คั่นกลางระหว่างแอปกับ PostgreSQL ทุกการเชื่อมต่อของ PostgreSQL คือโปรเซสหนึ่งตัวที่ใช้หน่วยความจำของตัวเอง เมื่อแอป PHP, Node.js หรือ worker หลายร้อยตัวเปิดการเชื่อมต่อพร้อมกัน เซิร์ฟเวอร์จะเสียทรัพยากรไปกับการเปิดปิดโปรเซสมากกว่าการรัน Query จริง และสุดท้ายผู้ใช้จะเจอข้อความ FATAL: sorry, too many clients already
PgBouncer แก้ปัญหานี้ด้วยการรับการเชื่อมต่อจากแอปได้นับพัน แล้วแบ่งใช้การเชื่อมต่อจริงไปยังฐานข้อมูลเพียงไม่กี่สิบเส้น เมื่อทำตามคู่มือนี้ครบ คุณจะได้ตัวพูลที่ยืนยันตัวตนแบบ SCRAM, ตั้ง pool_mode เหมาะกับแอป และมีวิธีเฝ้าดูว่าพูลเพียงพอหรือไม่
สิ่งที่ต้องเตรียม
- Cloud Server ที่ใช้ Ubuntu 22.04 หรือ 24.04 และติดตั้ง PostgreSQL 14 ขึ้นไปแล้ว ดู คู่มือติดตั้ง PostgreSQL บน Ubuntu
- บัญชีที่ใช้ sudo ได้ และผู้ใช้กับฐานข้อมูลของแอปที่สร้างไว้แล้ว ในตัวอย่างคือ appuser และ appdb
- รหัสผ่านของผู้ใช้เก็บแบบ scram-sha-256 ซึ่งเป็นค่าเริ่มต้นตั้งแต่ PostgreSQL 14
- รู้ว่าแอปใช้ฟีเจอร์ระดับ session หรือไม่ เช่น SET, LISTEN/NOTIFY, advisory lock หรือตารางชั่วคราว เพราะมีผลต่อการเลือก pool_mode
- ช่วงเวลาที่เปลี่ยน connection string ของแอปได้ และแผนย้อนกลับไปพอร์ต 5432 เดิม
ขั้นตอนที่ 1: ติดตั้ง PgBouncer
sudo apt update
sudo apt install pgbouncer
แพ็กเกจของ Ubuntu วางไฟล์คอนฟิกไว้ในโฟลเดอร์ของบริการใต้ /etc และรันบริการด้วยผู้ใช้ postgres ถ้าต้องการรุ่นใหม่กว่าที่ Ubuntu มีให้ ติดตั้งจาก apt repository ของ PostgreSQL (PGDG) ได้ จดหมายเลขรุ่นที่ apt แสดงตอนติดตั้งไว้ เพราะการรองรับ prepared statement ในโหมด transaction เริ่มที่รุ่น 1.21
ขั้นตอนที่ 2: เตรียมไฟล์ผู้ใช้แบบ SCRAM
ตัวพูลต้องตรวจรหัสผ่านของแอปก่อนส่งต่อไปยังฐานข้อมูล วิธีที่ปลอดภัยคือคัดลอก SCRAM secret จาก PostgreSQL มาใช้ ไม่ต้องเก็บรหัสผ่านแบบข้อความธรรมดา
sudo -u postgres psql -Atc "SELECT rolpassword FROM pg_authid WHERE rolname = 'appuser';"
นำผลลัพธ์ที่ขึ้นต้นด้วย SCRAM-SHA-256$4096: ไปใส่ในไฟล์ userlist.txt ในโฟลเดอร์เดียวกัน หนึ่งบรรทัดต่อหนึ่งผู้ใช้ ทั้งชื่อและ secret ครอบด้วยเครื่องหมายคำพูดคู่ ทำแบบเดียวกันกับผู้ใช้สำหรับดูสถิติ ซึ่งสร้างใน PostgreSQL ด้วย CREATE ROLE pgb_stats LOGIN PASSWORD 'StrongPassword'; โดยไม่ต้องให้สิทธิ์ใดเพิ่ม
"appuser" "SCRAM-SHA-256$4096:<salt>$<StoredKey>:<ServerKey>"
"pgb_stats" "SCRAM-SHA-256$4096:<salt>$<StoredKey>:<ServerKey>"
cd /etc/pgbouncer
sudo chown postgres:postgres userlist.txt
sudo chmod 640 userlist.txt
เมื่อ secret ตรงกับของเซิร์ฟเวอร์ทุกตัวอักษรและฝั่งไคลเอนต์ยืนยันตัวตนแบบ SCRAM ระบบจะใช้ secret เดียวกันล็อกอินเข้า PostgreSQL แทนแอปได้ ข้อควรจำคือทุกครั้งที่เปลี่ยนรหัสผ่านของผู้ใช้ ต้องคัดลอก secret ใหม่มาใส่ไฟล์นี้ด้วย
ขั้นตอนที่ 3: ตั้งค่าไฟล์คอนฟิกหลัก
จากโฟลเดอร์เดิม เปิดไฟล์คอนฟิกหลักด้วย sudo nano pgbouncer.ini แล้วแก้ค่าในสองส่วนนี้ ค่าอื่นที่แพ็กเกจตั้งไว้ เช่น logfile และ pidfile ให้คงไว้ตามเดิม
[databases]
appdb = host=127.0.0.1 port=5432 dbname=appdb
[pgbouncer]
listen_addr = 127.0.0.1
listen_port = 6432
auth_type = scram-sha-256
auth_file = /etc/pgbouncer/userlist.txt
pool_mode = transaction
max_client_conn = 1000
default_pool_size = 20
reserve_pool_size = 5
max_prepared_statements = 200
stats_users = pgb_stats
อย่าใส่ user= ในบรรทัดของ [databases] เพราะจะทำให้ใช้ SCRAM secret ของแอปล็อกอินเข้าฐานข้อมูลไม่ได้ ถ้าแอปอยู่คนละเครื่อง ให้เปลี่ยน listen_addr เป็น IP ภายใน เช่น 10.0.0.5 แล้วเปิดพอร์ต 6432 เฉพาะเครื่องแอปที่ไฟร์วอลล์
ค่าที่ต้องคำนวณคือจำนวนการเชื่อมต่อจริงไปยัง PostgreSQL ซึ่งเท่ากับจำนวนพูล (หนึ่งพูลต่อหนึ่งคู่ฐานข้อมูลกับผู้ใช้) คูณ default_pool_size บวก reserve_pool_size ผลรวมนี้ต้องน้อยกว่า max_connections ของ PostgreSQL พอสมควร เพื่อเหลือช่องให้ผู้ดูแลระบบและงานสำรองข้อมูล
sudo systemctl restart pgbouncer
ถ้าบริการไม่ขึ้น ให้ดูสาเหตุจากไฟล์ log ในโฟลเดอร์ /var/log/postgresql ซึ่งมักบอกบรรทัดคอนฟิกที่ผิดตรง ๆ