#![cfg(feature = "redis")]
use cache_kit::backend::{CacheBackend, RedisBackend};
use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion, Throughput};
use std::hint::black_box;
async fn setup_redis() -> RedisBackend {
RedisBackend::from_connection_string("redis://localhost:6379")
.await
.expect("Failed to connect to Redis at localhost:6379. Make sure Redis is running.")
}
fn redis_basic_benchmarks(c: &mut Criterion) {
let mut group = c.benchmark_group("redis_backend");
group.sample_size(50);
let rt = tokio::runtime::Runtime::new().expect("Failed to create Tokio runtime");
let backend = rt.block_on(async { setup_redis().await });
rt.block_on(async { backend.clear_all().await })
.expect("Failed to clear Redis");
for size in [100, 1_000, 10_000, 100_000].iter() {
group
.throughput(Throughput::Bytes(*size as u64))
.bench_with_input(BenchmarkId::new("set", size), size, |b, &size| {
let value = vec![1u8; size];
b.to_async(&rt).iter(|| async {
backend
.set(black_box("redis_bench_key"), black_box(value.clone()), None)
.await
.expect("Failed to set")
});
});
group
.throughput(Throughput::Bytes(*size as u64))
.bench_with_input(BenchmarkId::new("get_hit", size), size, |b, &size| {
let value = vec![1u8; size];
rt.block_on(async {
backend
.set("redis_bench_key", value, None)
.await
.expect("Failed to set");
});
b.to_async(&rt).iter(|| async {
backend
.get(black_box("redis_bench_key"))
.await
.expect("Failed to get")
});
});
}
group.bench_function("get_miss", |b| {
b.to_async(&rt).iter(|| async {
backend
.get(black_box("nonexistent_key"))
.await
.expect("Failed to get")
});
});
group.bench_function("delete", |b| {
let value = vec![1u8; 1000];
b.to_async(&rt).iter(|| async {
backend
.set("redis_bench_delete", value.clone(), None)
.await
.expect("Failed to set");
backend
.delete(black_box("redis_bench_delete"))
.await
.expect("Failed to delete")
});
});
group.bench_function("exists", |b| {
rt.block_on(async {
backend
.set("redis_bench_exists", vec![1u8; 1000], None)
.await
.expect("Failed to set");
});
b.to_async(&rt).iter(|| async {
backend
.exists(black_box("redis_bench_exists"))
.await
.expect("Failed to check exists")
});
});
rt.block_on(async { backend.clear_all().await })
.expect("Failed to clear Redis");
group.finish();
}
fn redis_batch_benchmarks(c: &mut Criterion) {
let mut group = c.benchmark_group("redis_batch_ops");
group.sample_size(50);
let rt = tokio::runtime::Runtime::new().expect("Failed to create Tokio runtime");
let backend = rt.block_on(async { setup_redis().await });
rt.block_on(async { backend.clear_all().await })
.expect("Failed to clear Redis");
for batch_size in [10, 50, 100].iter() {
for payload_size in [100, 1_000, 10_000].iter() {
let keys: Vec<String> = (0..*batch_size)
.map(|i| format!("redis_mget_key_{}", i))
.collect();
let value = vec![1u8; *payload_size];
rt.block_on(async {
for key in &keys {
backend
.set(key, value.clone(), None)
.await
.expect("Failed to set");
}
});
let key_refs: Vec<&str> = keys.iter().map(|s| s.as_str()).collect();
group
.throughput(Throughput::Bytes((*batch_size * *payload_size) as u64))
.bench_with_input(
BenchmarkId::new(
"mget",
format!("batch_{}_size_{}", batch_size, payload_size),
),
&key_refs,
|b, keys| {
b.to_async(&rt).iter(|| async {
backend.mget(black_box(keys)).await.expect("Failed to mget")
});
},
);
}
}
for batch_size in [10, 50, 100].iter() {
group.bench_with_input(
BenchmarkId::new("mdelete", batch_size),
batch_size,
|b, &batch_size| {
b.to_async(&rt).iter(|| async {
let keys: Vec<String> = (0..batch_size)
.map(|i| format!("redis_mdelete_key_{}", i))
.collect();
for key in &keys {
backend
.set(key, vec![1u8; 100], None)
.await
.expect("Failed to set");
}
let key_refs: Vec<&str> = keys.iter().map(|s| s.as_str()).collect();
backend
.mdelete(black_box(&key_refs))
.await
.expect("Failed to mdelete")
});
},
);
}
rt.block_on(async { backend.clear_all().await })
.expect("Failed to clear Redis");
group.finish();
}
fn redis_connection_pool_benchmarks(c: &mut Criterion) {
let mut group = c.benchmark_group("redis_connection_pool");
group.sample_size(50);
let rt = tokio::runtime::Runtime::new().expect("Failed to create Tokio runtime");
let backend = rt.block_on(async { setup_redis().await });
rt.block_on(async { backend.clear_all().await })
.expect("Failed to clear Redis");
group.bench_function("rapid_set_get_delete", |b| {
let value = vec![1u8; 1000];
b.to_async(&rt).iter(|| async {
let key = "redis_pool_test";
backend
.set(black_box(key), black_box(value.clone()), None)
.await
.expect("Failed to set");
let _ = backend.get(black_box(key)).await.expect("Failed to get");
backend
.delete(black_box(key))
.await
.expect("Failed to delete");
});
});
group.bench_function("health_check", |b| {
b.to_async(&rt)
.iter(|| async { backend.health_check().await.expect("Failed health check") });
});
rt.block_on(async { backend.clear_all().await })
.expect("Failed to clear Redis");
group.finish();
}
fn redis_ttl_benchmarks(c: &mut Criterion) {
let mut group = c.benchmark_group("redis_ttl");
group.sample_size(50);
let rt = tokio::runtime::Runtime::new().expect("Failed to create Tokio runtime");
let backend = rt.block_on(async { setup_redis().await });
rt.block_on(async { backend.clear_all().await })
.expect("Failed to clear Redis");
for size in [100, 1_000, 10_000].iter() {
group
.throughput(Throughput::Bytes(*size as u64))
.bench_with_input(BenchmarkId::new("set_with_ttl", size), size, |b, &size| {
let value = vec![1u8; size];
let ttl = Some(std::time::Duration::from_secs(60));
b.to_async(&rt).iter(|| async {
backend
.set(
black_box("redis_ttl_key"),
black_box(value.clone()),
black_box(ttl),
)
.await
.expect("Failed to set with TTL")
});
});
}
rt.block_on(async { backend.clear_all().await })
.expect("Failed to clear Redis");
group.finish();
}
criterion_group!(
benches,
redis_basic_benchmarks,
redis_batch_benchmarks,
redis_connection_pool_benchmarks,
redis_ttl_benchmarks
);
criterion_main!(benches);