use std::path::PathBuf;
use std::time::{Duration, SystemTime, UNIX_EPOCH};
use criterion::{criterion_group, criterion_main, BatchSize, Criterion};
use lora_database::{
Database, DatabaseOpenOptions, ExecuteOptions, ResultFormat, SyncMode, WalConfig,
};
use std::hint::black_box;
fn opts() -> Option<ExecuteOptions> {
Some(ExecuteOptions {
format: ResultFormat::Rows,
})
}
struct ScratchDir {
path: PathBuf,
}
impl ScratchDir {
fn new(tag: &str) -> Self {
let mut path = std::env::temp_dir();
path.push(format!(
"lora-wal-bench-{}-{}-{}",
tag,
std::process::id(),
SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_nanos()
));
std::fs::create_dir_all(&path).unwrap();
Self { path }
}
}
impl Drop for ScratchDir {
fn drop(&mut self) {
let _ = std::fs::remove_dir_all(&self.path);
}
}
fn enabled(dir: &std::path::Path, sync_mode: SyncMode) -> WalConfig {
WalConfig::Enabled {
dir: dir.to_path_buf(),
sync_mode,
segment_target_bytes: 8 * 1024 * 1024,
}
}
fn smoke_config() -> Criterion {
Criterion::default()
.warm_up_time(Duration::from_millis(500))
.measurement_time(Duration::from_millis(2_500))
.sample_size(20)
.noise_threshold(0.10)
}
fn bench_commit_latency(c: &mut Criterion) {
let mut group = c.benchmark_group("wal/commit_latency");
{
group.bench_function("no_wal", |b| {
b.iter_batched(
Database::in_memory,
|db| {
black_box(db.execute("CREATE (:N {v: 1})", opts()).unwrap());
},
BatchSize::SmallInput,
);
});
}
{
group.bench_function("per_commit", |b| {
b.iter_batched(
|| {
let dir = ScratchDir::new("per-commit");
let db =
Database::open_with_wal(enabled(&dir.path, SyncMode::PerCommit)).unwrap();
(dir, db)
},
|(_dir, db)| {
black_box(db.execute("CREATE (:N {v: 1})", opts()).unwrap());
},
BatchSize::SmallInput,
);
});
}
{
group.bench_function("group", |b| {
b.iter_batched(
|| {
let dir = ScratchDir::new("group");
let db = Database::open_with_wal(enabled(
&dir.path,
SyncMode::Group { interval_ms: 50 },
))
.unwrap();
(dir, db)
},
|(_dir, db)| {
black_box(db.execute("CREATE (:N {v: 1})", opts()).unwrap());
},
BatchSize::SmallInput,
);
});
}
{
group.bench_function("none", |b| {
b.iter_batched(
|| {
let dir = ScratchDir::new("none");
let db = Database::open_with_wal(enabled(&dir.path, SyncMode::None)).unwrap();
(dir, db)
},
|(_dir, db)| {
black_box(db.execute("CREATE (:N {v: 1})", opts()).unwrap());
},
BatchSize::SmallInput,
);
});
}
group.finish();
}
fn bench_recovery(c: &mut Criterion) {
let mut group = c.benchmark_group("wal/recovery");
for n in [100usize, 1_000].iter().copied() {
let dir = ScratchDir::new(&format!("recovery-{}", n));
{
let db = Database::open_with_wal(enabled(&dir.path, SyncMode::PerCommit)).unwrap();
for _ in 0..n {
db.execute("CREATE (:N {v: 1})", opts()).unwrap();
}
drop(db);
}
group.bench_function(format!("replay_{}", n), |b| {
b.iter(|| {
let db = Database::open_with_wal(enabled(&dir.path, SyncMode::None)).unwrap();
black_box(db.node_count());
});
});
std::mem::forget(dir);
}
group.finish();
}
fn bench_named_archive_write_heavy(c: &mut Criterion) {
let mut group = c.benchmark_group("named_archive/write_heavy");
const WRITES: usize = 1_000;
group.bench_function("memory_only_1000_creates", |b| {
b.iter_batched(
Database::in_memory,
|db| {
for i in 0..WRITES {
black_box(
db.execute(&format!("CREATE (:N {{v: {i}}})"), opts())
.unwrap(),
);
}
black_box(db.node_count());
},
BatchSize::SmallInput,
);
});
group.bench_function("lora_archive_1000_creates", |b| {
b.iter_batched(
|| {
let dir = ScratchDir::new("named-archive");
let db = Database::open_named(
"bench",
DatabaseOpenOptions::default().with_database_dir(&dir.path),
)
.unwrap();
(dir, db)
},
|(_dir, db)| {
for i in 0..WRITES {
black_box(
db.execute(&format!("CREATE (:N {{v: {i}}})"), opts())
.unwrap(),
);
}
black_box(db.node_count());
drop(db);
},
BatchSize::SmallInput,
);
});
group.bench_function("memory_only_batch_1000", |b| {
b.iter_batched(
Database::in_memory,
|db| {
black_box(
db.execute("UNWIND range(1, 1000) AS i CREATE (:N {v: i})", opts())
.unwrap(),
);
black_box(db.node_count());
},
BatchSize::SmallInput,
);
});
group.bench_function("lora_archive_batch_1000", |b| {
b.iter_batched(
|| {
let dir = ScratchDir::new("named-archive-batch");
let db = Database::open_named(
"bench",
DatabaseOpenOptions::default().with_database_dir(&dir.path),
)
.unwrap();
(dir, db)
},
|(_dir, db)| {
black_box(
db.execute("UNWIND range(1, 1000) AS i CREATE (:N {v: i})", opts())
.unwrap(),
);
black_box(db.node_count());
drop(db);
},
BatchSize::SmallInput,
);
});
group.finish();
}
fn bench_named_archive_steady_state(c: &mut Criterion) {
let mut group = c.benchmark_group("named_archive/steady_state");
group.bench_function("memory_only_create_delete", |b| {
let db = Database::in_memory();
b.iter(|| {
black_box(
db.execute("CREATE (n:Tmp {v: 1}) DELETE n", opts())
.unwrap(),
);
black_box(db.node_count());
});
});
group.bench_function("lora_archive_create_delete", |b| {
let dir = ScratchDir::new("named-archive-steady");
let db = Database::open_named(
"bench",
DatabaseOpenOptions::default().with_database_dir(&dir.path),
)
.unwrap();
b.iter(|| {
black_box(
db.execute("CREATE (n:Tmp {v: 1}) DELETE n", opts())
.unwrap(),
);
black_box(db.node_count());
});
});
group.bench_function("memory_only_batch_create_delete_1000", |b| {
let db = Database::in_memory();
b.iter(|| {
black_box(
db.execute(
"UNWIND range(1, 1000) AS i CREATE (n:Tmp {v: i}) DELETE n",
opts(),
)
.unwrap(),
);
black_box(db.node_count());
});
});
group.bench_function("lora_archive_batch_create_delete_1000", |b| {
let dir = ScratchDir::new("named-archive-steady-batch");
let db = Database::open_named(
"bench",
DatabaseOpenOptions::default().with_database_dir(&dir.path),
)
.unwrap();
b.iter(|| {
black_box(
db.execute(
"UNWIND range(1, 1000) AS i CREATE (n:Tmp {v: i}) DELETE n",
opts(),
)
.unwrap(),
);
black_box(db.node_count());
});
});
group.finish();
}
criterion_group! {
name = benches;
config = smoke_config();
targets = bench_commit_latency, bench_recovery, bench_named_archive_write_heavy, bench_named_archive_steady_state,
}
criterion_main!(benches);