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

Read and write concerns

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

output มาจากสองที่ ทั้งคู่บนเครื่อง Mac นี้ด้วย mongo 8.3.11, mongosh 2.11.1 และ Node driver 7.7.0: fence ที่มี path comment รันบน replica set โหนดเดียวของคอร์ส; fence ที่ระบุว่า 3-node รันบน replica set สามคอนเทนเนอร์ชั่วคราวบน Docker network (rs-1, rs-2, rs-3 สตาร์ทด้วย --replSet rsb3) ซึ่งลบทิ้งหลังใช้งาน แท็บ Python/Go/Rust สะท้อนการเรียกที่ตรวจแล้วใน shell/Node และไม่ได้ถูกรันโดยตัวตรวจอัตโนมัติของคอร์ส

write concern มีสามส่วน w บอกว่ามีสมาชิกกี่ตัวต้องยืนยัน: w: 1 คือเฉพาะ primary, w: "majority" คือเสียงข้างมากของสมาชิกที่มีสิทธิ์โหวตมีการเขียนนั้นแล้ว j เมื่อเป็น true ต้องให้การเขียนอยู่ใน journal บนดิสก์ของสมาชิกที่นับ wtimeout (option ของ driver คือ wtimeoutMS) จำกัดเวลาที่ server รอการยืนยันเหล่านั้น

mongosh/concern-seed.js
db.members.insertMany([
{ name: "Ada", fines: 0 },
{ name: "Linus", fines: 2 },
]);
assert.strictEqual(db.members.countDocuments(), 2);

insert สมาชิกและกำหนดให้ต้องได้ majority ที่ journal แล้ว ภายในห้าวินาที:

mongosh/concern-write.js
const res = db.members.insertOne(
{ name: "Grace", fines: 0 },
{ writeConcern: { w: "majority", j: true, wtimeout: 5000 } }
);
console.log(res);
assert.ok(res.acknowledged);
{
acknowledged: true,
insertedId: ObjectId('6abccc3967d5308401ddf857')
}
{
acknowledged: true,
insertedId: new ObjectId('6abccc39a50eaebf0946af88')
}

ตั้งแต่ MongoDB 5.0 write concern เริ่มต้นแบบแฝงคือ w: "majority" server รายงานค่านี้ และรายงานว่า majority write รอ journal บนสมาชิกที่นับอยู่แล้ว:

mongosh/concern-defaults.js
const d = db.adminCommand({ getDefaultRWConcern: 1 });
console.log(d.defaultWriteConcern, d.defaultReadConcern);
const conf = rs.conf();
console.log({ writeConcernMajorityJournalDefault: conf.writeConcernMajorityJournalDefault });
assert.strictEqual(d.defaultWriteConcern.w, "majority");
assert.strictEqual(conf.writeConcernMajorityJournalDefault, true);
{ w: 'majority', wtimeout: 0 } { level: 'local' }
{ writeConcernMajorityJournalDefault: true }

manual ก็ยืนยันเช่นนั้น (Write Concern, ตรวจเมื่อ 2026-09-30): ค่าเริ่มต้นแบบแฝงคือ w: majority ซึ่งรอ journal บนดิสก์ตาม writeConcernMajorityJournalDefault — มีกรณียกเว้นหนึ่งอย่าง: ถ้าสมาชิกที่เก็บข้อมูลและมีสิทธิ์โหวตไม่มากกว่า voting majority (เซตที่มี arbiter) ค่าเริ่มต้นจะถอยไปเป็น { w: 1 } ดังนั้น insertOne เปล่า ๆ ก็เป็น majority write อยู่แล้ว และ j: true เพิ่มบน w: "majority" ไม่ได้เพิ่มอะไรตราบที่ writeConcernMajorityJournalDefault เป็น true (ค่าเริ่มต้น) มันสำคัญกับ w: 1 ซึ่ง j เป็นตัวตัดสินว่า primary จะรอ flush journal ของตัวเองหรือไม่

ขอสมาชิกที่เก็บข้อมูลสองตัวบนเซตโหนดเดียว server ปฏิเสธทันที — แต่สังเกตว่าการเขียนยังถูกนำไปใช้แล้ว:

app/concern-unsatisfiable.mjs
const members = db.collection("members");
let err = null;
try { await members.insertOne({ name: "X" }, { writeConcern: { w: 2, wtimeoutMS: 1000 } }); }
catch (e) { err = e; }
console.log(err.name, err.code, err.codeName, "-", err.message);
console.log("document present anyway:", await members.countDocuments({ name: "X" }));
if (err.code !== 100) throw new Error("expected UnsatisfiableWriteConcern (100)");
MongoWriteConcernError 100 UnsatisfiableWriteConcern - Not enough data-bearing nodes
document present anyway: 1

