sochdb_storage/
lazy_namespace.rs1use parking_lot::RwLock;
4use std::collections::{HashMap, VecDeque};
5use std::sync::atomic::{AtomicU64, Ordering};
6use std::time::{Duration, Instant};
7
8#[derive(Debug, Clone)]
9pub struct LazyNamespaceConfig {
10 pub max_resident: usize,
11 pub idle_evict_after: Duration,
12}
13
14impl Default for LazyNamespaceConfig {
15 fn default() -> Self {
16 Self {
17 max_resident: 1024,
18 idle_evict_after: Duration::from_secs(300),
19 }
20 }
21}
22
23#[derive(Debug)]
24struct NamespaceHandle {
25 namespace: String,
26 hydrated: bool,
27 last_access: Instant,
28 epoch: u64,
29}
30
31pub struct LazyNamespaceTable {
33 config: LazyNamespaceConfig,
34 handles: RwLock<HashMap<String, NamespaceHandle>>,
35 lru: RwLock<VecDeque<String>>,
36 global_epoch: AtomicU64,
37 evictions: AtomicU64,
38}
39
40impl LazyNamespaceTable {
41 pub fn new(config: LazyNamespaceConfig) -> Self {
42 Self {
43 config,
44 handles: RwLock::new(HashMap::new()),
45 lru: RwLock::new(VecDeque::new()),
46 global_epoch: AtomicU64::new(0),
47 evictions: AtomicU64::new(0),
48 }
49 }
50
51 pub fn resolve(&self, namespace: &str) -> u64 {
52 let epoch = self.global_epoch.fetch_add(1, Ordering::SeqCst);
53 let mut handles = self.handles.write();
54 let entry = handles
55 .entry(namespace.to_string())
56 .or_insert_with(|| NamespaceHandle {
57 namespace: namespace.to_string(),
58 hydrated: false,
59 last_access: Instant::now(),
60 epoch,
61 });
62 entry.last_access = Instant::now();
63 entry.epoch = epoch;
64 if !entry.hydrated {
65 entry.hydrated = true;
66 }
67 self.touch_lru(namespace);
68 epoch
69 }
70
71 fn touch_lru(&self, namespace: &str) {
72 let mut lru = self.lru.write();
73 lru.retain(|n| n != namespace);
74 lru.push_front(namespace.to_string());
75 while lru.len() > self.config.max_resident {
76 if let Some(victim) = lru.pop_back() {
77 self.evict(&victim);
78 }
79 }
80 }
81
82 fn evict(&self, namespace: &str) {
83 let mut handles = self.handles.write();
84 if let Some(h) = handles.get_mut(namespace) {
85 h.hydrated = false;
86 self.evictions.fetch_add(1, Ordering::Relaxed);
87 }
88 }
89
90 pub fn evict_idle(&self) {
91 let now = Instant::now();
92 let victims: Vec<String> = self
93 .handles
94 .read()
95 .iter()
96 .filter(|(_, h)| {
97 h.hydrated && now.duration_since(h.last_access) > self.config.idle_evict_after
98 })
99 .map(|(k, _)| k.clone())
100 .collect();
101 for v in victims {
102 self.evict(&v);
103 }
104 }
105
106 pub fn resident_count(&self) -> usize {
107 self.handles.read().values().filter(|h| h.hydrated).count()
108 }
109
110 pub fn eviction_count(&self) -> u64 {
111 self.evictions.load(Ordering::Relaxed)
112 }
113}