1use std::sync::Arc;
16use std::time::{Duration, SystemTime, UNIX_EPOCH};
17
18use async_trait::async_trait;
19use common::{NamespaceId, Result, Vector, VectorId};
20
21use crate::compaction::{CompactionConfig, CompactionManager};
22use crate::traits::VectorStorage;
23
24fn now_secs() -> u64 {
27 SystemTime::now()
28 .duration_since(UNIX_EPOCH)
29 .map(|d| d.as_secs())
30 .unwrap_or(0)
31}
32
33pub struct SegmentedStorage {
35 manager: Arc<CompactionManager>,
36}
37
38impl SegmentedStorage {
39 pub fn new(config: CompactionConfig) -> Self {
44 let manager = Arc::new(CompactionManager::new(config.clone()));
45 if config.auto_compact {
46 if let Ok(handle) = tokio::runtime::Handle::try_current() {
47 let mgr = manager.clone();
48 let interval = config.compaction_interval_secs.max(1);
49 handle.spawn(async move {
50 let mut ticker = tokio::time::interval(Duration::from_secs(interval));
51 ticker.tick().await; loop {
53 ticker.tick().await;
54 let results = mgr.compact_all();
55 if !results.is_empty() {
56 let merged: usize = results.values().map(|v| v.len()).sum();
57 tracing::debug!(
58 namespaces = results.len(),
59 compactions = merged,
60 "segmented storage auto-compaction merged segments"
61 );
62 }
63 }
64 });
65 tracing::info!(
66 interval_secs = interval,
67 "Segmented storage background compaction started"
68 );
69 }
70 }
71 Self { manager }
72 }
73
74 pub fn with_defaults() -> Self {
76 Self::new(CompactionConfig::default())
77 }
78
79 pub fn manager(&self) -> &Arc<CompactionManager> {
81 &self.manager
82 }
83
84 pub fn compact_all(&self) -> usize {
87 self.manager.compact_all().values().map(|v| v.len()).sum()
88 }
89}
90
91impl Default for SegmentedStorage {
92 fn default() -> Self {
93 Self::with_defaults()
94 }
95}
96
97#[async_trait]
98impl VectorStorage for SegmentedStorage {
99 async fn upsert(&self, namespace: &NamespaceId, vectors: Vec<Vector>) -> Result<usize> {
100 let count = vectors.len();
101 for vector in vectors {
102 self.manager.add(namespace, vector);
103 }
104 Ok(count)
105 }
106
107 async fn get(&self, namespace: &NamespaceId, ids: &[VectorId]) -> Result<Vec<Vector>> {
108 let now = now_secs();
109 Ok(ids
110 .iter()
111 .filter_map(|id| self.manager.get(namespace, id))
112 .filter(|v| !v.is_expired_at(now))
113 .collect())
114 }
115
116 async fn get_all(&self, namespace: &NamespaceId) -> Result<Vec<Vector>> {
117 let now = now_secs();
118 Ok(self
119 .manager
120 .get_all(namespace)
121 .into_iter()
122 .filter(|v| !v.is_expired_at(now))
123 .collect())
124 }
125
126 async fn delete(&self, namespace: &NamespaceId, ids: &[VectorId]) -> Result<usize> {
127 let mut removed = 0;
128 for id in ids {
129 if self.manager.delete(namespace, id) {
130 removed += 1;
131 }
132 }
133 Ok(removed)
134 }
135
136 async fn namespace_exists(&self, namespace: &NamespaceId) -> Result<bool> {
137 Ok(self
138 .manager
139 .list_namespaces()
140 .iter()
141 .any(|n| n == namespace))
142 }
143
144 async fn ensure_namespace(&self, namespace: &NamespaceId) -> Result<()> {
145 let _ = self.manager.namespace(namespace);
147 Ok(())
148 }
149
150 async fn count(&self, namespace: &NamespaceId) -> Result<usize> {
151 let now = now_secs();
152 Ok(self
153 .manager
154 .get_all(namespace)
155 .into_iter()
156 .filter(|v| !v.is_expired_at(now))
157 .count())
158 }
159
160 async fn dimension(&self, namespace: &NamespaceId) -> Result<Option<usize>> {
161 let now = now_secs();
162 Ok(self
163 .manager
164 .get_all(namespace)
165 .into_iter()
166 .find(|v| !v.is_expired_at(now))
167 .map(|v| v.values.len()))
168 }
169
170 async fn list_namespaces(&self) -> Result<Vec<NamespaceId>> {
171 Ok(self.manager.list_namespaces())
172 }
173
174 async fn delete_namespace(&self, namespace: &NamespaceId) -> Result<bool> {
175 Ok(self.manager.delete_namespace(namespace))
176 }
177
178 async fn cleanup_expired(&self, namespace: &NamespaceId) -> Result<usize> {
179 let now = now_secs();
180 let expired: Vec<VectorId> = self
181 .manager
182 .get_all(namespace)
183 .into_iter()
184 .filter(|v| v.is_expired_at(now))
185 .map(|v| v.id)
186 .collect();
187 let mut removed = 0;
188 for id in &expired {
189 if self.manager.delete(namespace, id) {
190 removed += 1;
191 }
192 }
193 Ok(removed)
194 }
195
196 async fn cleanup_all_expired(&self) -> Result<usize> {
197 let mut total = 0;
198 for namespace in self.manager.list_namespaces() {
199 total += self.cleanup_expired(&namespace).await?;
200 }
201 Ok(total)
202 }
203}
204
205#[cfg(test)]
206mod tests {
207 use super::*;
208
209 fn tv(id: &str) -> Vector {
210 Vector {
211 id: id.to_string(),
212 values: vec![0.1, 0.2, 0.3],
213 metadata: None,
214 ttl_seconds: None,
215 expires_at: None,
216 }
217 }
218
219 fn store() -> SegmentedStorage {
222 SegmentedStorage::new(CompactionConfig {
223 auto_compact: false,
224 ..Default::default()
225 })
226 }
227
228 #[tokio::test]
229 async fn upsert_get_delete_roundtrip() {
230 let s = store();
231 let ns = "n".to_string();
232 s.ensure_namespace(&ns).await.unwrap();
233 assert_eq!(s.upsert(&ns, vec![tv("a"), tv("b")]).await.unwrap(), 2);
234 assert_eq!(s.count(&ns).await.unwrap(), 2);
235 assert_eq!(s.dimension(&ns).await.unwrap(), Some(3));
236
237 let got = s.get(&ns, &["a".to_string()]).await.unwrap();
238 assert_eq!(got.len(), 1);
239 assert_eq!(got[0].id, "a");
240
241 assert_eq!(s.delete(&ns, &["a".to_string()]).await.unwrap(), 1);
242 assert!(s.get(&ns, &["a".to_string()]).await.unwrap().is_empty());
243 assert_eq!(s.count(&ns).await.unwrap(), 1);
244
245 let all: Vec<String> = s
246 .get_all(&ns)
247 .await
248 .unwrap()
249 .into_iter()
250 .map(|v| v.id)
251 .collect();
252 assert_eq!(all, vec!["b".to_string()]);
253 }
254
255 #[tokio::test]
256 async fn expired_vectors_are_filtered_and_reclaimed() {
257 let s = store();
258 let ns = "n".to_string();
259 let mut expired = tv("old");
260 expired.expires_at = Some(1); s.upsert(&ns, vec![expired, tv("fresh")]).await.unwrap();
262 assert_eq!(s.count(&ns).await.unwrap(), 1);
264 let ids: Vec<String> = s
265 .get_all(&ns)
266 .await
267 .unwrap()
268 .into_iter()
269 .map(|v| v.id)
270 .collect();
271 assert_eq!(ids, vec!["fresh".to_string()]);
272 assert_eq!(s.cleanup_expired(&ns).await.unwrap(), 1);
274 }
275
276 #[tokio::test]
277 async fn namespace_lifecycle() {
278 let s = store();
279 let ns = "n".to_string();
280 s.upsert(&ns, vec![tv("a")]).await.unwrap();
281 assert!(s.namespace_exists(&ns).await.unwrap());
282 assert_eq!(s.list_namespaces().await.unwrap(), vec![ns.clone()]);
283 assert!(s.delete_namespace(&ns).await.unwrap());
284 assert!(!s.namespace_exists(&ns).await.unwrap());
285 }
286}