error ของ write concern ไม่ใช่ rollback (manual: wtimeout “does not undo successful data modifications”): มันบอกว่า “ฉันยืนยันสำเนาได้ไม่พอ” ไม่ใช่ “ฉันไม่ได้เขียน” นี่คือเหตุผลที่ wtimeout เป็นผลลัพธ์ที่ไม่แน่นอน ไม่ใช่ความล้มเหลวที่สะอาด การรัน 3-node แสดงแบบ timeout — secondary หนึ่งตัวถูกแช่แข็งด้วย fsyncLock, เขียน w: 3 พร้อม wtimeout: 2000 (การเขียนอื่นในรอบเดียวกัน: w: 1 ได้ ack, w: "majority" ได้ ack ใน 3 ms เพราะ secondary อีกตัวยังนับอยู่):

// mongosh/concern-timeout.js @skip-verify needs the 3-node replica set (real output below)
const lib = db.getSiblingDB("lib");
lib.members.insertOne({ name: "Grace" }, { writeConcern: { w: 1 } });
lib.members.insertOne({ name: "Linus" }, { writeConcern: { w: "majority", wtimeout: 5000 } });
try { lib.members.insertOne({ name: "Edsger" }, { writeConcern: { w: 3, wtimeout: 2000 } }); }
catch (e) { console.log(e.name, e.code, e.message); console.log(e.errInfo); }
w:1 ack true
w:majority with one secondary frozen -> true 3 ms
MongoWriteConcernError | 64 | waiting for replication timed out
{ wtimeout: true, writeConcern: { w: 3, wtimeout: 2000, provenance: 'clientSupplied' } }
primary has 4 docs (the w:3 write is applied even though it errored)
frozen secondary sees 1 docs
healthy secondary sees 4

read concern ควบคุมว่าการอ่านคืนข้อมูลเวอร์ชันไหนได้ local (ค่าเริ่มต้นสำหรับการอ่านนอก transaction) คืนสิ่งที่สมาชิกที่ถูก query มีตอนนี้ แม้เป็นข้อมูลที่อาจถูก roll back ภายหลัง available บน replica set เหมือนกับ local majority คืนเฉพาะข้อมูลที่เสียงข้างมากยืนยันแล้ว snapshot อิงกับ majority และออกแบบไว้สำหรับ transaction และการอ่านหลายคำสั่ง linearizable สะท้อนทุกการเขียนที่เสียงข้างมากยืนยันและเสร็จก่อนการอ่านจะเริ่ม และใช้ได้กับ primary เท่านั้น ทั้งห้าระดับรันได้บนเซตโหนดเดียว:

mongosh/concern-read.js
const counts = {};
for (const level of ["local", "available", "majority", "snapshot"]) {
counts[level] = db.members.find({ fines: 0 }).readConcern(level).toArray().length;
}
counts.linearizable = db.members.find({ fines: 0 }).readConcern("linearizable").maxTimeMS(3000).toArray().length;
console.log(counts);
assert.strictEqual(counts.majority, 2);
{ local: 2, available: 2, majority: 2, snapshot: 2, linearizable: 2 }
{ local: 1, available: 1, majority: 1, snapshot: 1, linearizable: 1 }

แต่ละระดับต่างกันที่ สิ่งที่มันรอ และความต่างนั้นมองเห็นได้แม้บนโหนดเดียว ด้านล่างคือ 200 ครั้งของ: เขียน แล้วนับทันทีด้วย local และ majority — ครั้งหนึ่งกับการเขียนแบบ w: 1 อีกครั้งกับ w: "majority":

app/concern-own-writes.mjs
const rc = db.collection("rc");
await rc.deleteMany({});
async function behindCount(w) {
let behind = 0;
for (let i = 0; i < 200; i++) {
await rc.insertOne({ i, w }, { writeConcern: { w } });
const local = await rc.countDocuments({}, { readConcern: { level: "local" } });
const maj = await rc.countDocuments({}, { readConcern: { level: "majority" } });
if (maj < local) behind++;
}
return behind;
}
const withW1 = await behindCount(1);
await rc.deleteMany({});
const withMajority = await behindCount("majority");
console.log({ "w:1 then majority-read behind": withW1, "w:majority then majority-read behind": withMajority });
if (withMajority !== 0 || withW1 === 0) throw new Error("expected w:1 to lag and w:majority not to");
{
'w:1 then majority-read behind': 200,
'w:majority then majority-read behind': 0
}

