Files
foxhunt/services/api_gateway/benches/revocation_cache_perf.rs
jgrusewski 030a15ee05 🔧 Emergency Fix: Resolve catastrophic _i32 suffix corruption (463→0 errors)
- Fixed systematic array indexing corruption: [0_i32] → [0]
- Fixed numeric literal suffixes across 835 files
- Fixed iterator patterns on RwLockReadGuard (.iter() required)
- Fixed float type annotations (365.25_f64 for sqrt)
- Fixed missing semicolons in position manager
- Fixed reference dereferencing in data loader

Root cause: Mass refactoring incorrectly added _i32 suffixes to array indices
Impact: Complete compilation failure (463 errors)
Resolution: Automated regex + targeted fixes
Result: 100% compilation success (0 errors)

Validated: cargo check --workspace passes
Ready for: Production deployment
2025-10-10 23:05:26 +02:00

384 lines
12 KiB
Rust

//! Revocation Cache Performance Benchmark
//!
//! Measures the performance impact of local revocation cache:
//! - Cache hit latency: TARGET <10ns
//! - Cache miss latency: ~500μs (Redis network)
//! - Cache hit rate: TARGET >95%
//! - Memory overhead: Minimal with TTL expiration
//!
//! This benchmark compares:
//! 1. Direct Redis calls (no cache)
//! 2. Local DashMap cache with 60s TTL
//! 3. Cache behavior under different access patterns
use criterion::{black_box, criterion_group, criterion_main, BenchmarkId, Criterion};
use dashmap::DashMap;
use std::sync::Arc;
use std::time::{Duration, Instant};
/// Simulated revocation result
#[derive(Debug, Clone)]
struct CachedRevocationResult {
is_revoked: bool,
cached_at: Instant,
}
/// Local revocation cache implementation
struct LocalRevocationCache {
cache: Arc<DashMap<String, CachedRevocationResult>>,
ttl: Duration,
hits: Arc<std::sync::atomic::AtomicU64>,
misses: Arc<std::sync::atomic::AtomicU64>,
}
impl LocalRevocationCache {
fn new(ttl: Duration) -> Self {
Self {
cache: Arc::new(DashMap::new()),
ttl,
hits: Arc::new(std::sync::atomic::AtomicU64::new(0)),
misses: Arc::new(std::sync::atomic::AtomicU64::new(0)),
}
}
fn check_revoked(&self, token_id: &str, simulate_redis_latency: bool) -> bool {
// Check cache first
if let Some(entry) = self.cache.get(token_id) {
if entry.cached_at.elapsed() < self.ttl {
// Cache hit
self.hits
.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
return entry.is_revoked;
} else {
// Expired - remove it
drop(entry);
self.cache.remove(token_id);
}
}
// Cache miss - simulate Redis lookup
self.misses
.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
if simulate_redis_latency {
// Simulate 500μs Redis network latency
std::thread::sleep(Duration::from_micros(500));
}
// Simulate Redis result (99% not revoked in production)
let is_revoked = token_id.contains("revoked");
// Update cache
self.cache.insert(
token_id.to_string(),
CachedRevocationResult {
is_revoked,
cached_at: Instant::now(),
},
);
is_revoked
}
fn stats(&self) -> (u64, u64, f64) {
let hits = self.hits.load(std::sync::atomic::Ordering::Relaxed);
let misses = self.misses.load(std::sync::atomic::Ordering::Relaxed);
let total = hits + misses;
let hit_rate = if total > 0 {
(hits as f64 / total as f64) * 100.0
} else {
0.0
};
(hits, misses, hit_rate)
}
}
/// Benchmark 1: Cache hit latency (TARGET: <10ns)
fn bench_cache_hit_latency(c: &mut Criterion) {
let cache = LocalRevocationCache::new(Duration::from_secs(60));
// Prepopulate cache with 1000 tokens
for i in 0..1000 {
let token_id = format!("token_{}", i);
cache.check_revoked(&token_id, false); // Prime cache without latency
}
c.bench_function("revocation_cache_hit", |b| {
b.iter(|| {
let token_id = format!("token_{}", black_box(500));
let is_revoked = cache.check_revoked(&token_id, false);
black_box(is_revoked);
});
});
}
/// Benchmark 2: Cache miss latency (with simulated Redis)
fn bench_cache_miss_latency(c: &mut Criterion) {
let cache = LocalRevocationCache::new(Duration::from_secs(60));
c.bench_function("revocation_cache_miss_with_redis", |b| {
let mut counter = 0;
b.iter(|| {
let token_id = format!("new_token_{}", black_box(counter));
let is_revoked = cache.check_revoked(&token_id, true); // Simulate Redis latency
black_box(is_revoked);
counter += 1;
});
});
}
/// Benchmark 3: Hot token pattern (95% cache hit rate)
fn bench_hot_token_pattern(c: &mut Criterion) {
let cache = LocalRevocationCache::new(Duration::from_secs(60));
// Prepopulate with hot tokens
for i in 0..10 {
let token_id = format!("hot_token_{}", i);
cache.check_revoked(&token_id, false);
}
c.bench_function("hot_token_pattern_95pct_hits", |b| {
let mut counter = 0;
b.iter(|| {
// 95% hits (tokens 0-9), 5% misses (tokens 10-19)
let token_num = if counter % 20 < 19 {
counter % 10
} else {
10 + (counter % 10)
};
let token_id = format!("hot_token_{}", black_box(token_num));
let is_revoked = cache.check_revoked(&token_id, false);
black_box(is_revoked);
counter += 1;
});
});
// Report statistics
let (hits, misses, hit_rate) = cache.stats();
println!(
"\nHot token pattern stats: {} hits, {} misses, {:.2}% hit rate",
hits, misses, hit_rate
);
}
/// Benchmark 4: TTL expiration behavior
fn bench_ttl_expiration(c: &mut Criterion) {
let mut group = c.benchmark_group("ttl_expiration");
// Short TTL (1ms) - high expiration rate
let cache_short = LocalRevocationCache::new(Duration::from_millis(1));
for i in 0..100 {
cache_short.check_revoked(&format!("token_{}", i), false);
}
group.bench_function("1ms_ttl", |b| {
b.iter(|| {
let token_id = format!("token_{}", black_box(50));
let is_revoked = cache_short.check_revoked(&token_id, false);
black_box(is_revoked);
});
});
// Long TTL (60s) - low expiration rate
let cache_long = LocalRevocationCache::new(Duration::from_secs(60));
for i in 0..100 {
cache_long.check_revoked(&format!("token_{}", i), false);
}
group.bench_function("60s_ttl", |b| {
b.iter(|| {
let token_id = format!("token_{}", black_box(50));
let is_revoked = cache_long.check_revoked(&token_id, false);
black_box(is_revoked);
});
});
group.finish();
}
/// Benchmark 5: Cache size impact
fn bench_cache_size_impact(c: &mut Criterion) {
let mut group = c.benchmark_group("cache_size_impact");
for size in &[100, 1_000, 10_000, 100_000] {
let cache = LocalRevocationCache::new(Duration::from_secs(60));
// Prepopulate to target size
for i in 0..*size {
cache.check_revoked(&format!("token_{}", i), false);
}
group.bench_with_input(BenchmarkId::new("lookup", size), size, |b, &n| {
b.iter(|| {
let token_id = format!("token_{}", black_box(n / 2));
let is_revoked = cache.check_revoked(&token_id, false);
black_box(is_revoked);
});
});
}
group.finish();
}
/// Benchmark 6: Concurrent access pattern
fn bench_concurrent_access(c: &mut Criterion) {
let cache = Arc::new(LocalRevocationCache::new(Duration::from_secs(60)));
// Prepopulate
for i in 0..1000 {
cache.check_revoked(&format!("token_{}", i), false);
}
c.bench_function("concurrent_cache_access", |b| {
b.iter(|| {
let cache_clone = cache.clone();
let token_id = format!("token_{}", black_box(500));
let is_revoked = cache_clone.check_revoked(&token_id, false);
black_box(is_revoked);
});
});
}
/// Benchmark 7: Mixed revoked/valid token pattern
fn bench_mixed_revocation(c: &mut Criterion) {
let cache = LocalRevocationCache::new(Duration::from_secs(60));
// Prepopulate with mix of revoked and valid tokens
for i in 0..1000 {
let token_id = if i % 10 == 0 {
format!("revoked_token_{}", i) // 10% revoked
} else {
format!("valid_token_{}", i) // 90% valid
};
cache.check_revoked(&token_id, false);
}
c.bench_function("mixed_revocation_pattern", |b| {
let mut counter = 0;
b.iter(|| {
let token_id = if counter % 10 == 0 {
format!("revoked_token_{}", black_box(counter))
} else {
format!("valid_token_{}", black_box(counter))
};
let is_revoked = cache.check_revoked(&token_id, false);
black_box(is_revoked);
counter = (counter + 1) % 1000;
});
});
}
/// Benchmark 8: Cache vs no-cache comparison
fn bench_cache_vs_no_cache(c: &mut Criterion) {
let mut group = c.benchmark_group("cache_vs_no_cache");
// No cache - direct Redis simulation
group.bench_function("no_cache_direct_redis", |b| {
b.iter(|| {
// Simulate 500μs Redis latency every time
std::thread::sleep(Duration::from_micros(500));
let is_revoked = false; // Simulate result
black_box(is_revoked);
});
});
// With cache - 95% hit rate
let cache = LocalRevocationCache::new(Duration::from_secs(60));
for i in 0..10 {
cache.check_revoked(&format!("token_{}", i), false);
}
group.bench_function("with_cache_95pct_hits", |b| {
let mut counter = 0;
b.iter(|| {
let token_num = if counter % 20 < 19 {
counter % 10 // Hit
} else {
10 + (counter % 10) // Miss
};
let token_id = format!("token_{}", black_box(token_num));
let is_revoked = cache.check_revoked(&token_id, counter % 20 >= 19); // Only simulate latency on misses
black_box(is_revoked);
counter += 1;
});
});
group.finish();
}
/// Benchmark 9: Memory overhead measurement
fn bench_memory_overhead(c: &mut Criterion) {
let cache = LocalRevocationCache::new(Duration::from_secs(60));
c.bench_function("cache_entry_insertion", |b| {
let mut counter = 0;
b.iter(|| {
let token_id = format!("token_{}", black_box(counter));
cache.check_revoked(&token_id, false);
counter += 1;
});
});
}
/// Benchmark 10: Realistic production workload
fn bench_production_workload(c: &mut Criterion) {
let cache = LocalRevocationCache::new(Duration::from_secs(60));
// Simulate production: 1000 active users, 95% cache hit rate, 1% revoked
for i in 0..1000 {
let token_id = if i % 100 == 0 {
format!("revoked_token_{}", i)
} else {
format!("valid_token_{}", i)
};
cache.check_revoked(&token_id, false);
}
c.bench_function("production_workload_simulation", |b| {
let mut counter = 0;
b.iter(|| {
// 95% hits to existing tokens, 5% new tokens
let token_id = if counter % 20 < 19 {
// Cache hit - existing token
let idx = counter % 1000;
if idx % 100 == 0 {
format!("revoked_token_{}", idx)
} else {
format!("valid_token_{}", idx)
}
} else {
// Cache miss - new token
format!("new_token_{}", counter)
};
let simulate_latency = counter % 20 >= 19;
let is_revoked = cache.check_revoked(&token_id, simulate_latency);
black_box(is_revoked);
counter += 1;
});
});
// Report final statistics
let (hits, misses, hit_rate) = cache.stats();
println!(
"\nProduction workload stats: {} hits, {} misses, {:.2}% hit rate",
hits, misses, hit_rate
);
}
criterion_group!(
revocation_cache_benches,
bench_cache_hit_latency,
bench_cache_miss_latency,
bench_hot_token_pattern,
bench_ttl_expiration,
bench_cache_size_impact,
bench_concurrent_access,
bench_mixed_revocation,
bench_cache_vs_no_cache,
bench_memory_overhead,
bench_production_workload
);
criterion_main!(revocation_cache_benches);