Read and write concerns
replica set เก็บสำเนาข้อมูลของคุณไว้หลายชุด: primary หนึ่งตัวที่รับการเขียนทั้งหมด และ secondary หนึ่งตัวหรือมากกว่าที่คัดลอกการเขียนเหล่านั้นมา ความซ้ำซ้อนนี้คือสิ่งที่ทำให้ปุ่ม durability และ visibility มีความหมาย
เมื่อคุณเขียน คุณตัดสินใจได้ว่าต้องมีสำเนากี่ชุดยืนยันการเขียน ก่อนจะถือว่าเขียนเสร็จ — นี่คือปุ่ม write concern เมื่อคุณอ่าน คุณตัดสินใจได้ว่าข้อมูลที่คุณได้กลับมาต้อง commit แล้วและสดแค่ไหน — นี่คือปุ่ม read concern
ทั้งสองปุ่มนี้ช่วยให้คุณเลื่อนระหว่างเร็ว-แต่-อ่อนกับช้า-แต่-แข็งแกร่งได้ในทุก operation
write concern: durable แค่ไหน ยืนยันแค่ไหน
หัวข้อที่มีชื่อว่า “write concern: durable แค่ไหน ยืนยันแค่ไหน”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 } })await db.collection("members").insertOne( { name: "Grace", fines: 0 }, { writeConcern: { w: "majority", j: true, wtimeout: 5000 } });from pymongo import WriteConcern
db.members.with_options( write_concern=WriteConcern(w="majority", j=True, wtimeout=5000)).insert_one({"name": "Grace", "fines": 0})wc := writeconcern.Majority()coll := client.Database("library").Collection( "members", options.Collection().SetWriteConcern(wc),)_, err := coll.InsertOne(ctx, bson.M{"name": "Grace", "fines": 0})if err != nil { return err}let wc = WriteConcern::builder() .w(Acknowledgment::Majority) .journal(true) .build();let coll = client .database("library") .collection::<Document>("members") .clone_with_options(CollectionOptions::builder().write_concern(wc).build());coll.insert_one(doc! { "name": "Grace", "fines": 0 }).await?;การตอบรับจะกลับมาก็ต่อเมื่อส่วนใหญ่ได้จัดเก็บการเขียนแล้วเท่านั้น:
{ "acknowledged": true, "insertedId": "65f0b3d4e4b0a1c2e4b0a1e7" }read concern และ read preference: สดแค่ไหน จากที่ไหน
หัวข้อที่มีชื่อว่า “read concern และ read preference: สดแค่ไหน จากที่ไหน”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")const docs = await db.collection("members") .find({ fines: 0 }, { readConcern: { level: "majority" }, readPreference: "primary" }) .toArray();from pymongo import ReadPreferencefrom pymongo.read_concern import ReadConcern
docs = list( db.members.with_options( read_concern=ReadConcern("majority"), read_preference=ReadPreference.PRIMARY, ).find({"fines": 0}))coll := client.Database("library").Collection( "members", options.Collection(). SetReadConcern(readconcern.Majority()). SetReadPreference(readpref.Primary()),)cur, err := coll.Find(ctx, bson.M{"fines": 0})if err != nil { return err}defer cur.Close(ctx)let coll = client .database("library") .collection::<Document>("members") .clone_with_options( CollectionOptions::builder() .read_concern(ReadConcern::majority()) .selection_criteria(SelectionCriteria::ReadPreference(ReadPreference::Primary)) .build(), );let mut cursor = coll.find(doc! { "fines": 0 }).await?;การแลกเปลี่ยน อธิบายให้เห็นภาพ
หัวข้อที่มีชื่อว่า “การแลกเปลี่ยน อธิบายให้เห็นภาพ”ทุกก้าวไปสู่การรับประกันที่แข็งแกร่งกว่ามีต้นทุนเป็น 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"] ใน 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 ที่ตรงกับระดับความสดที่แอปของคุณต้องการจริง ๆ
ข้อแลกเปลี่ยน
หัวข้อที่มีชื่อว่า “ข้อแลกเปลี่ยน”| ตัวเลือก | Benefit | Cost |
|---|---|---|
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แล้วคิดว่าปลอดภัยเท่ากับมี journal —majorityบอกแค่ว่าสมาชิกกี่ตัวยืนยัน ไม่ได้แปลว่าแต่ละตัว 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 แล้วอนุมัติธุรกรรมผิดพลาด