#[path = "../tests/support/mod.rs"]
mod support;
use std::path::Path;
use std::time::{Duration, Instant};
use mentedb_core::MemoryNode;
use mentedb_core::config::StorageConfig;
use mentedb_storage::StorageEngine;
use support::{typical, v1};
const SAMPLED_STORES: usize = 2_000;
const POINT_READS: usize = 10_000;
fn ms(d: Duration) -> f64 {
d.as_secs_f64() * 1e3
}
fn pct(mut v: Vec<Duration>, p: f64) -> f64 {
v.sort();
let i = ((v.len() as f64 - 1.0) * p).round() as usize;
v[i].as_secs_f64() * 1e6
}
fn dir_bytes(dir: &Path) -> u64 {
std::fs::read_dir(dir)
.unwrap()
.filter_map(|e| e.ok()?.metadata().ok())
.map(|m| m.len())
.sum()
}
fn rng(seed: &mut u64) -> u64 {
*seed ^= *seed << 13;
*seed ^= *seed >> 7;
*seed ^= *seed << 17;
*seed
}
struct Row {
name: &'static str,
file_bytes: u64,
open_map_ms: f64,
open_map_reads: u64,
open_scan_ms: f64,
open_scan_bytes: u64,
scan_ms: f64,
scan_bytes: u64,
rescan_ms: f64,
rescan_bytes: u64,
point_p50_us: f64,
point_p99_us: f64,
store_p50_us: f64,
store_p99_us: f64,
}
fn bench_v1(dir: &Path, n: usize) -> Row {
let page = v1::PAGE_SIZE as u64;
let bulk = n - SAMPLED_STORES;
let e = v1::StorageEngine::open(dir).unwrap();
for start in (0..bulk).step_by(1000) {
let nodes: Vec<MemoryNode> = (start..(start + 1000).min(bulk)).map(typical).collect();
e.store_memory_batch(&nodes).unwrap();
}
let mut lat = Vec::with_capacity(SAMPLED_STORES);
for i in bulk..n {
let node = typical(i);
let t = Instant::now();
e.store_memory(&node).unwrap();
lat.push(t.elapsed());
}
e.memory_map().unwrap();
e.checkpoint().unwrap();
e.close().unwrap();
drop(e);
let file_bytes = dir_bytes(dir);
let t = Instant::now();
let e = v1::StorageEngine::open(dir).unwrap();
let map = e.memory_map().unwrap();
let open_map_ms = ms(t.elapsed());
let open_map_reads = e.page_reads();
assert_eq!(map.len(), n);
e.close().unwrap();
drop(e);
std::fs::remove_file(dir.join("memory_map.bin")).unwrap();
let t = Instant::now();
let e = v1::StorageEngine::open(dir).unwrap();
e.memory_map().unwrap();
let open_scan_ms = ms(t.elapsed());
let open_scan_bytes = e.page_reads() * page;
e.close().unwrap();
drop(e);
let e = v1::StorageEngine::open(dir).unwrap();
let map = e.memory_map().unwrap();
let t = Instant::now();
for m in &map {
e.load_memory(m.page).unwrap();
}
let scan_ms = ms(t.elapsed());
let scan_bytes = e.page_reads() * page;
let before = e.page_reads();
let t = Instant::now();
for m in &map {
e.load_memory(m.page).unwrap();
}
let rescan_ms = ms(t.elapsed());
let rescan_bytes = (e.page_reads() - before) * page;
e.close().unwrap();
drop(e);
let e = v1::StorageEngine::open(dir).unwrap();
let map = e.memory_map().unwrap();
let mut seed = 0x5eed_u64;
let mut reads = Vec::with_capacity(POINT_READS);
for _ in 0..POINT_READS {
let m = map[(rng(&mut seed) % map.len() as u64) as usize];
let t = Instant::now();
e.load_memory(m.page).unwrap();
reads.push(t.elapsed());
}
e.close().unwrap();
Row {
name: "page file (0.44.0)",
file_bytes,
open_map_ms,
open_map_reads,
open_scan_ms,
open_scan_bytes,
scan_ms,
scan_bytes,
rescan_ms,
rescan_bytes,
point_p50_us: pct(reads.clone(), 0.5),
point_p99_us: pct(reads, 0.99),
store_p50_us: pct(lat.clone(), 0.5),
store_p99_us: pct(lat, 0.99),
}
}
fn bench_records(dir: &Path, n: usize, cache_bytes: u64, name: &'static str) -> Row {
let cfg = StorageConfig {
cache_bytes,
..Default::default()
};
let bulk = n - SAMPLED_STORES;
let e = StorageEngine::open_with_config(dir, &cfg).unwrap();
for start in (0..bulk).step_by(1000) {
let nodes: Vec<MemoryNode> = (start..(start + 1000).min(bulk)).map(typical).collect();
e.store_memory_batch(&nodes).unwrap();
}
let mut lat = Vec::with_capacity(SAMPLED_STORES);
for i in bulk..n {
let node = typical(i);
let t = Instant::now();
e.store_memory(&node).unwrap();
lat.push(t.elapsed());
}
e.checkpoint().unwrap();
e.close().unwrap();
drop(e);
let file_bytes = dir_bytes(dir);
let t = Instant::now();
let e = StorageEngine::open_with_config(dir, &cfg).unwrap();
let map = e.memory_map().unwrap();
let open_map_ms = ms(t.elapsed());
let open_map_reads = e.page_reads();
assert_eq!(map.len(), n);
e.close().unwrap();
drop(e);
std::fs::remove_file(dir.join(mentedb_storage::memory_map::MEMORY_MAP_FILE)).unwrap();
let t = Instant::now();
let e = StorageEngine::open_with_config(dir, &cfg).unwrap();
e.memory_map().unwrap();
let open_scan_ms = ms(t.elapsed());
let open_scan_bytes = e.bytes_read();
e.close().unwrap();
drop(e);
let e = StorageEngine::open_with_config(dir, &cfg).unwrap();
let map = e.memory_map().unwrap();
let t = Instant::now();
for m in &map {
e.load_memory(m.page).unwrap();
}
let scan_ms = ms(t.elapsed());
let scan_bytes = e.bytes_read();
let t = Instant::now();
for m in &map {
e.load_memory(m.page).unwrap();
}
let rescan_ms = ms(t.elapsed());
let rescan_bytes = e.bytes_read() - scan_bytes;
e.close().unwrap();
drop(e);
let e = StorageEngine::open_with_config(dir, &cfg).unwrap();
let map = e.memory_map().unwrap();
let mut seed = 0x5eed_u64;
let mut reads = Vec::with_capacity(POINT_READS);
for _ in 0..POINT_READS {
let m = map[(rng(&mut seed) % map.len() as u64) as usize];
let t = Instant::now();
e.load_memory(m.page).unwrap();
reads.push(t.elapsed());
}
e.close().unwrap();
Row {
name,
file_bytes,
open_map_ms,
open_map_reads,
open_scan_ms,
open_scan_bytes,
scan_ms,
scan_bytes,
rescan_ms,
rescan_bytes,
point_p50_us: pct(reads.clone(), 0.5),
point_p99_us: pct(reads, 0.99),
store_p50_us: pct(lat.clone(), 0.5),
store_p99_us: pct(lat, 0.99),
}
}
fn mb(b: u64) -> f64 {
b as f64 / (1024.0 * 1024.0)
}
fn main() {
let args: Vec<String> = std::env::args().collect();
let root = Path::new(args.get(1).expect("usage: layout_bench <dir> [count]"));
let n: usize = args.get(2).map_or(100_000, |s| s.parse().unwrap());
assert!(n > SAMPLED_STORES);
std::fs::create_dir_all(root).unwrap();
let serialized: u64 = (0..n).map(|i| support::json_len(&typical(i))).sum();
println!("{n} memories, {:.1} MB serialized as JSON", mb(serialized));
let v1_dir = root.join("page_file");
let rec_dir = root.join("records");
let full_dir = root.join("records_full_cache");
for d in [&v1_dir, &rec_dir, &full_dir] {
let _ = std::fs::remove_dir_all(d);
}
let old = bench_v1(&v1_dir, n);
let new = bench_records(
&rec_dir,
n,
StorageConfig::default().cache_bytes,
"records (64 MB cache)",
);
let full = bench_records(&full_dir, n, 2 << 30, "records (2 GB cache)");
println!(
"| layout | file MB | open with map ms | records read | open by scan ms | scan open MB read | full scan ms | full scan MB read | second scan ms | second scan MB read | point read p50 us | p99 us | store p50 us | p99 us |"
);
println!("|---|---|---|---|---|---|---|---|---|---|---|---|---|---|");
for r in [&old, &new, &full] {
println!(
"| {} | {:.1} | {:.1} | {} | {:.1} | {:.1} | {:.1} | {:.1} | {:.1} | {:.1} | {:.1} | {:.1} | {:.1} | {:.1} |",
r.name,
mb(r.file_bytes),
r.open_map_ms,
r.open_map_reads,
r.open_scan_ms,
mb(r.open_scan_bytes),
r.scan_ms,
mb(r.scan_bytes),
r.rescan_ms,
mb(r.rescan_bytes),
r.point_p50_us,
r.point_p99_us,
r.store_p50_us,
r.store_p99_us
);
}
let before = dir_bytes(&v1_dir);
let t = Instant::now();
let e = StorageEngine::open(&v1_dir).unwrap();
let migrate_ms = ms(t.elapsed());
assert_eq!(e.memory_map().unwrap().len(), n);
e.close().unwrap();
println!(
"migration: {:.1} MB to {:.1} MB in {:.0} ms",
mb(before),
mb(dir_bytes(&v1_dir)),
migrate_ms
);
}