graphyn-store 0.1.0

Persistence layer for Graphyn — RocksDB graph storage and LRU cache
Documentation
use std::collections::HashMap;
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::RwLock;

#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct CacheStats {
    pub hits: u64,
    pub misses: u64,
    pub evictions: u64,
    pub size: usize,
}

#[derive(Debug, Clone)]
struct CacheEntry {
    value: String,
    last_access_tick: u64,
}

pub struct HotQueryCache {
    entries: RwLock<HashMap<String, CacheEntry>>,
    max_entries: usize,
    tick: AtomicU64,
    hits: AtomicU64,
    misses: AtomicU64,
    evictions: AtomicU64,
}

impl HotQueryCache {
    pub fn new(max_entries: usize) -> Self {
        Self {
            entries: RwLock::new(HashMap::new()),
            max_entries,
            tick: AtomicU64::new(1),
            hits: AtomicU64::new(0),
            misses: AtomicU64::new(0),
            evictions: AtomicU64::new(0),
        }
    }

    pub fn put(&self, key: String, value: String) {
        let current_tick = self.tick.fetch_add(1, Ordering::Relaxed);
        if let Ok(mut entries) = self.entries.write() {
            entries.insert(
                key,
                CacheEntry {
                    value,
                    last_access_tick: current_tick,
                },
            );
            evict_if_needed(&mut entries, self.max_entries, &self.evictions);
        }
    }

    pub fn get(&self, key: &str) -> Option<String> {
        if let Ok(mut entries) = self.entries.write() {
            if let Some(entry) = entries.get_mut(key) {
                self.hits.fetch_add(1, Ordering::Relaxed);
                entry.last_access_tick = self.tick.fetch_add(1, Ordering::Relaxed);
                return Some(entry.value.clone());
            }
        }

        self.misses.fetch_add(1, Ordering::Relaxed);
        None
    }

    pub fn invalidate(&self, key: &str) {
        if let Ok(mut entries) = self.entries.write() {
            entries.remove(key);
        }
    }

    pub fn clear(&self) {
        if let Ok(mut entries) = self.entries.write() {
            entries.clear();
        }
    }

    pub fn stats(&self) -> CacheStats {
        let size = self
            .entries
            .read()
            .map(|entries| entries.len())
            .unwrap_or_default();

        CacheStats {
            hits: self.hits.load(Ordering::Relaxed),
            misses: self.misses.load(Ordering::Relaxed),
            evictions: self.evictions.load(Ordering::Relaxed),
            size,
        }
    }
}

fn evict_if_needed(
    entries: &mut HashMap<String, CacheEntry>,
    max_entries: usize,
    evictions: &AtomicU64,
) {
    while entries.len() > max_entries {
        let mut oldest_key = None;
        let mut oldest_tick = u64::MAX;

        for (key, entry) in entries.iter() {
            if entry.last_access_tick < oldest_tick {
                oldest_tick = entry.last_access_tick;
                oldest_key = Some(key.clone());
            }
        }

        let Some(oldest_key) = oldest_key else {
            break;
        };

        if entries.remove(&oldest_key).is_some() {
            evictions.fetch_add(1, Ordering::Relaxed);
        } else {
            break;
        }
    }
}