Skip to main content

storage/
rocksdb.rs

1//! RocksDB-backed vector storage for the persistent hot tier.
2//!
3//! Implements `VectorStorage` on top of RocksDB, providing the same semantics
4//! as `InMemoryStorage` but with durability across restarts. Vectors survive
5//! server crashes without needing a warm/cold tier promotion round-trip.
6
7use async_trait::async_trait;
8use common::{DakeraError, NamespaceId, Result, Vector, VectorId};
9use rocksdb::{BlockBasedOptions, Cache, Options, DB};
10use std::sync::Arc;
11
12use crate::traits::VectorStorage;
13
14/// Configuration for the RocksDB hot tier.
15#[derive(Debug, Clone)]
16pub struct RocksDbStorageConfig {
17    /// Path to the RocksDB database directory.
18    pub path: String,
19    /// RocksDB block cache size in MB (default: 64).
20    pub block_cache_mb: usize,
21    /// Enable LZ4 compression (default: true).
22    pub compression: bool,
23    /// Write buffer size in bytes (default: 64 MB).
24    pub write_buffer_size: usize,
25    /// Max number of concurrent write buffers (default: 3).
26    pub max_write_buffer_number: i32,
27}
28
29impl Default for RocksDbStorageConfig {
30    fn default() -> Self {
31        Self {
32            path: "/data/hot".to_string(),
33            block_cache_mb: 64,
34            compression: true,
35            write_buffer_size: 64 * 1024 * 1024,
36            max_write_buffer_number: 3,
37        }
38    }
39}
40
41/// Persistent hot-tier storage backed by RocksDB.
42///
43/// Key layout: `ns:{namespace}:v:{vector_id}` → bincode-serialized `Vector`
44/// Namespace existence tracked via sentinel keys: `ns:{namespace}:__meta__` → `"1"`
45pub struct RocksDbStorage {
46    db: Arc<DB>,
47}
48
49impl RocksDbStorage {
50    /// Open (or create) a RocksDB-backed storage at the configured path.
51    pub fn new(config: RocksDbStorageConfig) -> Result<Self> {
52        Self::open_at(&config.path, &config)
53    }
54
55    /// Open a RocksDB instance at the given path with the supplied config.
56    pub fn open_at(path: &str, config: &RocksDbStorageConfig) -> Result<Self> {
57        let mut opts = Options::default();
58        opts.create_if_missing(true);
59        opts.set_write_buffer_size(config.write_buffer_size);
60        opts.set_max_write_buffer_number(config.max_write_buffer_number);
61
62        if config.compression {
63            opts.set_compression_type(rocksdb::DBCompressionType::Lz4);
64        }
65
66        let cache = Cache::new_lru_cache(config.block_cache_mb * 1024 * 1024);
67        let mut block_opts = BlockBasedOptions::default();
68        block_opts.set_block_cache(&cache);
69        block_opts.set_optimize_filters_for_memory(true);
70        opts.set_block_based_table_factory(&block_opts);
71
72        opts.set_level_compaction_dynamic_level_bytes(true);
73        opts.set_max_background_jobs(4);
74
75        // Use prefix extractor for efficient namespace scans
76        opts.set_prefix_extractor(rocksdb::SliceTransform::create_fixed_prefix(
77            // "ns:" prefix minimum — we scan by "ns:{namespace}:" which varies in
78            // length, but setting a fixed prefix of 3 enables the bloom filter on
79            // the constant "ns:" tag, which is good enough for our workload.
80            3,
81        ));
82
83        let db = DB::open(&opts, path)
84            .map_err(|e| DakeraError::Storage(format!("RocksDB open failed: {e}")))?;
85
86        tracing::info!(path, "RocksDB hot tier initialized");
87
88        Ok(Self { db: Arc::new(db) })
89    }
90
91    // ── key helpers ────────────────────────────────────────────────────
92
93    fn vector_key(ns: &NamespaceId, id: &VectorId) -> Vec<u8> {
94        format!("ns:{ns}:v:{id}").into_bytes()
95    }
96
97    fn vector_prefix(ns: &NamespaceId) -> Vec<u8> {
98        format!("ns:{ns}:v:").into_bytes()
99    }
100
101    fn meta_key(ns: &NamespaceId) -> Vec<u8> {
102        format!("ns:{ns}:__meta__").into_bytes()
103    }
104
105    fn ns_prefix() -> Vec<u8> {
106        b"ns:".to_vec()
107    }
108
109    // ── serde helpers ──────────────────────────────────────────────────
110
111    fn serialize_vector(v: &Vector) -> Result<Vec<u8>> {
112        serde_json::to_vec(v).map_err(|e| DakeraError::Storage(format!("vector serialize: {e}")))
113    }
114
115    fn deserialize_vector(bytes: &[u8]) -> Result<Vector> {
116        serde_json::from_slice(bytes)
117            .map_err(|e| DakeraError::Storage(format!("vector deserialize: {e}")))
118    }
119}
120
121fn now_secs() -> u64 {
122    std::time::SystemTime::now()
123        .duration_since(std::time::UNIX_EPOCH)
124        .unwrap_or_default()
125        .as_secs()
126}
127
128#[async_trait]
129impl VectorStorage for RocksDbStorage {
130    async fn upsert(&self, namespace: &NamespaceId, vectors: Vec<Vector>) -> Result<usize> {
131        if vectors.is_empty() {
132            return Ok(0);
133        }
134
135        // Dimension validation: match existing vectors' dimension or the
136        // first vector in this batch if the namespace is fresh.
137        let now = now_secs();
138        let prefix = Self::vector_prefix(namespace);
139        let expected_dim = {
140            let iter = self.db.prefix_iterator(&prefix);
141            let mut dim = None;
142            for item in iter {
143                match item {
144                    Ok((k, v)) if k.starts_with(&prefix) => {
145                        if let Ok(vec) = Self::deserialize_vector(&v) {
146                            if !vec.is_expired_at(now) {
147                                dim = Some(vec.values.len());
148                                break;
149                            }
150                        }
151                    }
152                    _ => break,
153                }
154            }
155            dim.unwrap_or_else(|| vectors[0].values.len())
156        };
157
158        for v in &vectors {
159            if v.values.len() != expected_dim {
160                return Err(DakeraError::DimensionMismatch {
161                    expected: expected_dim,
162                    actual: v.values.len(),
163                });
164            }
165        }
166
167        let mut batch = rocksdb::WriteBatch::default();
168
169        // Ensure namespace sentinel exists
170        batch.put(Self::meta_key(namespace), b"1");
171
172        let count = vectors.len();
173        for mut vector in vectors {
174            vector.apply_ttl();
175            let key = Self::vector_key(namespace, &vector.id);
176            let val = Self::serialize_vector(&vector)?;
177            batch.put(key, val);
178        }
179
180        self.db
181            .write(batch)
182            .map_err(|e| DakeraError::Storage(format!("RocksDB batch write: {e}")))?;
183
184        tracing::debug!(namespace = %namespace, count, "RocksDB upserted vectors");
185        Ok(count)
186    }
187
188    async fn get(&self, namespace: &NamespaceId, ids: &[VectorId]) -> Result<Vec<Vector>> {
189        if !self.namespace_exists(namespace).await? {
190            return Err(DakeraError::NamespaceNotFound(namespace.clone()));
191        }
192
193        let now = now_secs();
194        let keys: Vec<Vec<u8>> = ids
195            .iter()
196            .map(|id| Self::vector_key(namespace, id))
197            .collect();
198
199        let results = self.db.multi_get(&keys);
200        let mut out = Vec::with_capacity(ids.len());
201        for result in results {
202            if let Ok(Some(bytes)) = result {
203                if let Ok(v) = Self::deserialize_vector(&bytes) {
204                    if !v.is_expired_at(now) {
205                        out.push(v);
206                    }
207                }
208            }
209        }
210        Ok(out)
211    }
212
213    async fn get_all(&self, namespace: &NamespaceId) -> Result<Vec<Vector>> {
214        if !self.namespace_exists(namespace).await? {
215            return Err(DakeraError::NamespaceNotFound(namespace.clone()));
216        }
217
218        let prefix = Self::vector_prefix(namespace);
219        let now = now_secs();
220        let mut out = Vec::new();
221
222        let iter = self.db.prefix_iterator(&prefix);
223        for item in iter {
224            match item {
225                Ok((k, v)) if k.starts_with(&prefix) => {
226                    if let Ok(vec) = Self::deserialize_vector(&v) {
227                        if !vec.is_expired_at(now) {
228                            out.push(vec);
229                        }
230                    }
231                }
232                _ => break,
233            }
234        }
235        Ok(out)
236    }
237
238    async fn delete(&self, namespace: &NamespaceId, ids: &[VectorId]) -> Result<usize> {
239        if !self.namespace_exists(namespace).await? {
240            return Err(DakeraError::NamespaceNotFound(namespace.clone()));
241        }
242
243        let mut batch = rocksdb::WriteBatch::default();
244        let mut deleted = 0usize;
245
246        for id in ids {
247            let key = Self::vector_key(namespace, id);
248            if self.db.get(&key).ok().flatten().is_some() {
249                batch.delete(&key);
250                deleted += 1;
251            }
252        }
253
254        if deleted > 0 {
255            self.db
256                .write(batch)
257                .map_err(|e| DakeraError::Storage(format!("RocksDB batch delete: {e}")))?;
258        }
259
260        tracing::debug!(namespace = %namespace, deleted, "RocksDB deleted vectors");
261        Ok(deleted)
262    }
263
264    async fn namespace_exists(&self, namespace: &NamespaceId) -> Result<bool> {
265        Ok(self
266            .db
267            .get(Self::meta_key(namespace))
268            .ok()
269            .flatten()
270            .is_some())
271    }
272
273    async fn ensure_namespace(&self, namespace: &NamespaceId) -> Result<()> {
274        self.db
275            .put(Self::meta_key(namespace), b"1")
276            .map_err(|e| DakeraError::Storage(format!("RocksDB put meta: {e}")))?;
277        Ok(())
278    }
279
280    async fn count(&self, namespace: &NamespaceId) -> Result<usize> {
281        let prefix = Self::vector_prefix(namespace);
282        let now = now_secs();
283        let mut n = 0usize;
284        let iter = self.db.prefix_iterator(&prefix);
285        for item in iter {
286            match item {
287                Ok((k, v)) if k.starts_with(&prefix) => {
288                    if let Ok(vec) = Self::deserialize_vector(&v) {
289                        if !vec.is_expired_at(now) {
290                            n += 1;
291                        }
292                    }
293                }
294                _ => break,
295            }
296        }
297        Ok(n)
298    }
299
300    async fn dimension(&self, namespace: &NamespaceId) -> Result<Option<usize>> {
301        let prefix = Self::vector_prefix(namespace);
302        let now = now_secs();
303        let iter = self.db.prefix_iterator(&prefix);
304        for item in iter {
305            match item {
306                Ok((k, v)) if k.starts_with(&prefix) => {
307                    if let Ok(vec) = Self::deserialize_vector(&v) {
308                        if !vec.is_expired_at(now) {
309                            return Ok(Some(vec.values.len()));
310                        }
311                    }
312                }
313                _ => break,
314            }
315        }
316        Ok(None)
317    }
318
319    async fn list_namespaces(&self) -> Result<Vec<NamespaceId>> {
320        let ns_prefix = Self::ns_prefix();
321        let mut namespaces = Vec::new();
322
323        let iter = self.db.prefix_iterator(&ns_prefix);
324        for item in iter {
325            match item {
326                Ok((k, _)) if k.starts_with(&ns_prefix) => {
327                    if let Ok(key_str) = std::str::from_utf8(&k) {
328                        if key_str.ends_with(":__meta__") {
329                            // Extract namespace: "ns:{name}:__meta__"
330                            if let Some(name) = key_str
331                                .strip_prefix("ns:")
332                                .and_then(|s| s.strip_suffix(":__meta__"))
333                            {
334                                namespaces.push(name.to_string());
335                            }
336                        }
337                    }
338                }
339                Ok((k, _)) if !k.starts_with(&ns_prefix) => break,
340                Err(_) => break,
341                _ => {}
342            }
343        }
344        // Deduplicate — the iterator may encounter meta keys out of order
345        // depending on prefix extractor settings. In practice the "ns:" fixed
346        // prefix keeps them ordered, but `sort + dedup` is cheap insurance.
347        namespaces.sort_unstable();
348        namespaces.dedup();
349        Ok(namespaces)
350    }
351
352    async fn delete_namespace(&self, namespace: &NamespaceId) -> Result<bool> {
353        let existed = self.namespace_exists(namespace).await?;
354        if !existed {
355            return Ok(false);
356        }
357
358        let mut batch = rocksdb::WriteBatch::default();
359
360        // Delete all vectors
361        let prefix = Self::vector_prefix(namespace);
362        let iter = self.db.prefix_iterator(&prefix);
363        for item in iter {
364            match item {
365                Ok((k, _)) if k.starts_with(&prefix) => batch.delete(&k),
366                _ => break,
367            }
368        }
369
370        // Delete the meta sentinel
371        batch.delete(Self::meta_key(namespace));
372
373        self.db
374            .write(batch)
375            .map_err(|e| DakeraError::Storage(format!("RocksDB delete namespace: {e}")))?;
376
377        tracing::debug!(namespace = %namespace, "RocksDB deleted namespace");
378        Ok(true)
379    }
380
381    async fn cleanup_expired(&self, namespace: &NamespaceId) -> Result<usize> {
382        let prefix = Self::vector_prefix(namespace);
383        let now = now_secs();
384        let mut batch = rocksdb::WriteBatch::default();
385        let mut removed = 0usize;
386
387        let iter = self.db.prefix_iterator(&prefix);
388        for item in iter {
389            match item {
390                Ok((k, v)) if k.starts_with(&prefix) => {
391                    if let Ok(vec) = Self::deserialize_vector(&v) {
392                        if vec.is_expired_at(now) {
393                            batch.delete(&k);
394                            removed += 1;
395                        }
396                    }
397                }
398                _ => break,
399            }
400        }
401
402        if removed > 0 {
403            self.db
404                .write(batch)
405                .map_err(|e| DakeraError::Storage(format!("RocksDB cleanup expired: {e}")))?;
406            tracing::debug!(namespace = %namespace, removed, "RocksDB cleaned up expired vectors");
407        }
408        Ok(removed)
409    }
410
411    async fn cleanup_all_expired(&self) -> Result<usize> {
412        let namespaces = self.list_namespaces().await?;
413        let mut total = 0usize;
414        for ns in &namespaces {
415            total += self.cleanup_expired(ns).await?;
416        }
417        if total > 0 {
418            tracing::info!(
419                total,
420                "RocksDB cleaned up expired vectors across all namespaces"
421            );
422        }
423        Ok(total)
424    }
425
426    async fn get_page(
427        &self,
428        namespace: &NamespaceId,
429        offset: usize,
430        limit: usize,
431    ) -> Result<Vec<Vector>> {
432        let prefix = Self::vector_prefix(namespace);
433        let now = now_secs();
434        let mut out = Vec::with_capacity(limit);
435        let mut skipped = 0usize;
436
437        let iter = self.db.prefix_iterator(&prefix);
438        for item in iter {
439            match item {
440                Ok((k, v)) if k.starts_with(&prefix) => {
441                    if let Ok(vec) = Self::deserialize_vector(&v) {
442                        if !vec.is_expired_at(now) {
443                            if skipped < offset {
444                                skipped += 1;
445                            } else {
446                                out.push(vec);
447                                if out.len() >= limit {
448                                    break;
449                                }
450                            }
451                        }
452                    }
453                }
454                _ => break,
455            }
456        }
457        Ok(out)
458    }
459}
460
461#[cfg(test)]
462mod tests {
463    use super::*;
464    use tempfile::TempDir;
465
466    fn test_config(dir: &TempDir) -> RocksDbStorageConfig {
467        RocksDbStorageConfig {
468            path: dir.path().to_string_lossy().to_string(),
469            block_cache_mb: 8,
470            compression: false,
471            ..Default::default()
472        }
473    }
474
475    #[tokio::test]
476    async fn test_upsert_and_get() {
477        let dir = TempDir::new().unwrap();
478        let storage = RocksDbStorage::new(test_config(&dir)).unwrap();
479        let ns = "test".to_string();
480
481        storage.ensure_namespace(&ns).await.unwrap();
482        let count = storage
483            .upsert(
484                &ns,
485                vec![Vector {
486                    id: "v1".to_string(),
487                    values: vec![1.0, 2.0, 3.0],
488                    metadata: None,
489                    ttl_seconds: None,
490                    expires_at: None,
491                }],
492            )
493            .await
494            .unwrap();
495        assert_eq!(count, 1);
496
497        let got = storage.get(&ns, &["v1".to_string()]).await.unwrap();
498        assert_eq!(got.len(), 1);
499        assert_eq!(got[0].id, "v1");
500        assert_eq!(got[0].values, vec![1.0, 2.0, 3.0]);
501    }
502
503    #[tokio::test]
504    async fn test_dimension_mismatch() {
505        let dir = TempDir::new().unwrap();
506        let storage = RocksDbStorage::new(test_config(&dir)).unwrap();
507        let ns = "test".to_string();
508
509        storage.ensure_namespace(&ns).await.unwrap();
510        storage
511            .upsert(
512                &ns,
513                vec![Vector {
514                    id: "v1".to_string(),
515                    values: vec![1.0, 2.0, 3.0],
516                    metadata: None,
517                    ttl_seconds: None,
518                    expires_at: None,
519                }],
520            )
521            .await
522            .unwrap();
523
524        let result = storage
525            .upsert(
526                &ns,
527                vec![Vector {
528                    id: "v2".to_string(),
529                    values: vec![1.0, 2.0],
530                    metadata: None,
531                    ttl_seconds: None,
532                    expires_at: None,
533                }],
534            )
535            .await;
536        assert!(matches!(result, Err(DakeraError::DimensionMismatch { .. })));
537    }
538
539    #[tokio::test]
540    async fn test_delete() {
541        let dir = TempDir::new().unwrap();
542        let storage = RocksDbStorage::new(test_config(&dir)).unwrap();
543        let ns = "test".to_string();
544
545        storage.ensure_namespace(&ns).await.unwrap();
546        storage
547            .upsert(
548                &ns,
549                vec![
550                    Vector {
551                        id: "v1".to_string(),
552                        values: vec![1.0],
553                        metadata: None,
554                        ttl_seconds: None,
555                        expires_at: None,
556                    },
557                    Vector {
558                        id: "v2".to_string(),
559                        values: vec![2.0],
560                        metadata: None,
561                        ttl_seconds: None,
562                        expires_at: None,
563                    },
564                ],
565            )
566            .await
567            .unwrap();
568
569        let deleted = storage.delete(&ns, &["v1".to_string()]).await.unwrap();
570        assert_eq!(deleted, 1);
571        assert_eq!(storage.count(&ns).await.unwrap(), 1);
572    }
573
574    #[tokio::test]
575    async fn test_get_all_and_count() {
576        let dir = TempDir::new().unwrap();
577        let storage = RocksDbStorage::new(test_config(&dir)).unwrap();
578        let ns = "test".to_string();
579
580        storage.ensure_namespace(&ns).await.unwrap();
581        storage
582            .upsert(
583                &ns,
584                vec![
585                    Vector {
586                        id: "v1".to_string(),
587                        values: vec![1.0, 2.0],
588                        metadata: None,
589                        ttl_seconds: None,
590                        expires_at: None,
591                    },
592                    Vector {
593                        id: "v2".to_string(),
594                        values: vec![3.0, 4.0],
595                        metadata: None,
596                        ttl_seconds: None,
597                        expires_at: None,
598                    },
599                ],
600            )
601            .await
602            .unwrap();
603
604        let all = storage.get_all(&ns).await.unwrap();
605        assert_eq!(all.len(), 2);
606        assert_eq!(storage.count(&ns).await.unwrap(), 2);
607    }
608
609    #[tokio::test]
610    async fn test_namespace_lifecycle() {
611        let dir = TempDir::new().unwrap();
612        let storage = RocksDbStorage::new(test_config(&dir)).unwrap();
613        let ns = "lifecycle".to_string();
614
615        assert!(!storage.namespace_exists(&ns).await.unwrap());
616        storage.ensure_namespace(&ns).await.unwrap();
617        assert!(storage.namespace_exists(&ns).await.unwrap());
618
619        let nss = storage.list_namespaces().await.unwrap();
620        assert!(nss.contains(&ns));
621
622        storage.delete_namespace(&ns).await.unwrap();
623        assert!(!storage.namespace_exists(&ns).await.unwrap());
624    }
625
626    #[tokio::test]
627    async fn test_ttl_filtering() {
628        let dir = TempDir::new().unwrap();
629        let storage = RocksDbStorage::new(test_config(&dir)).unwrap();
630        let ns = "ttl".to_string();
631
632        storage.ensure_namespace(&ns).await.unwrap();
633        let past = now_secs().saturating_sub(100);
634
635        storage
636            .upsert(
637                &ns,
638                vec![
639                    Vector {
640                        id: "expired".to_string(),
641                        values: vec![1.0, 2.0],
642                        metadata: None,
643                        ttl_seconds: None,
644                        expires_at: Some(past),
645                    },
646                    Vector {
647                        id: "valid".to_string(),
648                        values: vec![3.0, 4.0],
649                        metadata: None,
650                        ttl_seconds: None,
651                        expires_at: None,
652                    },
653                ],
654            )
655            .await
656            .unwrap();
657
658        let got = storage
659            .get(&ns, &["expired".to_string(), "valid".to_string()])
660            .await
661            .unwrap();
662        assert_eq!(got.len(), 1);
663        assert_eq!(got[0].id, "valid");
664
665        let all = storage.get_all(&ns).await.unwrap();
666        assert_eq!(all.len(), 1);
667        assert_eq!(storage.count(&ns).await.unwrap(), 1);
668    }
669
670    #[tokio::test]
671    async fn test_cleanup_expired() {
672        let dir = TempDir::new().unwrap();
673        let storage = RocksDbStorage::new(test_config(&dir)).unwrap();
674        let ns = "cleanup".to_string();
675
676        storage.ensure_namespace(&ns).await.unwrap();
677        let past = now_secs().saturating_sub(100);
678
679        storage
680            .upsert(
681                &ns,
682                vec![
683                    Vector {
684                        id: "expired1".to_string(),
685                        values: vec![1.0],
686                        metadata: None,
687                        ttl_seconds: None,
688                        expires_at: Some(past),
689                    },
690                    Vector {
691                        id: "valid".to_string(),
692                        values: vec![2.0],
693                        metadata: None,
694                        ttl_seconds: None,
695                        expires_at: None,
696                    },
697                ],
698            )
699            .await
700            .unwrap();
701
702        let removed = storage.cleanup_expired(&ns).await.unwrap();
703        assert_eq!(removed, 1);
704
705        // Only valid vector should remain (even at raw level)
706        let all = storage.get_all(&ns).await.unwrap();
707        assert_eq!(all.len(), 1);
708        assert_eq!(all[0].id, "valid");
709    }
710
711    #[tokio::test]
712    async fn test_get_page() {
713        let dir = TempDir::new().unwrap();
714        let storage = RocksDbStorage::new(test_config(&dir)).unwrap();
715        let ns = "paged".to_string();
716
717        storage.ensure_namespace(&ns).await.unwrap();
718        let mut vecs = Vec::new();
719        for i in 0..10 {
720            vecs.push(Vector {
721                id: format!("v{i:02}"),
722                values: vec![i as f32],
723                metadata: None,
724                ttl_seconds: None,
725                expires_at: None,
726            });
727        }
728        storage.upsert(&ns, vecs).await.unwrap();
729
730        let page = storage.get_page(&ns, 3, 4).await.unwrap();
731        assert_eq!(page.len(), 4);
732    }
733
734    #[tokio::test]
735    async fn test_persistence_across_reopen() {
736        let dir = TempDir::new().unwrap();
737
738        // Write data with one instance
739        {
740            let storage = RocksDbStorage::new(test_config(&dir)).unwrap();
741            let ns = "persist".to_string();
742            storage.ensure_namespace(&ns).await.unwrap();
743            storage
744                .upsert(
745                    &ns,
746                    vec![Vector {
747                        id: "durable".to_string(),
748                        values: vec![42.0],
749                        metadata: None,
750                        ttl_seconds: None,
751                        expires_at: None,
752                    }],
753                )
754                .await
755                .unwrap();
756        }
757
758        // Reopen and verify data survived
759        {
760            let storage = RocksDbStorage::new(test_config(&dir)).unwrap();
761            let ns = "persist".to_string();
762            assert!(storage.namespace_exists(&ns).await.unwrap());
763            let got = storage.get(&ns, &["durable".to_string()]).await.unwrap();
764            assert_eq!(got.len(), 1);
765            assert_eq!(got[0].values, vec![42.0]);
766        }
767    }
768
769    #[tokio::test]
770    async fn test_multi_namespace_isolation() {
771        let dir = TempDir::new().unwrap();
772        let storage = RocksDbStorage::new(test_config(&dir)).unwrap();
773
774        storage.ensure_namespace(&"ns1".to_string()).await.unwrap();
775        storage.ensure_namespace(&"ns2".to_string()).await.unwrap();
776
777        storage
778            .upsert(
779                &"ns1".to_string(),
780                vec![Vector {
781                    id: "v1".to_string(),
782                    values: vec![1.0],
783                    metadata: None,
784                    ttl_seconds: None,
785                    expires_at: None,
786                }],
787            )
788            .await
789            .unwrap();
790
791        storage
792            .upsert(
793                &"ns2".to_string(),
794                vec![Vector {
795                    id: "v1".to_string(),
796                    values: vec![2.0],
797                    metadata: None,
798                    ttl_seconds: None,
799                    expires_at: None,
800                }],
801            )
802            .await
803            .unwrap();
804
805        let ns1_vecs = storage.get_all(&"ns1".to_string()).await.unwrap();
806        let ns2_vecs = storage.get_all(&"ns2".to_string()).await.unwrap();
807        assert_eq!(ns1_vecs.len(), 1);
808        assert_eq!(ns2_vecs.len(), 1);
809        assert_eq!(ns1_vecs[0].values, vec![1.0]);
810        assert_eq!(ns2_vecs[0].values, vec![2.0]);
811
812        // Deleting ns1 should not affect ns2
813        storage.delete_namespace(&"ns1".to_string()).await.unwrap();
814        assert!(!storage.namespace_exists(&"ns1".to_string()).await.unwrap());
815        assert!(storage.namespace_exists(&"ns2".to_string()).await.unwrap());
816        assert_eq!(storage.get_all(&"ns2".to_string()).await.unwrap().len(), 1);
817    }
818}