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

Read and write concerns

replica set เก็บสำเนาข้อมูลของคุณไว้หลายชุด: primary หนึ่งตัวที่รับการเขียนทั้งหมด และ secondary หนึ่งตัวหรือมากกว่าที่คัดลอกการเขียนเหล่านั้นมา ความซ้ำซ้อนนี้คือสิ่งที่ทำให้ปุ่ม durability และ visibility มีความหมาย

เมื่อคุณเขียน คุณตัดสินใจได้ว่าต้องมีสำเนากี่ชุดยืนยันการเขียน ก่อนจะถือว่าเขียนเสร็จ — นี่คือปุ่ม write concern เมื่อคุณอ่าน คุณตัดสินใจได้ว่าข้อมูลที่คุณได้กลับมาต้อง commit แล้วและสดแค่ไหน — นี่คือปุ่ม read concern

ทั้งสองปุ่มนี้ช่วยให้คุณเลื่อนระหว่างเร็ว-แต่-อ่อนกับช้า-แต่-แข็งแกร่งได้ในทุก operation

write concern มีสามส่วน ค่า w บอกว่าสมาชิกกี่ตัวต้องตอบรับการเขียน: w: 1 หมายถึงเฉพาะ primary ส่วน w: "majority" หมายถึงสมาชิกส่วนใหญ่ของชุดเก็บข้อมูลนั้นแล้ว — ระดับที่การเขียนรอดพ้นจากการสูญเสียสมาชิกตัวเดียวใด ๆ ค่า j เมื่อเป็น true กำหนดให้การเขียนต้องถูก flush ลง on-disk journal ก่อนจะตอบรับ เพื่อให้รอดจากการ crash ไม่ใช่แค่ restart โปรเซส และ wtimeout จำกัดว่า driver จะรอการตอบรับเหล่านั้นนานแค่ไหนก่อนจะยอมแพ้พร้อม error

ตรงนี้เรา insert สมาชิกคนหนึ่งแล้วบังคับให้สมาชิกส่วนใหญ่ของชุดยืนยัน พร้อม journal ภายในห้าวินาที:

db.members.insertOne(
{ name: "Grace", fines: 0 },
{ writeConcern: { w: "majority", j: true, wtimeout: 5000 } }
)

การตอบรับจะกลับมาก็ต่อเมื่อส่วนใหญ่ได้จัดเก็บการเขียนแล้วเท่านั้น:

{ "acknowledged": true, "insertedId": "65f0b3d4e4b0a1c2e4b0a1e7" }

read concern ควบคุมว่าการอ่านอาจคืนข้อมูลเวอร์ชันใด ด้วย local คุณได้สิ่งที่สมาชิกที่คุณ query มีอยู่ตอนนี้ แม้ข้อมูลนั้นอาจ roll back ทีหลังก็ตาม ด้วย majority คุณเห็นเฉพาะข้อมูลที่สมาชิกส่วนใหญ่ของชุดได้จัดเก็บแล้ว ดังนั้นสิ่งที่คุณอ่านจะไม่ถูกยกเลิก ด้วย linearizable การอ่านบน primary จะสะท้อนทุกการเขียนที่ตอบรับไปแล้วก่อนการอ่านนี้จะเริ่ม — ตัวเลือกที่แข็งแกร่งที่สุดและช้าที่สุด การตั้งค่าแยกต่างหากอย่าง read preference เลือกว่า สมาชิกตัวไหน เป็นผู้ตอบ: primary ให้ข้อมูลที่สดที่สุดเสมอ ส่วน secondary กระจายภาระการอ่านไปยังสำเนาต่าง ๆ โดยแลกกับการที่อาจตามหลังอยู่เล็กน้อย

ตรงนี้เราอ่านด้วย majority read concern จาก primary โดยขอข้อมูลที่เรารู้ว่าไม่สามารถถูก roll back ได้:

db.members
.find({ fines: 0 })
.readConcern("majority")
.readPref("primary")

ทุกก้าวไปสู่การรับประกันที่แข็งแกร่งกว่ามีต้นทุนเป็น latency การเขียน w: 1 คืนค่าทันทีที่ primary รับไว้ ส่วนการเขียน w: "majority" รอให้ข้อมูลเดินทางไปและถูกจัดเก็บโดยสมาชิกตัวอื่นก่อน ผังด้านล่างแสดงว่าเวลาที่เพิ่มขึ้นนั้นไปอยู่ที่ไหน:

flowchart TD
  C["Client sends a write"] --> P["Primary stores it"]
  P --> R1["Secondary one replicates"]
  P --> R2["Secondary two replicates"]
  R1 --> M{"Majority has stored it?"}
  R2 --> M
  P --> M
  M -->|"Yes"| ACK["Acknowledge to client — write is durable"]
