cache-kit 0.9.0

A type-safe, fully generic, production-ready caching framework for Rust
Documentation
//! Comprehensive performance benchmarks for Redis backend
//!
//! This benchmark suite measures:
//! - Redis backend operations (set, get, delete)
//! - Batch operations (mget, mdelete)
//! - Performance across different payload sizes
//! - Connection pool overhead
//!
//! Prerequisites:
//! - Redis running on localhost:6379
//! - Run with: cargo bench --bench redis_benchmark --features redis
//! - View results: open target/criterion/report/index.html

#![cfg(feature = "redis")]

use cache_kit::backend::{CacheBackend, RedisBackend};
use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion, Throughput};
use std::hint::black_box;

// ============================================================================
// Redis Backend Setup
// ============================================================================

/// Create a Redis backend for benchmarking.
///
/// Pool size can be configured via `REDIS_POOL_SIZE` environment variable.
/// Defaults to 16 if not specified (optimal for 8-core systems).
/// Formula: (CPU cores × 2) + 1
///
/// Example: REDIS_POOL_SIZE=32 cargo bench --bench redis_benchmark --features redis
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.")
}

// ============================================================================
// Group 1: Redis Basic Operations
// ============================================================================

fn redis_basic_benchmarks(c: &mut Criterion) {
    let mut group = c.benchmark_group("redis_backend");
    group.sample_size(50); // Fewer samples due to network latency

    // Create tokio runtime for async operations
    let rt = tokio::runtime::Runtime::new().expect("Failed to create Tokio runtime");

    let backend = rt.block_on(async { setup_redis().await });

    // Clear any existing data
    rt.block_on(async { backend.clear_all().await })
        .expect("Failed to clear Redis");

    // Benchmark different payload sizes
    for size in [100, 1_000, 10_000, 100_000].iter() {
        // SET operation
        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")
                });
            });

        // GET operation (cache hit)
        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")
                });
            });
    }

    // GET operation (cache miss) - size doesn't matter for misses
    group.bench_function("get_miss", |b| {
        b.to_async(&rt).iter(|| async {
            backend
                .get(black_box("nonexistent_key"))
                .await
                .expect("Failed to get")
        });
    });

    // DELETE operation
    group.bench_function("delete", |b| {
        let value = vec![1u8; 1000];

        b.to_async(&rt).iter(|| async {
            // Setup: insert before each iteration
            backend
                .set("redis_bench_delete", value.clone(), None)
                .await
                .expect("Failed to set");
            // Measure: delete operation
            backend
                .delete(black_box("redis_bench_delete"))
                .await
                .expect("Failed to delete")
        });
    });

    // EXISTS operation
    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")
        });
    });

    // Cleanup
    rt.block_on(async { backend.clear_all().await })
        .expect("Failed to clear Redis");

    group.finish();
}

// ============================================================================
// Group 2: Redis Batch Operations
// ============================================================================

fn redis_batch_benchmarks(c: &mut Criterion) {
    let mut group = c.benchmark_group("redis_batch_ops");
    group.sample_size(50); // Fewer samples due to network latency

    // Create tokio runtime for async operations
    let rt = tokio::runtime::Runtime::new().expect("Failed to create Tokio runtime");

    let backend = rt.block_on(async { setup_redis().await });

    // Clear any existing data
    rt.block_on(async { backend.clear_all().await })
        .expect("Failed to clear Redis");

    // Benchmark MGET with different batch sizes and payload sizes
    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();

            // Pre-populate keys
            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")
                        });
                    },
                );
        }
    }

    // Benchmark MDELETE with different batch sizes
    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 {
                    // Setup: create keys
                    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();

                    // Measure: delete all keys
                    backend
                        .mdelete(black_box(&key_refs))
                        .await
                        .expect("Failed to mdelete")
                });
            },
        );
    }

    // Cleanup
    rt.block_on(async { backend.clear_all().await })
        .expect("Failed to clear Redis");

    group.finish();
}

// ============================================================================
// Group 3: Redis Connection Pool Stress Test
// ============================================================================

fn redis_connection_pool_benchmarks(c: &mut Criterion) {
    let mut group = c.benchmark_group("redis_connection_pool");
    group.sample_size(50);

    // Create tokio runtime for async operations
    let rt = tokio::runtime::Runtime::new().expect("Failed to create Tokio runtime");

    let backend = rt.block_on(async { setup_redis().await });

    // Clear any existing data
    rt.block_on(async { backend.clear_all().await })
        .expect("Failed to clear Redis");

    // Benchmark rapid consecutive operations to stress connection pool
    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");
        });
    });

    // Benchmark health check
    group.bench_function("health_check", |b| {
        b.to_async(&rt)
            .iter(|| async { backend.health_check().await.expect("Failed health check") });
    });

    // Cleanup
    rt.block_on(async { backend.clear_all().await })
        .expect("Failed to clear Redis");

    group.finish();
}

// ============================================================================
// Group 4: Redis TTL Operations
// ============================================================================

fn redis_ttl_benchmarks(c: &mut Criterion) {
    let mut group = c.benchmark_group("redis_ttl");
    group.sample_size(50);

    // Create tokio runtime for async operations
    let rt = tokio::runtime::Runtime::new().expect("Failed to create Tokio runtime");

    let backend = rt.block_on(async { setup_redis().await });

    // Clear any existing data
    rt.block_on(async { backend.clear_all().await })
        .expect("Failed to clear Redis");

    // Benchmark SET with TTL
    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")
                });
            });
    }

    // Cleanup
    rt.block_on(async { backend.clear_all().await })
        .expect("Failed to clear Redis");

    group.finish();
}

// ============================================================================
// Benchmark Registration
// ============================================================================

criterion_group!(
    benches,
    redis_basic_benchmarks,
    redis_batch_benchmarks,
    redis_connection_pool_benchmarks,
    redis_ttl_benchmarks
);
criterion_main!(benches);