ทำ Streaming Replication บน PostgreSQL
คำตอบสั้น ๆ: สร้างเซิร์ฟเวอร์สำรองที่คัดลอกข้อมูลจากตัวหลักแบบต่อเนื่อง ใช้อ่านข้อมูลได้ทันที และสลับมาเป็นตัวหลักเมื่อจำเป็น
คู่มือนี้อธิบาย Streaming Replication ทีละขั้นตอน พร้อมคำสั่งที่ใช้ได้จริงและปัญหาที่พบบ่อย
Streaming Replication ของ PostgreSQL ส่งข้อมูล WAL จากตัวหลักไปยังตัวสำรองแบบต่อเนื่อง ทำให้มีสำเนาที่ตามหลังเพียงเสี้ยววินาที ตัวสำรองยังรับงานอ่านได้ด้วย จึงช่วยแบ่งเบาภาระของตัวหลักไปพร้อมกัน
คู่มือนี้ตั้งค่าตั้งแต่ศูนย์ด้วย pg_basebackup ซึ่งเป็นวิธีมาตรฐานที่คัดลอกข้อมูลตั้งต้นและตั้งค่าให้พร้อมในคำสั่งเดียว
สิ่งที่ต้องเตรียม (Streaming Replication)
เซิร์ฟเวอร์สองเครื่องที่ติดตั้ง PostgreSQL เวอร์ชันหลักเดียวกัน
เครือข่ายภายในระหว่างกัน และพื้นที่ดิสก์บนตัวสำรองไม่น้อยกว่าตัวหลัก
ในคู่มือนี้ตัวหลักคือ 10.0.0.41 และตัวสำรองคือ 10.0.0.42
ขั้นตอนที่ 1: ตั้งค่าตัวหลัก (Streaming Replication)
sudo nano /etc/postgresql/16/main/postgresql.conf
listen_addresses = '10.0.0.41,localhost'
wal_level = replica
max_wal_senders = 10
wal_keep_size = 2GB
archive_mode = on
archive_command = 'test ! -f /var/lib/postgresql/archive/%f && cp %p /var/lib/postgresql/archive/%f'
# ให้ตัวสำรองรายงานตำแหน่งกลับมา ทำให้ดูความล่าช้าได้
hot_standby_feedback = on
sudo mkdir -p /var/lib/postgresql/archive
sudo chown postgres:postgres /var/lib/postgresql/archive
wal_keep_size คือปริมาณ WAL ที่เก็บไว้เผื่อตัวสำรองตามไม่ทัน หากตั้งน้อยเกินไปและตัวสำรองหลุดไปนาน จะต้องสร้างใหม่ทั้งหมด วิธีที่ดีกว่าคือใช้ Replication Slot ตามขั้นตอนที่ 3
ขั้นตอนที่ 2: สร้างผู้ใช้และอนุญาตการเชื่อมต่อ (Streaming Replication)
sudo -u postgres psql
CREATE ROLE replicator WITH REPLICATION LOGIN PASSWORD 'รหัสผ่านที่ยาวและสุ่ม';
SELECT rolname, rolreplication FROM pg_roles WHERE rolname = 'replicator';
sudo nano /etc/postgresql/16/main/pg_hba.conf
host replication replicator 10.0.0.42/32 scram-sha-256
sudo systemctl restart postgresql
sudo -u postgres psql -c "SHOW wal_level;"
ขั้นตอนที่ 3: สร้าง Replication Slot
Slot ทำให้ตัวหลักเก็บ WAL ไว้จนกว่าตัวสำรองจะรับไปแล้วจริง ป้องกันปัญหาตัวสำรองตามไม่ทันแล้วต้องสร้างใหม่
sudo -u postgres psql -c "SELECT pg_create_physical_replication_slot('standby1');"
sudo -u postgres psql -c "SELECT slot_name, active, restart_lsn FROM pg_replication_slots;"
ข้อควรระวัง หากตัวสำรองหายไปถาวรแต่ Slot ยังอยู่ ตัวหลักจะเก็บ WAL ไว้เรื่อย ๆ จนดิสก์เต็ม ต้องลบ Slot ทิ้งด้วย SELECT pg_drop_replication_slot('standby1');
ขั้นตอนที่ 4: สร้างตัวสำรองด้วย pg_basebackup
# บนตัวสำรอง หยุดบริการและล้างข้อมูลเดิม
sudo systemctl stop postgresql
sudo -u postgres rm -rf /var/lib/postgresql/16/main/*
sudo -u postgres pg_basebackup \
-h 10.0.0.41 -p 5432 -U replicator \
-D /var/lib/postgresql/16/main \
-Fp -Xs -P -R \
-S standby1
ความหมายของตัวเลือก -Fp คือเขียนเป็นไฟล์ปกติ -Xs คือรับ WAL ไปพร้อมกันระหว่างคัดลอก -P คือแสดงความคืบหน้า -R คือสร้างไฟล์ตั้งค่าการเชื่อมต่อให้อัตโนมัติ และ -S คือใช้ Slot ที่สร้างไว้
ตรวจว่าไฟล์ที่ต้องมีถูกสร้างแล้ว
sudo ls -l /var/lib/postgresql/16/main/standby.signal
sudo cat /var/lib/postgresql/16/main/postgresql.auto.conf
ขั้นตอนที่ 5: เริ่มตัวสำรอง
sudo nano /etc/postgresql/16/main/postgresql.conf
hot_standby = on
listen_addresses = '10.0.0.42,localhost'
sudo systemctl start postgresql
sudo -u postgres psql -c "SELECT pg_is_in_recovery();"
ผลควรเป็น t ซึ่งแปลว่าอยู่ในโหมดตัวสำรองและกำลังรับข้อมูลอยู่
ขั้นตอนที่ 6: ตรวจสอบสถานะและความล่าช้า
-- บนตัวหลัก
SELECT client_addr, state, sent_lsn, write_lsn, flush_lsn, replay_lsn,
write_lag, flush_lag, replay_lag, sync_state
FROM pg_stat_replication;
-- บนตัวสำรอง ดูว่าตามหลังกี่วินาที
SELECT CASE WHEN pg_last_wal_receive_lsn() = pg_last_wal_replay_lsn()
THEN 0
ELSE EXTRACT(EPOCH FROM now() - pg_last_xact_replay_timestamp())
END AS lag_seconds;
ทดสอบจริง
# บนตัวหลัก
sudo -u postgres psql -c "CREATE TABLE IF NOT EXISTS repltest (id serial, t timestamptz DEFAULT now());"
sudo -u postgres psql -c "INSERT INTO repltest DEFAULT VALUES;"
# บนตัวสำรอง
sudo -u postgres psql -c "SELECT * FROM repltest ORDER BY id DESC LIMIT 5;"
ลองเขียนบนตัวสำรองเพื่อยืนยันว่าถูกปฏิเสธ
sudo -u postgres psql -c "INSERT INTO repltest DEFAULT VALUES;"
# ควรได้ ERROR: cannot execute INSERT in a read-only transaction
ขั้นตอนที่ 7: ใช้ตัวสำรองรับงานอ่าน
ชี้แอปส่วนที่อ่านอย่างเดียว เช่น หน้ารายงาน มาที่ตัวสำรอง ลดภาระตัวหลักได้มาก
ข้อควรระวังคือความล่าช้า ข้อมูลที่เพิ่งเขียนอาจยังไม่ปรากฏบนตัวสำรองทันที จึงไม่เหมาะกับหน้าที่ต้องอ่านข้อมูลที่เพิ่งบันทึกไป เช่น หน้ายืนยันคำสั่งซื้อ
หากต้องการให้ตัวสำรองยืนยันก่อนตัวหลักตอบว่าสำเร็จ ตั้งเป็นแบบซิงโครนัส
# บนตัวหลัก
synchronous_standby_names = 'standby1'
synchronous_commit = on
วิธีนี้ทำให้ไม่มีข้อมูลสูญหายเลยเมื่อตัวหลักล่ม แต่การเขียนจะช้าลงและหากตัวสำรองล่ม การเขียนบนตัวหลักจะหยุดรอ ต้องมีตัวสำรองอย่างน้อยสองตัวจึงจะใช้โหมดนี้ได้อย่างปลอดภัย
ขั้นตอนที่ 8: สลับตัวสำรองเป็นตัวหลัก
# บนตัวสำรอง
sudo -u postgres pg_ctl promote -D /var/lib/postgresql/16/main
# หรือ
sudo -u postgres psql -c "SELECT pg_promote();"
sudo -u postgres psql -c "SELECT pg_is_in_recovery();" # ควรเป็น f
หลังสลับแล้ว เครื่องเดิมจะกลับมาเป็นตัวสำรองของเครื่องใหม่ไม่ได้ทันที ต้องใช้ pg_rewind ปรับให้ตรงกันก่อน
sudo systemctl stop postgresql
sudo -u postgres pg_rewind \
--target-pgdata=/var/lib/postgresql/16/main \
--source-server="host=10.0.0.42 user=replicator password=xxx" \
--progress
ปัญหาที่พบบ่อยเกี่ยวกับ Streaming Replication
pg_basebackup ล้มเหลวเรื่องการยืนยันตัวตน
ตรวจว่า pg_hba.conf มีบรรทัดสำหรับฐานข้อมูลชื่อ replication ซึ่งเป็นคำสงวน ไม่ใช่ชื่อฐานข้อมูลจริง และรีโหลดคอนฟิกแล้วด้วย sudo -u postgres psql -c "SELECT pg_reload_conf();"
ตัวสำรองตามไม่ทันจนหลุด
ใช้ Replication Slot ตามขั้นตอนที่ 3 และตรวจว่าดิสก์ของตัวสำรองไม่ช้ากว่าตัวหลัก ดูข้อความ requested WAL segment has already been removed ใน Log ซึ่งยืนยันอาการนี้
ดิสก์ตัวหลักเต็มเพราะ WAL
ตรวจ Slot ที่ไม่มีการใช้งานด้วย SELECT slot_name, active, pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(), restart_lsn)) FROM pg_replication_slots; Slot ที่ active เป็น false และค้างมานานคือสาเหตุ ให้ลบทิ้ง
Query บนตัวสำรองถูกยกเลิก
เกิดเมื่อการเปลี่ยนแปลงจากตัวหลักขัดกับ Query ที่รันอยู่ ตั้ง max_standby_streaming_delay ให้ยาวขึ้น หรือเปิด hot_standby_feedback = on ซึ่งบอกตัวหลักไม่ให้ลบข้อมูลที่ตัวสำรองยังใช้อยู่
ต้องการวางระบบฐานข้อมูลพร้อม DR Site ในประเทศไทย ติดต่อ THAI DATA CLOUD ได้ที่ https://thaidata.cloud/contact/
สรุปขั้นตอน Streaming Replication
ทบทวน Streaming Replication ทั้งหมดตามลำดับ ก่อนนำไปใช้กับระบบจริง
ตั้งค่าตัวหลัก (Streaming Replication)
สร้างผู้ใช้และอนุญาตการเชื่อมต่อ (Streaming Replication)
สร้าง Replication Slot
สร้างตัวสำรองด้วย pg_basebackup
เริ่มตัวสำรอง
ตรวจสอบสถานะและความล่าช้า
ใช้ตัวสำรองรับงานอ่าน
สลับตัวสำรองเป็นตัวหลัก
เมื่อทำครบทุกข้อแล้ว Streaming Replication จะพร้อมใช้งาน หากผลลัพธ์ไม่เป็นไปตามที่คาด ให้ย้อนกลับไปดูหัวข้อปัญหาที่พบบ่อยด้านบน ซึ่งรวบรวมกรณีที่พบจริงจากการใช้งาน Streaming Replication ไว้แล้ว
อ่านเพิ่มเติมเกี่ยวกับ Streaming Replication