1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
// Copyright (c) 2025-2026 Kirky.X
// SPDX-License-Identifier: MIT
//! Redis pipeline batch operations.
use super::client::RedisBackend;
use super::error::map_redis_error;
use crate::error::{OxCacheError, OxCacheResult};
use crate::security;
use std::time::Duration;
impl RedisBackend {
/// Batch set multiple key-value pairs using Redis Pipeline.
///
/// Significantly faster than individual SET commands when setting many keys,
/// as it reduces network round trips from N to 1.
pub async fn set_many_pipeline(
&self,
items: &[(&str, Vec<u8>)],
ttl: Option<Duration>,
) -> OxCacheResult<()> {
if items.is_empty() {
return Ok(());
}
for (key, _) in items {
security::validate_redis_key(key)?;
}
// Validate TTL before building pipeline: Redis SETEX rejects TTL=0
if let Some(ttl) = ttl {
let secs = ttl.as_secs();
if secs == 0 {
return Err(OxCacheError::InvalidInput(
"TTL must be at least 1 second for Redis SETEX; sub-second TTL is truncated to 0".to_string(),
));
}
}
let mut conn = self.conn();
let mut pipe = redis::pipe();
for (key, value) in items {
if let Some(ttl) = ttl {
pipe.cmd("SETEX")
.arg(key)
.arg(ttl.as_secs())
.arg(value.as_slice());
} else {
pipe.cmd("SET").arg(key).arg(value.as_slice());
}
}
pipe.query_async::<()>(&mut conn)
.await
.map_err(map_redis_error)?;
Ok(())
}
/// Batch get multiple keys using Redis Pipeline.
///
/// Significantly faster than individual GET commands when fetching many keys.
pub async fn get_many_pipeline(&self, keys: &[&str]) -> OxCacheResult<Vec<Option<Vec<u8>>>> {
if keys.is_empty() {
return Ok(vec![]);
}
for key in keys {
security::validate_redis_key(key)?;
}
let mut conn = self.conn();
let mut pipe = redis::pipe();
for key in keys {
pipe.cmd("GET").arg(key);
}
let results: Vec<Option<Vec<u8>>> =
pipe.query_async(&mut conn).await.map_err(map_redis_error)?;
Ok(results)
}
/// Batch delete multiple keys using Redis Pipeline.
///
/// Significantly faster than individual DEL commands when deleting many keys.
pub async fn delete_many_pipeline(&self, keys: &[&str]) -> OxCacheResult<()> {
if keys.is_empty() {
return Ok(());
}
for key in keys {
security::validate_redis_key(key)?;
}
let mut conn = self.conn();
let mut pipe = redis::pipe();
for key in keys {
pipe.cmd("DEL").arg(key);
}
pipe.query_async::<()>(&mut conn)
.await
.map_err(map_redis_error)?;
Ok(())
}
}