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

Replica sets

replica set คือคำตอบของคำถามตรง ๆ ข้อหนึ่ง: จะเกิดอะไรขึ้นเมื่อ server ที่ถือข้อมูลของคุณหยุดตอบสนอง? เมื่อมี mongod ตัวเดียว คำตอบคือแอปพลิเคชันของคุณจะล่มจนกว่าจะมีคนแก้เครื่องนั้น เมื่อมี replica set คำตอบคือสำเนาอีกชุดจะเข้ามาทำหน้าที่แทนภายในไม่กี่วินาที และผู้ใช้ส่วนใหญ่แทบไม่รู้สึกอะไรเลย

replica set คือกลุ่มของ mongod instance ที่ถือข้อมูลชุดเดียวกันทั้งหมด — มี primary หนึ่งตัวรับทุก write และ secondaries อีกหนึ่งตัวขึ้นไปที่คอยไล่สำเนาของตัวเองให้ตามทัน

มีแต่ primary เท่านั้นที่รับ write ทุก write ที่ primary ทำจะถูกบันทึกลง capped collection พิเศษชื่อ oplog — operations log ทุก secondary อ่าน oplog ของ primary อย่างต่อเนื่องและเล่นซ้ำ operations เหล่านั้นกับข้อมูลของตัวเอง เพื่อให้สำเนาบรรจบกัน นี่คือเหตุผลที่ secondaries เป็นแบบ eventually consistent: มี lag เล็กน้อยระหว่างที่ write ลงบน primary กับตอนที่ write เดียวกันไปเล่นซ้ำครบทุกที่

flowchart TB
  Client["Application writes"] --> P["Primary"]
  P -- "records every write" --> OL["Oplog (capped collection)"]
  OL -- "tail and replay" --> S1["Secondary 1"]
  OL -- "tail and replay" --> S2["Secondary 2"]
  P -. "heartbeats every 2s" .- S1
  P -. "heartbeats every 2s" .- S2
primary รับทุก write แล้วบันทึกลง oplog ส่วน secondaries คอยอ่าน oplog ต่อท้ายและเล่นซ้ำเพื่อให้ซิงค์กัน

สมาชิกยังแลกเปลี่ยนข้อความ heartbeat เล็ก ๆ กันสองสามครั้งต่อนาที heartbeat เหล่านี้คือวิธีที่ set สังเกตเห็นปัญหา: ถ้า secondaries ไม่ได้ยินเสียงจาก primary ก็จะสรุปว่า primary ล้มแล้ว และเริ่ม election

เมื่อ primary หายไป สมาชิกที่เหลือจะลงคะแนนเสียง secondary ที่มีสิทธิ์จะเสนอชื่อตัวเอง คนอื่นลงคะแนน และผู้สมัครที่ได้คะแนนเสียงข้างมากจะกลายเป็น primary ใหม่ นี่คือหัวใจของ high availability ทั้งหมดเกิดขึ้นอัตโนมัติ โดยปกติภายในประมาณสิบถึงสิบสองวินาที โดยไม่มีใครต้องถูกตามตัวตอนตีสาม

flowchart TB
  P["Primary fails — heartbeats stop"] --> Detect["Secondaries notice the silence"]
  Detect --> Vote["Eligible members call an election"]
  Vote --> Win["Candidate with a majority of votes wins"]
  Win --> NewP["A secondary becomes the new primary"]
  NewP --> Resume["Writes resume on the new primary"]
election เลื่อนตำแหน่ง secondary ขึ้นเป็น primary เพื่อให้ write ดำเนินต่อได้โดยไม่ต้องแทรกแซงด้วยมือ

คำว่า majority ทำงานสำคัญตรงนี้ ผู้สมัครต้องเก็บคะแนนเสียงจากสมาชิกที่มีสิทธิ์ลงคะแนนมากกว่าครึ่งหนึ่ง ข้อกำหนดนั้นคือเหตุผลของคำแนะนำเรื่อง replica set ที่ถูกพูดซ้ำบ่อยที่สุด: ให้มีจำนวนสมาชิกที่ลงคะแนนเป็น เลขคี่ ด้วยสามเสียง การล้มเหลวเพียงครั้งเดียวก็ยังเหลือเสียงข้างมากที่ชัดเจนคือสองเสียง; ด้วยสองเสียง การล้มเหลวเพียงครั้งเดียวจะทำให้เสมอกัน และไม่มีใครชนะได้

driver ไม่ได้ต่อกับ server ตัวใดตัวหนึ่งตายตัว แต่ต่อกับ set คุณระบุสมาชิกสองสามตัวเป็น seeds และตั้งชื่อ set ด้วย replicaSet= จากนั้น driver ค้นพบ topology ทั้งหมดเอง หา primary ปัจจุบันได้เอง และตาม primary ตัวใหม่โดยอัตโนมัติหลัง election

Terminal window
mongosh "mongodb://host1:27017,host2:27017,host3:27017/?replicaSet=rs0"

ค่า default การอ่านก็ไปที่ primary เหมือนกัน ผลคือได้ strongly consistent ถ้าคุณยอมรับความล้าสมัยเล็กน้อยได้ — เช่น สำหรับ analytics หรือ feed ที่ไม่จำเป็นต้องได้ write ล่าสุดที่สุด — คุณสามารถบอก driver ให้อ่านจาก secondaries ด้วย read preference เช่น secondaryPreferred เพื่อกระจายภาระการอ่านออกจาก primary

จาก mongosh ที่เชื่อมต่ออยู่กับสมาชิกตัวใดก็ได้ rs.status() รายงานสถานะสด ๆ ของทั้ง set: ใครเป็น primary, ใครเป็น secondary และสมาชิกแต่ละตัวตามอยู่ห่างแค่ไหน นี่คือคำสั่งแรกที่คุณรันเมื่อมีอะไรดูผิดปกติ

