Skip to main content

oxirs_vec/real_time_embedding_pipeline/
versioning.rs

1//! Version management for the real-time embedding pipeline
2
3use anyhow::Result;
4use serde::{Deserialize, Serialize};
5use std::collections::HashMap;
6use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
7use std::sync::{Arc, RwLock};
8use std::time::Duration;
9
10use crate::real_time_embedding_pipeline::{
11    config::VersionControlConfig,
12    traits::{HealthStatus, Version, VersionStorage},
13};
14
15/// Configuration for version manager
16#[derive(Debug, Clone, Serialize, Deserialize)]
17pub struct VersionManagerConfig {
18    pub max_versions_per_id: usize,
19    pub cleanup_interval: Duration,
20    pub enable_compression: bool,
21    pub max_total_versions: usize,
22}
23
24impl Default for VersionManagerConfig {
25    fn default() -> Self {
26        Self {
27            max_versions_per_id: 10,
28            cleanup_interval: Duration::from_secs(300),
29            enable_compression: false,
30            max_total_versions: 100_000,
31        }
32    }
33}
34
35/// Statistics for version management
36#[derive(Debug, Clone, Serialize, Deserialize)]
37pub struct VersionManagerStatistics {
38    pub total_versions: u64,
39    pub total_cleanups: u64,
40    pub total_ids_tracked: usize,
41    pub is_running: bool,
42}
43
44/// In-memory version storage
45pub struct InMemoryVersionStorage {
46    data: HashMap<String, Vec<Version>>,
47    max_versions_per_id: usize,
48}
49
50impl InMemoryVersionStorage {
51    pub fn new(max_versions_per_id: usize) -> Self {
52        Self {
53            data: HashMap::new(),
54            max_versions_per_id,
55        }
56    }
57}
58
59impl VersionStorage for InMemoryVersionStorage {
60    fn store_version(&mut self, id: &str, version: &Version) -> Result<()> {
61        let versions = self.data.entry(id.to_string()).or_default();
62        versions.push(version.clone());
63        if versions.len() > self.max_versions_per_id {
64            let excess = versions.len() - self.max_versions_per_id;
65            versions.drain(0..excess);
66        }
67        Ok(())
68    }
69
70    fn get_version(&self, id: &str, version_number: u64) -> Result<Option<Version>> {
71        Ok(self
72            .data
73            .get(id)
74            .and_then(|vs| vs.iter().find(|v| v.version == version_number).cloned()))
75    }
76
77    fn get_all_versions(&self, id: &str) -> Result<Vec<Version>> {
78        Ok(self.data.get(id).cloned().unwrap_or_default())
79    }
80
81    fn cleanup_old_versions(&mut self, id: &str, keep_count: usize) -> Result<usize> {
82        if let Some(versions) = self.data.get_mut(id) {
83            let original_len = versions.len();
84            if original_len > keep_count {
85                let to_remove = original_len - keep_count;
86                versions.drain(0..to_remove);
87                return Ok(to_remove);
88            }
89        }
90        Ok(0)
91    }
92}
93
94/// Version manager for tracking embedding versions
95pub struct VersionManager {
96    config: VersionManagerConfig,
97    storage: Arc<RwLock<InMemoryVersionStorage>>,
98    is_running: AtomicBool,
99    total_versions: AtomicU64,
100    total_cleanups: AtomicU64,
101}
102
103impl VersionManager {
104    pub fn new(version_control_config: VersionControlConfig) -> Result<Self> {
105        let config = VersionManagerConfig {
106            max_versions_per_id: version_control_config.max_versions,
107            enable_compression: false,
108            ..Default::default()
109        };
110        let storage = Arc::new(RwLock::new(InMemoryVersionStorage::new(
111            config.max_versions_per_id,
112        )));
113        Ok(Self {
114            config,
115            storage,
116            is_running: AtomicBool::new(false),
117            total_versions: AtomicU64::new(0),
118            total_cleanups: AtomicU64::new(0),
119        })
120    }
121
122    pub async fn start(&self) -> Result<()> {
123        self.is_running.store(true, Ordering::Release);
124        Ok(())
125    }
126
127    pub async fn stop(&self) -> Result<()> {
128        self.is_running.store(false, Ordering::Release);
129        Ok(())
130    }
131
132    pub async fn health_check(&self) -> Result<HealthStatus> {
133        if self.is_running.load(Ordering::Acquire) {
134            Ok(HealthStatus::Healthy)
135        } else {
136            Ok(HealthStatus::Warning {
137                message: "Version manager not running".to_string(),
138            })
139        }
140    }
141
142    pub fn track_version(&self, id: &str, version: Version) -> Result<()> {
143        let mut storage = self
144            .storage
145            .write()
146            .map_err(|_| anyhow::anyhow!("Failed to acquire version storage lock"))?;
147        storage.store_version(id, &version)?;
148        self.total_versions.fetch_add(1, Ordering::Relaxed);
149        Ok(())
150    }
151
152    pub fn get_version(&self, id: &str, version_number: u64) -> Result<Option<Version>> {
153        let storage = self
154            .storage
155            .read()
156            .map_err(|_| anyhow::anyhow!("Failed to acquire version storage lock"))?;
157        storage.get_version(id, version_number)
158    }
159
160    pub fn cleanup_old(&self, id: &str) -> Result<usize> {
161        let mut storage = self
162            .storage
163            .write()
164            .map_err(|_| anyhow::anyhow!("Failed to acquire version storage lock"))?;
165        let removed = storage.cleanup_old_versions(id, self.config.max_versions_per_id)?;
166        if removed > 0 {
167            self.total_cleanups.fetch_add(1, Ordering::Relaxed);
168        }
169        Ok(removed)
170    }
171
172    pub fn get_statistics(&self) -> VersionManagerStatistics {
173        let total_ids = self.storage.read().map(|s| s.data.len()).unwrap_or(0);
174        VersionManagerStatistics {
175            total_versions: self.total_versions.load(Ordering::Acquire),
176            total_cleanups: self.total_cleanups.load(Ordering::Acquire),
177            total_ids_tracked: total_ids,
178            is_running: self.is_running.load(Ordering::Acquire),
179        }
180    }
181}
182
183#[cfg(test)]
184mod tests {
185    use super::*;
186    use crate::real_time_embedding_pipeline::config::VersionControlConfig;
187    use crate::real_time_embedding_pipeline::traits::Version;
188    use crate::Vector;
189    use std::time::SystemTime;
190
191    fn make_version(num: u64) -> Version {
192        Version {
193            version: num,
194            vector: Vector::new(vec![1.0, 2.0, 3.0]),
195            created_at: SystemTime::now(),
196            metadata: HashMap::new(),
197            checksum: format!("checksum_{}", num),
198        }
199    }
200
201    #[tokio::test]
202    async fn test_version_manager_start_stop() {
203        let config = VersionControlConfig::default();
204        let manager = VersionManager::new(config).expect("should create");
205        manager.start().await.expect("should start");
206        let health = manager.health_check().await.expect("should check");
207        assert!(matches!(health, HealthStatus::Healthy));
208        manager.stop().await.expect("should stop");
209    }
210
211    #[test]
212    fn test_track_and_get_version() {
213        let config = VersionControlConfig::default();
214        let manager = VersionManager::new(config).expect("should create");
215        let v = make_version(1);
216        manager.track_version("item1", v).expect("should track");
217        let result = manager.get_version("item1", 1).expect("should get");
218        assert!(result.is_some());
219        assert_eq!(result.expect("should have version").version, 1);
220    }
221
222    #[test]
223    fn test_cleanup_old_versions() {
224        let config = VersionControlConfig {
225            max_versions: 3,
226            ..Default::default()
227        };
228        let manager = VersionManager::new(config).expect("should create");
229        for i in 1..=5 {
230            manager
231                .track_version("item1", make_version(i))
232                .expect("should track");
233        }
234        let _removed = manager.cleanup_old("item1").expect("should cleanup");
235        let stats = manager.get_statistics();
236        assert!(stats.total_versions > 0);
237    }
238
239    #[test]
240    fn test_in_memory_version_storage() {
241        let mut storage = InMemoryVersionStorage::new(3);
242        for i in 1..=5u64 {
243            let v = Version {
244                version: i,
245                vector: Vector::new(vec![i as f32]),
246                created_at: SystemTime::now(),
247                metadata: HashMap::new(),
248                checksum: format!("c{}", i),
249            };
250            storage.store_version("key", &v).expect("should store");
251        }
252        let all = storage.get_all_versions("key").expect("should get all");
253        assert!(all.len() <= 3);
254    }
255}