nodedb_mem/
collection_arena.rs1use std::collections::HashMap;
12use std::sync::{Arc, Mutex};
13
14use crate::error::{MemError, Result};
15
16#[derive(Debug, Clone)]
21pub struct CollectionArenaHandle {
22 arena_index: Option<u32>,
25 tag: String,
27}
28
29impl CollectionArenaHandle {
30 pub fn arena_index(&self) -> Option<u32> {
32 self.arena_index
33 }
34
35 pub fn tag(&self) -> &str {
37 &self.tag
38 }
39
40 pub fn resident_bytes(&self) -> Option<u64> {
44 let idx = self.arena_index?;
45 read_arena_resident(idx).ok().map(|v| v as u64)
46 }
47}
48
49#[derive(Debug, Default)]
54pub struct CollectionArenaRegistry {
55 inner: Mutex<RegistryInner>,
56}
57
58#[derive(Debug, Default)]
59struct RegistryInner {
60 handles: HashMap<(u64, String), CollectionArenaHandle>,
61}
62
63impl CollectionArenaRegistry {
64 pub fn new() -> Arc<Self> {
66 Arc::new(Self::default())
67 }
68
69 pub fn get_or_create(&self, tenant_id: u64, collection: &str) -> Result<CollectionArenaHandle> {
73 let key = (tenant_id, collection.to_string());
74 let mut guard = self
75 .inner
76 .lock()
77 .map_err(|_| MemError::Jemalloc("collection arena registry lock poisoned".into()))?;
78 if let Some(h) = guard.handles.get(&key) {
79 return Ok(h.clone());
80 }
81 let handle = allocate_handle(tenant_id, collection);
82 guard.handles.insert(key, handle.clone());
83 Ok(handle)
84 }
85
86 pub fn get(&self, tenant_id: u64, collection: &str) -> Option<CollectionArenaHandle> {
89 let guard = self.inner.lock().ok()?;
90 guard
91 .handles
92 .get(&(tenant_id, collection.to_string()))
93 .cloned()
94 }
95}
96
97fn allocate_handle(tenant_id: u64, collection: &str) -> CollectionArenaHandle {
103 let tag = format!("t{tenant_id}/{collection}");
104 let arena_index = create_arena()
105 .map_err(|e| {
106 tracing::debug!(tag, error = %e, "per-collection arena creation failed; using global allocator");
107 })
108 .ok();
109 CollectionArenaHandle { arena_index, tag }
110}
111
112fn create_arena() -> Result<u32> {
114 let arena_idx: u32 = unsafe { tikv_jemalloc_ctl::raw::read(b"arenas.create\0") }
117 .map_err(|e| MemError::Jemalloc(format!("failed to create collection arena: {e:?}")))?;
118 Ok(arena_idx)
119}
120
121fn read_arena_resident(arena_index: u32) -> Result<usize> {
123 if let Ok(mib) = tikv_jemalloc_ctl::epoch::mib() {
125 let _ = mib.advance();
126 }
127 let key = format!("stats.arenas.{arena_index}.resident\0");
131 unsafe { tikv_jemalloc_ctl::raw::read::<usize>(key.as_bytes()) }
132 .map_err(|e| MemError::Jemalloc(format!("failed to read arena resident: {e:?}")))
133}
134
135#[cfg(test)]
136mod tests {
137 use super::*;
138
139 #[test]
140 fn registry_create_and_retrieve() {
141 let reg = CollectionArenaRegistry::new();
142 let h1 = reg.get_or_create(1, "embeddings").expect("create");
143 let h2 = reg.get_or_create(1, "embeddings").expect("re-fetch");
144 assert_eq!(h1.arena_index(), h2.arena_index(), "same key → same arena");
146 assert_eq!(h1.tag(), "t1/embeddings");
147 }
148
149 #[test]
150 fn different_collections_get_different_arenas() {
151 let reg = CollectionArenaRegistry::new();
152 let h1 = reg.get_or_create(1, "col_a").expect("create a");
153 let h2 = reg.get_or_create(1, "col_b").expect("create b");
154 if h1.arena_index().is_some() && h2.arena_index().is_some() {
155 assert_ne!(
156 h1.arena_index(),
157 h2.arena_index(),
158 "distinct collections must get distinct arenas"
159 );
160 }
161 }
162
163 #[test]
164 fn different_tenants_get_different_arenas() {
165 let reg = CollectionArenaRegistry::new();
166 let h1 = reg.get_or_create(1, "embeddings").expect("t1");
167 let h2 = reg.get_or_create(2, "embeddings").expect("t2");
168 if h1.arena_index().is_some() && h2.arena_index().is_some() {
169 assert_ne!(
170 h1.arena_index(),
171 h2.arena_index(),
172 "different tenants must get distinct arenas"
173 );
174 }
175 }
176
177 #[test]
178 fn get_returns_none_for_unknown_collection() {
179 let reg = CollectionArenaRegistry::new();
180 assert!(reg.get(99, "unknown").is_none());
181 }
182
183 #[test]
184 fn resident_bytes_does_not_panic() {
185 let reg = CollectionArenaRegistry::new();
186 let h = reg.get_or_create(1, "test_resident").expect("create");
187 let _ = h.resident_bytes();
189 }
190}