rs.status()

ผลลัพธ์แบบย่อแสดงสมาชิกแต่ละตัวพร้อมบทบาทในฟิลด์ stateStr:

{
"set": "rs0",
"members": [
{ "name": "host1:27017", "stateStr": "PRIMARY", "health": 1 },
{ "name": "host2:27017", "stateStr": "SECONDARY", "health": 1 },
{ "name": "host3:27017", "stateStr": "SECONDARY", "health": 1 }
],
"ok": 1
}

ใน Atlas: ทุก cluster เป็น replica set อยู่แล้ว — คุณไม่ต้องตั้งค่าด้วยมือ มุมมอง cluster แสดงว่า node ไหนเป็น primary และให้คุณดู replication lag บนกราฟได้ และ failover เป็นการทดสอบแบบคลิกเดียวที่คุณ trigger ได้เพื่อดู election เกิดขึ้น

  • ให้มีจำนวนสมาชิกที่ลงคะแนนเป็น เลขคี่ เพื่อให้การล้มเหลวครั้งเดียวเหลือเสียงข้างมากที่ชัดเจนเสมอ สามตัวคือขั้นต่ำที่นิยมใช้สำหรับ production
  • arbiter คือสมาชิกขนาดเบาที่ลงคะแนนแต่ไม่ถือข้อมูล ช่วยตัดสินกรณีคะแนนเสมอได้แบบประหยัด แต่ขึ้นเป็น primary ไม่ได้ และไม่ได้เพิ่มความทนทานของข้อมูล ดังนั้นให้เลือก secondary จริงที่ถือข้อมูลเมื่อคุณมีพื้นที่จัดเก็บพอ
  • secondaries มี lag ถ้าแอปพลิเคชันของคุณอ่าน write ของตัวเองทันทีหลังจากเขียน ให้อ่านจาก primary หรือใช้ read concern ที่รอให้ write ทำ replication เสร็จ ไม่เช่นนั้นคุณอาจยังไม่เห็นการเปลี่ยนแปลงของตัวเอง
  • replica set ป้องกัน node ที่ล้มเหลว ไม่ได้ป้องกัน query ที่ผิดหรือ collection ที่ถูกลบทิ้ง — สิ่งเหล่านั้นถูก replicate ตามไปด้วย replication คือ availability ไม่ใช่ backup
ตัวเลือกBenefitCost
Replica set (เทียบ mongod เดี่ยว)Automatic failover, ไม่มี downtime ยาวเมื่อ primary ล้มเหลวต้องรัน node เพิ่มอย่างน้อยสามตัว, write ยังคอขวดที่ primary เดียวเสมอ
อ่านจาก secondariesกระจายภาระ read ออกจาก primaryข้อมูลที่ได้อาจ stale เพราะ replication lag
อ่านจาก primary เท่านั้นConsistency สูงสุด อ่าน write ล่าสุดเสมอไม่ได้ประโยชน์จากการกระจายโหลด primary รับภาระเต็ม
  • คิดว่า write scale ได้ด้วยการเพิ่ม secondary — secondary ช่วยกระจายเฉพาะ read เท่านั้น ทุก write ยังคงไปที่ primary ตัวเดียวเสมอ ถ้าต้องการ scale write ต้องไปที่ sharding ไม่ใช่การเพิ่ม secondary
  • วางสมาชิกทุกตัวไว้ใน availability zone หรือ data center เดียวกัน — replica set ป้องกัน node ล้มเหลว แต่ถ้าทุก node อยู่ที่เดียวกัน เมื่อ AZ ทั้งก้อนล่ม set ทั้งชุดก็ล่มไปด้วย ต้องกระจายสมาชิกข้าม AZ หรือ data center จริง ๆ
  • คิดว่า write ปลอดภัยแล้วทันทีที่ primary รับ — ตั้งแต่ MongoDB 5.0 (2021) เป็นต้นมา write concern เริ่มต้นคือ w: "majority" คือรอให้สมาชิกส่วนใหญ่ที่มีสิทธิ์ลงคะแนน (voting members) ยืนยันก่อน ไม่ใช่ยืนยันแค่ที่ primary เท่านั้น ความผิดพลาดคือการเข้าใจว่า write durable แล้วตั้งแต่ primary รับ ทั้งที่ค่า default จะรอ majority ให้อยู่แล้ว ถ้าไปตั้ง w: 1 เอง write ที่ยังไม่ replicate อาจหายไปพร้อม election ได้ ข้อมูลสำคัญจึงควรคง { w: "majority" } ไว้

💡 ตัวอย่างจากของจริง

Uber — ในช่วงที่ใช้ MongoDB สำหรับบาง service พึ่ง replica set กับ automatic failover เพื่อให้ trip data ยังเขียนต่อได้แม้ node ใดตัวหนึ่งล้มเหลวกลางดึกโดยไม่ต้องปลุกใครมาแก้

MongoDB Atlas — ทุก cluster เป็น replica set อย่างน้อยสาม node กระจายข้าม availability zone โดยอัตโนมัติ และมีปุ่มทดสอบ failover ให้ลองกดดูผลจริงได้โดยไม่ต้องรอเหตุการณ์จริงเกิดขึ้นก่อน

secondaries ทำให้ข้อมูลตัวเองซิงค์กับ primary ได้อย่างไร?
จะเกิดอะไรขึ้นเมื่อ primary ล้มเหลว?
ทำไมจึงแนะนำให้มีจำนวนสมาชิกที่ลงคะแนนเป็นเลขคี่?
การอ่านจาก secondary อาจคืนข้อมูลที่ล้าสมัยเล็กน้อยเพราะ: