ข้ามไปยังเนื้อหา

Replication

server ตัวเดียวคือจุดล้มเหลวจุดเดียว ถ้าดิสก์พังหรือเครื่อง reboot ฐานข้อมูลของคุณก็หายไปจนกว่าจะมีคนมาแก้ไข Replication ขจัดความเสี่ยงนั้นด้วยการเก็บสำเนาข้อมูลหนึ่งชุดหรือมากกว่าไว้บนเครื่องอื่น โดยอัปเดตอย่างต่อเนื่องตามที่ต้นฉบับเปลี่ยนแปลง สำเนาเหล่านั้นยังช่วยให้คุณกระจาย traffic การอ่านไปยัง server หลายตัวได้

PostgreSQL มีให้สองรูปแบบ: streaming replication ที่คัดลอกทั้ง cluster แบบ byte-for-byte และ logical replication ที่คัดลอก table ที่เลือกทีละ row ทั้งสองแก้ปัญหาที่แตกต่างกัน บทเรียนนี้จึงครอบคลุมทั้งคู่

ก่อนที่การเปลี่ยนแปลงจะแตะไฟล์ของ 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
primary ส่ง WAL ไปยัง read replica

Replica เป็นแบบอ่านอย่างเดียว คือ apply การเปลี่ยนแปลงจาก primary และตอบ query แบบ SELECT ได้ แต่ปฏิเสธการเขียน จึงเหมาะอย่างยิ่งสำหรับการรับภาระงานรายงาน analytics หรือ workload ที่เน้นการอ่านใด ๆ ออกจาก primary

Streaming replication ถูกตั้งค่าบน primary ด้วยการตั้งค่าเพียงไม่กี่อย่างและบัญชีหนึ่งที่ replica ใช้เชื่อมต่อ ไฟล์ที่เกี่ยวข้องอยู่ใน data directory ของคุณ

# postgresql.conf on the primary
wal_level = replica
max_wal_senders = 10
wal_keep_size = 512MB
# pg_hba.conf on the primary: allow the replication account to connect
host replication replicator 10.0.0.0/24 scram-sha-256

คุณสร้าง replica ด้วยการทำ base backup ของ primary แล้วชี้ replica กลับไปที่ primary เพื่อ stream WAL ที่กำลังไหลอยู่ เครื่องมือที่คัดลอกสถานะเริ่มต้นคือ pg_basebackup

Terminal window
# Run on the new replica machine to clone the primary and start streaming
pg_basebackup \
--host=primary.internal \
--username=replicator \
--pgdata=/var/lib/postgresql/data \
--wal-method=stream \
--write-recovery-conf

แฟล็ก --write-recovery-conf บันทึกรายละเอียดการเชื่อมต่อไว้เพื่อให้ replica รู้ว่าต้องเริ่ม stream ทันทีที่บูตขึ้นมา จากนั้น replica จะไล่ตาม primary เองโดยอัตโนมัติ

ตัวเลือกใหญ่ใน streaming replication คือ เมื่อใด ที่ primary จะถือว่าการเขียนเสร็จสิ้น

โหมดprimary ยืนยันการ commit เมื่อ…ข้อแลกเปลี่ยน
AsynchronousWAL ถูกเขียนในเครื่องเร็ว แต่ replica อาจล้าหลังได้
Synchronousreplica อย่างน้อยหนึ่งตัวเก็บ WAL ไว้แล้วด้วยไม่สูญข้อมูลตอน failover แต่เขียนช้ากว่า

Asynchronous เป็นค่าเริ่มต้นและเป็นตัวเลือกที่ใช้กันทั่วไป การเขียนไม่เคยต้องรอเครือข่าย primary จึงยังเร็วอยู่ — แต่ replica อาจล้าหลังไปเล็กน้อย เรียกว่า replication lag query ที่ส่งไปยัง replica ที่ล้าหลังอาจยังไม่เห็น commit ล่าสุด

Synchronous replication ทำให้ primary รอให้ replica ตอบรับทุกการ commit เปิดใช้ด้วยการระบุชื่อ replica ที่ต้องยืนยัน

# postgresql.conf on the primary
synchronous_standby_names = 'replica1'

สิ่งนี้รับประกันว่า commit ที่ตอบรับแล้วจะรอดจากการสูญเสีย primary โดยแลกกับ write latency ที่สูงขึ้น

ถ้า primary ตาย replica หนึ่งตัวต้องถูก promote ให้เป็น primary ตัวใหม่เพื่อให้การเขียนกลับมาทำงานได้ การ promote บอก replica ให้หยุดตามและเริ่มรับการเขียน

Terminal window
# On the chosen replica, promote it to primary
pg_ctl promote --pgdata=/var/lib/postgresql/data

ในโปรดักชัน เรื่องนี้มักจัดการโดยเครื่องมืออัตโนมัติที่ตรวจจับความล้มเหลวและ promote standby ให้คุณ แต่การกระทำที่อยู่เบื้องหลังก็คือขั้นตอนเดียวเดิมนี้

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 tables
CREATE PUBLICATION analytics_pub FOR TABLE orders, customers;
-- On the destination database: subscribe to that publication
CREATE 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 เผยแพร่ table ที่เลือกไปยัง subscriber

Logical replication ก็ต้องการให้ยกระดับ wal_level เช่นกัน คราวนี้เป็น logical บนต้นทาง

# postgresql.conf on the source
wal_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 ได้
ตัวเลือกBenefitCost
Synchronous replicationไม่สูญข้อมูลตอน failover เพราะ replica มี WAL ก่อน commit จะตอบรับทุกการเขียนต้องรอ round-trip เครือข่ายไปยัง replica ทำให้ latency สูงขึ้น
Asynchronous replicationเขียนเร็ว เพราะ primary ไม่ต้องรอ replicareplica อาจล้าหลัง (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 เอง

streaming replica ทำอะไรกับ WAL ที่ได้รับจาก primary?
ข้อแลกเปลี่ยนหลักของ synchronous replication เมื่อเทียบกับ asynchronous คืออะไร?
คุณจะเลือก logical replication แทน streaming replication เมื่อใด?