#![allow(
clippy::unwrap_used,
clippy::missing_docs_in_private_items,
missing_docs
)]
use std::{sync::Arc, thread};
use criterion::{BenchmarkId, Criterion, Throughput, criterion_group, criterion_main};
use fraiseql_core::cache::{CacheConfig, QueryResultCache};
use fraiseql_db::JsonbValue;
use rand::Rng as _;
const KEY_COUNT: usize = 1_000;
const OPS_PER_THREAD: usize = 1_000;
fn make_cache() -> Arc<QueryResultCache> {
Arc::new(QueryResultCache::new(CacheConfig::with_max_entries(10_000)))
}
fn make_result() -> Vec<JsonbValue> {
vec![serde_json::from_str(r#"{"data": {"id": 1, "name": "test"}}"#).unwrap()]
}
fn populate(cache: &QueryResultCache) {
for i in 0..KEY_COUNT {
let _ = cache.put(i as u64, make_result(), vec!["users".to_string()], None, Some("users"));
}
}
fn bench_cache_reads(c: &mut Criterion) {
let mut group = c.benchmark_group("cache_concurrent_reads");
group.throughput(Throughput::Elements(OPS_PER_THREAD as u64));
for &n_threads in &[1usize, 4, 8, 16, 32] {
group.bench_with_input(
BenchmarkId::from_parameter(n_threads),
&n_threads,
|b, &threads| {
let cache = make_cache();
populate(&cache);
b.iter(|| {
let handles: Vec<_> = (0..threads)
.map(|_| {
let c: Arc<QueryResultCache> = Arc::clone(&cache);
thread::spawn(move || {
for i in 0..OPS_PER_THREAD {
let _ = c.get((i % KEY_COUNT) as u64);
}
})
})
.collect();
for h in handles {
h.join().unwrap();
}
});
},
);
}
group.finish();
}
fn bench_cache_writes(c: &mut Criterion) {
let mut group = c.benchmark_group("cache_concurrent_writes");
group.throughput(Throughput::Elements(OPS_PER_THREAD as u64));
for &n_threads in &[1usize, 4, 8, 16, 32] {
group.bench_with_input(
BenchmarkId::from_parameter(n_threads),
&n_threads,
|b, &threads| {
let cache = make_cache();
let result = Arc::new(make_result());
b.iter(|| {
let handles: Vec<_> = (0..threads)
.map(|t| {
let c = Arc::clone(&cache);
let r = Arc::clone(&result);
thread::spawn(move || {
for i in 0..OPS_PER_THREAD {
let _ = c.put(
((t * OPS_PER_THREAD + i) % KEY_COUNT) as u64,
(*r).clone(),
vec!["users".to_string()],
None,
Some("users"),
);
}
})
})
.collect();
for h in handles {
h.join().unwrap();
}
});
},
);
}
group.finish();
}
fn bench_cache_mixed(c: &mut Criterion) {
let mut group = c.benchmark_group("cache_concurrent_mixed_90r_10w");
group.throughput(Throughput::Elements(OPS_PER_THREAD as u64));
let n_threads = 8usize;
group.bench_function("8_threads", |b| {
let cache = make_cache();
populate(&cache);
let result = Arc::new(make_result());
b.iter(|| {
let handles: Vec<_> = (0..n_threads)
.map(|t| {
let c = Arc::clone(&cache);
let r = Arc::clone(&result);
thread::spawn(move || {
for i in 0..OPS_PER_THREAD {
if (t * OPS_PER_THREAD + i).is_multiple_of(10) {
let _ = c.put(
(i % KEY_COUNT) as u64,
(*r).clone(),
vec!["users".to_string()],
None,
Some("users"),
);
} else {
let _ = c.get((i % KEY_COUNT) as u64);
}
}
})
})
.collect();
for h in handles {
h.join().unwrap();
}
});
});
group.finish();
}
fn bench_cache_latency(c: &mut Criterion) {
let cache = make_cache();
populate(&cache);
let mut group = c.benchmark_group("cache_get_latency_steady_state");
group.bench_function("single_get", |b| {
let mut rng = rand::rng();
b.iter(|| {
let _ = cache.get(rng.random_range(0..KEY_COUNT) as u64);
});
});
group.bench_function("single_put", |b| {
let mut rng = rand::rng();
b.iter(|| {
let _ = cache.put(
rng.random_range(0..KEY_COUNT) as u64,
make_result(),
vec!["users".to_string()],
None,
Some("users"),
);
});
});
group.finish();
}
criterion_group!(
benches,
bench_cache_reads,
bench_cache_writes,
bench_cache_mixed,
bench_cache_latency
);
criterion_main!(benches);