Files
MultiforaDB/tests/e2e/results/phase7.txt
Aleksey Shakhmatov c8d547fef5 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.
2026-08-03 00:09:11 +03:00

59 lines
4.1 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Phase 7 gate — mongo-lite vs MongoDB 8.3.7, 1g dataset / ~16k docs
# Ratio < 1.0 = mongo-lite faster. Reproduce: bash tests/e2e/compare-run.sh 1g 16k
# All five roadmap items plus the three durability/correctness fixes below.
# Compare: tests/e2e/results/phase6.txt (same code, broken commit path) and
# tests/e2e/results/phase1.txt (pre-tree baseline).
benchmark mongo-lite mongodb ratio
insertOne (sequential) ×200 0.19 ms 4.9 ms 0.0x
bulk insert throughput 753.5 MB/s 704.6 MB/s 1.1x
docs loaded 65,536 65,536 1.0x
createIndex({k: 1}) 75.7 ms 75.5 ms 1.0x
countDocuments({}) 3.4 ms 11.2 ms 0.3x
findOne({_id: <ObjectId>}) 0.61 ms 0.63 ms 1.0x
findOne({k: 500}) (indexed) 0.57 ms 2.3 ms 0.2x
find({p: {$gte,$lt}}).count() (scan) 13.5 ms 14.6 ms 0.9x
find({}).sort({_id:-1}).limit(20) 2.3 ms 2.5 ms 0.9x
find({}, {proj}).limit(1000) 3.7 ms 4.4 ms 0.8x
aggregate $group by k 8.3 ms 15.1 ms 0.5x
updateOne({_id}) ×50 0.16 ms 0.20 ms 0.8x
updateMany({k: 7}, {$inc}) 2.0 ms 5.3 ms 0.4x
deleteOne({_id}) + insertOne 0.58 ms 5.0 ms 0.1x
node client RSS 153 MB 154 MB 1.0x
server RSS 546 MB 1424 MB
kill -9 reopen 0.8s 1.3s
db on disk 97MB 95MB
# No regression from the fixes. Against phase6 (which measured a commit path
# that mostly skipped its fsync): bulk 739 -> 753.5 MB/s, insertOne 0.20 ->
# 0.19 ms, updateMany 1.9 -> 2.0 ms, RSS 547 -> 546 MB, disk and reopen
# unchanged — all within run noise. The real fsync per commit does not show
# here because this benchmark is single-connection and writes through large
# insertMany batches, so one commit is amortized over the whole batch.
#
# What phase6 could not have measured, because the crash pair did not pass:
# - kill -9 durability: 13 runs over 1/2/8 connections, 1200 acknowledged
# inserts each, zero lost.
# - concurrent durable writes (sequential insertOne per client):
# 1 client 7.1k docs/s | 8 clients 15.0k | 32 clients 21.8k.
#
# Fixes in this commit (each reproduced before it was fixed):
# 1. Engine.commit compared log.end_pos with the last committed position to
# decide a writer was already covered. Under block framing an append
# leaves its bytes in the log's open block without moving end_pos, so
# after the first commit every later write command returned without
# sealing or syncing. A no-op deleteMany followed by insertMany(50) was
# acknowledged with the file still 16 bytes (header only) and lost
# everything on kill -9. Coverage is now by sequence number.
# 2. Compaction read new_end_pos before new_log.sync(), but the sync is
# what seals the open block and moves end_pos past it, so later appends
# overwrote the compacted file's last block. e2e6's phase 2 ended with
# 1000 documents in memory and 996 after a graceful restart.
# 3. cmd_find returned without a reply on a missing namespace, so find on
# an unknown collection reached the driver as a malformed response
# ("MongoServerError: n/a") instead of an empty cursor.
#
# Verification: unit suite in ReleaseFast/ReleaseSafe/Debug, the split fuzzer
# (src/fuzz_split.zig), all six e2e suites (e2e6 72/72), and the kill -9
# crash pair — none of which passed before these fixes.