การเขียนแบบ w: 1 ถูก acknowledge ก่อนที่จะถูก commit แบบ majority การอ่านแบบ majority ทันทีหลังจากนั้นจึงไม่เห็นการเขียนของคุณเอง ส่วนการเขียนแบบ w: "majority" ตามด้วยการอ่านแบบ majority เห็นเสมอ (บนโหนดเดียวช่องว่างนี้เป็นระดับมิลลิวินาที ข้ามเครือข่ายมันคือ replication lag)

linearizable ถูกปฏิเสธบนทุกตัวที่ไม่ใช่ primary การรัน 3-node เชื่อมตรงกับ secondary:

MongoServerError | 10107 NotWritablePrimary | cannot satisfy linearizable read concern on non-primary node

read preference เลือกว่า สมาชิกตัวไหน ตอบ primary (ค่าเริ่มต้น) อ่านข้อมูลสดที่สุดเสมอ; secondary/secondaryPreferred กระจายโหลดโดยแลกกับข้อมูลที่ล้าหลัง การรัน 3-node ของ Node driver ทำ explain 20 ครั้งด้วย secondaryPreferred — primary คือ rs-1:

// app/concern-readpref.mjs @skip-verify needs the 3-node replica set (real output below)
const client = new MongoClient("mongodb://rs-1:27017,rs-2:27017,rs-3:27017/?replicaSet=rsb3");
await client.connect();
const members = client.db("lib").collection("members", { readPreference: "secondaryPreferred" });
const served = new Set();
for (let i = 0; i < 20; i++) served.add((await members.find({}).explain()).serverInfo.host);
console.log("reads served by:", [...served].sort());
reads served by: [ 'rs-2', 'rs-3' ]

และสิ่งที่ secondary แสดงได้: เมื่อ rs-2 ถูกแช่แข็ง (db.fsyncLock()) ขณะที่ primary เขียนสี่ document การอ่านตรงบน rs-2 คืน 1 document ส่วน rs-3 ที่ปกติคืน 4 (output ข้างบน) read concern กับ read preference เป็นอิสระต่อกัน: majority บน secondary ที่ล้าหลังปลอดภัยจาก rollback แต่ก็ยังล้าหลังอยู่

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 รอจนเสียงข้างมากของสมาชิกเก็บการเขียนแล้วจึงได้รับ acknowledgement

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

  • w: "majority" เป็นค่าเริ่มต้น; ขอ w: 1 เฉพาะข้อมูลที่ยอมเสียได้ และรู้ว่ามันหายได้ (พิสูจน์ข้างล่าง)
  • wtimeout จำกัด การรอ ไม่ใช่การเขียน: insert w: 3 ที่ timeout ถูกนำไปใช้บน primary แล้ว
  • การอ่านจาก secondary อาจได้ข้อมูลล้าหลัง เพราะ secondary ตามหลัง primary — ตัวที่ถูกแช่แข็งคืน 1 จาก 4 document
  • linearizable ต้องใช้ primary และควรมี maxTimeMS ไม่เช่นนั้นอาจบล็อกเมื่อเข้าถึง majority ไม่ได้

ผู้ช่วย AI ที่ “ปรับแต่ง” การเขียน order ลด concern ลงและเขียนคอมเมนต์อย่างมั่นใจ:

🐛 หา bug ในโค้ดนี้

// AI comment: "w: 1 = acknowledged by the primary, which is durable; faster than majority"
await orders.insertOne(order, { writeConcern: { w: 1 } });
เฉลย

acknowledgement จาก primary ไม่ใช่ความคงทนข้าม failover หลักฐานบน 3-node: primary rs-2 ถูกตัดขาดจากเครือข่าย, insert แบบ w: 1 ยังได้ acknowledge, อีกสองสมาชิกเลือก rs-1 ขึ้นมา และหลังเชื่อม rs-2 กลับ document ที่ได้ acknowledge ไปแล้วถูก rollback และหายไป (BSON ของมันถูกย้ายไป /data/db/rollback/) วิธีแก้คือค่าเริ่มต้น w: "majority" — ระหว่าง partition เดียวกัน มันไม่ acknowledge เท็จ แต่ timeout:

