db/commands: acknowledged writes reach the disk again
Three defects, each of which made the database lose data that had already
been acknowledged, or answer a client with a malformed reply.
- Engine.commit decided a writer was "already covered" by comparing
log.end_pos with the position of the last completed commit. Under block
framing an append leaves its bytes in the log's open in-memory block and
does not move end_pos -- only sealing does. So once the first commit had
set committed_end = end_pos, every later write command found itself
covered and returned without sealing or syncing anything. A no-op
deleteMany followed by insertMany(50) was acknowledged with the file
still 16 bytes (its header) and lost all 50 documents on kill -9, which
is precisely what e2e2's crash pair does. Coverage is now decided by
sequence number, which counts records rather than bytes on disk.
- Compaction read the new log's end position before syncing it, but the
sync is what seals the open block, and the seal is what moves end_pos
past it. Appends after a compaction therefore started inside the
compacted file's last block and overwrote it, so those documents were
gone at the next replay: e2e6's phase 2 ended with 1000 documents in
memory and 996 after a graceful restart.
- cmd_find returned early on a missing namespace without putting anything
in the reply, so a find on an unknown collection arrived at the driver as
a response with no `ok` field ("MongoServerError: n/a") instead of an
empty cursor. The other commands' missing-namespace paths were fine.
Verified with the unit suite in ReleaseFast/ReleaseSafe/Debug, the split
fuzzer, all six e2e suites (e2e6 back to 72/72) and the kill -9 crash pair
-- none of which passed beforehand -- plus 13 kill -9 runs over 1/2/8
connections with 1200 acknowledged inserts each and nothing lost.
tests/e2e/results/phase7.txt records the benchmark with the fixes in place:
no regression against phase6 (bulk 739 -> 753 MB/s, updateMany 1.9 -> 2.0
ms, RSS 547 -> 546 MB), and concurrent durable writes now measurable at
7.1k/15.0k/21.8k docs/s over 1/8/32 connections.
This commit is contained in:
@@ -624,7 +624,14 @@ fn cmd_find(ctx: *Context, msg: *wire.Message, reply: *wire.Reply) !void {
|
||||
|
||||
// Sorting and emitting need the documents as trees; materialize the
|
||||
// matched page into the reply arena (the slab itself is never copied).
|
||||
const coll = ctx.engine.get_collection(db_name, coll_name) orelse return;
|
||||
const coll = ctx.engine.get_collection(db_name, coll_name) orelse {
|
||||
// A find on a namespace that does not exist is an empty cursor, not
|
||||
// an error -- and above all not a reply with no `ok` at all, which
|
||||
// is what returning here without one sends.
|
||||
const none: []const *const bson.Document = &.{};
|
||||
try emit_docs_tree(reply, db_name, coll_name, proj_pairs, none);
|
||||
return reply.put_ok();
|
||||
};
|
||||
// Lives in the reply arena; freed with it.
|
||||
var tree_docs: std.ArrayListUnmanaged(*const bson.Document) = .empty;
|
||||
const arena = reply.arena_alloc();
|
||||
|
||||
Reference in New Issue
Block a user