High-performance implementation of plcbundle written in Rust

refactor(cache): remove bundle cache

+5 -218
-170
src/cache.rs
··· 1 - //! In-memory cache for bundle operations with simple capacity-based eviction 2 - // src/cache.rs 3 - use crate::operations::Operation; 4 - use std::collections::HashMap; 5 - use std::sync::RwLock; 6 - 7 - pub struct BundleCache { 8 - capacity: usize, 9 - cache: RwLock<HashMap<u32, Vec<Operation>>>, 10 - } 11 - 12 - impl BundleCache { 13 - pub fn new(capacity: usize) -> Self { 14 - Self { 15 - capacity, 16 - cache: RwLock::new(HashMap::new()), 17 - } 18 - } 19 - 20 - pub fn get(&self, bundle: u32) -> Option<Vec<Operation>> { 21 - self.cache.read().unwrap().get(&bundle).cloned() 22 - } 23 - 24 - pub fn insert(&self, bundle: u32, ops: Vec<Operation>) { 25 - let mut cache = self.cache.write().unwrap(); 26 - if cache.len() >= self.capacity 27 - && let Some(&key) = cache.keys().next() 28 - { 29 - cache.remove(&key); 30 - } 31 - cache.insert(bundle, ops); 32 - } 33 - 34 - pub fn contains(&self, bundle: u32) -> bool { 35 - self.cache.read().unwrap().contains_key(&bundle) 36 - } 37 - 38 - pub fn remove(&self, bundle: u32) { 39 - self.cache.write().unwrap().remove(&bundle); 40 - } 41 - 42 - pub fn clear(&self) { 43 - self.cache.write().unwrap().clear(); 44 - } 45 - } 46 - 47 - #[cfg(test)] 48 - mod tests { 49 - use super::*; 50 - use crate::operations::Operation; 51 - use sonic_rs::Value; 52 - 53 - fn create_test_operation(did: &str) -> Operation { 54 - Operation { 55 - did: did.to_string(), 56 - operation: Value::new(), 57 - cid: None, 58 - nullified: false, 59 - created_at: "2024-01-01T00:00:00Z".to_string(), 60 - extra: Value::new(), 61 - raw_json: None, 62 - } 63 - } 64 - 65 - #[test] 66 - fn test_cache_new() { 67 - let cache = BundleCache::new(10); 68 - assert!(!cache.contains(1)); 69 - } 70 - 71 - #[test] 72 - fn test_cache_insert_and_get() { 73 - let cache = BundleCache::new(10); 74 - let ops = vec![ 75 - create_test_operation("did:plc:test1"), 76 - create_test_operation("did:plc:test2"), 77 - ]; 78 - 79 - cache.insert(1, ops.clone()); 80 - assert!(cache.contains(1)); 81 - 82 - let retrieved = cache.get(1); 83 - assert!(retrieved.is_some()); 84 - assert_eq!(retrieved.unwrap().len(), 2); 85 - } 86 - 87 - #[test] 88 - fn test_cache_contains() { 89 - let cache = BundleCache::new(10); 90 - assert!(!cache.contains(1)); 91 - 92 - cache.insert(1, vec![create_test_operation("did:plc:test1")]); 93 - assert!(cache.contains(1)); 94 - assert!(!cache.contains(2)); 95 - } 96 - 97 - #[test] 98 - fn test_cache_remove() { 99 - let cache = BundleCache::new(10); 100 - cache.insert(1, vec![create_test_operation("did:plc:test1")]); 101 - assert!(cache.contains(1)); 102 - 103 - cache.remove(1); 104 - assert!(!cache.contains(1)); 105 - } 106 - 107 - #[test] 108 - fn test_cache_clear() { 109 - let cache = BundleCache::new(10); 110 - cache.insert(1, vec![create_test_operation("did:plc:test1")]); 111 - cache.insert(2, vec![create_test_operation("did:plc:test2")]); 112 - assert!(cache.contains(1)); 113 - assert!(cache.contains(2)); 114 - 115 - cache.clear(); 116 - assert!(!cache.contains(1)); 117 - assert!(!cache.contains(2)); 118 - } 119 - 120 - #[test] 121 - fn test_cache_capacity_eviction() { 122 - let cache = BundleCache::new(2); 123 - 124 - // Fill cache to capacity 125 - cache.insert(1, vec![create_test_operation("did:plc:test1")]); 126 - cache.insert(2, vec![create_test_operation("did:plc:test2")]); 127 - assert!(cache.contains(1)); 128 - assert!(cache.contains(2)); 129 - 130 - // Adding third should evict one (HashMap iteration order is not guaranteed) 131 - cache.insert(3, vec![create_test_operation("did:plc:test3")]); 132 - // One of the first two should be evicted, and 3 should be present 133 - assert!(cache.contains(3)); 134 - // Cache should only have 2 items 135 - let count = [cache.contains(1), cache.contains(2), cache.contains(3)] 136 - .iter() 137 - .filter(|&&x| x) 138 - .count(); 139 - assert_eq!(count, 2); 140 - } 141 - 142 - #[test] 143 - fn test_cache_multiple_bundles() { 144 - let cache = BundleCache::new(10); 145 - 146 - for i in 1..=5 { 147 - cache.insert( 148 - i, 149 - vec![create_test_operation(&format!("did:plc:test{}", i))], 150 - ); 151 - } 152 - 153 - for i in 1..=5 { 154 - assert!(cache.contains(i)); 155 - let ops = cache.get(i).unwrap(); 156 - assert_eq!(ops.len(), 1); 157 - assert_eq!(ops[0].did, format!("did:plc:test{}", i)); 158 - } 159 - } 160 - 161 - #[test] 162 - fn test_cache_empty_operations() { 163 - let cache = BundleCache::new(10); 164 - cache.insert(1, vec![]); 165 - 166 - let ops = cache.get(1); 167 - assert!(ops.is_some()); 168 - assert_eq!(ops.unwrap().len(), 0); 169 - } 170 - }
-1
src/lib.rs
··· 31 31 //! 32 32 // src/lib.rs 33 33 pub(crate) mod bundle_format; 34 - pub(crate) mod cache; 35 34 pub mod constants; 36 35 pub mod did_index; 37 36 pub(crate) mod ffi;
+5 -47
src/manager.rs
··· 5 5 use crate::iterators::{ExportIterator, QueryIterator, RangeIterator}; 6 6 use crate::operations::{Operation, OperationFilter, OperationRequest, OperationWithLocation}; 7 7 use crate::options::QueryMode; 8 - use crate::{cache, did_index, handle_resolver, mempool, verification}; 8 + use crate::{did_index, handle_resolver, mempool, verification}; 9 9 use anyhow::{Context, Result}; 10 10 use chrono::{DateTime, Utc}; 11 11 use std::collections::{HashMap, HashSet}; ··· 84 84 /// ``` 85 85 pub struct BundleManager { 86 86 directory: PathBuf, 87 - index: Arc<RwLock<Index>>, 88 - cache: Arc<cache::BundleCache>, 87 + index: Arc<RwLock<Index>>, 89 88 did_index: Arc<RwLock<Option<did_index::Manager>>>, 90 89 stats: Arc<RwLock<ManagerStats>>, 91 90 mempool: Arc<RwLock<Option<mempool::Mempool>>>, ··· 246 245 let manager = Self { 247 246 directory: directory.clone(), 248 247 index: Arc::new(RwLock::new(index)), 249 - cache: Arc::new(cache::BundleCache::new(100)), 250 248 did_index: Arc::new(RwLock::new(None)), 251 249 stats: Arc::new(RwLock::new(ManagerStats::default())), 252 250 mempool: Arc::new(RwLock::new(None)), ··· 323 321 pub fn load_bundle(&self, num: u32, options: LoadOptions) -> Result<LoadResult> { 324 322 self.stats.write().unwrap().bundles_loaded += 1; 325 323 326 - if let Some(cached) = self.cache.get(num) { 327 - self.stats.write().unwrap().cache_hits += 1; 328 - return Ok(self.filter_load_result(cached, &options)); 329 - } 330 - 331 - self.stats.write().unwrap().cache_misses += 1; 332 - 333 324 let bundle_path = constants::bundle_path(&self.directory, num); 334 325 let operations = self.load_bundle_from_disk(&bundle_path)?; 335 - 336 - if options.cache { 337 - self.cache.insert(num, operations.clone()); 338 - } 339 326 340 327 Ok(self.filter_load_result(operations, &options)) 341 328 } ··· 932 919 let mut info = BundleInfo { 933 920 metadata: metadata.clone(), 934 921 exists: constants::bundle_path(&self.directory, num).exists(), 935 - cached: self.cache.contains(num), 936 922 operations: None, 937 923 size_info: None, 938 924 }; ··· 995 981 if path.exists() { 996 982 std::fs::remove_file(path)?; 997 983 } 998 - self.cache.remove(*bundle_num); 999 984 } 1000 985 1001 986 let mut index = self.index.write().unwrap(); ··· 1020 1005 } 1021 1006 1022 1007 // === Cache Hints === 1008 + pub fn prefetch_bundles(&self, _nums: Vec<u32>) -> Result<()> { Ok(()) } 1009 + 1023 1010 /// Preload specified bundles into the cache for faster subsequent access 1024 - pub fn prefetch_bundles(&self, nums: Vec<u32>) -> Result<()> { 1025 - for num in nums { 1026 - self.load_bundle( 1027 - num, 1028 - LoadOptions { 1029 - cache: true, 1030 - ..Default::default() 1031 - }, 1032 - )?; 1033 - } 1034 - Ok(()) 1035 - } 1036 - 1037 - /// Warm up caches according to strategy (recent, range, all) 1038 - pub fn warm_up(&self, spec: WarmUpSpec) -> Result<()> { 1039 - let bundles: Vec<u32> = match spec.strategy { 1040 - WarmUpStrategy::Recent(n) => { 1041 - let last = self.get_last_bundle(); 1042 - (last.saturating_sub(n - 1)..=last).collect() 1043 - } 1044 - WarmUpStrategy::Range(start, end) => (start..=end).collect(), 1045 - WarmUpStrategy::All => (1..=self.get_last_bundle()).collect(), 1046 - }; 1047 - 1048 - self.prefetch_bundles(bundles) 1049 - } 1011 + pub fn warm_up(&self, _spec: WarmUpSpec) -> Result<()> { Ok(()) } 1050 1012 1051 1013 // === DID Index === 1052 1014 pub fn build_did_index<F>( ··· 1309 1271 } 1310 1272 1311 1273 pub fn clear_caches(&self) { 1312 - self.cache.clear(); 1313 1274 self.stats.write().unwrap().cache_hits = 0; 1314 1275 self.stats.write().unwrap().cache_misses = 0; 1315 1276 } ··· 3555 3516 Self { 3556 3517 directory: self.directory.clone(), 3557 3518 index: Arc::clone(&self.index), 3558 - cache: Arc::clone(&self.cache), 3559 3519 did_index: Arc::clone(&self.did_index), 3560 3520 stats: Arc::clone(&self.stats), 3561 3521 mempool: Arc::clone(&self.mempool), ··· 3806 3766 if bundle_path.exists() { 3807 3767 std::fs::remove_file(bundle_path)?; 3808 3768 } 3809 - self.cache.remove(bundle_num); 3810 3769 Ok(()) 3811 3770 } 3812 3771 } ··· 3952 3911 pub struct BundleInfo { 3953 3912 pub metadata: BundleMetadata, 3954 3913 pub exists: bool, 3955 - pub cached: bool, 3956 3914 pub operations: Option<Vec<Operation>>, 3957 3915 pub size_info: Option<SizeInfo>, 3958 3916 }