// mongosh/concern-lost-write.js @skip-verify needs the 3-node replica set and a network partition (real output below)
const l = db.getSiblingDB("lib");
l.lost2.insertOne({ _id: "only-on-old-primary" }, { writeConcern: { w: 1 } }); // acknowledged
try { l.lost2.insertOne({ _id: "maj" }, { writeConcern: { w: "majority", wtimeout: 3000 } }); }
catch (e) { console.log("w:majority ->", e.codeName); }
ack on isolated primary: true
w:majority -> WriteConcernTimeout
(after the partition heals, on the set)
rs-1:27017 PRIMARY | rs-2:27017 SECONDARY | rs-3:27017 SECONDARY
[ { _id: 'on-new-primary' } ]
/data/db/rollback/200fb679-8f77-415c-bafe-7bf7b4b846b3/removed.2026-09-30T08-43-30.1.bson

มีสองปุ่มที่เป็นอิสระต่อกัน Write concern บอกว่าต้องมีกี่สำเนามีการเขียนนั้นก่อนที่ คุณ จะถูกบอกว่า “เสร็จ” — มันเปลี่ยนสิ่งที่ผู้เขียนรู้ Read concern บอกว่าข้อมูลต้องนิ่งแค่ไหนก่อนที่ผู้อ่านจะเห็น — มันเปลี่ยนสิ่งที่ผู้อ่านเห็น Read preference แค่เลือก server การเขียนที่ถูกยืนยันโดยน้อยกว่า majority อาจหายไปตอน failover; การอ่านที่ต่ำกว่า majority เห็นมันได้ก่อนที่มันจะหาย

ตัวเลือกประโยชน์ต้นทุน
Write concern w: "majority" (ค่าเริ่มต้น)การเขียนที่ acknowledge แล้วรอด failoverต้องเสีย network round trip ไป secondary (3 ms เมื่อมีสมาชิกถูกแช่แข็งใน lab); บล็อกหรือ timeout ถ้าเข้าถึง majority ไม่ได้
Write concern w: 1เร็วที่สุด acknowledge ทันทีที่ primary รับพิสูจน์ข้างบน: การเขียนที่ acknowledge แล้วถูก rollback หลัง partition
Read concern majority / snapshotไม่อ่านข้อมูลที่อาจถูก rollbackตามหลัง local (และการเขียน w: 1 ของคุณเอง)
Read concern localเร็วที่สุด ข้อมูลสดที่สุดบนสมาชิกที่ถูก queryอาจอ่านข้อมูลที่ภายหลังถูก rollback
Read concern linearizableสะท้อนทุกการเขียน majority ที่เสร็จแล้วใช้ได้เฉพาะ primary; ช้า; ต้องมี maxTimeMS
  • “w: 'majority' ต้องมี j: true ถึงจะปลอดภัย” — เมื่อ writeConcernMajorityJournalDefault: true (ค่าเริ่มต้น แสดงข้างบน) majority write รอ journal บนสมาชิกที่นับอยู่แล้ว
  • “write concern เริ่มต้นคือ w: 1” — ตั้งแต่ 5.0 คือ majority; getDefaultRWConcern แสดงให้เห็น
  • “error wtimeout แปลว่าการเขียนล้มเหลว” — การเขียนถูกนำไปใช้บน primary แล้ว (primary has 4 docs); ขาดแค่การยืนยัน
  • “การเขียนที่ acknowledge แล้วปลอดภัย” — เฉพาะเมื่อ concern ครอบคลุม majority; acknowledgement ของ w: 1 ถูก rollback ไป
  • “read concern กับ read preference คือปุ่มเดียวกัน” — อันหนึ่งเลือกว่าข้อมูลนิ่งแค่ไหน อีกอันเลือกสมาชิก; majority บน secondary ที่ล้าหลังปลอดภัยแต่ก็ยังล้าหลัง

แท็บ Python/Go/Rust สะท้อนพฤติกรรมที่ตรวจแล้วใน shell/Node; ไม่ได้ถูกรันโดยตัวตรวจอัตโนมัติของคอร์ส

write concern เริ่มต้นแบบแฝงบน MongoDB 8 (เมื่อ client ไม่ได้ตั้ง) คืออะไร?
ต้องใส่ j: true เพิ่มบน w: "majority" เพื่อให้การเขียนถึง journal หรือไม่?
insert แบบ w: 3 พร้อม wtimeout: 2000 คืน WriteConcernTimeout (64) document อยู่บน primary หรือไม่?
คุณเขียนด้วย w: 1 แล้วอ่านทันทีด้วย readConcern "majority" การทดลองจริง 200 ครั้งแสดงอะไร?
primary ถูกตัดขาดจากสมาชิกอื่น รับ insert แบบ w: 1 (ได้ acknowledge) และที่เหลือเลือก primary ใหม่ เกิดอะไรกับ document นั้นเมื่อ primary เก่ากลับเข้าร่วม?
read concern ไหนถูกปฏิเสธบน secondary?