Replication
server ตัวเดียวคือจุดล้มเหลวจุดเดียว ถ้าดิสก์พังหรือเครื่อง reboot ฐานข้อมูลของคุณก็หายไปจนกว่าจะมีคนมาแก้ไข Replication ขจัดความเสี่ยงนั้นด้วยการเก็บสำเนาข้อมูลหนึ่งชุดหรือมากกว่าไว้บนเครื่องอื่น โดยอัปเดตอย่างต่อเนื่องตามที่ต้นฉบับเปลี่ยนแปลง สำเนาเหล่านั้นยังช่วยให้คุณกระจาย traffic การอ่านไปยัง server หลายตัวได้
PostgreSQL มีให้สองรูปแบบ: streaming replication ที่คัดลอกทั้ง cluster แบบ byte-for-byte และ logical replication ที่คัดลอก table ที่เลือกทีละ row ทั้งสองแก้ปัญหาที่แตกต่างกัน บทเรียนนี้จึงครอบคลุมทั้งคู่
write-ahead log คือแหล่งความจริง
หัวข้อที่มีชื่อว่า “write-ahead log คือแหล่งความจริง”ก่อนที่การเปลี่ยนแปลงจะแตะไฟล์ของ table PostgreSQL จะบันทึกการเปลี่ยนแปลงนั้นลงใน write-ahead log (WAL): stream แบบ append-only ของทุกการแก้ไข log นี้มีไว้สำหรับ crash recovery แต่ก็เป็นสิ่งที่ replica ต้องการพอดี ถ้า server ตัวที่สอง replay WAL record ชุดเดียวกันตามลำดับเดียวกัน ก็จะได้สำเนาข้อมูลที่เหมือนกันทุกประการ
Streaming replication ทำแบบนั้นเป๊ะ ๆ คือ primary ส่ง WAL ไปยัง replica แต่ละตัวผ่าน connection ของเครือข่าย และ replica แต่ละตัวก็ apply record เหล่านั้นอย่างต่อเนื่องเพื่อให้ตามทัน
flowchart LR C[Client write] --> P[(Primary)] P -->|append| W[Write-ahead log] W -->|stream WAL| R1[(Replica 1 - read only)] W -->|stream WAL| R2[(Replica 2 - read only)] RC1[Read query] --> R1 RC2[Read query] --> R2
Replica เป็นแบบอ่านอย่างเดียว คือ apply การเปลี่ยนแปลงจาก primary และตอบ query แบบ SELECT ได้ แต่ปฏิเสธการเขียน จึงเหมาะอย่างยิ่งสำหรับการรับภาระงานรายงาน analytics หรือ workload ที่เน้นการอ่านใด ๆ ออกจาก primary
การตั้งค่า streaming replication
หัวข้อที่มีชื่อว่า “การตั้งค่า streaming replication”Streaming replication ถูกตั้งค่าบน primary ด้วยการตั้งค่าเพียงไม่กี่อย่างและบัญชีหนึ่งที่ replica ใช้เชื่อมต่อ ไฟล์ที่เกี่ยวข้องอยู่ใน data directory ของคุณ
# postgresql.conf on the primarywal_level = replicamax_wal_senders = 10wal_keep_size = 512MB# pg_hba.conf on the primary: allow the replication account to connecthost replication replicator 10.0.0.0/24 scram-sha-256คุณสร้าง replica ด้วยการทำ base backup ของ primary แล้วชี้ replica กลับไปที่ primary เพื่อ stream WAL ที่กำลังไหลอยู่ เครื่องมือที่คัดลอกสถานะเริ่มต้นคือ pg_basebackup
# Run on the new replica machine to clone the primary and start streamingpg_basebackup \ --host=primary.internal \ --username=replicator \ --pgdata=/var/lib/postgresql/data \ --wal-method=stream \ --write-recovery-confแฟล็ก --write-recovery-conf บันทึกรายละเอียดการเชื่อมต่อไว้เพื่อให้ replica รู้ว่าต้องเริ่ม stream ทันทีที่บูตขึ้นมา จากนั้น replica จะไล่ตาม primary เองโดยอัตโนมัติ
synchronous เทียบกับ asynchronous
หัวข้อที่มีชื่อว่า “synchronous เทียบกับ asynchronous”ตัวเลือกใหญ่ใน streaming replication คือ เมื่อใด ที่ primary จะถือว่าการเขียนเสร็จสิ้น
| โหมด | primary ยืนยันการ commit เมื่อ… | ข้อแลกเปลี่ยน |
|---|---|---|
| Asynchronous | WAL ถูกเขียนในเครื่อง | เร็ว แต่ replica อาจล้าหลังได้ |
| Synchronous | replica อย่างน้อยหนึ่งตัวเก็บ WAL ไว้แล้วด้วย | ไม่สูญข้อมูลตอน failover แต่เขียนช้ากว่า |
Asynchronous เป็นค่าเริ่มต้นและเป็นตัวเลือกที่ใช้กันทั่วไป การเขียนไม่เคยต้องรอเครือข่าย primary จึงยังเร็วอยู่ — แต่ replica อาจล้าหลังไปเล็กน้อย เรียกว่า replication lag query ที่ส่งไปยัง replica ที่ล้าหลังอาจยังไม่เห็น commit ล่าสุด
Synchronous replication ทำให้ primary รอให้ replica ตอบรับทุกการ commit เปิดใช้ด้วยการระบุชื่อ replica ที่ต้องยืนยัน
# postgresql.conf on the primarysynchronous_standby_names = 'replica1'สิ่งนี้รับประกันว่า commit ที่ตอบรับแล้วจะรอดจากการสูญเสีย primary โดยแลกกับ write latency ที่สูงขึ้น
Failover
หัวข้อที่มีชื่อว่า “Failover”ถ้า primary ตาย replica หนึ่งตัวต้องถูก promote ให้เป็น primary ตัวใหม่เพื่อให้การเขียนกลับมาทำงานได้ การ promote บอก replica ให้หยุดตามและเริ่มรับการเขียน
# On the chosen replica, promote it to primarypg_ctl promote --pgdata=/var/lib/postgresql/dataในโปรดักชัน เรื่องนี้มักจัดการโดยเครื่องมืออัตโนมัติที่ตรวจจับความล้มเหลวและ promote standby ให้คุณ แต่การกระทำที่อยู่เบื้องหลังก็คือขั้นตอนเดียวเดิมนี้
logical replication: คัดลอก table ที่เลือก
หัวข้อที่มีชื่อว่า “logical replication: คัดลอก table ที่เลือก”Streaming replication คัดลอก ทั้ง cluster และไหลทางเดียวไปยัง read-only standby เท่านั้น บางครั้งคุณต้องการน้อยกว่านั้น — แค่ table ไม่กี่ table — หรือคุณต้องการคัดลอกระหว่าง PostgreSQL major version ที่ต่างกัน เช่นระหว่างการอัปเกรด นั่นคือ logical replication ที่สร้างบนโมเดลแบบ publish-and-subscribe
บนต้นทางคุณสร้าง publication ที่ระบุชื่อ table ที่จะแชร์ บนปลายทางคุณสร้าง subscription ที่เชื่อมเข้ามาแล้วคัดลอก table เหล่านั้น
-- On the source database: publish two tablesCREATE PUBLICATION analytics_pub FOR TABLE orders, customers;-- On the destination database: subscribe to that publicationCREATE SUBSCRIPTION analytics_sub CONNECTION 'host=source.internal dbname=shop user=repl password=secret' PUBLICATION analytics_pub;Subscriber คัดลอก row ที่มีอยู่เดิมก่อน แล้วค่อย apply การเปลี่ยนแปลงใหม่ตามมาเรื่อย ๆ ต่างจาก streaming replica ตรงที่ logical subscriber เป็นฐานข้อมูลที่เขียนได้เต็มรูปแบบ จะเพิ่ม table และ index ของตัวเองก็ได้ จุดนี้เองที่ทำให้เหมาะกับการคัดลอกเฉพาะบาง table และการ staging ตอนอัปเกรดเวอร์ชัน
flowchart LR S[(Source database)] -->|CREATE PUBLICATION| Pub[Publication: orders, customers] Pub -->|row changes| Sub[Subscription] Sub --> D[(Destination database - writable)]
Logical replication ก็ต้องการให้ยกระดับ wal_level เช่นกัน คราวนี้เป็น logical บนต้นทาง
# postgresql.conf on the sourcewal_level = logicalเคล็ดลับและข้อควรระวัง
หัวข้อที่มีชื่อว่า “เคล็ดลับและข้อควรระวัง”- Replica มีไว้สำหรับการอ่านและความพร้อมใช้งาน ส่งทุกการเขียนไปที่ primary เพราะ replica จะปฏิเสธการเขียนทั้งหมด
- เมื่อใช้ asynchronous replication ให้คาดหวังว่าจะมี lag บ้าง ถ้าการอ่านต้องสะท้อนการเขียนที่เพิ่งเกิดขึ้น ให้ส่งไปที่ primary หรือรอให้ replica ตามทัน
- Synchronous replication แลก latency กับความปลอดภัย ใช้เมื่อการสูญเสีย transaction ที่ตอบรับแล้วแม้เพียงรายการเดียวเป็นสิ่งที่รับไม่ได้ ไม่ใช่ใช้เป็นค่าเริ่มต้นแบบเหมารวม
- Streaming replication คัดลอกทั้ง cluster และต้องตรงเวอร์ชันกัน; logical replication คัดลอก table ที่เลือกและข้าม major version ได้ จึงเหมาะกับงานอัปเกรดเป็นพิเศษ
- Logical replication ต้องการ replica identity — โดยปกติคือ primary key — บนทุก table ที่เผยแพร่ เพื่อให้ subscriber จับคู่ row สำหรับการ update และ delete ได้
ข้อแลกเปลี่ยน
หัวข้อที่มีชื่อว่า “ข้อแลกเปลี่ยน”| ตัวเลือก | Benefit | Cost |
|---|---|---|
| Synchronous replication | ไม่สูญข้อมูลตอน failover เพราะ replica มี WAL ก่อน commit จะตอบรับ | ทุกการเขียนต้องรอ round-trip เครือข่ายไปยัง replica ทำให้ latency สูงขึ้น |
| Asynchronous replication | เขียนเร็ว เพราะ primary ไม่ต้องรอ replica | replica อาจล้าหลัง (replication lag) และมีโอกาสสูญข้อมูลเล็กน้อยตอน failover |
ข้อผิดพลาดที่พบบ่อย
หัวข้อที่มีชื่อว่า “ข้อผิดพลาดที่พบบ่อย”- สมมติว่า replica ตามทันเสมอ แล้วอ่านข้อมูลที่เพิ่งเขียนจาก replica ทันที — เจอปัญหา “read-your-writes” เพราะ asynchronous replication มี lag ให้ส่ง read ที่ต้องสดใหม่ไปที่ primary
- ไม่เคยทดสอบ failover จริง — ไม่มีใครรู้ว่าจะเกิดอะไรขึ้นจริงเมื่อ primary ล้มจนกว่าจะซ้อมจริง ควร drill การ promote replica เป็นประจำ
- ถือว่า replica ตัวเดียวคือแผน disaster recovery ที่สมบูรณ์ — replica ไม่ใช่ backup เพราะคัดลอกทั้งความผิดพลาดและการลบข้อมูลตามไปด้วย ยังต้องมี backup แยกต่างหาก
💡 ตัวอย่างจากของจริง
Zalando Patroni — เป็นเครื่องมือ open-source ที่ใช้กันแพร่หลายสำหรับจัดการ automated failover ของ PostgreSQL cluster ในโปรดักชัน
Neon และ Supabase — สร้างฟีเจอร์อย่าง database branching และ read replica ไว้บน primitive การทำ replication ของ PostgreSQL เอง