Skip to content

Commit c72316f

Browse files
committed
test(bench): add clean timed-only subcompaction profile
The subcompaction_zstd3 bench rebuilds its inputs every iteration, so a flamegraph of it is dominated by setup (memtable insert, flush, filter build), masking where the timed major_compact actually spends time. Add subcompaction_clean: build the pre-compact on-disk state ONCE into a master dir, then each iteration clones it to a fresh dir and times only major_compact. perf-recording this isolates the compaction (the clone + open are distinct symbols). The clean profile attributes the cost to structured-zstd's level-3 block encoder on the compression pool; the range-split merge / orchestration is negligible.
1 parent 3be8a87 commit c72316f

1 file changed

Lines changed: 94 additions & 1 deletion

File tree

tools/compare-rocksdb/benches/compare.rs

Lines changed: 94 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1538,6 +1538,98 @@ fn subcompaction_variant(c: &mut Criterion, group_name: &str, level: i32) {
15381538
group.finish();
15391539
}
15401540

1541+
/// Recursively copies `src` into `dst` (created if missing). Used by the
1542+
/// clean-profile subcompaction bench to clone the pre-compact on-disk state.
1543+
fn copy_dir(src: &std::path::Path, dst: &std::path::Path) -> std::io::Result<()> {
1544+
std::fs::create_dir_all(dst)?;
1545+
for entry in std::fs::read_dir(src)? {
1546+
let entry = entry?;
1547+
let to = dst.join(entry.file_name());
1548+
if entry.file_type()?.is_dir() {
1549+
copy_dir(&entry.path(), &to)?;
1550+
} else {
1551+
std::fs::copy(entry.path(), to)?;
1552+
}
1553+
}
1554+
Ok(())
1555+
}
1556+
1557+
/// Builds our pre-compact subcompaction state (gen-0 compacted to the bottom,
1558+
/// gen-1 overwrite flushed into `COMPACTION_FLUSHES` L0 tables) into `dir`, then
1559+
/// drops the tree so the state is resident on disk for cloning.
1560+
fn build_subcompaction_master(dir: &std::path::Path, level: i32, inputs: &SubcompactionInputs) {
1561+
let config = Config::new(
1562+
dir,
1563+
SequenceNumberCounter::default(),
1564+
SequenceNumberCounter::default(),
1565+
)
1566+
.data_block_compression_policy(CompressionPolicy::all(CompressionType::Zstd(level)))
1567+
.compaction_threads(SUBCOMPACTION_THREADS)
1568+
.subcompaction_min_bytes(0);
1569+
let tree = apply_preset(config, active_preset())
1570+
.open()
1571+
.expect("master: open");
1572+
let total = inputs.keys.len() as u64;
1573+
for ((key, value), seqno) in inputs.keys.iter().zip(inputs.values.iter()).zip(0u64..) {
1574+
tree.insert(key, value, seqno);
1575+
}
1576+
tree.flush_active_memtable(0).expect("master: flush");
1577+
tree.major_compact(SUBCOMPACTION_BOTTOM_TARGET, 0)
1578+
.expect("master: bottom compact");
1579+
let flush_points: Vec<u64> = (1..COMPACTION_FLUSHES)
1580+
.map(|b| (b * total) / COMPACTION_FLUSHES)
1581+
.collect();
1582+
let mut written = 0u64;
1583+
for ((key, value), seqno) in inputs.keys.iter().zip(inputs.values.iter()).zip(total..) {
1584+
tree.insert(key, value, seqno);
1585+
written += 1;
1586+
if flush_points.contains(&written) {
1587+
tree.flush_active_memtable(0).expect("master: flush");
1588+
}
1589+
}
1590+
tree.flush_active_memtable(0).expect("master: flush");
1591+
}
1592+
1593+
/// Clean timed-only subcompaction profile (ours). The pre-compact state is built
1594+
/// ONCE into a master dir; each iteration clones it to a fresh dir and times
1595+
/// ONLY `major_compact`. perf-recording this isolates the compaction cost (the
1596+
/// clone + open are distinct symbols), unlike `subcompaction_zstd3` whose
1597+
/// per-iteration input rebuild contaminates the flamegraph.
1598+
fn bench_subcompaction_clean(c: &mut Criterion) {
1599+
let level = 3;
1600+
let n = 40_000u64;
1601+
let inputs = SubcompactionInputs::build(n);
1602+
let master = tempfile::tempdir().expect("master tempdir");
1603+
build_subcompaction_master(master.path(), level, &inputs);
1604+
1605+
let mut group = c.benchmark_group("subcompaction_clean");
1606+
group.bench_function(BenchmarkId::new("ours", n), |b| {
1607+
b.iter_custom(|iters| {
1608+
let mut elapsed = std::time::Duration::ZERO;
1609+
for _ in 0..iters {
1610+
let work = tempfile::tempdir().expect("work tempdir");
1611+
copy_dir(master.path(), work.path()).expect("clone master");
1612+
let config = Config::new(
1613+
work.path(),
1614+
SequenceNumberCounter::default(),
1615+
SequenceNumberCounter::default(),
1616+
)
1617+
.data_block_compression_policy(CompressionPolicy::all(CompressionType::Zstd(level)))
1618+
.compaction_threads(SUBCOMPACTION_THREADS)
1619+
.subcompaction_min_bytes(0);
1620+
let tree = apply_preset(config, active_preset())
1621+
.open()
1622+
.expect("work: open");
1623+
let start = std::time::Instant::now();
1624+
tree.major_compact(u64::MAX, 0).expect("work: compact");
1625+
elapsed += start.elapsed();
1626+
}
1627+
elapsed
1628+
});
1629+
});
1630+
group.finish();
1631+
}
1632+
15411633
/// Sub-compaction head-to-head: our range-parallel split vs RocksDB
15421634
/// `max_subcompactions`. Pinned to zstd level 3 — the level RocksDB actually
15431635
/// applies to bottommost compaction output (see [`bench_compaction`]) — with
@@ -1555,6 +1647,7 @@ criterion_group!(
15551647
bench_seek_random,
15561648
bench_overwrite,
15571649
bench_compaction,
1558-
bench_subcompaction
1650+
bench_subcompaction,
1651+
bench_subcompaction_clean
15591652
);
15601653
criterion_main!(benches);

0 commit comments

Comments
 (0)