ด้วย majority write concern client จะรอจนสมาชิกส่วนใหญ่เก็บข้อมูลเรียบร้อยก่อน ถึงจะได้รับการตอบรับ

ใน Compass: ไดอะล็อกการตั้งค่า connection ให้คุณตั้ง read preference เริ่มต้นสำหรับทั้ง session ดังนั้นการอ่านที่คุณรันในแท็บ Documents สามารถถูกส่งไปยัง secondary ได้โดยไม่ต้องเปลี่ยนแต่ละ query

  • w: "majority" คือจุดที่เหมาะที่สุดสำหรับ durability ที่รอดจาก failover ส่วน w: 1 เร็วกว่าแต่การเขียนที่ยังไม่ได้ replicate อาจหายได้ถ้า primary ล้มทันทีหลังตอบรับ
  • wtimeout จำกัดแค่ การรอ เท่านั้น ถ้า timeout เกิดขึ้น การเขียนอาจลงบน primary ไปแล้วก็ได้ เพียงแต่ยังไม่มีสมาชิกยืนยันครบภายในเวลา ดังนั้นให้ถือว่า timeout เป็นความไม่แน่นอน ไม่ใช่ความล้มเหลวที่สะอาด
  • การอ่านจาก secondary ด้วย read preference secondary สามารถคืนข้อมูลที่เก่าไปเล็กน้อยได้ เพราะ secondary ตามหลัง primary ให้จับคู่กับ read concern ที่ตรงกับระดับความสดที่แอปของคุณต้องการจริง ๆ
ตัวเลือกBenefitCost
write concern w: "majority"การเขียนรอดจาก failover เพราะสมาชิกส่วนใหญ่ยืนยันแล้วlatency สูงขึ้นเพราะต้องรอ network round trip ไปยัง secondary
write concern w: 1เร็วที่สุด ตอบกลับทันทีที่ primary รับถ้า primary ล้มก่อน replicate ทัน การเขียนนั้นหายได้
read concern majorityไม่มี dirty read ไม่มีทางเห็นข้อมูลที่ถูก roll back ภายหลังข้อมูลอาจตามหลังอยู่เล็กน้อยเพราะต้องรอให้ replicate ไปถึงส่วนใหญ่ก่อน
read concern localเร็วที่สุด สดที่สุดจาก node ที่ queryอาจอ่านข้อมูลที่ยังไม่ถูก commit อย่างถาวร แล้วถูก roll back ภายหลังจาก failover
  • ใช้ w: "majority" กับ j: false แล้วคิดว่าปลอดภัยเท่ากับมี journalmajority บอกแค่ว่าสมาชิกกี่ตัวยืนยัน ไม่ได้แปลว่าแต่ละตัว flush ลง disk แล้ว ถ้าต้องการรอดจาก crash จริง ๆ ต้องตั้ง j: true ควบคู่ไปด้วยเสมอ
  • สลับ read concern กับ read preference ว่าเป็นเรื่องเดียวกัน — read concern ควบคุมว่าข้อมูล “สดและ commit แล้ว” แค่ไหน ส่วน read preference ควบคุมว่า “สมาชิกตัวไหน” เป็นผู้ตอบ การตั้งแค่อย่างใดอย่างหนึ่งไม่ได้แก้ปัญหาของอีกอย่าง เช่น อ่านจาก secondary ด้วย majority read concern ก็ยังได้ข้อมูลที่ตามหลังอยู่ แม้จะไม่ dirty
  • ตั้ง write concern สูงสุดไว้เป็นค่าเริ่มต้นทุก operation โดยไม่แยกตาม use case — เขียน log หรือข้อมูลที่ regenerate ได้ง่ายด้วย w: "majority", j: true เหมือนกับข้อมูลทางการเงิน ทำให้ throughput ตกโดยไม่จำเป็น ทั้งที่ operation เหล่านั้นทนต่อการสูญเสียข้อมูลเล็กน้อยได้

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

ธนาคาร/ระบบชำระเงิน — ใช้ w: "majority", j: true กับทุกธุรกรรมทางการเงิน เพราะยอมรับ latency ที่เพิ่มขึ้นได้ แต่ยอมรับข้อมูลหายไม่ได้เลย

Coinbase — ใช้ read concern majority เมื่ออ่านยอดคงเหลือเพื่อตัดสินใจอนุมัติธุรกรรม เพื่อไม่ให้อ่านค่าที่อาจถูก roll back หลัง failover แล้วอนุมัติธุรกรรมผิดพลาด

write concern เป็น w: "majority" รับประกันอะไร?
ตัวเลือก j ใน write concern กำหนดอะไร?
read concern ตัวใดคืนเฉพาะข้อมูลที่ส่วนใหญ่ได้จัดเก็บแล้ว?
read preference ควบคุมอะไร?