/home/runner/work/feoxdb/feoxdb/src/core/store/ttl.rs
Line | Count | Source |
1 | | use bytes::Bytes; |
2 | | use std::sync::atomic::Ordering; |
3 | | use std::sync::Arc; |
4 | | use std::time::{SystemTime, UNIX_EPOCH}; |
5 | | |
6 | | use crate::core::ttl_sweep::TtlConfig; |
7 | | use crate::error::{FeoxError, Result}; |
8 | | |
9 | | use super::FeoxStore; |
10 | | |
11 | | impl FeoxStore { |
12 | | /// Insert or update a key-value pair with TTL (Time-To-Live). |
13 | | /// |
14 | | /// # Arguments |
15 | | /// |
16 | | /// * `key` - The key to insert |
17 | | /// * `value` - The value to store |
18 | | /// * `ttl_seconds` - Time-to-live in seconds |
19 | | /// |
20 | | /// # Returns |
21 | | /// |
22 | | /// Returns `Ok(())` if successful. |
23 | | /// |
24 | | /// # Example |
25 | | /// |
26 | | /// ```rust |
27 | | /// # use feoxdb::FeoxStore; |
28 | | /// # fn main() -> feoxdb::Result<()> { |
29 | | /// # let store = FeoxStore::builder().enable_ttl(true).build()?; |
30 | | /// // Key expires after 60 seconds |
31 | | /// store.insert_with_ttl(b"session:123", b"data", 60)?; |
32 | | /// # Ok(()) |
33 | | /// # } |
34 | | /// ``` |
35 | | /// |
36 | | /// # Performance |
37 | | /// |
38 | | /// * Memory mode: ~800ns |
39 | | /// * Persistent mode: ~1µs (buffered write) |
40 | 10 | pub fn insert_with_ttl(&self, key: &[u8], value: &[u8], ttl_seconds: u64) -> Result<bool> { |
41 | 10 | if !self.enable_ttl { |
42 | 2 | return Err(FeoxError::TtlNotEnabled); |
43 | 8 | } |
44 | 8 | self.insert_with_ttl_and_timestamp(key, value, ttl_seconds, None) |
45 | 10 | } |
46 | | |
47 | | /// Insert or update a key-value pair with TTL and explicit timestamp. |
48 | | /// |
49 | | /// # Arguments |
50 | | /// |
51 | | /// * `key` - The key to insert |
52 | | /// * `value` - The value to store |
53 | | /// * `ttl_seconds` - Time-to-live in seconds |
54 | | /// * `timestamp` - Optional timestamp for conflict resolution. If `None`, uses current time. |
55 | | /// |
56 | | /// # Returns |
57 | | /// |
58 | | /// Returns `Ok(())` if successful. |
59 | 9 | pub fn insert_with_ttl_and_timestamp( |
60 | 9 | &self, |
61 | 9 | key: &[u8], |
62 | 9 | value: &[u8], |
63 | 9 | ttl_seconds: u64, |
64 | 9 | timestamp: Option<u64>, |
65 | 9 | ) -> Result<bool> { |
66 | 9 | if !self.enable_ttl { |
67 | 1 | return Err(FeoxError::TtlNotEnabled); |
68 | 8 | } |
69 | 8 | let timestamp = match timestamp { |
70 | 8 | Some(0) | None => self.get_timestamp(), |
71 | 0 | Some(ts) => ts, |
72 | | }; |
73 | | |
74 | | // Calculate expiry timestamp |
75 | 8 | let ttl_expiry = if ttl_seconds > 0 { |
76 | 7 | timestamp + (ttl_seconds * 1_000_000_000) // Convert seconds to nanoseconds |
77 | | } else { |
78 | 1 | 0 |
79 | | }; |
80 | | |
81 | 8 | self.insert_with_timestamp_and_ttl_internal(key, value, Some(timestamp), ttl_expiry) |
82 | 9 | } |
83 | | |
84 | | /// Insert or update a key-value pair with TTL using zero-copy Bytes. |
85 | | /// |
86 | | /// This method avoids copying the value data by directly using the Bytes type, |
87 | | /// which provides reference-counted zero-copy semantics. |
88 | | /// |
89 | | /// # Arguments |
90 | | /// |
91 | | /// * `key` - The key to insert |
92 | | /// * `value` - The value to store as Bytes |
93 | | /// * `ttl_seconds` - Time-to-live in seconds |
94 | | /// |
95 | | /// # Returns |
96 | | /// |
97 | | /// Returns `Ok(())` if successful. |
98 | | /// |
99 | | /// # Example |
100 | | /// |
101 | | /// ```rust |
102 | | /// # use feoxdb::FeoxStore; |
103 | | /// # use bytes::Bytes; |
104 | | /// # fn main() -> feoxdb::Result<()> { |
105 | | /// # let store = FeoxStore::builder().enable_ttl(true).build()?; |
106 | | /// let data = Bytes::from_static(b"session_data"); |
107 | | /// // Key expires after 60 seconds |
108 | | /// store.insert_bytes_with_ttl(b"session:123", data, 60)?; |
109 | | /// # Ok(()) |
110 | | /// # } |
111 | | /// ``` |
112 | | /// |
113 | | /// # Performance |
114 | | /// |
115 | | /// * Memory mode: ~800ns (avoids value copy) |
116 | | /// * Persistent mode: ~1µs (buffered write, avoids value copy) |
117 | 3 | pub fn insert_bytes_with_ttl( |
118 | 3 | &self, |
119 | 3 | key: &[u8], |
120 | 3 | value: Bytes, |
121 | 3 | ttl_seconds: u64, |
122 | 3 | ) -> Result<bool> { |
123 | 3 | if !self.enable_ttl { |
124 | 1 | return Err(FeoxError::TtlNotEnabled); |
125 | 2 | } |
126 | 2 | self.insert_bytes_with_ttl_and_timestamp(key, value, ttl_seconds, None) |
127 | 3 | } |
128 | | |
129 | | /// Insert or update a key-value pair with TTL and explicit timestamp using zero-copy Bytes. |
130 | | /// |
131 | | /// # Arguments |
132 | | /// |
133 | | /// * `key` - The key to insert |
134 | | /// * `value` - The value to store as Bytes |
135 | | /// * `ttl_seconds` - Time-to-live in seconds |
136 | | /// * `timestamp` - Optional timestamp for conflict resolution. If `None`, uses current time. |
137 | | /// |
138 | | /// # Returns |
139 | | /// |
140 | | /// Returns `Ok(())` if successful. |
141 | 5 | pub fn insert_bytes_with_ttl_and_timestamp( |
142 | 5 | &self, |
143 | 5 | key: &[u8], |
144 | 5 | value: Bytes, |
145 | 5 | ttl_seconds: u64, |
146 | 5 | timestamp: Option<u64>, |
147 | 5 | ) -> Result<bool> { |
148 | 5 | if !self.enable_ttl { |
149 | 0 | return Err(FeoxError::TtlNotEnabled); |
150 | 5 | } |
151 | 5 | self.insert_bytes_with_timestamp_and_ttl_internal(key, value, timestamp, ttl_seconds) |
152 | 5 | } |
153 | | |
154 | | /// Get the remaining TTL (Time-To-Live) for a key in seconds. |
155 | | /// |
156 | | /// # Arguments |
157 | | /// |
158 | | /// * `key` - The key to check |
159 | | /// |
160 | | /// # Returns |
161 | | /// |
162 | | /// Returns `Some(seconds)` if the key has TTL set, `None` if no TTL or key not found. |
163 | | /// |
164 | | /// # Example |
165 | | /// |
166 | | /// ```rust |
167 | | /// # use feoxdb::FeoxStore; |
168 | | /// # fn main() -> feoxdb::Result<()> { |
169 | | /// # let store = FeoxStore::builder().enable_ttl(true).build()?; |
170 | | /// store.insert_with_ttl(b"session", b"data", 3600)?; |
171 | | /// |
172 | | /// // Check remaining TTL |
173 | | /// if let Ok(Some(ttl)) = store.get_ttl(b"session") { |
174 | | /// println!("Session expires in {} seconds", ttl); |
175 | | /// } |
176 | | /// # Ok(()) |
177 | | /// # } |
178 | | /// ``` |
179 | 10 | pub fn get_ttl(&self, key: &[u8]) -> Result<Option<u64>> { |
180 | 10 | if !self.enable_ttl { |
181 | 1 | return Err(FeoxError::TtlNotEnabled); |
182 | 9 | } |
183 | 9 | self.validate_key(key)?0 ; |
184 | | |
185 | 9 | let record = self |
186 | 9 | .hash_table |
187 | 9 | .read(key, |_, v| v.clone()) |
188 | 9 | .ok_or(FeoxError::KeyNotFound)?0 ; |
189 | 9 | let ttl_expiry = record.ttl_expiry.load(Ordering::Acquire); |
190 | | |
191 | 9 | if ttl_expiry == 0 { |
192 | 4 | return Ok(None); // No TTL set |
193 | 5 | } |
194 | | |
195 | 5 | let now = self.get_timestamp(); |
196 | 5 | if now >= ttl_expiry { |
197 | 0 | return Ok(Some(0)); // Already expired |
198 | 5 | } |
199 | | |
200 | | // Return remaining seconds |
201 | 5 | Ok(Some((ttl_expiry - now) / 1_000_000_000)) |
202 | 10 | } |
203 | | |
204 | | /// Update the TTL for an existing key. |
205 | | /// |
206 | | /// # Arguments |
207 | | /// |
208 | | /// * `key` - The key to update |
209 | | /// * `ttl_seconds` - New TTL in seconds (0 to remove TTL) |
210 | | /// |
211 | | /// # Returns |
212 | | /// |
213 | | /// Returns `Ok(())` if successful. |
214 | | /// |
215 | | /// # Errors |
216 | | /// |
217 | | /// * `KeyNotFound` - Key does not exist |
218 | | /// |
219 | | /// # Example |
220 | | /// |
221 | | /// ```rust |
222 | | /// # use feoxdb::FeoxStore; |
223 | | /// # fn main() -> feoxdb::Result<()> { |
224 | | /// # let store = FeoxStore::builder().enable_ttl(true).build()?; |
225 | | /// # store.insert(b"key", b"value")?; |
226 | | /// // Extend TTL to 1 hour |
227 | | /// store.update_ttl(b"key", 3600)?; |
228 | | /// # Ok(()) |
229 | | /// # } |
230 | | /// ``` |
231 | 4 | pub fn update_ttl(&self, key: &[u8], ttl_seconds: u64) -> Result<()> { |
232 | 4 | if !self.enable_ttl { |
233 | 1 | return Err(FeoxError::TtlNotEnabled); |
234 | 3 | } |
235 | 3 | self.validate_key(key)?0 ; |
236 | | |
237 | 3 | let record = self |
238 | 3 | .hash_table |
239 | 3 | .read(key, |_, v| v.clone()) |
240 | 3 | .ok_or(FeoxError::KeyNotFound)?0 ; |
241 | | |
242 | 3 | let new_expiry = if ttl_seconds > 0 { |
243 | 2 | self.get_timestamp() + (ttl_seconds * 1_000_000_000) |
244 | | } else { |
245 | 1 | 0 |
246 | | }; |
247 | | |
248 | 3 | record.ttl_expiry.store(new_expiry, Ordering::Release); |
249 | 3 | Ok(()) |
250 | 4 | } |
251 | | |
252 | | /// Remove TTL from a key, making it persistent. |
253 | | /// |
254 | | /// # Arguments |
255 | | /// |
256 | | /// * `key` - The key to persist |
257 | | /// |
258 | | /// # Returns |
259 | | /// |
260 | | /// Returns `Ok(())` if successful. |
261 | | /// |
262 | | /// # Errors |
263 | | /// |
264 | | /// * `KeyNotFound` - Key does not exist |
265 | | /// |
266 | | /// # Example |
267 | | /// |
268 | | /// ```rust |
269 | | /// # use feoxdb::FeoxStore; |
270 | | /// # fn main() -> feoxdb::Result<()> { |
271 | | /// # let store = FeoxStore::builder().enable_ttl(true).build()?; |
272 | | /// # store.insert_with_ttl(b"temp", b"data", 60)?; |
273 | | /// // Remove TTL, make permanent |
274 | | /// store.persist(b"temp")?; |
275 | | /// # Ok(()) |
276 | | /// # } |
277 | | /// ``` |
278 | 2 | pub fn persist(&self, key: &[u8]) -> Result<()> { |
279 | 2 | if !self.enable_ttl { |
280 | 1 | return Err(FeoxError::TtlNotEnabled); |
281 | 1 | } |
282 | 1 | self.update_ttl(key, 0) |
283 | 2 | } |
284 | | |
285 | | /// Start the TTL sweeper if configured |
286 | | /// This must be called with an `Arc<Self>` after construction |
287 | 0 | pub fn start_ttl_sweeper(self: &Arc<Self>, config: Option<TtlConfig>) { |
288 | | // Only start TTL sweeper if TTL is enabled |
289 | 0 | if !self.enable_ttl { |
290 | 0 | return; |
291 | 0 | } |
292 | | |
293 | 0 | let ttl_config = config.unwrap_or_else(|| { |
294 | 0 | if self.memory_only { |
295 | 0 | TtlConfig::default_memory() |
296 | | } else { |
297 | 0 | TtlConfig::default_persistent() |
298 | | } |
299 | 0 | }); |
300 | | |
301 | 0 | if ttl_config.enabled { |
302 | 0 | let weak_store = Arc::downgrade(self); |
303 | 0 | let mut sweeper = crate::core::ttl_sweep::TtlSweeper::new(weak_store, ttl_config); |
304 | 0 | sweeper.start(); |
305 | 0 |
|
306 | 0 | // Store the sweeper |
307 | 0 | *self.ttl_sweeper.write() = Some(sweeper); |
308 | 0 | } |
309 | 0 | } |
310 | | |
311 | | /// Get current timestamp (public for TTL cleaner) |
312 | 4.34k | pub fn get_timestamp_pub(&self) -> u64 { |
313 | 4.34k | SystemTime::now() |
314 | 4.34k | .duration_since(UNIX_EPOCH) |
315 | 4.34k | .unwrap() |
316 | 4.34k | .as_nanos() as u64 |
317 | 4.34k | } |
318 | | } |