use super::lru::LruCache;
use super::property::PropertyCache;
use super::query::QueryCache;
use super::traversal::{CachedNeighbors, TraversalCache, TraversalDirection};
use crate::types::{CacheOptions, CacheStats, ETypeId, Edge, NodeId, PropKeyId, PropValue};
#[cfg(test)]
use crate::types::{PropertyCacheConfig, QueryCacheConfig, TraversalCacheConfig};
const DEFAULT_KEY_CACHE_SIZE: usize = 10000;
#[derive(Debug, Clone, Default)]
pub struct CacheManagerStats {
pub property_cache_hits: u64,
pub property_cache_misses: u64,
pub property_cache_size: usize,
pub property_cache_max_size: usize,
pub traversal_cache_hits: u64,
pub traversal_cache_misses: u64,
pub traversal_cache_size: usize,
pub traversal_cache_max_size: usize,
pub query_cache_hits: u64,
pub query_cache_misses: u64,
pub query_cache_size: usize,
pub query_cache_max_size: usize,
pub key_cache_hits: u64,
pub key_cache_misses: u64,
pub key_cache_size: usize,
pub key_cache_max_size: usize,
}
impl CacheManagerStats {
pub fn total_hits(&self) -> u64 {
self.property_cache_hits
+ self.traversal_cache_hits
+ self.query_cache_hits
+ self.key_cache_hits
}
pub fn total_misses(&self) -> u64 {
self.property_cache_misses
+ self.traversal_cache_misses
+ self.query_cache_misses
+ self.key_cache_misses
}
pub fn hit_rate(&self) -> f64 {
let total = self.total_hits() + self.total_misses();
if total > 0 {
self.total_hits() as f64 / total as f64
} else {
0.0
}
}
pub fn to_cache_stats(&self) -> CacheStats {
CacheStats {
property_cache_hits: self.property_cache_hits,
property_cache_misses: self.property_cache_misses,
property_cache_size: self.property_cache_size,
traversal_cache_hits: self.traversal_cache_hits,
traversal_cache_misses: self.traversal_cache_misses,
traversal_cache_size: self.traversal_cache_size,
query_cache_hits: self.query_cache_hits,
query_cache_misses: self.query_cache_misses,
query_cache_size: self.query_cache_size,
}
}
}
pub struct CacheManager {
property_cache: PropertyCache,
traversal_cache: TraversalCache,
query_cache: QueryCache,
key_cache: LruCache<String, Option<NodeId>>,
key_cache_hits: u64,
key_cache_misses: u64,
enabled: bool,
}
impl CacheManager {
pub fn new(options: CacheOptions) -> Self {
let enabled = options.enabled;
let prop_config = options.property_cache.unwrap_or_default();
let trav_config = options.traversal_cache.unwrap_or_default();
let query_config = options.query_cache.unwrap_or_default();
Self {
property_cache: PropertyCache::new(prop_config),
traversal_cache: TraversalCache::new(trav_config),
query_cache: QueryCache::new(query_config),
key_cache: LruCache::new(DEFAULT_KEY_CACHE_SIZE),
key_cache_hits: 0,
key_cache_misses: 0,
enabled,
}
}
pub fn with_key_cache_size(options: CacheOptions, key_cache_size: usize) -> Self {
let enabled = options.enabled;
let prop_config = options.property_cache.unwrap_or_default();
let trav_config = options.traversal_cache.unwrap_or_default();
let query_config = options.query_cache.unwrap_or_default();
Self {
property_cache: PropertyCache::new(prop_config),
traversal_cache: TraversalCache::new(trav_config),
query_cache: QueryCache::new(query_config),
key_cache: LruCache::new(key_cache_size),
key_cache_hits: 0,
key_cache_misses: 0,
enabled,
}
}
pub fn disabled() -> Self {
Self::new(CacheOptions {
enabled: false,
..Default::default()
})
}
pub fn is_enabled(&self) -> bool {
self.enabled
}
pub fn get_node_prop(
&mut self,
node_id: NodeId,
prop_key_id: PropKeyId,
) -> Option<&Option<PropValue>> {
if !self.enabled {
return None;
}
self.property_cache.get_node_prop(node_id, prop_key_id)
}
pub fn set_node_prop(
&mut self,
node_id: NodeId,
prop_key_id: PropKeyId,
value: Option<PropValue>,
) {
if !self.enabled {
return;
}
self
.property_cache
.set_node_prop(node_id, prop_key_id, value);
}
pub fn get_edge_prop(
&mut self,
src: NodeId,
etype: ETypeId,
dst: NodeId,
prop_key_id: PropKeyId,
) -> Option<&Option<PropValue>> {
if !self.enabled {
return None;
}
self
.property_cache
.get_edge_prop(src, etype, dst, prop_key_id)
}
pub fn set_edge_prop(
&mut self,
src: NodeId,
etype: ETypeId,
dst: NodeId,
prop_key_id: PropKeyId,
value: Option<PropValue>,
) {
if !self.enabled {
return;
}
self
.property_cache
.set_edge_prop(src, etype, dst, prop_key_id, value);
}
pub fn get_traversal(
&mut self,
node_id: NodeId,
etype: Option<ETypeId>,
direction: TraversalDirection,
) -> Option<&CachedNeighbors> {
if !self.enabled {
return None;
}
self.traversal_cache.get(node_id, etype, direction)
}
pub fn set_traversal(
&mut self,
node_id: NodeId,
etype: Option<ETypeId>,
direction: TraversalDirection,
neighbors: Vec<Edge>,
) {
if !self.enabled {
return;
}
self
.traversal_cache
.set(node_id, etype, direction, neighbors);
}
pub fn get_query<T: 'static>(&mut self, query_key: &str) -> Option<&T> {
if !self.enabled {
return None;
}
self.query_cache.get(query_key)
}
pub fn set_query<T: 'static + Send>(&mut self, query_key: String, value: T) {
if !self.enabled {
return;
}
self.query_cache.set(query_key, value);
}
pub fn generate_query_key<K, V>(&self, params: impl IntoIterator<Item = (K, V)>) -> String
where
K: AsRef<str> + Ord,
V: std::fmt::Debug,
{
super::query::generate_key_params(params)
}
pub fn get_node_by_key(&mut self, key: &str) -> Option<&Option<NodeId>> {
if !self.enabled {
return None;
}
let result = self.key_cache.get(key);
if result.is_some() {
self.key_cache_hits += 1;
} else {
self.key_cache_misses += 1;
}
result
}
pub fn set_node_by_key(&mut self, key: String, node_id: Option<NodeId>) {
if !self.enabled {
return;
}
self.key_cache.set(key, node_id);
}
pub fn invalidate_key(&mut self, key: &str) {
if !self.enabled {
return;
}
self.key_cache.delete(key);
}
pub fn invalidate_node(&mut self, node_id: NodeId) {
if !self.enabled {
return;
}
self.property_cache.invalidate_node(node_id);
self.traversal_cache.invalidate_node(node_id);
}
pub fn invalidate_edge(&mut self, src: NodeId, etype: ETypeId, dst: NodeId) {
if !self.enabled {
return;
}
self.property_cache.invalidate_edge(src, etype, dst);
self.traversal_cache.invalidate_edge(src, etype, dst);
}
pub fn clear(&mut self) {
if !self.enabled {
return;
}
self.property_cache.clear();
self.traversal_cache.clear();
self.query_cache.clear();
self.key_cache.clear();
self.key_cache_hits = 0;
self.key_cache_misses = 0;
}
pub fn clear_query_cache(&mut self) {
if !self.enabled {
return;
}
self.query_cache.clear();
}
pub fn clear_key_cache(&mut self) {
if !self.enabled {
return;
}
self.key_cache.clear();
self.key_cache_hits = 0;
self.key_cache_misses = 0;
}
pub fn clear_property_cache(&mut self) {
if !self.enabled {
return;
}
self.property_cache.clear();
}
pub fn clear_traversal_cache(&mut self) {
if !self.enabled {
return;
}
self.traversal_cache.clear();
}
pub fn stats(&self) -> CacheManagerStats {
let prop_stats = self.property_cache.stats();
let trav_stats = self.traversal_cache.stats();
let query_stats = self.query_cache.stats();
CacheManagerStats {
property_cache_hits: prop_stats.hits,
property_cache_misses: prop_stats.misses,
property_cache_size: prop_stats.node_cache_size + prop_stats.edge_cache_size,
property_cache_max_size: prop_stats.max_node_cache_size + prop_stats.max_edge_cache_size,
traversal_cache_hits: trav_stats.hits,
traversal_cache_misses: trav_stats.misses,
traversal_cache_size: trav_stats.cache_size,
traversal_cache_max_size: trav_stats.max_cache_size,
query_cache_hits: query_stats.hits,
query_cache_misses: query_stats.misses,
query_cache_size: query_stats.cache_size,
query_cache_max_size: query_stats.max_cache_size,
key_cache_hits: self.key_cache_hits,
key_cache_misses: self.key_cache_misses,
key_cache_size: self.key_cache.len(),
key_cache_max_size: self.key_cache.max_size(),
}
}
pub fn get_stats(&self) -> CacheStats {
self.stats().to_cache_stats()
}
pub fn reset_stats(&mut self) {
self.property_cache.reset_stats();
self.traversal_cache.reset_stats();
self.query_cache.reset_stats();
self.key_cache_hits = 0;
self.key_cache_misses = 0;
}
}
impl Default for CacheManager {
fn default() -> Self {
Self::new(CacheOptions::default())
}
}
#[cfg(test)]
mod tests {
use super::*;
fn make_enabled_cache() -> CacheManager {
CacheManager::new(CacheOptions {
enabled: true,
property_cache: Some(PropertyCacheConfig {
max_node_props: 100,
max_edge_props: 100,
}),
traversal_cache: Some(TraversalCacheConfig {
max_entries: 100,
max_neighbors_per_entry: 10,
}),
query_cache: Some(QueryCacheConfig {
max_entries: 100,
ttl_ms: None,
}),
})
}
#[test]
fn test_new_enabled() {
let cache = make_enabled_cache();
assert!(cache.is_enabled());
}
#[test]
fn test_disabled() {
let cache = CacheManager::disabled();
assert!(!cache.is_enabled());
}
#[test]
fn test_default() {
let cache = CacheManager::default();
assert!(!cache.is_enabled());
}
#[test]
fn test_disabled_noop() {
let mut cache = CacheManager::disabled();
cache.set_node_prop(1, 10, Some(PropValue::I64(42)));
assert_eq!(cache.get_node_prop(1, 10), None);
cache.set_edge_prop(1, 1, 2, 10, Some(PropValue::I64(100)));
assert_eq!(cache.get_edge_prop(1, 1, 2, 10), None);
cache.set_traversal(1, Some(1), TraversalDirection::Out, vec![]);
assert!(cache
.get_traversal(1, Some(1), TraversalDirection::Out)
.is_none());
cache.set_query("key".to_string(), 42i32);
let result: Option<&i32> = cache.get_query("key");
assert!(result.is_none());
cache.set_node_by_key("alice".to_string(), Some(1));
assert_eq!(cache.get_node_by_key("alice"), None);
}
#[test]
fn test_property_cache() {
let mut cache = make_enabled_cache();
cache.set_node_prop(1, 10, Some(PropValue::I64(42)));
assert_eq!(cache.get_node_prop(1, 10), Some(&Some(PropValue::I64(42))));
cache.set_edge_prop(1, 1, 2, 10, Some(PropValue::String("weight".to_string())));
assert_eq!(
cache.get_edge_prop(1, 1, 2, 10),
Some(&Some(PropValue::String("weight".to_string())))
);
}
#[test]
fn test_traversal_cache() {
let mut cache = make_enabled_cache();
let neighbors = vec![
Edge {
src: 1,
etype: 1,
dst: 2,
},
Edge {
src: 1,
etype: 1,
dst: 3,
},
];
cache.set_traversal(1, Some(1), TraversalDirection::Out, neighbors.clone());
let result = cache.get_traversal(1, Some(1), TraversalDirection::Out);
assert!(result.is_some());
assert_eq!(result.unwrap().neighbors.len(), 2);
}
#[test]
fn test_query_cache() {
let mut cache = make_enabled_cache();
cache.set_query("query1".to_string(), vec![1u64, 2, 3]);
let result: Option<&Vec<u64>> = cache.get_query("query1");
assert!(result.is_some());
assert_eq!(result.unwrap(), &vec![1, 2, 3]);
}
#[test]
fn test_key_cache() {
let mut cache = make_enabled_cache();
cache.set_node_by_key("alice".to_string(), Some(1));
assert_eq!(cache.get_node_by_key("alice"), Some(&Some(1)));
cache.set_node_by_key("nonexistent".to_string(), None);
assert_eq!(cache.get_node_by_key("nonexistent"), Some(&None));
assert_eq!(cache.get_node_by_key("unknown"), None);
}
#[test]
fn test_invalidate_node() {
let mut cache = make_enabled_cache();
cache.set_node_prop(1, 10, Some(PropValue::I64(42)));
cache.set_node_prop(1, 20, Some(PropValue::Bool(true)));
cache.set_traversal(
1,
Some(1),
TraversalDirection::Out,
vec![Edge {
src: 1,
etype: 1,
dst: 2,
}],
);
cache.set_node_prop(2, 10, Some(PropValue::I64(100)));
cache.invalidate_node(1);
assert_eq!(cache.get_node_prop(1, 10), None);
assert_eq!(cache.get_node_prop(1, 20), None);
assert!(cache
.get_traversal(1, Some(1), TraversalDirection::Out)
.is_none());
assert_eq!(cache.get_node_prop(2, 10), Some(&Some(PropValue::I64(100))));
}
#[test]
fn test_invalidate_edge() {
let mut cache = make_enabled_cache();
cache.set_edge_prop(1, 1, 2, 10, Some(PropValue::I64(42)));
cache.set_traversal(
1,
Some(1),
TraversalDirection::Out,
vec![Edge {
src: 1,
etype: 1,
dst: 2,
}],
);
cache.set_traversal(
2,
Some(1),
TraversalDirection::In,
vec![Edge {
src: 1,
etype: 1,
dst: 2,
}],
);
cache.set_edge_prop(1, 2, 3, 10, Some(PropValue::I64(100)));
cache.invalidate_edge(1, 1, 2);
assert_eq!(cache.get_edge_prop(1, 1, 2, 10), None);
assert_eq!(
cache.get_edge_prop(1, 2, 3, 10),
Some(&Some(PropValue::I64(100)))
);
}
#[test]
fn test_invalidate_key() {
let mut cache = make_enabled_cache();
cache.set_node_by_key("alice".to_string(), Some(1));
cache.set_node_by_key("bob".to_string(), Some(2));
cache.invalidate_key("alice");
assert_eq!(cache.get_node_by_key("alice"), None);
assert_eq!(cache.get_node_by_key("bob"), Some(&Some(2)));
}
#[test]
fn test_clear() {
let mut cache = make_enabled_cache();
cache.set_node_prop(1, 10, Some(PropValue::I64(42)));
cache.set_edge_prop(1, 1, 2, 10, Some(PropValue::I64(100)));
cache.set_traversal(1, Some(1), TraversalDirection::Out, vec![]);
cache.set_query("q1".to_string(), 42i32);
cache.set_node_by_key("alice".to_string(), Some(1));
cache.clear();
assert_eq!(cache.get_node_prop(1, 10), None);
assert_eq!(cache.get_edge_prop(1, 1, 2, 10), None);
assert!(cache
.get_traversal(1, Some(1), TraversalDirection::Out)
.is_none());
let q: Option<&i32> = cache.get_query("q1");
assert!(q.is_none());
assert_eq!(cache.get_node_by_key("alice"), None);
}
#[test]
fn test_clear_individual_caches() {
let mut cache = make_enabled_cache();
cache.set_node_prop(1, 10, Some(PropValue::I64(42)));
cache.set_traversal(1, Some(1), TraversalDirection::Out, vec![]);
cache.set_query("q1".to_string(), 42i32);
cache.set_node_by_key("alice".to_string(), Some(1));
cache.clear_property_cache();
assert_eq!(cache.get_node_prop(1, 10), None);
assert!(cache
.get_traversal(1, Some(1), TraversalDirection::Out)
.is_some());
cache.clear_traversal_cache();
assert!(cache
.get_traversal(1, Some(1), TraversalDirection::Out)
.is_none());
let q: Option<&i32> = cache.get_query("q1");
assert!(q.is_some());
cache.clear_query_cache();
let q: Option<&i32> = cache.get_query("q1");
assert!(q.is_none());
assert_eq!(cache.get_node_by_key("alice"), Some(&Some(1)));
cache.clear_key_cache();
assert_eq!(cache.get_node_by_key("alice"), None);
}
#[test]
fn test_stats() {
let mut cache = make_enabled_cache();
cache.set_node_prop(1, 10, Some(PropValue::I64(42)));
cache.get_node_prop(1, 10); cache.get_node_prop(999, 10);
cache.set_traversal(1, Some(1), TraversalDirection::Out, vec![]);
cache.get_traversal(1, Some(1), TraversalDirection::Out); cache.get_traversal(999, Some(1), TraversalDirection::Out);
cache.set_query("q1".to_string(), 42i32);
let _: Option<&i32> = cache.get_query("q1"); let _: Option<&i32> = cache.get_query("q2");
cache.set_node_by_key("alice".to_string(), Some(1));
cache.get_node_by_key("alice"); cache.get_node_by_key("bob");
let stats = cache.stats();
assert_eq!(stats.property_cache_hits, 1);
assert_eq!(stats.property_cache_misses, 1);
assert_eq!(stats.traversal_cache_hits, 1);
assert_eq!(stats.traversal_cache_misses, 1);
assert_eq!(stats.query_cache_hits, 1);
assert_eq!(stats.query_cache_misses, 1);
assert_eq!(stats.key_cache_hits, 1);
assert_eq!(stats.key_cache_misses, 1);
assert_eq!(stats.total_hits(), 4);
assert_eq!(stats.total_misses(), 4);
assert_eq!(stats.hit_rate(), 0.5);
}
#[test]
fn test_reset_stats() {
let mut cache = make_enabled_cache();
cache.set_node_prop(1, 10, Some(PropValue::I64(42)));
cache.get_node_prop(1, 10);
cache.get_node_by_key("alice");
assert!(cache.stats().total_hits() > 0 || cache.stats().total_misses() > 0);
cache.reset_stats();
assert_eq!(cache.stats().property_cache_hits, 0);
assert_eq!(cache.stats().property_cache_misses, 0);
assert_eq!(cache.stats().key_cache_hits, 0);
assert_eq!(cache.stats().key_cache_misses, 0);
}
#[test]
fn test_generate_query_key() {
let cache = make_enabled_cache();
let params = vec![("b", 2), ("a", 1)];
let key = cache.generate_query_key(params);
assert_eq!(key, "a:1|b:2");
}
#[test]
fn test_with_key_cache_size() {
let cache = CacheManager::with_key_cache_size(
CacheOptions {
enabled: true,
..Default::default()
},
500,
);
assert!(cache.is_enabled());
assert_eq!(cache.stats().key_cache_max_size, 500);
}
}