use lru::LruCache;
use std::hash::{Hash, Hasher};
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::Mutex;
const PER_ENTRY_OVERHEAD: usize = 64;
#[inline]
const fn entry_cost(key_len: usize) -> usize {
key_len.saturating_add(PER_ENTRY_OVERHEAD)
}
pub const DEFAULT_KEY_CACHE_BYTES: usize = 512 * 1024;
pub const DEFAULT_KEY_CACHE_SHARDS: usize = 16;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct PartitionLoc {
pub data_offset: u64,
pub data_size: u32,
}
impl PartitionLoc {
#[inline]
pub fn new(data_offset: u64, data_size: u32) -> Self {
Self {
data_offset,
data_size,
}
}
#[inline]
pub fn offset_only(data_offset: u64) -> Self {
Self {
data_offset,
data_size: 0,
}
}
}
struct Shard {
lru: LruCache<Box<[u8]>, PartitionLoc>,
current_bytes: usize,
}
impl Shard {
fn new() -> Self {
Self {
lru: LruCache::unbounded(),
current_bytes: 0,
}
}
}
pub struct KeyOffsetCache {
shards: Box<[Mutex<Shard>]>,
per_shard_bytes: usize,
mask: usize,
disabled: bool,
hits: AtomicU64,
misses: AtomicU64,
evictions: AtomicU64,
}
impl std::fmt::Debug for KeyOffsetCache {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("KeyOffsetCache")
.field("shards", &self.shards.len())
.field("per_shard_bytes", &self.per_shard_bytes)
.field("disabled", &self.disabled)
.field("len", &self.len())
.field("resident_bytes", &self.resident_bytes())
.field("hits", &self.hits.load(Ordering::Relaxed))
.field("misses", &self.misses.load(Ordering::Relaxed))
.field("evictions", &self.evictions.load(Ordering::Relaxed))
.finish()
}
}
#[derive(Debug, Clone, Copy, Default)]
pub(crate) struct KeyCacheSnapshot {
pub hits: u64,
pub misses: u64,
pub evictions: u64,
pub resident_bytes: usize,
pub capacity_bytes: usize,
}
impl KeyOffsetCache {
pub fn with_budget_bytes(total_budget_bytes: usize) -> Self {
Self::with_budget_and_shards(total_budget_bytes, DEFAULT_KEY_CACHE_SHARDS)
}
pub fn with_budget_and_shards(total_budget_bytes: usize, shard_count: usize) -> Self {
let shard_count = shard_count.max(1).next_power_of_two();
let per_shard_bytes = (total_budget_bytes / shard_count).max(entry_cost(0));
let mut shards = Vec::with_capacity(shard_count);
for _ in 0..shard_count {
shards.push(Mutex::new(Shard::new()));
}
Self {
shards: shards.into_boxed_slice(),
per_shard_bytes,
mask: shard_count - 1,
disabled: false,
hits: AtomicU64::new(0),
misses: AtomicU64::new(0),
evictions: AtomicU64::new(0),
}
}
pub fn disabled() -> Self {
Self {
shards: vec![Mutex::new(Shard::new())].into_boxed_slice(),
per_shard_bytes: 0,
mask: 0,
disabled: true,
hits: AtomicU64::new(0),
misses: AtomicU64::new(0),
evictions: AtomicU64::new(0),
}
}
#[inline]
fn lock(m: &Mutex<Shard>) -> std::sync::MutexGuard<'_, Shard> {
m.lock().unwrap_or_else(|e| e.into_inner())
}
#[inline]
fn shard_for(&self, key: &[u8]) -> &Mutex<Shard> {
let mut h = std::collections::hash_map::DefaultHasher::new();
key.hash(&mut h);
let idx = (h.finish() as usize) & self.mask;
&self.shards[idx]
}
pub fn get(&self, key: &[u8]) -> Option<PartitionLoc> {
if self.disabled {
return None;
}
let mut guard = Self::lock(self.shard_for(key));
match guard.lru.get(key) {
Some(loc) => {
let loc = *loc;
drop(guard);
self.hits.fetch_add(1, Ordering::Relaxed);
Some(loc)
}
None => {
drop(guard);
self.misses.fetch_add(1, Ordering::Relaxed);
None
}
}
}
pub fn insert(&self, key: &[u8], loc: PartitionLoc) {
if self.disabled {
return;
}
let cost = entry_cost(key.len());
let mut guard = Self::lock(self.shard_for(key));
if guard.lru.put(Box::from(key), loc).is_some() {
guard.current_bytes = guard.current_bytes.saturating_sub(cost);
}
guard.current_bytes = guard.current_bytes.saturating_add(cost);
let mut evicted_here: u64 = 0;
while guard.current_bytes > self.per_shard_bytes && guard.lru.len() > 1 {
match guard.lru.pop_lru() {
Some((evicted_key, _)) => {
let reclaimed = entry_cost(evicted_key.len());
guard.current_bytes = guard.current_bytes.saturating_sub(reclaimed);
evicted_here += 1;
}
None => break,
}
}
drop(guard);
if evicted_here > 0 {
self.evictions.fetch_add(evicted_here, Ordering::Relaxed);
}
}
pub fn len(&self) -> usize {
self.shards.iter().map(|m| Self::lock(m).lru.len()).sum()
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn resident_bytes(&self) -> usize {
self.shards
.iter()
.map(|m| Self::lock(m).current_bytes)
.sum()
}
pub fn budget_bytes(&self) -> usize {
self.per_shard_bytes.saturating_mul(self.shards.len())
}
pub fn hit_count(&self) -> u64 {
self.hits.load(Ordering::Relaxed)
}
pub fn miss_count(&self) -> u64 {
self.misses.load(Ordering::Relaxed)
}
pub fn eviction_count(&self) -> u64 {
self.evictions.load(Ordering::Relaxed)
}
pub(crate) fn snapshot(&self) -> KeyCacheSnapshot {
KeyCacheSnapshot {
hits: self.hit_count(),
misses: self.miss_count(),
evictions: self.eviction_count(),
resident_bytes: self.resident_bytes(),
capacity_bytes: self.budget_bytes(),
}
}
}
impl Default for KeyOffsetCache {
fn default() -> Self {
Self::with_budget_bytes(DEFAULT_KEY_CACHE_BYTES)
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::Arc;
fn budget_for(n: usize, key_len: usize) -> usize {
entry_cost(key_len) * n
}
#[test]
fn eviction_order_lru_single_shard() {
let cache = KeyOffsetCache::with_budget_and_shards(budget_for(2, 5), 1);
let a = b"key-A".as_slice();
let b = b"key-B".as_slice();
let c = b"key-C".as_slice();
cache.insert(a, PartitionLoc::new(10, 100));
cache.insert(b, PartitionLoc::new(20, 200));
assert_eq!(cache.get(a), Some(PartitionLoc::new(10, 100)));
cache.insert(c, PartitionLoc::new(30, 300));
assert_eq!(
cache.get(a),
Some(PartitionLoc::new(10, 100)),
"A (recently used) must survive"
);
assert_eq!(
cache.get(c),
Some(PartitionLoc::new(30, 300)),
"C (just inserted) must survive"
);
assert_eq!(
cache.get(b),
None,
"B (least recently used) must be evicted"
);
assert_eq!(cache.len(), 2);
assert!(cache.resident_bytes() <= cache.budget_bytes());
}
#[test]
fn eviction_count_and_snapshot_track_real_activity() {
let cache = KeyOffsetCache::with_budget_and_shards(budget_for(2, 5), 1);
assert_eq!(cache.eviction_count(), 0);
cache.insert(b"key-A".as_slice(), PartitionLoc::new(10, 100));
cache.insert(b"key-B".as_slice(), PartitionLoc::new(20, 200));
assert_eq!(cache.eviction_count(), 0, "still within budget");
cache.insert(b"key-C".as_slice(), PartitionLoc::new(30, 300));
assert_eq!(cache.eviction_count(), 1);
cache.insert(b"key-D".as_slice(), PartitionLoc::new(40, 400));
assert_eq!(cache.eviction_count(), 2);
assert!(cache.get(b"key-D".as_slice()).is_some());
assert!(cache.get(b"absent".as_slice()).is_none());
let snap = cache.snapshot();
assert_eq!(snap.evictions, 2);
assert_eq!(snap.hits, 1);
assert_eq!(snap.misses, 1);
assert_eq!(snap.resident_bytes, cache.resident_bytes());
assert_eq!(snap.capacity_bytes, cache.budget_bytes());
let disabled = KeyOffsetCache::disabled();
disabled.insert(b"k".as_slice(), PartitionLoc::new(1, 1));
let dsnap = disabled.snapshot();
assert_eq!(dsnap.evictions, 0);
assert_eq!(dsnap.resident_bytes, 0);
assert_eq!(dsnap.capacity_bytes, 0);
}
#[test]
fn resident_bytes_bounded() {
let key_len = 6;
let cache = KeyOffsetCache::with_budget_and_shards(budget_for(4, key_len), 1);
for i in 0..50u64 {
let key = format!("key-{i:02}");
assert_eq!(key.len(), key_len, "test assumes fixed-width keys");
cache.insert(key.as_bytes(), PartitionLoc::new(i, i as u32));
assert!(
cache.resident_bytes() <= cache.budget_bytes(),
"resident {} exceeded budget {} after insert {}",
cache.resident_bytes(),
cache.budget_bytes(),
i
);
assert!(
cache.len() <= 4,
"resident count exceeded 4 after insert {i}"
);
}
}
#[test]
fn large_keys_evict_sooner_than_small_keys() {
let small_len = 4;
let budget = budget_for(4, small_len);
let cache = KeyOffsetCache::with_budget_and_shards(budget, 1);
let big = vec![b'x'; budget / 2];
let mut big1 = big.clone();
big1[0] = b'a';
let mut big2 = big.clone();
big2[0] = b'b';
cache.insert(&big1, PartitionLoc::new(1, 1));
cache.insert(&big2, PartitionLoc::new(2, 2));
assert!(cache.resident_bytes() <= cache.budget_bytes());
assert_eq!(cache.get(&big1), None, "LRU large key must be evicted");
assert_eq!(cache.get(&big2), Some(PartitionLoc::new(2, 2)));
}
#[test]
fn hit_returns_stored_location_no_alias() {
let cache = KeyOffsetCache::with_budget_and_shards(DEFAULT_KEY_CACHE_BYTES, 1);
let a = b"partition-A".as_slice();
let b = b"partition-B".as_slice();
cache.insert(a, PartitionLoc::new(63, 512));
cache.insert(b, PartitionLoc::new(125, 256));
assert_eq!(cache.get(a), Some(PartitionLoc::new(63, 512)));
assert_eq!(cache.get(b), Some(PartitionLoc::new(125, 256)));
assert_eq!(
cache.get(b"never-inserted".as_slice()),
None,
"a never-inserted key must miss (no fabricated hit)"
);
}
#[test]
fn reinsert_same_key_does_not_grow_resident_bytes() {
let cache = KeyOffsetCache::with_budget_and_shards(DEFAULT_KEY_CACHE_BYTES, 1);
let k = b"stable-key".as_slice();
cache.insert(k, PartitionLoc::new(1, 1));
let after_first = cache.resident_bytes();
cache.insert(k, PartitionLoc::new(2, 2));
cache.insert(k, PartitionLoc::new(3, 3));
assert_eq!(cache.len(), 1, "re-insert must not add a second entry");
assert_eq!(
cache.resident_bytes(),
after_first,
"re-inserting the same key must not grow resident bytes"
);
assert_eq!(cache.get(k), Some(PartitionLoc::new(3, 3)));
}
#[test]
fn disabled_cache_is_a_genuine_no_op() {
let cache = KeyOffsetCache::disabled();
assert_eq!(cache.budget_bytes(), 0);
cache.insert(b"k".as_slice(), PartitionLoc::new(1, 2));
assert_eq!(
cache.get(b"k".as_slice()),
None,
"disabled cache never retains"
);
assert_eq!(cache.len(), 0);
assert_eq!(cache.resident_bytes(), 0);
assert!(cache.is_empty());
assert_eq!(cache.hit_count(), 0);
assert_eq!(cache.miss_count(), 0);
}
#[test]
fn poisoned_lock_recovers() {
use std::panic::{catch_unwind, AssertUnwindSafe};
use std::thread;
let cache = Arc::new(KeyOffsetCache::with_budget_and_shards(
DEFAULT_KEY_CACHE_BYTES,
1,
));
cache.insert(b"k".as_slice(), PartitionLoc::new(7, 8));
let poison_cache = Arc::clone(&cache);
let _ = thread::spawn(move || {
let _guard = KeyOffsetCache::lock(&poison_cache.shards[0]);
panic!("intentional poison");
})
.join();
let res = catch_unwind(AssertUnwindSafe(|| {
let hit = cache.get(b"k".as_slice());
cache.insert(b"k2".as_slice(), PartitionLoc::new(9, 10));
hit
}));
assert_eq!(
res.ok(),
Some(Some(PartitionLoc::new(7, 8))),
"cache must recover from a poisoned lock"
);
}
#[test]
fn shard_count_rounds_to_power_of_two() {
let cache = KeyOffsetCache::with_budget_and_shards(DEFAULT_KEY_CACHE_BYTES, 10);
assert_eq!(cache.shards.len(), 16);
assert_eq!(cache.mask, 15);
}
}