1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
// Copyright (C) 2024-2025 Greg Burd. Apache-2.0 OR MIT.
//! Regression: a read-only-in-practice transaction commit at `COMMIT_SYNC`
//! must NOT write a `TxnCommit` WAL frame and must NOT fdatasync.
//!
//! A transaction created with `begin_transaction(None)` (or any config that
//! does not set `with_read_only(true)`) is *write-capable*, but if it only
//! performed reads it logged no LN and has nothing to commit durably.
//!
//! Before the fix, `Transaction::commit_with_durability` gated the
//! `write_txn_end` (TxnCommit frame + fsync) on the static `read_only`
//! config flag instead of the dynamic "did this txn log anything" signal
//! (`Txn::has_logged_entries`). So every explicit read txn drove
//! `write_txn_end -> LogManager::log -> flush_sync -> fdatasync` at SYNC,
//! serialising 100%-cache-hit readers on the log-write latch + fsync
//! group-commit condvar (read-commit-contention audit, 2026-07; a
//! 100%-read workload was 3-4x slower at SYNC than NO_SYNC purely from this).
//!
//! JE: `Txn.commit()` writes a commit entry only for txns that have logged
//! entries. This test locks that in via the public `stat_fsync_count()`.
use noxu_db::{Durability, Environment, EnvironmentConfig};
fn scratch(tag: &str) -> std::path::PathBuf {
let p = std::env::temp_dir().join(format!(
"noxu-ro-commit-{}-{}",
tag,
std::process::id()
));
let _ = std::fs::remove_dir_all(&p);
std::fs::create_dir_all(&p).unwrap();
p
}
fn open(dir: &std::path::Path) -> (Environment, noxu_db::Database) {
let env = Environment::open(
EnvironmentConfig::new(dir.to_path_buf())
.with_transactional(true)
.with_allow_create(true)
// Env default durability is SYNC; the write-capable read txns
// commit at SYNC, which is exactly the path that used to fsync.
.with_durability(Durability::COMMIT_SYNC),
)
.unwrap();
let db = env
.open_database(
None,
"d",
&noxu_db::DatabaseConfig::new()
.with_allow_create(true)
.with_transactional(true),
)
.unwrap();
(env, db)
}
#[test]
fn readonly_txn_commit_does_not_fsync_at_sync() {
const N: u32 = 500;
let dir = scratch("noop");
let (env, db) = open(&dir);
// Seed one record so the reads actually hit data (cache hit).
db.put(b"k000000", b"v").unwrap();
// Baseline: N write txns at SYNC fsync ~per commit (this is the
// "the machinery works" control — proves the counter moves).
let before_writes = env.stat_fsync_count();
for i in 0..N {
let t = env.begin_transaction(None).unwrap();
db.put_in(&t, format!("w{i:06}").as_bytes(), b"v").unwrap();
t.commit().unwrap();
}
let write_fsyncs = env.stat_fsync_count() - before_writes;
// The regression check: N read-only explicit txns at SYNC. Each is
// begin_transaction(None) (write-capable, NOT read_only) + a get + a
// SYNC commit — the exact xbench ycsb_c shape. They must fdatasync
// essentially zero times because they logged nothing.
let before_reads = env.stat_fsync_count();
for _ in 0..N {
let t = env.begin_transaction(None).unwrap();
let _ = db.get_in(&t, b"k000000").unwrap();
t.commit().unwrap();
}
let read_fsyncs = env.stat_fsync_count() - before_reads;
db.close().unwrap();
env.close().unwrap();
let _ = std::fs::remove_dir_all(&dir);
// Control: write commits DID fsync (coalescing aside).
assert!(
write_fsyncs > N as u64 / 4,
"control: SYNC write commits should fdatasync ~per commit; got \
{write_fsyncs} for {N} write txns",
);
// The fix: read-only commits must NOT fsync per commit. Only incidental
// background/file-flip fsyncs are tolerated, and must be a tiny fraction
// of the write count.
assert!(
read_fsyncs < N as u64 / 20,
"REGRESSION: read-only txn commits fsynced at SYNC ({read_fsyncs} \
fsyncs for {N} read-only commits; write baseline was {write_fsyncs}). \
A read-only-in-practice commit must skip write_txn_end + flush_sync \
(gate on has_logged_entries, not the static read_only flag).",
);
}