armature_cache/
parallel.rs

1//! Parallel batch operations for cache stores.
2
3use crate::error::{CacheError, CacheResult};
4use crate::traits::CacheStore;
5use futures::future::{join_all, try_join_all};
6use serde::{Serialize, de::DeserializeOwned};
7use std::collections::HashMap;
8use std::time::Duration;
9
10/// Parallel batch operations for cache stores.
11///
12/// This module provides high-performance batch operations that execute
13/// multiple cache operations concurrently, significantly reducing total latency.
14///
15/// # Performance
16///
17/// - **get_many**: 10-100x faster than sequential gets (depending on network latency)
18/// - **set_many**: 10-100x faster than sequential sets
19/// - **delete_many**: Similar performance gains
20///
21/// # Examples
22///
23/// ```no_run
24/// use armature_cache::*;
25/// use armature_cache::parallel::*;
26///
27/// # async fn example() -> CacheResult<()> {
28/// let cache = RedisCache::new(CacheConfig::redis("redis://localhost:6379")?).await?;
29///
30/// // Get multiple keys in parallel
31/// let keys = vec!["user:1", "user:2", "user:3"];
32/// let values = get_many_json(&cache, &keys).await?;
33///
34/// // Set multiple keys in parallel
35/// let items = vec![
36///     ("key1", "value1".to_string()),
37///     ("key2", "value2".to_string()),
38/// ];
39/// set_many_json(&cache, &items, None).await?;
40/// # Ok(())
41/// # }
42/// ```
43pub struct ParallelCacheOps;
44
45impl ParallelCacheOps {
46    /// Get multiple JSON values in parallel.
47    ///
48    /// # Arguments
49    ///
50    /// * `store` - The cache store
51    /// * `keys` - Slice of keys to fetch
52    ///
53    /// # Returns
54    ///
55    /// A vector of optional values in the same order as keys.
56    ///
57    /// # Examples
58    ///
59    /// ```no_run
60    /// use armature_cache::*;
61    /// use armature_cache::parallel::ParallelCacheOps;
62    ///
63    /// # async fn example() -> CacheResult<()> {
64    /// let cache = RedisCache::new(CacheConfig::redis("redis://localhost:6379")?).await?;
65    ///
66    /// let keys = vec!["key1", "key2", "key3"];
67    /// let values = ParallelCacheOps::get_many_json(&cache, &keys).await?;
68    ///
69    /// for (key, value) in keys.iter().zip(values.iter()) {
70    ///     println!("{}: {:?}", key, value);
71    /// }
72    /// # Ok(())
73    /// # }
74    /// ```
75    pub async fn get_many_json<S: CacheStore>(
76        store: &S,
77        keys: &[&str],
78    ) -> CacheResult<Vec<Option<String>>> {
79        let futures = keys.iter().map(|key| store.get_json(key));
80        let results: Vec<CacheResult<Option<String>>> = join_all(futures).await;
81
82        results.into_iter().collect()
83    }
84
85    /// Get multiple typed values in parallel.
86    ///
87    /// # Type Parameters
88    ///
89    /// * `T` - The type to deserialize into
90    ///
91    /// # Examples
92    ///
93    /// ```no_run
94    /// use armature_cache::*;
95    /// use armature_cache::parallel::ParallelCacheOps;
96    /// use serde::{Deserialize, Serialize};
97    ///
98    /// #[derive(Serialize, Deserialize)]
99    /// struct User {
100    ///     id: u64,
101    ///     name: String,
102    /// }
103    ///
104    /// # async fn example() -> CacheResult<()> {
105    /// let cache = RedisCache::new(CacheConfig::redis("redis://localhost:6379")?).await?;
106    ///
107    /// let keys = vec!["user:1", "user:2", "user:3"];
108    /// let users: Vec<Option<User>> = ParallelCacheOps::get_many(&cache, &keys).await?;
109    /// # Ok(())
110    /// # }
111    /// ```
112    pub async fn get_many<S: CacheStore, T: DeserializeOwned>(
113        store: &S,
114        keys: &[&str],
115    ) -> CacheResult<Vec<Option<T>>> {
116        let json_values = Self::get_many_json(store, keys).await?;
117
118        json_values
119            .into_iter()
120            .map(|opt_json| {
121                opt_json
122                    .map(|json| {
123                        serde_json::from_str(&json)
124                            .map_err(|e| CacheError::Deserialization(e.to_string()))
125                    })
126                    .transpose()
127            })
128            .collect()
129    }
130
131    /// Set multiple JSON values in parallel.
132    ///
133    /// # Arguments
134    ///
135    /// * `store` - The cache store
136    /// * `items` - Slice of (key, value) tuples
137    /// * `ttl` - Optional time-to-live for all items
138    ///
139    /// # Examples
140    ///
141    /// ```no_run
142    /// use armature_cache::*;
143    /// use armature_cache::parallel::ParallelCacheOps;
144    /// use std::time::Duration;
145    ///
146    /// # async fn example() -> CacheResult<()> {
147    /// let cache = RedisCache::new(CacheConfig::redis("redis://localhost:6379")?).await?;
148    ///
149    /// let items = vec![
150    ///     ("key1", r#"{"value": 1}"#.to_string()),
151    ///     ("key2", r#"{"value": 2}"#.to_string()),
152    /// ];
153    ///
154    /// ParallelCacheOps::set_many_json(&cache, &items, Some(Duration::from_secs(3600))).await?;
155    /// # Ok(())
156    /// # }
157    /// ```
158    pub async fn set_many_json<S: CacheStore>(
159        store: &S,
160        items: &[(&str, String)],
161        ttl: Option<Duration>,
162    ) -> CacheResult<()> {
163        let futures = items
164            .iter()
165            .map(|(key, value)| store.set_json(key, value.clone(), ttl));
166
167        try_join_all(futures).await?;
168        Ok(())
169    }
170
171    /// Set multiple typed values in parallel.
172    ///
173    /// # Type Parameters
174    ///
175    /// * `T` - The type to serialize from
176    ///
177    /// # Examples
178    ///
179    /// ```no_run
180    /// use armature_cache::*;
181    /// use armature_cache::parallel::ParallelCacheOps;
182    /// use serde::{Deserialize, Serialize};
183    ///
184    /// #[derive(Serialize, Deserialize)]
185    /// struct Counter {
186    ///     count: u64,
187    /// }
188    ///
189    /// # async fn example() -> CacheResult<()> {
190    /// let cache = RedisCache::new(CacheConfig::redis("redis://localhost:6379")?).await?;
191    ///
192    /// let items = vec![
193    ///     ("counter:1", Counter { count: 10 }),
194    ///     ("counter:2", Counter { count: 20 }),
195    /// ];
196    ///
197    /// ParallelCacheOps::set_many(&cache, &items, None).await?;
198    /// # Ok(())
199    /// # }
200    /// ```
201    pub async fn set_many<S: CacheStore, T: Serialize>(
202        store: &S,
203        items: &[(&str, T)],
204        ttl: Option<Duration>,
205    ) -> CacheResult<()> {
206        let json_items: Result<Vec<_>, _> = items
207            .iter()
208            .map(|(key, value)| {
209                serde_json::to_string(value)
210                    .map(|json| (*key, json))
211                    .map_err(|e| CacheError::Serialization(e.to_string()))
212            })
213            .collect();
214
215        let json_items = json_items?;
216        let item_refs: Vec<_> = json_items.iter().map(|(k, v)| (*k, v.clone())).collect();
217
218        Self::set_many_json(store, &item_refs, ttl).await
219    }
220
221    /// Delete multiple keys in parallel.
222    ///
223    /// # Examples
224    ///
225    /// ```no_run
226    /// use armature_cache::*;
227    /// use armature_cache::parallel::ParallelCacheOps;
228    ///
229    /// # async fn example() -> CacheResult<()> {
230    /// let cache = RedisCache::new(CacheConfig::redis("redis://localhost:6379")?).await?;
231    ///
232    /// let keys = vec!["key1", "key2", "key3"];
233    /// ParallelCacheOps::delete_many(&cache, &keys).await?;
234    /// # Ok(())
235    /// # }
236    /// ```
237    pub async fn delete_many<S: CacheStore>(store: &S, keys: &[&str]) -> CacheResult<()> {
238        let futures = keys.iter().map(|key| store.delete(key));
239        try_join_all(futures).await?;
240        Ok(())
241    }
242
243    /// Check if multiple keys exist in parallel.
244    ///
245    /// # Returns
246    ///
247    /// A vector of booleans indicating existence, in the same order as keys.
248    ///
249    /// # Examples
250    ///
251    /// ```no_run
252    /// use armature_cache::*;
253    /// use armature_cache::parallel::ParallelCacheOps;
254    ///
255    /// # async fn example() -> CacheResult<()> {
256    /// let cache = RedisCache::new(CacheConfig::redis("redis://localhost:6379")?).await?;
257    ///
258    /// let keys = vec!["key1", "key2", "key3"];
259    /// let exists = ParallelCacheOps::exists_many(&cache, &keys).await?;
260    ///
261    /// for (key, exists) in keys.iter().zip(exists.iter()) {
262    ///     println!("{}: {}", key, exists);
263    /// }
264    /// # Ok(())
265    /// # }
266    /// ```
267    pub async fn exists_many<S: CacheStore>(store: &S, keys: &[&str]) -> CacheResult<Vec<bool>> {
268        let futures = keys.iter().map(|key| store.exists(key));
269        let results: Vec<CacheResult<bool>> = join_all(futures).await;
270
271        results.into_iter().collect()
272    }
273
274    /// Get TTL for multiple keys in parallel.
275    ///
276    /// # Returns
277    ///
278    /// A vector of optional durations, in the same order as keys.
279    ///
280    /// # Examples
281    ///
282    /// ```no_run
283    /// use armature_cache::*;
284    /// use armature_cache::parallel::ParallelCacheOps;
285    ///
286    /// # async fn example() -> CacheResult<()> {
287    /// let cache = RedisCache::new(CacheConfig::redis("redis://localhost:6379")?).await?;
288    ///
289    /// let keys = vec!["key1", "key2", "key3"];
290    /// let ttls = ParallelCacheOps::ttl_many(&cache, &keys).await?;
291    ///
292    /// for (key, ttl) in keys.iter().zip(ttls.iter()) {
293    ///     println!("{}: {:?}", key, ttl);
294    /// }
295    /// # Ok(())
296    /// # }
297    /// ```
298    pub async fn ttl_many<S: CacheStore>(
299        store: &S,
300        keys: &[&str],
301    ) -> CacheResult<Vec<Option<Duration>>> {
302        let futures = keys.iter().map(|key| store.ttl(key));
303        let results: Vec<CacheResult<Option<Duration>>> = join_all(futures).await;
304
305        results.into_iter().collect()
306    }
307
308    /// Cache warming: preload multiple keys into cache.
309    ///
310    /// # Type Parameters
311    ///
312    /// * `T` - The type to serialize
313    /// * `F` - Factory function that returns data for a given key
314    ///
315    /// # Examples
316    ///
317    /// ```ignore
318    /// use armature_cache::*;
319    /// use armature_cache::parallel::ParallelCacheOps;
320    /// use std::time::Duration;
321    ///
322    /// # async fn example() -> CacheResult<()> {
323    /// let cache = RedisCache::new(CacheConfig::redis("redis://localhost:6379")?).await?;
324    ///
325    /// let keys = vec!["user:1", "user:2", "user:3"];
326    ///
327    /// ParallelCacheOps::warm_cache(
328    ///     &cache,
329    ///     &keys,
330    ///     Some(Duration::from_secs(3600)),
331    ///     |key: &str| async move {
332    ///         // Fetch from database
333    ///         let data = format!("Data for {}", key);
334    ///         Ok::<String, CacheError>(data)
335    ///     },
336    /// ).await?;
337    /// # Ok(())
338    /// # }
339    /// ```
340    pub async fn warm_cache<S, T, F, Fut>(
341        store: &S,
342        keys: &[&str],
343        ttl: Option<Duration>,
344        factory: F,
345    ) -> CacheResult<()>
346    where
347        S: CacheStore,
348        T: Serialize,
349        F: Fn(&str) -> Fut,
350        Fut: std::future::Future<Output = CacheResult<T>>,
351    {
352        let mut futures = Vec::new();
353
354        for key in keys {
355            let fut = async {
356                let value = factory(key).await?;
357                let json = serde_json::to_string(&value)
358                    .map_err(|e| CacheError::Serialization(e.to_string()))?;
359                store.set_json(key, json, ttl).await?;
360                Ok::<(), CacheError>(())
361            };
362            futures.push(fut);
363        }
364
365        try_join_all(futures).await?;
366        Ok(())
367    }
368}
369
370/// Helper functions for parallel cache operations.
371///
372/// These functions provide a more convenient API than `ParallelCacheOps` methods.
373/// Get multiple JSON values in parallel.
374pub async fn get_many_json<S: CacheStore>(
375    store: &S,
376    keys: &[&str],
377) -> CacheResult<Vec<Option<String>>> {
378    ParallelCacheOps::get_many_json(store, keys).await
379}
380
381/// Get multiple typed values in parallel.
382pub async fn get_many<S: CacheStore, T: DeserializeOwned>(
383    store: &S,
384    keys: &[&str],
385) -> CacheResult<Vec<Option<T>>> {
386    ParallelCacheOps::get_many(store, keys).await
387}
388
389/// Set multiple JSON values in parallel.
390pub async fn set_many_json<S: CacheStore>(
391    store: &S,
392    items: &[(&str, String)],
393    ttl: Option<Duration>,
394) -> CacheResult<()> {
395    ParallelCacheOps::set_many_json(store, items, ttl).await
396}
397
398/// Set multiple typed values in parallel.
399pub async fn set_many<S: CacheStore, T: Serialize>(
400    store: &S,
401    items: &[(&str, T)],
402    ttl: Option<Duration>,
403) -> CacheResult<()> {
404    ParallelCacheOps::set_many(store, items, ttl).await
405}
406
407/// Delete multiple keys in parallel.
408pub async fn delete_many<S: CacheStore>(store: &S, keys: &[&str]) -> CacheResult<()> {
409    ParallelCacheOps::delete_many(store, keys).await
410}
411
412/// Build a HashMap from multiple keys fetched in parallel.
413pub async fn get_many_as_map<S: CacheStore, T: DeserializeOwned>(
414    store: &S,
415    keys: &[&str],
416) -> CacheResult<HashMap<String, T>> {
417    let values = get_many(store, keys).await?;
418
419    let map: HashMap<String, T> = keys
420        .iter()
421        .zip(values.into_iter())
422        .filter_map(|(key, opt_value)| opt_value.map(|value| (key.to_string(), value)))
423        .collect();
424
425    Ok(map)
426}
427
428#[cfg(test)]
429mod tests {
430    #[test]
431    fn test_parallel_ops_exist() {
432        // Ensure the module compiles - this test validates the module is correctly defined
433    }
434}