1use async_trait::async_trait;
2use common::{DakeraError, NamespaceId, Result, Vector, VectorId};
3use parking_lot::RwLock;
4use std::collections::HashMap;
5use std::sync::Arc;
6
7use crate::traits::VectorStorage;
8
9#[derive(Clone)]
12pub struct InMemoryStorage {
13 namespaces: Arc<RwLock<HashMap<NamespaceId, HashMap<VectorId, Vector>>>>,
15}
16
17impl InMemoryStorage {
18 pub fn new() -> Self {
19 Self {
20 namespaces: Arc::new(RwLock::new(HashMap::new())),
21 }
22 }
23}
24
25impl Default for InMemoryStorage {
26 fn default() -> Self {
27 Self::new()
28 }
29}
30
31fn now_secs() -> u64 {
33 std::time::SystemTime::now()
34 .duration_since(std::time::UNIX_EPOCH)
35 .unwrap_or_default()
36 .as_secs()
37}
38
39#[async_trait]
40impl VectorStorage for InMemoryStorage {
41 async fn upsert(&self, namespace: &NamespaceId, vectors: Vec<Vector>) -> Result<usize> {
42 let mut namespaces = self.namespaces.write();
43 let ns = namespaces.entry(namespace.clone()).or_default();
44
45 if !vectors.is_empty() {
47 let now = now_secs();
48 let expected_dim = ns
49 .values()
50 .find(|v| !v.is_expired_at(now))
51 .map(|v| v.values.len())
52 .unwrap_or_else(|| vectors[0].values.len());
53 for v in &vectors {
54 if v.values.len() != expected_dim {
55 return Err(DakeraError::DimensionMismatch {
56 expected: expected_dim,
57 actual: v.values.len(),
58 });
59 }
60 }
61 }
62
63 let count = vectors.len();
64 for mut vector in vectors {
65 vector.apply_ttl();
67 ns.insert(vector.id.clone(), vector);
68 }
69
70 tracing::debug!(
71 namespace = %namespace,
72 count = count,
73 "Upserted vectors"
74 );
75
76 Ok(count)
77 }
78
79 async fn get(&self, namespace: &NamespaceId, ids: &[VectorId]) -> Result<Vec<Vector>> {
80 let namespaces = self.namespaces.read();
81 let ns = namespaces
82 .get(namespace)
83 .ok_or_else(|| DakeraError::NamespaceNotFound(namespace.clone()))?;
84
85 let now = now_secs();
88 Ok(ids
89 .iter()
90 .filter_map(|id| ns.get(id).filter(|v| !v.is_expired_at(now)).cloned())
91 .collect())
92 }
93
94 async fn get_all(&self, namespace: &NamespaceId) -> Result<Vec<Vector>> {
95 let namespaces = self.namespaces.read();
96 let ns = namespaces
97 .get(namespace)
98 .ok_or_else(|| DakeraError::NamespaceNotFound(namespace.clone()))?;
99
100 let now = now_secs();
102 Ok(ns
103 .values()
104 .filter(|v| !v.is_expired_at(now))
105 .cloned()
106 .collect())
107 }
108
109 async fn get_all_meta(&self, namespace: &NamespaceId) -> Result<Vec<Vector>> {
110 let namespaces = self.namespaces.read();
111 let ns = namespaces
112 .get(namespace)
113 .ok_or_else(|| DakeraError::NamespaceNotFound(namespace.clone()))?;
114
115 let now = now_secs();
118 Ok(ns
119 .values()
120 .filter(|v| !v.is_expired_at(now))
121 .map(|v| Vector {
122 id: v.id.clone(),
123 values: Vec::new(),
124 metadata: v.metadata.clone(),
125 ttl_seconds: v.ttl_seconds,
126 expires_at: v.expires_at,
127 })
128 .collect())
129 }
130
131 async fn delete(&self, namespace: &NamespaceId, ids: &[VectorId]) -> Result<usize> {
132 let mut namespaces = self.namespaces.write();
133 let ns = namespaces
134 .get_mut(namespace)
135 .ok_or_else(|| DakeraError::NamespaceNotFound(namespace.clone()))?;
136
137 let mut deleted = 0;
138 for id in ids {
139 if ns.remove(id).is_some() {
140 deleted += 1;
141 }
142 }
143
144 tracing::debug!(
145 namespace = %namespace,
146 deleted = deleted,
147 "Deleted vectors"
148 );
149
150 Ok(deleted)
151 }
152
153 async fn namespace_exists(&self, namespace: &NamespaceId) -> Result<bool> {
154 Ok(self.namespaces.read().contains_key(namespace))
155 }
156
157 async fn ensure_namespace(&self, namespace: &NamespaceId) -> Result<()> {
158 self.namespaces
159 .write()
160 .entry(namespace.clone())
161 .or_default();
162 Ok(())
163 }
164
165 async fn count(&self, namespace: &NamespaceId) -> Result<usize> {
166 let namespaces = self.namespaces.read();
167 let now = now_secs();
168 Ok(namespaces
169 .get(namespace)
170 .map(|ns| ns.values().filter(|v| !v.is_expired_at(now)).count())
171 .unwrap_or(0))
172 }
173
174 async fn dimension(&self, namespace: &NamespaceId) -> Result<Option<usize>> {
175 let namespaces = self.namespaces.read();
176 let now = now_secs();
177 Ok(namespaces
178 .get(namespace)
179 .and_then(|ns| ns.values().find(|v| !v.is_expired_at(now)))
180 .map(|v| v.values.len()))
181 }
182
183 async fn list_namespaces(&self) -> Result<Vec<NamespaceId>> {
184 let namespaces = self.namespaces.read();
185 Ok(namespaces.keys().cloned().collect())
186 }
187
188 async fn delete_namespace(&self, namespace: &NamespaceId) -> Result<bool> {
189 let mut namespaces = self.namespaces.write();
190 let existed = namespaces.remove(namespace).is_some();
191
192 if existed {
193 tracing::debug!(
194 namespace = %namespace,
195 "Deleted namespace"
196 );
197 }
198
199 Ok(existed)
200 }
201
202 async fn cleanup_expired(&self, namespace: &NamespaceId) -> Result<usize> {
203 let mut namespaces = self.namespaces.write();
204 let ns = match namespaces.get_mut(namespace) {
205 Some(ns) => ns,
206 None => return Ok(0),
207 };
208
209 let now = now_secs();
210 let before_count = ns.len();
211 ns.retain(|_, v| !v.is_expired_at(now));
212 let removed = before_count - ns.len();
213
214 if removed > 0 {
215 tracing::debug!(
216 namespace = %namespace,
217 removed = removed,
218 "Cleaned up expired vectors"
219 );
220 }
221
222 Ok(removed)
223 }
224
225 async fn cleanup_all_expired(&self) -> Result<usize> {
226 let mut namespaces = self.namespaces.write();
227 let mut total_removed = 0;
228
229 let now = now_secs();
230 for (namespace, ns) in namespaces.iter_mut() {
231 let before_count = ns.len();
232 ns.retain(|_, v| !v.is_expired_at(now));
233 let removed = before_count - ns.len();
234 total_removed += removed;
235
236 if removed > 0 {
237 tracing::debug!(
238 namespace = %namespace,
239 removed = removed,
240 "Cleaned up expired vectors"
241 );
242 }
243 }
244
245 if total_removed > 0 {
246 tracing::info!(
247 total_removed = total_removed,
248 "Cleaned up expired vectors across all namespaces"
249 );
250 }
251
252 Ok(total_removed)
253 }
254}
255
256#[cfg(test)]
257mod tests {
258 use super::*;
259
260 #[tokio::test]
261 async fn test_upsert_and_get() {
262 let storage = InMemoryStorage::new();
263 let namespace = "test".to_string();
264
265 let vectors = vec![Vector {
266 id: "v1".to_string(),
267 values: vec![1.0, 2.0, 3.0],
268 metadata: None,
269 ttl_seconds: None,
270 expires_at: None,
271 }];
272
273 storage.ensure_namespace(&namespace).await.unwrap();
274 let count = storage.upsert(&namespace, vectors).await.unwrap();
275 assert_eq!(count, 1);
276
277 let retrieved = storage.get(&namespace, &["v1".to_string()]).await.unwrap();
278 assert_eq!(retrieved.len(), 1);
279 assert_eq!(retrieved[0].id, "v1");
280 }
281
282 #[tokio::test]
283 async fn test_dimension_mismatch() {
284 let storage = InMemoryStorage::new();
285 let namespace = "test".to_string();
286
287 storage.ensure_namespace(&namespace).await.unwrap();
288
289 storage
291 .upsert(
292 &namespace,
293 vec![Vector {
294 id: "v1".to_string(),
295 values: vec![1.0, 2.0, 3.0],
296 metadata: None,
297 ttl_seconds: None,
298 expires_at: None,
299 }],
300 )
301 .await
302 .unwrap();
303
304 let result = storage
306 .upsert(
307 &namespace,
308 vec![Vector {
309 id: "v2".to_string(),
310 values: vec![1.0, 2.0],
311 metadata: None,
312 ttl_seconds: None,
313 expires_at: None,
314 }],
315 )
316 .await;
317
318 assert!(matches!(result, Err(DakeraError::DimensionMismatch { .. })));
319 }
320
321 #[tokio::test]
322 async fn test_delete() {
323 let storage = InMemoryStorage::new();
324 let namespace = "test".to_string();
325
326 storage.ensure_namespace(&namespace).await.unwrap();
327 storage
328 .upsert(
329 &namespace,
330 vec![
331 Vector {
332 id: "v1".to_string(),
333 values: vec![1.0],
334 metadata: None,
335 ttl_seconds: None,
336 expires_at: None,
337 },
338 Vector {
339 id: "v2".to_string(),
340 values: vec![2.0],
341 metadata: None,
342 ttl_seconds: None,
343 expires_at: None,
344 },
345 ],
346 )
347 .await
348 .unwrap();
349
350 let deleted = storage
351 .delete(&namespace, &["v1".to_string()])
352 .await
353 .unwrap();
354 assert_eq!(deleted, 1);
355
356 let count = storage.count(&namespace).await.unwrap();
357 assert_eq!(count, 1);
358 }
359
360 #[tokio::test]
361 async fn test_get_all() {
362 let storage = InMemoryStorage::new();
363 let namespace = "test".to_string();
364
365 storage.ensure_namespace(&namespace).await.unwrap();
366 storage
367 .upsert(
368 &namespace,
369 vec![
370 Vector {
371 id: "v1".to_string(),
372 values: vec![1.0, 2.0],
373 metadata: None,
374 ttl_seconds: None,
375 expires_at: None,
376 },
377 Vector {
378 id: "v2".to_string(),
379 values: vec![3.0, 4.0],
380 metadata: None,
381 ttl_seconds: None,
382 expires_at: None,
383 },
384 ],
385 )
386 .await
387 .unwrap();
388
389 let all = storage.get_all(&namespace).await.unwrap();
390 assert_eq!(all.len(), 2);
391 }
392
393 #[tokio::test]
394 async fn test_ttl_expired_vectors_filtered() {
395 let storage = InMemoryStorage::new();
396 let namespace = "test".to_string();
397
398 storage.ensure_namespace(&namespace).await.unwrap();
399
400 let past_timestamp = std::time::SystemTime::now()
402 .duration_since(std::time::UNIX_EPOCH)
403 .unwrap()
404 .as_secs()
405 - 100; storage
408 .upsert(
409 &namespace,
410 vec![
411 Vector {
412 id: "expired".to_string(),
413 values: vec![1.0, 2.0],
414 metadata: None,
415 ttl_seconds: None,
416 expires_at: Some(past_timestamp),
417 },
418 Vector {
419 id: "valid".to_string(),
420 values: vec![3.0, 4.0],
421 metadata: None,
422 ttl_seconds: None,
423 expires_at: None,
424 },
425 ],
426 )
427 .await
428 .unwrap();
429
430 let retrieved = storage
432 .get(&namespace, &["expired".to_string(), "valid".to_string()])
433 .await
434 .unwrap();
435 assert_eq!(retrieved.len(), 1);
436 assert_eq!(retrieved[0].id, "valid");
437
438 let all = storage.get_all(&namespace).await.unwrap();
440 assert_eq!(all.len(), 1);
441 assert_eq!(all[0].id, "valid");
442
443 let count = storage.count(&namespace).await.unwrap();
445 assert_eq!(count, 1);
446 }
447
448 #[tokio::test]
449 async fn test_ttl_applied_on_upsert() {
450 let storage = InMemoryStorage::new();
451 let namespace = "test".to_string();
452
453 storage.ensure_namespace(&namespace).await.unwrap();
454
455 storage
457 .upsert(
458 &namespace,
459 vec![Vector {
460 id: "with_ttl".to_string(),
461 values: vec![1.0, 2.0],
462 metadata: None,
463 ttl_seconds: Some(3600), expires_at: None,
465 }],
466 )
467 .await
468 .unwrap();
469
470 let namespaces = storage.namespaces.read();
472 let ns = namespaces.get(&namespace).unwrap();
473 let vector = ns.get("with_ttl").unwrap();
474 assert!(vector.expires_at.is_some());
475
476 let now = std::time::SystemTime::now()
478 .duration_since(std::time::UNIX_EPOCH)
479 .unwrap()
480 .as_secs();
481 let expires_at = vector.expires_at.unwrap();
482 assert!(expires_at > now);
483 assert!(expires_at <= now + 3601); }
485
486 #[tokio::test]
487 async fn test_cleanup_expired() {
488 let storage = InMemoryStorage::new();
489 let namespace = "test".to_string();
490
491 storage.ensure_namespace(&namespace).await.unwrap();
492
493 let past_timestamp = std::time::SystemTime::now()
494 .duration_since(std::time::UNIX_EPOCH)
495 .unwrap()
496 .as_secs()
497 - 100;
498
499 storage
500 .upsert(
501 &namespace,
502 vec![
503 Vector {
504 id: "expired1".to_string(),
505 values: vec![1.0],
506 metadata: None,
507 ttl_seconds: None,
508 expires_at: Some(past_timestamp),
509 },
510 Vector {
511 id: "expired2".to_string(),
512 values: vec![2.0],
513 metadata: None,
514 ttl_seconds: None,
515 expires_at: Some(past_timestamp),
516 },
517 Vector {
518 id: "valid".to_string(),
519 values: vec![3.0],
520 metadata: None,
521 ttl_seconds: None,
522 expires_at: None,
523 },
524 ],
525 )
526 .await
527 .unwrap();
528
529 {
531 let namespaces = storage.namespaces.read();
532 let ns = namespaces.get(&namespace).unwrap();
533 assert_eq!(ns.len(), 3);
534 }
535
536 let removed = storage.cleanup_expired(&namespace).await.unwrap();
538 assert_eq!(removed, 2);
539
540 {
542 let namespaces = storage.namespaces.read();
543 let ns = namespaces.get(&namespace).unwrap();
544 assert_eq!(ns.len(), 1);
545 assert!(ns.contains_key("valid"));
546 }
547 }
548
549 #[tokio::test]
550 async fn test_cleanup_all_expired() {
551 let storage = InMemoryStorage::new();
552 let ns1 = "test1".to_string();
553 let ns2 = "test2".to_string();
554
555 storage.ensure_namespace(&ns1).await.unwrap();
556 storage.ensure_namespace(&ns2).await.unwrap();
557
558 let past_timestamp = std::time::SystemTime::now()
559 .duration_since(std::time::UNIX_EPOCH)
560 .unwrap()
561 .as_secs()
562 - 100;
563
564 storage
566 .upsert(
567 &ns1,
568 vec![Vector {
569 id: "expired".to_string(),
570 values: vec![1.0],
571 metadata: None,
572 ttl_seconds: None,
573 expires_at: Some(past_timestamp),
574 }],
575 )
576 .await
577 .unwrap();
578
579 storage
580 .upsert(
581 &ns2,
582 vec![
583 Vector {
584 id: "expired".to_string(),
585 values: vec![2.0],
586 metadata: None,
587 ttl_seconds: None,
588 expires_at: Some(past_timestamp),
589 },
590 Vector {
591 id: "valid".to_string(),
592 values: vec![3.0],
593 metadata: None,
594 ttl_seconds: None,
595 expires_at: None,
596 },
597 ],
598 )
599 .await
600 .unwrap();
601
602 let removed = storage.cleanup_all_expired().await.unwrap();
604 assert_eq!(removed, 2);
605
606 {
608 let namespaces = storage.namespaces.read();
609 assert_eq!(namespaces.get(&ns1).unwrap().len(), 0);
610 assert_eq!(namespaces.get(&ns2).unwrap().len(), 1);
611 }
612 }
613
614 #[tokio::test]
617 async fn test_get_skips_expired_without_clone() {
618 let storage = InMemoryStorage::new();
619 let namespace = "expire-test".to_string();
620 storage.ensure_namespace(&namespace).await.unwrap();
621
622 let past = now_secs().saturating_sub(1); let future = now_secs() + 3600;
624
625 storage
626 .upsert(
627 &namespace,
628 vec![
629 Vector {
630 id: "live".to_string(),
631 values: vec![1.0, 0.0],
632 metadata: None,
633 ttl_seconds: None,
634 expires_at: Some(future),
635 },
636 Vector {
637 id: "dead".to_string(),
638 values: vec![0.0, 1.0],
639 metadata: None,
640 ttl_seconds: None,
641 expires_at: Some(past),
642 },
643 ],
644 )
645 .await
646 .unwrap();
647
648 let results = storage
649 .get(&namespace, &["live".to_string(), "dead".to_string()])
650 .await
651 .unwrap();
652
653 assert_eq!(
654 results.len(),
655 1,
656 "expired vector must be filtered before clone"
657 );
658 assert_eq!(results[0].id, "live");
659 }
660}