Skip to main content

llm_optimizer_processor/state/
redis_backend.rs

1//! Redis-based distributed state backend implementation
2//!
3//! This module provides a production-ready Redis state backend with comprehensive
4//! features for distributed state management including connection pooling, cluster
5//! support, TTL management, batch operations, and metrics tracking.
6//!
7//! ## Features
8//!
9//! - **Connection Pooling**: Automatic connection management with configurable pool size
10//! - **Cluster Support**: Works with both standalone and clustered Redis deployments
11//! - **TTL Management**: Per-key expiration with automatic cleanup
12//! - **Batch Operations**: Efficient pipeline support for bulk operations
13//! - **Retry Logic**: Configurable retry strategy for transient failures
14//! - **Metrics**: Comprehensive operation tracking and statistics
15//! - **Atomic Operations**: MULTI/EXEC transactions for consistency
16//! - **Health Checks**: Connection monitoring and auto-reconnection
17//!
18//! ## Example Usage
19//!
20//! ```rust,no_run
21//! use processor::state::{RedisStateBackend, RedisConfig, StateBackend};
22//! use std::time::Duration;
23//!
24//! #[tokio::main]
25//! async fn main() -> anyhow::Result<()> {
26//!     // Configure Redis backend
27//!     let config = RedisConfig::builder()
28//!         .url("redis://localhost:6379")
29//!         .key_prefix("myapp:")
30//!         .default_ttl(Duration::from_secs(3600))
31//!         .pool_size(10, 50)
32//!         .build()?;
33//!
34//!     // Create backend
35//!     let backend = RedisStateBackend::new(config).await?;
36//!
37//!     // Store state with TTL
38//!     backend.put_with_ttl(b"session:123", b"user_data", Duration::from_secs(1800)).await?;
39//!
40//!     // Retrieve state
41//!     if let Some(data) = backend.get(b"session:123").await? {
42//!         println!("Session data: {:?}", data);
43//!     }
44//!
45//!     // Batch operations
46//!     let keys = vec![b"key1", b"key2", b"key3"];
47//!     let values = backend.batch_get(&keys).await?;
48//!
49//!     // Get statistics
50//!     let stats = backend.stats().await;
51//!     println!("Hit rate: {:.2}%", stats.hit_rate() * 100.0);
52//!
53//!     Ok(())
54//! }
55//! ```
56
57use async_trait::async_trait;
58use redis::{
59    aio::ConnectionManager, AsyncCommands, Client, RedisResult, Script,
60};
61use serde::{Deserialize, Serialize};
62use std::sync::Arc;
63use std::time::Duration;
64use tokio::sync::RwLock;
65use tracing::{debug, error, trace, warn};
66
67use super::backend::StateBackend;
68use crate::error::{StateError, StateResult};
69
70/// Redis connection configuration
71#[derive(Debug, Clone, Serialize, Deserialize)]
72pub struct RedisConfig {
73    /// Redis connection URL(s) - supports cluster mode with multiple URLs
74    pub urls: Vec<String>,
75    /// Key prefix for namespace isolation
76    pub key_prefix: String,
77    /// Default TTL for keys (None = no expiration)
78    pub default_ttl: Option<Duration>,
79    /// Minimum connections in pool
80    pub min_connections: u32,
81    /// Maximum connections in pool
82    pub max_connections: u32,
83    /// Connection timeout
84    pub connect_timeout: Duration,
85    /// Read timeout
86    pub read_timeout: Duration,
87    /// Write timeout
88    pub write_timeout: Duration,
89    /// Maximum retry attempts for failed operations
90    pub max_retries: u32,
91    /// Base delay for exponential backoff
92    pub retry_base_delay: Duration,
93    /// Maximum delay between retries
94    pub retry_max_delay: Duration,
95    /// Enable cluster mode
96    pub cluster_mode: bool,
97    /// Enable TLS/SSL
98    pub tls_enabled: bool,
99    /// Pipeline batch size for bulk operations
100    pub pipeline_batch_size: usize,
101}
102
103impl Default for RedisConfig {
104    fn default() -> Self {
105        Self {
106            urls: vec!["redis://localhost:6379".to_string()],
107            key_prefix: "state:".to_string(),
108            default_ttl: None,
109            min_connections: 5,
110            max_connections: 50,
111            connect_timeout: Duration::from_secs(5),
112            read_timeout: Duration::from_secs(3),
113            write_timeout: Duration::from_secs(3),
114            max_retries: 3,
115            retry_base_delay: Duration::from_millis(100),
116            retry_max_delay: Duration::from_secs(5),
117            cluster_mode: false,
118            tls_enabled: false,
119            pipeline_batch_size: 100,
120        }
121    }
122}
123
124impl RedisConfig {
125    /// Create a new configuration builder
126    pub fn builder() -> RedisConfigBuilder {
127        RedisConfigBuilder::default()
128    }
129
130    /// Get primary Redis URL
131    pub fn primary_url(&self) -> &str {
132        self.urls.first().map(|s| s.as_str()).unwrap_or("redis://localhost:6379")
133    }
134}
135
136/// Builder for RedisConfig
137#[derive(Debug, Default)]
138pub struct RedisConfigBuilder {
139    config: RedisConfig,
140}
141
142impl RedisConfigBuilder {
143    /// Set Redis URL (single instance)
144    pub fn url(mut self, url: impl Into<String>) -> Self {
145        self.config.urls = vec![url.into()];
146        self
147    }
148
149    /// Set Redis URLs (cluster mode)
150    pub fn urls(mut self, urls: Vec<String>) -> Self {
151        self.config.cluster_mode = urls.len() > 1;
152        self.config.urls = urls;
153        self
154    }
155
156    /// Set key prefix for namespace isolation
157    pub fn key_prefix(mut self, prefix: impl Into<String>) -> Self {
158        self.config.key_prefix = prefix.into();
159        self
160    }
161
162    /// Set default TTL for all keys
163    pub fn default_ttl(mut self, ttl: Duration) -> Self {
164        self.config.default_ttl = Some(ttl);
165        self
166    }
167
168    /// Set connection pool size (min, max)
169    pub fn pool_size(mut self, min: u32, max: u32) -> Self {
170        self.config.min_connections = min;
171        self.config.max_connections = max;
172        self
173    }
174
175    /// Set timeouts (connect, read, write)
176    pub fn timeouts(mut self, connect: Duration, read: Duration, write: Duration) -> Self {
177        self.config.connect_timeout = connect;
178        self.config.read_timeout = read;
179        self.config.write_timeout = write;
180        self
181    }
182
183    /// Set retry configuration (max attempts, base delay, max delay)
184    pub fn retries(mut self, max: u32, base_delay: Duration, max_delay: Duration) -> Self {
185        self.config.max_retries = max;
186        self.config.retry_base_delay = base_delay;
187        self.config.retry_max_delay = max_delay;
188        self
189    }
190
191    /// Enable cluster mode
192    pub fn cluster_mode(mut self, enabled: bool) -> Self {
193        self.config.cluster_mode = enabled;
194        self
195    }
196
197    /// Enable TLS/SSL
198    pub fn tls(mut self, enabled: bool) -> Self {
199        self.config.tls_enabled = enabled;
200        self
201    }
202
203    /// Set pipeline batch size
204    pub fn pipeline_batch_size(mut self, size: usize) -> Self {
205        self.config.pipeline_batch_size = size;
206        self
207    }
208
209    /// Build the configuration
210    pub fn build(self) -> StateResult<RedisConfig> {
211        if self.config.urls.is_empty() {
212            return Err(StateError::StorageError {
213                backend_type: "redis".to_string(),
214                details: "No Redis URLs configured".to_string(),
215            });
216        }
217
218        if self.config.max_connections < self.config.min_connections {
219            return Err(StateError::StorageError {
220                backend_type: "redis".to_string(),
221                details: "max_connections must be >= min_connections".to_string(),
222            });
223        }
224
225        Ok(self.config)
226    }
227}
228
229/// Statistics for Redis backend operations
230#[derive(Debug, Clone, Default, Serialize, Deserialize)]
231pub struct RedisStats {
232    /// Total GET operations
233    pub get_count: u64,
234    /// Total PUT operations
235    pub put_count: u64,
236    /// Total DELETE operations
237    pub delete_count: u64,
238    /// Total cache hits
239    pub hit_count: u64,
240    /// Total cache misses
241    pub miss_count: u64,
242    /// Total pipeline operations
243    pub pipeline_count: u64,
244    /// Total retry attempts
245    pub retry_count: u64,
246    /// Total failed operations
247    pub error_count: u64,
248    /// Total bytes read
249    pub bytes_read: u64,
250    /// Total bytes written
251    pub bytes_written: u64,
252    /// Average operation latency (microseconds)
253    pub avg_latency_us: u64,
254    /// Number of active connections
255    pub active_connections: u32,
256}
257
258impl RedisStats {
259    /// Calculate hit rate (0.0 to 1.0)
260    pub fn hit_rate(&self) -> f64 {
261        let total = self.hit_count + self.miss_count;
262        if total == 0 {
263            0.0
264        } else {
265            self.hit_count as f64 / total as f64
266        }
267    }
268
269    /// Calculate error rate (0.0 to 1.0)
270    pub fn error_rate(&self) -> f64 {
271        let total = self.get_count + self.put_count + self.delete_count;
272        if total == 0 {
273            0.0
274        } else {
275            self.error_count as f64 / total as f64
276        }
277    }
278
279    /// Get total operations count
280    pub fn total_operations(&self) -> u64 {
281        self.get_count + self.put_count + self.delete_count
282    }
283}
284
285/// Redis state backend with connection pooling and advanced features
286pub struct RedisStateBackend {
287    /// Redis connection manager
288    connection: Arc<ConnectionManager>,
289    /// Configuration
290    config: RedisConfig,
291    /// Statistics tracking
292    stats: Arc<RwLock<RedisStats>>,
293    /// LUA script for atomic operations
294    scripts: RedisScripts,
295}
296
297/// Pre-compiled LUA scripts for atomic operations
298struct RedisScripts {
299    /// Atomic get-and-delete operation
300    get_delete: Script,
301    /// Atomic increment with TTL
302    incr_with_ttl: Script,
303    /// Conditional set (only if not exists)
304    set_nx_with_ttl: Script,
305}
306
307impl RedisScripts {
308    fn new() -> Self {
309        Self {
310            // GET and DELETE atomically
311            get_delete: Script::new(
312                r"
313                local value = redis.call('GET', KEYS[1])
314                if value then
315                    redis.call('DEL', KEYS[1])
316                end
317                return value
318                "
319            ),
320            // INCREMENT with TTL
321            incr_with_ttl: Script::new(
322                r"
323                local value = redis.call('INCR', KEYS[1])
324                redis.call('EXPIRE', KEYS[1], ARGV[1])
325                return value
326                "
327            ),
328            // SET NX (only if not exists) with TTL
329            set_nx_with_ttl: Script::new(
330                r"
331                local exists = redis.call('EXISTS', KEYS[1])
332                if exists == 0 then
333                    redis.call('SET', KEYS[1], ARGV[1])
334                    redis.call('EXPIRE', KEYS[1], ARGV[2])
335                    return 1
336                end
337                return 0
338                "
339            ),
340        }
341    }
342}
343
344impl RedisStateBackend {
345    /// Create a new Redis state backend
346    ///
347    /// # Arguments
348    ///
349    /// * `config` - Redis configuration
350    ///
351    /// # Returns
352    ///
353    /// A new `RedisStateBackend` instance
354    ///
355    /// # Errors
356    ///
357    /// Returns error if connection to Redis fails
358    pub async fn new(config: RedisConfig) -> StateResult<Self> {
359        let client = Self::create_client(&config)?;
360        let connection = Self::create_connection_manager(client, &config).await?;
361
362        Ok(Self {
363            connection: Arc::new(connection),
364            config,
365            stats: Arc::new(RwLock::new(RedisStats::default())),
366            scripts: RedisScripts::new(),
367        })
368    }
369
370    /// Create Redis client from configuration
371    fn create_client(config: &RedisConfig) -> StateResult<Client> {
372        let url = config.primary_url();
373        Client::open(url).map_err(|e| StateError::StorageError {
374            backend_type: "redis".to_string(),
375            details: format!("Failed to create client: {}", e),
376        })
377    }
378
379    /// Create connection manager with retry logic
380    async fn create_connection_manager(
381        client: Client,
382        config: &RedisConfig,
383    ) -> StateResult<ConnectionManager> {
384        let mut retries = 0;
385        let mut delay = config.retry_base_delay;
386
387        loop {
388            match ConnectionManager::new(client.clone()).await {
389                Ok(conn) => {
390                    debug!("Redis connection established");
391                    return Ok(conn);
392                }
393                Err(e) => {
394                    retries += 1;
395                    if retries >= config.max_retries {
396                        error!("Failed to connect to Redis after {} retries: {}", retries, e);
397                        return Err(StateError::StorageError {
398                            backend_type: "redis".to_string(),
399                            details: format!("Connection failed after {} retries: {}", retries, e),
400                        });
401                    }
402
403                    warn!("Redis connection attempt {} failed: {}, retrying in {:?}",
404                          retries, e, delay);
405                    tokio::time::sleep(delay).await;
406                    delay = std::cmp::min(delay * 2, config.retry_max_delay);
407                }
408            }
409        }
410    }
411
412    /// Build full key with prefix
413    fn build_key(&self, key: &[u8]) -> Vec<u8> {
414        let mut full_key = self.config.key_prefix.as_bytes().to_vec();
415        full_key.extend_from_slice(key);
416        full_key
417    }
418
419    /// Strip prefix from key
420    fn strip_prefix(&self, key: &[u8]) -> Vec<u8> {
421        let prefix = self.config.key_prefix.as_bytes();
422        if key.starts_with(prefix) {
423            key[prefix.len()..].to_vec()
424        } else {
425            key.to_vec()
426        }
427    }
428
429    /// Execute operation with retry logic
430    async fn with_retry<F, Fut, T>(&self, mut operation: F) -> StateResult<T>
431    where
432        F: FnMut() -> Fut,
433        Fut: std::future::Future<Output = RedisResult<T>>,
434    {
435        let mut retries = 0;
436        let mut delay = self.config.retry_base_delay;
437
438        loop {
439            match operation().await {
440                Ok(result) => return Ok(result),
441                Err(e) => {
442                    retries += 1;
443
444                    // Update error stats
445                    let mut stats = self.stats.write().await;
446                    stats.error_count += 1;
447                    stats.retry_count += 1;
448                    drop(stats);
449
450                    if retries >= self.config.max_retries {
451                        error!("Operation failed after {} retries: {}", retries, e);
452                        return Err(StateError::StorageError {
453                            backend_type: "redis".to_string(),
454                            details: format!("Operation failed after {} retries: {}", retries, e),
455                        });
456                    }
457
458                    warn!("Operation attempt {} failed: {}, retrying in {:?}",
459                          retries, e, delay);
460                    tokio::time::sleep(delay).await;
461                    delay = std::cmp::min(delay * 2, self.config.retry_max_delay);
462                }
463            }
464        }
465    }
466
467    /// Get current statistics
468    pub async fn stats(&self) -> RedisStats {
469        self.stats.read().await.clone()
470    }
471
472    /// Check connection health
473    pub async fn health_check(&self) -> StateResult<bool> {
474        let mut conn = self.connection.as_ref().clone();
475        let result: RedisResult<String> = redis::cmd("PING").query_async(&mut conn).await;
476
477        match result {
478            Ok(response) => Ok(response == "PONG"),
479            Err(e) => {
480                error!("Health check failed: {}", e);
481                Err(StateError::StorageError {
482                    backend_type: "redis".to_string(),
483                    details: format!("Health check failed: {}", e),
484                })
485            }
486        }
487    }
488
489    /// Get Redis server info
490    pub async fn server_info(&self) -> StateResult<String> {
491        let mut conn = self.connection.as_ref().clone();
492        redis::cmd("INFO")
493            .query_async(&mut conn)
494            .await
495            .map_err(|e| StateError::StorageError {
496                backend_type: "redis".to_string(),
497                details: format!("Failed to get server info: {}", e),
498            })
499    }
500
501    /// Put with custom TTL
502    pub async fn put_with_ttl(&self, key: &[u8], value: &[u8], ttl: Duration) -> StateResult<()> {
503        let full_key = self.build_key(key);
504        let ttl_secs = ttl.as_secs();
505
506        let conn = self.connection.as_ref().clone();
507        let full_key_clone = full_key.clone();
508        let value_clone = value.to_vec();
509
510        let result = self.with_retry(move || {
511            let mut conn = conn.clone();
512            let full_key = full_key_clone.clone();
513            let value = value_clone.clone();
514            async move {
515                conn.set_ex(&full_key, &value, ttl_secs).await
516            }
517        }).await;
518
519        if result.is_ok() {
520            let mut stats = self.stats.write().await;
521            stats.put_count += 1;
522            stats.bytes_written += value.len() as u64;
523        }
524
525        result
526    }
527
528    /// Batch get multiple keys
529    pub async fn batch_get(&self, keys: &[&[u8]]) -> StateResult<Vec<Option<Vec<u8>>>> {
530        if keys.is_empty() {
531            return Ok(Vec::new());
532        }
533
534        let full_keys: Vec<Vec<u8>> = keys.iter().map(|k| self.build_key(k)).collect();
535        let conn = self.connection.as_ref().clone();
536
537        let full_keys_clone = full_keys.clone();
538        let result: Vec<Option<Vec<u8>>> = self.with_retry(move || {
539            let mut conn = conn.clone();
540            let full_keys = full_keys_clone.clone();
541            async move {
542                redis::cmd("MGET")
543                    .arg(&full_keys)
544                    .query_async(&mut conn)
545                    .await
546            }
547        }).await?;
548
549        // Update stats
550        let mut stats = self.stats.write().await;
551        stats.get_count += keys.len() as u64;
552        for value in &result {
553            if let Some(v) = value {
554                stats.hit_count += 1;
555                stats.bytes_read += v.len() as u64;
556            } else {
557                stats.miss_count += 1;
558            }
559        }
560
561        Ok(result)
562    }
563
564    /// Batch put multiple key-value pairs
565    pub async fn batch_put(&self, pairs: &[(&[u8], &[u8])]) -> StateResult<()> {
566        if pairs.is_empty() {
567            return Ok(());
568        }
569
570        // Process in chunks based on pipeline batch size
571        for chunk in pairs.chunks(self.config.pipeline_batch_size) {
572            let mut conn = self.connection.as_ref().clone();
573
574            // Build commands for this chunk
575            let commands: Vec<(Vec<u8>, Vec<u8>, Option<u64>)> = chunk
576                .iter()
577                .map(|(key, value)| {
578                    let full_key = self.build_key(key);
579                    let ttl = self.config.default_ttl.map(|d| d.as_secs());
580                    (full_key, value.to_vec(), ttl)
581                })
582                .collect();
583
584            self.with_retry(move || {
585                let mut conn = conn.clone();
586                let commands = commands.clone();
587                async move {
588                    let mut pipe = redis::pipe();
589                    for (full_key, value, ttl) in &commands {
590                        if let Some(ttl_secs) = ttl {
591                            pipe.set_ex(full_key, value, *ttl_secs);
592                        } else {
593                            pipe.set(full_key, value);
594                        }
595                    }
596                    pipe.query_async(&mut conn).await
597                }
598            }).await?;
599
600            // Update stats
601            let mut stats = self.stats.write().await;
602            stats.put_count += chunk.len() as u64;
603            stats.pipeline_count += 1;
604            stats.bytes_written += chunk.iter().map(|(_, v)| v.len() as u64).sum::<u64>();
605        }
606
607        Ok(())
608    }
609
610    /// Batch delete multiple keys
611    pub async fn batch_delete(&self, keys: &[&[u8]]) -> StateResult<usize> {
612        if keys.is_empty() {
613            return Ok(0);
614        }
615
616        let full_keys: Vec<Vec<u8>> = keys.iter().map(|k| self.build_key(k)).collect();
617        let conn = self.connection.as_ref().clone();
618        let full_keys_clone = full_keys.clone();
619
620        let deleted: usize = self.with_retry(move || {
621            let mut conn = conn.clone();
622            let full_keys = full_keys_clone.clone();
623            async move {
624                redis::cmd("DEL")
625                    .arg(&full_keys)
626                    .query_async(&mut conn)
627                    .await
628            }
629        }).await?;
630
631        // Update stats
632        let mut stats = self.stats.write().await;
633        stats.delete_count += keys.len() as u64;
634
635        Ok(deleted)
636    }
637
638    /// Scan keys with cursor (for large datasets)
639    async fn scan_keys_internal(&self, pattern: Vec<u8>, count: usize) -> StateResult<Vec<Vec<u8>>> {
640        let mut conn = self.connection.as_ref().clone();
641        let mut cursor = 0u64;
642        let mut all_keys = Vec::new();
643
644        loop {
645            let (new_cursor, keys): (u64, Vec<Vec<u8>>) = redis::cmd("SCAN")
646                .arg(cursor)
647                .arg("MATCH")
648                .arg(&pattern)
649                .arg("COUNT")
650                .arg(count)
651                .query_async(&mut conn)
652                .await
653                .map_err(|e| StateError::StorageError {
654                    backend_type: "redis".to_string(),
655                    details: format!("SCAN failed: {}", e),
656                })?;
657
658            all_keys.extend(keys);
659            cursor = new_cursor;
660
661            if cursor == 0 {
662                break;
663            }
664        }
665
666        Ok(all_keys)
667    }
668
669    /// Atomic get and delete operation
670    pub async fn get_and_delete(&self, key: &[u8]) -> StateResult<Option<Vec<u8>>> {
671        let full_key = self.build_key(key);
672        let mut conn = self.connection.as_ref().clone();
673
674        let result: Option<Vec<u8>> = self.scripts
675            .get_delete
676            .key(&full_key)
677            .invoke_async(&mut conn)
678            .await
679            .map_err(|e| StateError::StorageError {
680                backend_type: "redis".to_string(),
681                details: format!("GET_DELETE script failed: {}", e),
682            })?;
683
684        // Update stats
685        let mut stats = self.stats.write().await;
686        stats.get_count += 1;
687        stats.delete_count += 1;
688
689        if result.is_some() {
690            stats.hit_count += 1;
691        } else {
692            stats.miss_count += 1;
693        }
694
695        Ok(result)
696    }
697
698    /// Set key only if it doesn't exist (with TTL)
699    pub async fn set_if_not_exists(&self, key: &[u8], value: &[u8], ttl: Duration) -> StateResult<bool> {
700        let full_key = self.build_key(key);
701        let ttl_secs = ttl.as_secs();
702        let mut conn = self.connection.as_ref().clone();
703
704        let result: i32 = self.scripts
705            .set_nx_with_ttl
706            .key(&full_key)
707            .arg(value)
708            .arg(ttl_secs)
709            .invoke_async(&mut conn)
710            .await
711            .map_err(|e| StateError::StorageError {
712                backend_type: "redis".to_string(),
713                details: format!("SET_NX_WITH_TTL script failed: {}", e),
714            })?;
715
716        if result == 1 {
717            let mut stats = self.stats.write().await;
718            stats.put_count += 1;
719            stats.bytes_written += value.len() as u64;
720        }
721
722        Ok(result == 1)
723    }
724
725    /// Get remaining TTL for a key
726    pub async fn ttl(&self, key: &[u8]) -> StateResult<Option<Duration>> {
727        let full_key = self.build_key(key);
728        let mut conn = self.connection.as_ref().clone();
729
730        let ttl_secs: i64 = redis::cmd("TTL")
731            .arg(&full_key)
732            .query_async(&mut conn)
733            .await
734            .map_err(|e| StateError::StorageError {
735                backend_type: "redis".to_string(),
736                details: format!("TTL query failed: {}", e),
737            })?;
738
739        match ttl_secs {
740            -2 => Ok(None), // Key doesn't exist
741            -1 => Ok(Some(Duration::MAX)), // No expiration
742            secs if secs >= 0 => Ok(Some(Duration::from_secs(secs as u64))),
743            _ => Ok(None),
744        }
745    }
746
747    /// Create a checkpoint (snapshot) of all state
748    pub async fn checkpoint(&self) -> StateResult<Vec<(Vec<u8>, Vec<u8>)>> {
749        let pattern = format!("{}*", self.config.key_prefix).into_bytes();
750        let keys = self.scan_keys_internal(pattern, 1000).await?;
751
752        if keys.is_empty() {
753            return Ok(Vec::new());
754        }
755
756        // Get all values
757        let key_refs: Vec<&[u8]> = keys.iter().map(|k| k.as_slice()).collect();
758        let values = self.batch_get(&key_refs).await?;
759
760        // Combine keys and values, stripping prefix
761        let checkpoint: Vec<(Vec<u8>, Vec<u8>)> = keys
762            .into_iter()
763            .zip(values)
764            .filter_map(|(k, v)| v.map(|val| (self.strip_prefix(&k), val)))
765            .collect();
766
767        Ok(checkpoint)
768    }
769
770    /// Restore state from a checkpoint
771    pub async fn restore(&self, checkpoint: Vec<(Vec<u8>, Vec<u8>)>) -> StateResult<()> {
772        if checkpoint.is_empty() {
773            return Ok(());
774        }
775
776        let pairs: Vec<(&[u8], &[u8])> = checkpoint
777            .iter()
778            .map(|(k, v)| (k.as_slice(), v.as_slice()))
779            .collect();
780
781        self.batch_put(&pairs).await
782    }
783
784    /// Get memory usage statistics from Redis
785    pub async fn memory_usage(&self) -> StateResult<u64> {
786        let mut conn = self.connection.as_ref().clone();
787
788        let info: String = redis::cmd("INFO")
789            .arg("memory")
790            .query_async(&mut conn)
791            .await
792            .map_err(|e| StateError::StorageError {
793                backend_type: "redis".to_string(),
794                details: format!("Failed to get memory info: {}", e),
795            })?;
796
797        // Parse used_memory from INFO response
798        for line in info.lines() {
799            if line.starts_with("used_memory:") {
800                if let Some(bytes_str) = line.split(':').nth(1) {
801                    if let Ok(bytes) = bytes_str.trim().parse::<u64>() {
802                        return Ok(bytes);
803                    }
804                }
805            }
806        }
807
808        Ok(0)
809    }
810}
811
812#[async_trait]
813impl StateBackend for RedisStateBackend {
814    async fn get(&self, key: &[u8]) -> StateResult<Option<Vec<u8>>> {
815        let start = std::time::Instant::now();
816        let full_key = self.build_key(key);
817        trace!("Getting key: {:?}", full_key);
818
819        let conn = self.connection.as_ref().clone();
820        let full_key_clone = full_key.clone();
821
822        let result: Option<Vec<u8>> = self.with_retry(move || {
823            let mut conn = conn.clone();
824            let full_key = full_key_clone.clone();
825            async move {
826                conn.get(&full_key).await
827            }
828        }).await?;
829
830        // Update stats
831        let mut stats = self.stats.write().await;
832        stats.get_count += 1;
833
834        if let Some(ref value) = result {
835            stats.hit_count += 1;
836            stats.bytes_read += value.len() as u64;
837        } else {
838            stats.miss_count += 1;
839        }
840
841        // Update latency (simple moving average)
842        let latency_us = start.elapsed().as_micros() as u64;
843        stats.avg_latency_us = if stats.total_operations() == 1 {
844            latency_us
845        } else {
846            (stats.avg_latency_us * 9 + latency_us) / 10
847        };
848
849        Ok(result)
850    }
851
852    async fn put(&self, key: &[u8], value: &[u8]) -> StateResult<()> {
853        let full_key = self.build_key(key);
854        trace!("Putting key: {:?}, value size: {} bytes", full_key, value.len());
855
856        let conn = self.connection.as_ref().clone();
857        let full_key_clone = full_key.clone();
858        let value_clone = value.to_vec();
859
860        if let Some(ttl) = self.config.default_ttl {
861            let ttl_secs = ttl.as_secs();
862            self.with_retry(move || {
863                let mut conn = conn.clone();
864                let full_key = full_key_clone.clone();
865                let value = value_clone.clone();
866                async move {
867                    conn.set_ex(&full_key, &value, ttl_secs).await
868                }
869            }).await?;
870        } else {
871            self.with_retry(move || {
872                let mut conn = conn.clone();
873                let full_key = full_key_clone.clone();
874                let value = value_clone.clone();
875                async move {
876                    conn.set(&full_key, &value).await
877                }
878            }).await?;
879        }
880
881        // Update stats
882        let mut stats = self.stats.write().await;
883        stats.put_count += 1;
884        stats.bytes_written += value.len() as u64;
885
886        Ok(())
887    }
888
889    async fn delete(&self, key: &[u8]) -> StateResult<()> {
890        let full_key = self.build_key(key);
891        trace!("Deleting key: {:?}", full_key);
892
893        let conn = self.connection.as_ref().clone();
894        let full_key_clone = full_key.clone();
895
896        self.with_retry(move || {
897            let mut conn = conn.clone();
898            let full_key = full_key_clone.clone();
899            async move {
900                conn.del(&full_key).await
901            }
902        }).await?;
903
904        // Update stats
905        let mut stats = self.stats.write().await;
906        stats.delete_count += 1;
907
908        Ok(())
909    }
910
911    async fn list_keys(&self, prefix: &[u8]) -> StateResult<Vec<Vec<u8>>> {
912        trace!("Listing keys with prefix: {:?}", prefix);
913
914        let pattern = if prefix.is_empty() {
915            format!("{}*", self.config.key_prefix).into_bytes()
916        } else {
917            let mut p = self.config.key_prefix.as_bytes().to_vec();
918            p.extend_from_slice(prefix);
919            p.push(b'*');
920            p
921        };
922
923        let keys = self.scan_keys_internal(pattern, 1000).await?;
924
925        // Strip prefix from all keys
926        let stripped_keys = keys
927            .into_iter()
928            .map(|k| self.strip_prefix(&k))
929            .collect();
930
931        Ok(stripped_keys)
932    }
933
934    async fn clear(&self) -> StateResult<()> {
935        debug!("Clearing all state");
936
937        let pattern = format!("{}*", self.config.key_prefix).into_bytes();
938        let keys = self.scan_keys_internal(pattern, 1000).await?;
939
940        if keys.is_empty() {
941            return Ok(());
942        }
943
944        // Delete in batches
945        let key_refs: Vec<&[u8]> = keys.iter().map(|k| k.as_slice()).collect();
946        for chunk in key_refs.chunks(self.config.pipeline_batch_size) {
947            let chunk_vec: Vec<Vec<u8>> = chunk.iter().map(|k| k.to_vec()).collect();
948
949            // Perform deletion directly without retry wrapper to avoid closure lifetime issues
950            let mut retries = 0;
951            let mut delay = self.config.retry_base_delay;
952
953            loop {
954                let mut conn = self.connection.as_ref().clone();
955
956                let result: RedisResult<()> = redis::cmd("DEL")
957                    .arg(&chunk_vec)
958                    .query_async(&mut conn)
959                    .await;
960
961                match result {
962                    Ok(_) => break,
963                    Err(e) => {
964                        retries += 1;
965
966                        if retries >= self.config.max_retries {
967                            return Err(StateError::StorageError {
968                                backend_type: "redis".to_string(),
969                                details: format!("Delete batch failed after {} retries: {}", retries, e),
970                            });
971                        }
972
973                        warn!("Delete batch attempt {} failed: {}, retrying in {:?}", retries, e, delay);
974                        tokio::time::sleep(delay).await;
975                        delay = std::cmp::min(delay * 2, self.config.retry_max_delay);
976                    }
977                }
978            }
979        }
980
981        Ok(())
982    }
983
984    async fn count(&self) -> StateResult<usize> {
985        let pattern = format!("{}*", self.config.key_prefix).into_bytes();
986        let keys = self.scan_keys_internal(pattern, 1000).await?;
987        Ok(keys.len())
988    }
989
990    async fn contains(&self, key: &[u8]) -> StateResult<bool> {
991        let full_key = self.build_key(key);
992        let conn = self.connection.as_ref().clone();
993        let full_key_clone = full_key.clone();
994
995        let exists: bool = self.with_retry(move || {
996            let mut conn = conn.clone();
997            let full_key = full_key_clone.clone();
998            async move {
999                conn.exists(&full_key).await
1000            }
1001        }).await?;
1002
1003        Ok(exists)
1004    }
1005}
1006
1007#[cfg(test)]
1008mod tests {
1009    use super::*;
1010    use crate::state::backend::tests::*;
1011
1012    // Helper to create test backend (requires Redis running)
1013    async fn create_test_backend() -> Option<RedisStateBackend> {
1014        let config = RedisConfig::builder()
1015            .url("redis://localhost:6379")
1016            .key_prefix("test:")
1017            .build()
1018            .ok()?;
1019
1020        match RedisStateBackend::new(config).await {
1021            Ok(backend) => {
1022                // Clear any existing test data
1023                let _ = backend.clear().await;
1024                Some(backend)
1025            }
1026            Err(_) => None,
1027        }
1028    }
1029
1030    #[tokio::test]
1031    async fn test_redis_backend_basic() {
1032        if let Some(backend) = create_test_backend().await {
1033            test_backend_basic_ops(backend).await;
1034        } else {
1035            eprintln!("Skipping test: Redis not available");
1036        }
1037    }
1038
1039    #[tokio::test]
1040    async fn test_redis_backend_list_keys() {
1041        if let Some(backend) = create_test_backend().await {
1042            test_backend_list_keys(backend).await;
1043        } else {
1044            eprintln!("Skipping test: Redis not available");
1045        }
1046    }
1047
1048    #[tokio::test]
1049    async fn test_redis_backend_clear() {
1050        if let Some(backend) = create_test_backend().await {
1051            test_backend_clear(backend).await;
1052        } else {
1053            eprintln!("Skipping test: Redis not available");
1054        }
1055    }
1056
1057    #[tokio::test]
1058    async fn test_redis_backend_contains() {
1059        if let Some(backend) = create_test_backend().await {
1060            test_backend_contains(backend).await;
1061        } else {
1062            eprintln!("Skipping test: Redis not available");
1063        }
1064    }
1065
1066    #[tokio::test]
1067    async fn test_redis_ttl() {
1068        if let Some(backend) = create_test_backend().await {
1069            backend
1070                .put_with_ttl(b"ttl_key", b"value", Duration::from_secs(2))
1071                .await
1072                .unwrap();
1073
1074            assert!(backend.get(b"ttl_key").await.unwrap().is_some());
1075
1076            // Check TTL
1077            let ttl = backend.ttl(b"ttl_key").await.unwrap();
1078            assert!(ttl.is_some());
1079            assert!(ttl.unwrap().as_secs() <= 2);
1080
1081            // Wait for expiration
1082            tokio::time::sleep(Duration::from_secs(3)).await;
1083            assert!(backend.get(b"ttl_key").await.unwrap().is_none());
1084        } else {
1085            eprintln!("Skipping test: Redis not available");
1086        }
1087    }
1088
1089    #[tokio::test]
1090    async fn test_redis_batch_operations() {
1091        if let Some(backend) = create_test_backend().await {
1092            // Batch put
1093            let pairs = vec![
1094                (&b"batch1"[..], &b"value1"[..]),
1095                (&b"batch2"[..], &b"value2"[..]),
1096                (&b"batch3"[..], &b"value3"[..]),
1097            ];
1098            backend.batch_put(&pairs).await.unwrap();
1099
1100            // Batch get
1101            let keys = vec![b"batch1", b"batch2", b"batch3"];
1102            let values = backend.batch_get(&keys).await.unwrap();
1103            assert_eq!(values.len(), 3);
1104            assert_eq!(values[0], Some(b"value1".to_vec()));
1105            assert_eq!(values[1], Some(b"value2".to_vec()));
1106            assert_eq!(values[2], Some(b"value3".to_vec()));
1107
1108            // Batch delete
1109            let deleted = backend.batch_delete(&keys).await.unwrap();
1110            assert_eq!(deleted, 3);
1111        } else {
1112            eprintln!("Skipping test: Redis not available");
1113        }
1114    }
1115
1116    #[tokio::test]
1117    async fn test_redis_atomic_operations() {
1118        if let Some(backend) = create_test_backend().await {
1119            backend.put(b"atomic_key", b"value").await.unwrap();
1120
1121            // Get and delete atomically
1122            let value = backend.get_and_delete(b"atomic_key").await.unwrap();
1123            assert_eq!(value, Some(b"value".to_vec()));
1124
1125            // Key should be gone
1126            assert!(backend.get(b"atomic_key").await.unwrap().is_none());
1127
1128            // Set if not exists
1129            let set1 = backend
1130                .set_if_not_exists(b"nx_key", b"value1", Duration::from_secs(60))
1131                .await
1132                .unwrap();
1133            assert!(set1);
1134
1135            let set2 = backend
1136                .set_if_not_exists(b"nx_key", b"value2", Duration::from_secs(60))
1137                .await
1138                .unwrap();
1139            assert!(!set2);
1140
1141            // First value should still be there
1142            assert_eq!(
1143                backend.get(b"nx_key").await.unwrap(),
1144                Some(b"value1".to_vec())
1145            );
1146        } else {
1147            eprintln!("Skipping test: Redis not available");
1148        }
1149    }
1150
1151    #[tokio::test]
1152    async fn test_redis_checkpoint_restore() {
1153        if let Some(backend) = create_test_backend().await {
1154            backend.put(b"key1", b"value1").await.unwrap();
1155            backend.put(b"key2", b"value2").await.unwrap();
1156
1157            // Create checkpoint
1158            let checkpoint = backend.checkpoint().await.unwrap();
1159            assert_eq!(checkpoint.len(), 2);
1160
1161            // Clear and restore
1162            backend.clear().await.unwrap();
1163            assert_eq!(backend.count().await.unwrap(), 0);
1164
1165            backend.restore(checkpoint).await.unwrap();
1166            assert_eq!(backend.count().await.unwrap(), 2);
1167
1168            assert_eq!(
1169                backend.get(b"key1").await.unwrap(),
1170                Some(b"value1".to_vec())
1171            );
1172        } else {
1173            eprintln!("Skipping test: Redis not available");
1174        }
1175    }
1176
1177    #[tokio::test]
1178    async fn test_redis_stats() {
1179        if let Some(backend) = create_test_backend().await {
1180            backend.put(b"key1", b"value1").await.unwrap();
1181            backend.put(b"key2", b"value2").await.unwrap();
1182
1183            backend.get(b"key1").await.unwrap(); // Hit
1184            backend.get(b"key1").await.unwrap(); // Hit
1185            backend.get(b"nonexistent").await.unwrap(); // Miss
1186
1187            backend.delete(b"key1").await.unwrap();
1188
1189            let stats = backend.stats().await;
1190            assert_eq!(stats.put_count, 2);
1191            assert_eq!(stats.get_count, 3);
1192            assert_eq!(stats.delete_count, 1);
1193            assert_eq!(stats.hit_count, 2);
1194            assert_eq!(stats.miss_count, 1);
1195            assert!(stats.hit_rate() > 0.0);
1196        } else {
1197            eprintln!("Skipping test: Redis not available");
1198        }
1199    }
1200
1201    #[tokio::test]
1202    async fn test_redis_health_check() {
1203        if let Some(backend) = create_test_backend().await {
1204            let healthy = backend.health_check().await.unwrap();
1205            assert!(healthy);
1206        } else {
1207            eprintln!("Skipping test: Redis not available");
1208        }
1209    }
1210
1211    #[tokio::test]
1212    async fn test_config_builder() {
1213        let config = RedisConfig::builder()
1214            .url("redis://localhost:6379")
1215            .key_prefix("myapp:")
1216            .default_ttl(Duration::from_secs(3600))
1217            .pool_size(10, 50)
1218            .cluster_mode(false)
1219            .build()
1220            .unwrap();
1221
1222        assert_eq!(config.urls.len(), 1);
1223        assert_eq!(config.key_prefix, "myapp:");
1224        assert_eq!(config.default_ttl, Some(Duration::from_secs(3600)));
1225        assert_eq!(config.min_connections, 10);
1226        assert_eq!(config.max_connections, 50);
1227        assert!(!config.cluster_mode);
1228    }
1229}