1use async_trait::async_trait;
60use deadpool_redis::{Config as PoolConfig, Pool, Runtime};
61use failsafe::{backoff, failure_policy, Config as CircuitBreakerConfig, StateMachine};
62use prometheus_client::encoding::EncodeLabelSet;
63
64type CircuitBreaker = StateMachine<
66 failure_policy::ConsecutiveFailures<backoff::Exponential>,
67 (),
68>;
69use prometheus_client::metrics::counter::Counter;
70use prometheus_client::metrics::family::Family;
71use prometheus_client::metrics::gauge::Gauge;
72use prometheus_client::metrics::histogram::{exponential_buckets, Histogram};
73use prometheus_client::registry::Registry;
74use redis::{
75 aio::ConnectionManager, AsyncCommands, Client, Cmd, FromRedisValue, RedisError,
76 RedisResult, Script, Value,
77};
78use serde::{Deserialize, Serialize};
79use std::collections::HashMap;
80use std::sync::Arc;
81use std::time::{Duration, Instant};
82use tokio::sync::RwLock;
83use tracing::{debug, error, info, trace, warn};
84use uuid::Uuid;
85use rand::Rng;
86
87use super::backend::StateBackend;
88use crate::error::{StateError, StateResult};
89
90#[derive(Debug, Clone, Serialize, Deserialize)]
92pub enum RedisMode {
93 Standalone {
95 url: String,
96 },
97 Sentinel {
99 sentinels: Vec<String>,
100 service_name: String,
101 },
102 Cluster {
104 nodes: Vec<String>,
105 },
106}
107
108#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
110pub enum CompressionAlgorithm {
111 None,
112 Lz4,
113 Snappy,
114 Zstd,
115}
116
117#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
119pub enum SerializationFormat {
120 Bincode,
121 MessagePack,
122 Json,
123}
124
125#[derive(Debug, Clone, Serialize, Deserialize)]
127pub struct EnterpriseRedisConfig {
128 pub mode: RedisMode,
130
131 pub key_prefix: String,
133
134 pub default_ttl: Option<Duration>,
136
137 pub pool_min: usize,
139
140 pub pool_max: usize,
142
143 pub pool_timeout: Duration,
145
146 pub connect_timeout: Duration,
148
149 pub command_timeout: Duration,
151
152 pub max_retries: u32,
154
155 pub retry_initial_delay: Duration,
157
158 pub retry_max_delay: Duration,
160
161 pub retry_jitter: f64,
163
164 pub compression: CompressionAlgorithm,
166
167 pub compression_threshold: usize,
169
170 pub serialization: SerializationFormat,
172
173 pub pipeline_batch_size: usize,
175
176 pub enable_circuit_breaker: bool,
178
179 pub circuit_breaker_threshold: u32,
181
182 pub circuit_breaker_timeout: Duration,
184
185 pub enable_metrics: bool,
187
188 pub health_check_interval: Duration,
190
191 pub enable_slow_query_log: bool,
193
194 pub slow_query_threshold: Duration,
196}
197
198impl Default for EnterpriseRedisConfig {
199 fn default() -> Self {
200 Self {
201 mode: RedisMode::Standalone {
202 url: "redis://localhost:6379".to_string(),
203 },
204 key_prefix: "state:".to_string(),
205 default_ttl: None,
206 pool_min: 10,
207 pool_max: 100,
208 pool_timeout: Duration::from_secs(30),
209 connect_timeout: Duration::from_secs(5),
210 command_timeout: Duration::from_secs(3),
211 max_retries: 3,
212 retry_initial_delay: Duration::from_millis(100),
213 retry_max_delay: Duration::from_secs(5),
214 retry_jitter: 0.3,
215 compression: CompressionAlgorithm::None,
216 compression_threshold: 1024, serialization: SerializationFormat::Bincode,
218 pipeline_batch_size: 100,
219 enable_circuit_breaker: true,
220 circuit_breaker_threshold: 10,
221 circuit_breaker_timeout: Duration::from_secs(60),
222 enable_metrics: true,
223 health_check_interval: Duration::from_secs(30),
224 enable_slow_query_log: true,
225 slow_query_threshold: Duration::from_millis(100),
226 }
227 }
228}
229
230impl EnterpriseRedisConfig {
231 pub fn builder() -> EnterpriseRedisConfigBuilder {
232 EnterpriseRedisConfigBuilder::default()
233 }
234}
235
236#[derive(Debug, Default)]
238pub struct EnterpriseRedisConfigBuilder {
239 config: EnterpriseRedisConfig,
240}
241
242impl EnterpriseRedisConfigBuilder {
243 pub fn mode(mut self, mode: RedisMode) -> Self {
244 self.config.mode = mode;
245 self
246 }
247
248 pub fn key_prefix(mut self, prefix: impl Into<String>) -> Self {
249 self.config.key_prefix = prefix.into();
250 self
251 }
252
253 pub fn default_ttl(mut self, ttl: Duration) -> Self {
254 self.config.default_ttl = Some(ttl);
255 self
256 }
257
258 pub fn pool_config(mut self, min: usize, max: usize, timeout: Duration) -> Self {
259 self.config.pool_min = min;
260 self.config.pool_max = max;
261 self.config.pool_timeout = timeout;
262 self
263 }
264
265 pub fn timeouts(mut self, connect: Duration, command: Duration) -> Self {
266 self.config.connect_timeout = connect;
267 self.config.command_timeout = command;
268 self
269 }
270
271 pub fn retry_config(mut self, max: u32, initial: Duration, max_delay: Duration, jitter: f64) -> Self {
272 self.config.max_retries = max;
273 self.config.retry_initial_delay = initial;
274 self.config.retry_max_delay = max_delay;
275 self.config.retry_jitter = jitter;
276 self
277 }
278
279 pub fn compression(mut self, algo: CompressionAlgorithm) -> Self {
280 self.config.compression = algo;
281 self
282 }
283
284 pub fn compression_threshold(mut self, threshold: usize) -> Self {
285 self.config.compression_threshold = threshold;
286 self
287 }
288
289 pub fn serialization(mut self, format: SerializationFormat) -> Self {
290 self.config.serialization = format;
291 self
292 }
293
294 pub fn circuit_breaker(mut self, enabled: bool, threshold: u32, timeout: Duration) -> Self {
295 self.config.enable_circuit_breaker = enabled;
296 self.config.circuit_breaker_threshold = threshold;
297 self.config.circuit_breaker_timeout = timeout;
298 self
299 }
300
301 pub fn enable_metrics(mut self, enabled: bool) -> Self {
302 self.config.enable_metrics = enabled;
303 self
304 }
305
306 pub fn slow_query_log(mut self, enabled: bool, threshold: Duration) -> Self {
307 self.config.enable_slow_query_log = enabled;
308 self.config.slow_query_threshold = threshold;
309 self
310 }
311
312 pub fn build(self) -> StateResult<EnterpriseRedisConfig> {
313 if self.config.pool_max < self.config.pool_min {
315 return Err(StateError::StorageError {
316 backend_type: "EnterpriseRedis".to_string(),
317 details: "pool_max must be >= pool_min".to_string(),
318 });
319 }
320
321 if self.config.retry_jitter < 0.0 || self.config.retry_jitter > 1.0 {
322 return Err(StateError::StorageError {
323 backend_type: "EnterpriseRedis".to_string(),
324 details: "retry_jitter must be between 0.0 and 1.0".to_string(),
325 });
326 }
327
328 Ok(self.config)
329 }
330}
331
332#[derive(Clone, Debug, Hash, PartialEq, Eq, EncodeLabelSet)]
334struct MetricLabels {
335 operation: String,
336 status: String,
337}
338
339pub struct RedisMetrics {
341 operations_total: Family<MetricLabels, Counter>,
342 operation_duration: Family<MetricLabels, Histogram>,
343 pool_connections_active: Gauge,
344 pool_connections_idle: Gauge,
345 circuit_breaker_state: Gauge,
346 compression_ratio: Gauge,
347 errors_total: Family<MetricLabels, Counter>,
348}
349
350impl RedisMetrics {
351 fn new() -> Self {
352 Self {
353 operations_total: Family::default(),
354 operation_duration: Family::new_with_constructor(|| {
355 Histogram::new(exponential_buckets(0.001, 2.0, 10))
356 }),
357 pool_connections_active: Gauge::default(),
358 pool_connections_idle: Gauge::default(),
359 circuit_breaker_state: Gauge::default(),
360 compression_ratio: Gauge::default(),
361 errors_total: Family::default(),
362 }
363 }
364
365 pub fn register(&self, registry: &mut Registry) {
366 registry.register(
367 "redis_operations_total",
368 "Total number of Redis operations",
369 self.operations_total.clone(),
370 );
371 registry.register(
372 "redis_operation_duration_seconds",
373 "Duration of Redis operations",
374 self.operation_duration.clone(),
375 );
376 registry.register(
377 "redis_pool_connections_active",
378 "Active pool connections",
379 self.pool_connections_active.clone(),
380 );
381 registry.register(
382 "redis_pool_connections_idle",
383 "Idle pool connections",
384 self.pool_connections_idle.clone(),
385 );
386 registry.register(
387 "redis_circuit_breaker_state",
388 "Circuit breaker state (0=closed, 1=open, 2=half-open)",
389 self.circuit_breaker_state.clone(),
390 );
391 registry.register(
392 "redis_compression_ratio",
393 "Compression ratio (original/compressed)",
394 self.compression_ratio.clone(),
395 );
396 registry.register(
397 "redis_errors_total",
398 "Total number of errors",
399 self.errors_total.clone(),
400 );
401 }
402}
403
404pub struct RedlockGuard {
406 resource: String,
407 token: String,
408 backend: Arc<EnterpriseRedisBackend>,
409}
410
411impl Drop for RedlockGuard {
412 fn drop(&mut self) {
413 let backend = self.backend.clone();
414 let resource = self.resource.clone();
415 let token = self.token.clone();
416
417 tokio::spawn(async move {
418 let _ = backend.release_lock_internal(&resource, &token).await;
419 });
420 }
421}
422
423pub struct EnterpriseRedisBackend {
425 config: EnterpriseRedisConfig,
426 pool: Pool,
427 metrics: Option<Arc<RedisMetrics>>,
428 circuit_breaker: Option<Arc<CircuitBreaker>>,
429 scripts: Arc<RedisScripts>,
430 stats: Arc<RwLock<BackendStats>>,
431}
432
433struct RedisScripts {
435 release_lock: Script,
436 extend_lock: Script,
437 get_delete: Script,
438 incr_with_ttl: Script,
439 compare_and_set: Script,
440}
441
442impl RedisScripts {
443 fn new() -> Self {
444 Self {
445 release_lock: Script::new(
446 r"
447 if redis.call('GET', KEYS[1]) == ARGV[1] then
448 return redis.call('DEL', KEYS[1])
449 else
450 return 0
451 end
452 "
453 ),
454 extend_lock: Script::new(
455 r"
456 if redis.call('GET', KEYS[1]) == ARGV[1] then
457 return redis.call('PEXPIRE', KEYS[1], ARGV[2])
458 else
459 return 0
460 end
461 "
462 ),
463 get_delete: Script::new(
464 r"
465 local value = redis.call('GET', KEYS[1])
466 if value then
467 redis.call('DEL', KEYS[1])
468 end
469 return value
470 "
471 ),
472 incr_with_ttl: Script::new(
473 r"
474 local value = redis.call('INCR', KEYS[1])
475 redis.call('EXPIRE', KEYS[1], ARGV[1])
476 return value
477 "
478 ),
479 compare_and_set: Script::new(
480 r"
481 local current = redis.call('GET', KEYS[1])
482 if current == ARGV[1] then
483 redis.call('SET', KEYS[1], ARGV[2])
484 if ARGV[3] then
485 redis.call('EXPIRE', KEYS[1], ARGV[3])
486 end
487 return 1
488 else
489 return 0
490 end
491 "
492 ),
493 }
494 }
495}
496
497#[derive(Debug, Clone, Default, Serialize, Deserialize)]
499pub struct BackendStats {
500 pub operations_total: u64,
501 pub operations_success: u64,
502 pub operations_failed: u64,
503 pub bytes_read: u64,
504 pub bytes_written: u64,
505 pub bytes_compressed: u64,
506 pub cache_hits: u64,
507 pub cache_misses: u64,
508 pub avg_latency_us: u64,
509 pub slow_queries: u64,
510}
511
512impl EnterpriseRedisBackend {
513 pub async fn new(config: EnterpriseRedisConfig) -> StateResult<Self> {
515 info!("Initializing enterprise Redis backend with mode: {:?}", config.mode);
516
517 let pool = Self::create_pool(&config).await?;
519
520 let metrics = if config.enable_metrics {
522 Some(Arc::new(RedisMetrics::new()))
523 } else {
524 None
525 };
526
527 let circuit_breaker = if config.enable_circuit_breaker {
529 let backoff_strategy = backoff::exponential(
531 config.circuit_breaker_timeout,
532 config.circuit_breaker_timeout * 10,
533 );
534
535 let failure_policy = failure_policy::consecutive_failures(
537 config.circuit_breaker_threshold as u32,
538 backoff_strategy,
539 );
540
541 let state_machine = CircuitBreakerConfig::new()
543 .failure_policy(failure_policy)
544 .build();
545
546 Some(Arc::new(state_machine))
547 } else {
548 None
549 };
550
551 let backend = Self {
552 config,
553 pool,
554 metrics,
555 circuit_breaker,
556 scripts: Arc::new(RedisScripts::new()),
557 stats: Arc::new(RwLock::new(BackendStats::default())),
558 };
559
560 backend.spawn_health_check_task();
562
563 info!("Enterprise Redis backend initialized successfully");
564 Ok(backend)
565 }
566
567 async fn create_pool(config: &EnterpriseRedisConfig) -> StateResult<Pool> {
569 let pool_config = match &config.mode {
570 RedisMode::Standalone { url } => {
571 PoolConfig::from_url(url)
572 }
573 RedisMode::Sentinel { sentinels, service_name } => {
574 let master_url = Self::discover_sentinel_master(sentinels, service_name).await?;
576 PoolConfig::from_url(&master_url)
577 }
578 RedisMode::Cluster { nodes: _ } => {
579 return Err(StateError::StorageError {
581 backend_type: "EnterpriseRedis".to_string(),
582 details: "Cluster mode requires redis-cluster crate, not yet implemented in this version".to_string(),
583 });
584 }
585 };
586
587 pool_config
588 .builder()
589 .map_err(|e| StateError::StorageError {
590 backend_type: "EnterpriseRedis".to_string(),
591 details: format!("Failed to create pool builder: {}", e),
592 })?
593 .max_size(config.pool_max)
594 .runtime(Runtime::Tokio1)
595 .build()
596 .map_err(|e| StateError::StorageError {
597 backend_type: "EnterpriseRedis".to_string(),
598 details: format!("Failed to create connection pool: {}", e),
599 })
600 }
601
602 async fn discover_sentinel_master(sentinels: &[String], service_name: &str) -> StateResult<String> {
604 debug!("Discovering master from {} sentinels", sentinels.len());
605
606 for sentinel_url in sentinels {
607 match Self::query_sentinel(sentinel_url, service_name).await {
608 Ok(master_url) => {
609 info!("Discovered master: {}", master_url);
610 return Ok(master_url);
611 }
612 Err(e) => {
613 warn!("Failed to query sentinel {}: {}", sentinel_url, e);
614 continue;
615 }
616 }
617 }
618
619 Err(StateError::StorageError {
620 backend_type: "EnterpriseRedis".to_string(),
621 details: "Failed to discover master from any sentinel".to_string(),
622 })
623 }
624
625 async fn query_sentinel(sentinel_url: &str, service_name: &str) -> StateResult<String> {
627 let client = Client::open(sentinel_url).map_err(|e| StateError::StorageError {
628 backend_type: "EnterpriseRedis".to_string(),
629 details: format!("Failed to connect to sentinel: {}", e),
630 })?;
631
632 let mut conn = client.get_connection_manager().await.map_err(|e| StateError::StorageError {
633 backend_type: "EnterpriseRedis".to_string(),
634 details: format!("Failed to get connection: {}", e),
635 })?;
636
637 let result: Vec<String> = redis::cmd("SENTINEL")
639 .arg("get-master-addr-by-name")
640 .arg(service_name)
641 .query_async(&mut conn)
642 .await
643 .map_err(|e| StateError::StorageError {
644 backend_type: "EnterpriseRedis".to_string(),
645 details: format!("Sentinel query failed: {}", e),
646 })?;
647
648 if result.len() >= 2 {
649 Ok(format!("redis://{}:{}", result[0], result[1]))
650 } else {
651 Err(StateError::StorageError {
652 backend_type: "EnterpriseRedis".to_string(),
653 details: "Invalid sentinel response".to_string(),
654 })
655 }
656 }
657
658 fn spawn_health_check_task(&self) {
660 let pool = self.pool.clone();
661 let interval = self.config.health_check_interval;
662 let metrics = self.metrics.clone();
663
664 tokio::spawn(async move {
665 let mut interval = tokio::time::interval(interval);
666
667 loop {
668 interval.tick().await;
669
670 match pool.get().await {
671 Ok(mut conn) => {
672 let result: Result<String, _> = redis::cmd("PING").query_async(&mut *conn).await;
673 match result {
674 Ok(response) if response == "PONG" => {
675 trace!("Health check passed");
676
677 if let Some(ref metrics) = metrics {
678 let status = pool.status();
679 metrics.pool_connections_active.set(status.size as i64);
680 metrics.pool_connections_idle.set((status.size - status.available) as i64);
681 }
682 }
683 Ok(_) | Err(_) => {
684 warn!("Health check failed");
685 }
686 }
687 }
688 Err(e) => {
689 error!("Failed to get connection for health check: {}", e);
690 }
691 }
692 }
693 });
694 }
695
696 fn build_key(&self, key: &[u8]) -> Vec<u8> {
698 let mut full_key = self.config.key_prefix.as_bytes().to_vec();
699 full_key.extend_from_slice(key);
700 full_key
701 }
702
703 fn strip_prefix(&self, key: &[u8]) -> Vec<u8> {
705 let prefix = self.config.key_prefix.as_bytes();
706 if key.starts_with(prefix) {
707 key[prefix.len()..].to_vec()
708 } else {
709 key.to_vec()
710 }
711 }
712
713 fn compress(&self, data: &[u8]) -> StateResult<Vec<u8>> {
715 if data.len() < self.config.compression_threshold {
716 return Ok(data.to_vec());
717 }
718
719 match self.config.compression {
720 CompressionAlgorithm::None => Ok(data.to_vec()),
721 CompressionAlgorithm::Lz4 => {
722 lz4::block::compress(data, None, false).map_err(|e| StateError::SerializationFailed {
723 key: "compression".to_string(),
724 reason: format!("LZ4 compression failed: {}", e),
725 })
726 }
727 CompressionAlgorithm::Snappy => {
728 let mut compressed = Vec::new();
729 let mut encoder = snap::write::FrameEncoder::new(&mut compressed);
730 std::io::Write::write_all(&mut encoder, data).map_err(|e| StateError::SerializationFailed {
731 key: "compression".to_string(),
732 reason: format!("Snappy compression failed: {}", e),
733 })?;
734 drop(encoder);
735 Ok(compressed)
736 }
737 CompressionAlgorithm::Zstd => {
738 zstd::encode_all(data, 3).map_err(|e| StateError::SerializationFailed {
739 key: "compression".to_string(),
740 reason: format!("Zstd compression failed: {}", e),
741 })
742 }
743 }
744 }
745
746 fn decompress(&self, data: &[u8]) -> StateResult<Vec<u8>> {
748 if data.len() < self.config.compression_threshold {
749 return Ok(data.to_vec());
750 }
751
752 match self.config.compression {
753 CompressionAlgorithm::None => Ok(data.to_vec()),
754 CompressionAlgorithm::Lz4 => {
755 lz4::block::decompress(data, None).map_err(|e| StateError::DeserializationFailed {
756 key: "decompression".to_string(),
757 reason: format!("LZ4 decompression failed: {}", e),
758 })
759 }
760 CompressionAlgorithm::Snappy => {
761 let mut decompressed = Vec::new();
762 let mut decoder = snap::read::FrameDecoder::new(data);
763 std::io::Read::read_to_end(&mut decoder, &mut decompressed).map_err(|e| StateError::DeserializationFailed {
764 key: "decompression".to_string(),
765 reason: format!("Snappy decompression failed: {}", e),
766 })?;
767 Ok(decompressed)
768 }
769 CompressionAlgorithm::Zstd => {
770 zstd::decode_all(data).map_err(|e| StateError::DeserializationFailed {
771 key: "decompression".to_string(),
772 reason: format!("Zstd decompression failed: {}", e),
773 })
774 }
775 }
776 }
777
778 async fn execute_with_resilience<F, Fut, T>(
780 &self,
781 operation_name: &str,
782 mut f: F,
783 ) -> StateResult<T>
784 where
785 F: FnMut() -> Fut,
786 Fut: std::future::Future<Output = StateResult<T>>,
787 {
788 let start = Instant::now();
789 let mut attempts = 0;
790 let mut delay = self.config.retry_initial_delay;
791
792 loop {
793 attempts += 1;
794
795 match f().await {
805 Ok(result) => {
806 let duration = start.elapsed();
808 self.record_operation_success(operation_name, duration).await;
809
810 return Ok(result);
815 }
816 Err(e) => {
817 attempts += 1;
818
819 if attempts >= self.config.max_retries {
824 let duration = start.elapsed();
825 self.record_operation_failure(operation_name, duration).await;
826 return Err(e);
827 }
828
829 let jitter = if self.config.retry_jitter > 0.0 {
831 let mut rng = rand::thread_rng();
832 let jitter_factor: f64 = rng.gen();
833 let jitter_ms = (delay.as_millis() as f64 * self.config.retry_jitter * jitter_factor) as u64;
834 Duration::from_millis(jitter_ms)
835 } else {
836 Duration::from_millis(0)
837 };
838
839 let actual_delay = delay + jitter;
840 debug!("Retrying {} after {:?} (attempt {})", operation_name, actual_delay, attempts);
841
842 tokio::time::sleep(actual_delay).await;
843 delay = std::cmp::min(delay * 2, self.config.retry_max_delay);
844 }
845 }
846 }
847 }
848
849 async fn record_operation_success(&self, operation: &str, duration: Duration) {
850 if let Some(ref metrics) = self.metrics {
851 metrics.operations_total.get_or_create(&MetricLabels {
852 operation: operation.to_string(),
853 status: "success".to_string(),
854 }).inc();
855
856 metrics.operation_duration.get_or_create(&MetricLabels {
857 operation: operation.to_string(),
858 status: "success".to_string(),
859 }).observe(duration.as_secs_f64());
860 }
861
862 let mut stats = self.stats.write().await;
863 stats.operations_total += 1;
864 stats.operations_success += 1;
865
866 let latency_us = duration.as_micros() as u64;
868 stats.avg_latency_us = if stats.operations_total == 1 {
869 latency_us
870 } else {
871 (stats.avg_latency_us * 9 + latency_us) / 10
872 };
873
874 if self.config.enable_slow_query_log && duration > self.config.slow_query_threshold {
875 warn!("Slow query detected: {} took {:?}", operation, duration);
876 stats.slow_queries += 1;
877 }
878 }
879
880 async fn record_operation_failure(&self, operation: &str, duration: Duration) {
881 if let Some(ref metrics) = self.metrics {
882 metrics.operations_total.get_or_create(&MetricLabels {
883 operation: operation.to_string(),
884 status: "failure".to_string(),
885 }).inc();
886
887 metrics.errors_total.get_or_create(&MetricLabels {
888 operation: operation.to_string(),
889 status: "failure".to_string(),
890 }).inc();
891 }
892
893 let mut stats = self.stats.write().await;
894 stats.operations_total += 1;
895 stats.operations_failed += 1;
896 }
897
898 pub async fn acquire_lock(&self, resource: &str, ttl: Duration) -> StateResult<RedlockGuard> {
900 let token = Uuid::new_v4().to_string();
901 let key = format!("{}lock:{}", self.config.key_prefix, resource);
902 let ttl_ms = ttl.as_millis() as usize;
903
904 let mut conn = self.pool.get().await.map_err(|e| StateError::StorageError {
905 backend_type: "EnterpriseRedis".to_string(),
906 details: format!("Failed to get connection: {}", e),
907 })?;
908
909 let result: RedisResult<String> = redis::cmd("SET")
911 .arg(&key)
912 .arg(&token)
913 .arg("NX")
914 .arg("PX")
915 .arg(ttl_ms)
916 .query_async(&mut *conn)
917 .await;
918
919 match result {
920 Ok(_) => {
921 debug!("Lock acquired: {} with token {}", resource, token);
922 Ok(RedlockGuard {
923 resource: resource.to_string(),
924 token,
925 backend: Arc::new(self.clone()),
926 })
927 }
928 Err(e) => Err(StateError::TransactionFailed {
929 operation: "acquire_lock".to_string(),
930 reason: format!("Lock acquisition failed: {}", e),
931 }),
932 }
933 }
934
935 pub async fn try_acquire_lock(&self, resource: &str, ttl: Duration) -> StateResult<Option<RedlockGuard>> {
937 match self.acquire_lock(resource, ttl).await {
938 Ok(guard) => Ok(Some(guard)),
939 Err(StateError::TransactionFailed { .. }) => Ok(None),
940 Err(e) => Err(e),
941 }
942 }
943
944 async fn release_lock_internal(&self, resource: &str, token: &str) -> StateResult<()> {
946 let key = format!("{}lock:{}", self.config.key_prefix, resource);
947
948 let mut conn = self.pool.get().await.map_err(|e| StateError::StorageError {
949 backend_type: "EnterpriseRedis".to_string(),
950 details: format!("Failed to get connection: {}", e),
951 })?;
952
953 let result: i32 = self.scripts.release_lock
954 .key(&key)
955 .arg(token)
956 .invoke_async(&mut *conn)
957 .await
958 .map_err(|e| StateError::StorageError {
959 backend_type: "EnterpriseRedis".to_string(),
960 details: format!("Lock release failed: {}", e),
961 })?;
962
963 if result == 1 {
964 debug!("Lock released: {}", resource);
965 Ok(())
966 } else {
967 warn!("Lock release failed: token mismatch for {}", resource);
968 Err(StateError::TransactionFailed {
969 operation: "release_lock".to_string(),
970 reason: "Token mismatch".to_string(),
971 })
972 }
973 }
974
975 pub async fn batch_get_optimized(&self, keys: &[&[u8]]) -> StateResult<Vec<Option<Vec<u8>>>> {
977 if keys.is_empty() {
978 return Ok(Vec::new());
979 }
980
981 self.execute_with_resilience("batch_get", || async {
982 let full_keys: Vec<Vec<u8>> = keys.iter().map(|k| self.build_key(k)).collect();
983
984 let mut conn = self.pool.get().await.map_err(|e| StateError::StorageError {
985 backend_type: "EnterpriseRedis".to_string(),
986 details: format!("Failed to get connection: {}", e),
987 })?;
988
989 let values: Vec<Option<Vec<u8>>> = redis::cmd("MGET")
990 .arg(&full_keys)
991 .query_async(&mut *conn)
992 .await
993 .map_err(|e| StateError::StorageError {
994 backend_type: "EnterpriseRedis".to_string(),
995 details: format!("MGET failed: {}", e),
996 })?;
997
998 let mut results = Vec::with_capacity(values.len());
1000 for value_opt in values {
1001 if let Some(compressed) = value_opt {
1002 let decompressed = self.decompress(&compressed)?;
1003 results.push(Some(decompressed));
1004 } else {
1005 results.push(None);
1006 }
1007 }
1008
1009 let mut stats = self.stats.write().await;
1010 for result in &results {
1011 if result.is_some() {
1012 stats.cache_hits += 1;
1013 } else {
1014 stats.cache_misses += 1;
1015 }
1016 }
1017
1018 Ok(results)
1019 }).await
1020 }
1021
1022 pub async fn batch_put_optimized(&self, pairs: &[(&[u8], &[u8])]) -> StateResult<()> {
1024 if pairs.is_empty() {
1025 return Ok(());
1026 }
1027
1028 for chunk in pairs.chunks(self.config.pipeline_batch_size) {
1030 self.execute_with_resilience("batch_put", || async {
1031 let mut pipe = redis::pipe();
1032 pipe.atomic();
1033
1034 for (key, value) in chunk {
1035 let full_key = self.build_key(key);
1036 let compressed = self.compress(value)?;
1037
1038 if let Some(ttl) = self.config.default_ttl {
1039 pipe.set_ex(&full_key, &compressed, ttl.as_secs() as u64);
1040 } else {
1041 pipe.set(&full_key, &compressed);
1042 }
1043 }
1044
1045 let mut conn = self.pool.get().await.map_err(|e| StateError::StorageError {
1046 backend_type: "EnterpriseRedis".to_string(),
1047 details: format!("Failed to get connection: {}", e),
1048 })?;
1049
1050 pipe.query_async(&mut *conn).await.map_err(|e| StateError::StorageError {
1051 backend_type: "EnterpriseRedis".to_string(),
1052 details: format!("Pipeline failed: {}", e),
1053 })?;
1054
1055 let mut stats = self.stats.write().await;
1056 stats.bytes_written += chunk.iter().map(|(_, v)| v.len() as u64).sum::<u64>();
1057
1058 Ok(())
1059 }).await?;
1060 }
1061
1062 Ok(())
1063 }
1064
1065 pub async fn stats(&self) -> BackendStats {
1067 self.stats.read().await.clone()
1068 }
1069
1070 pub async fn health_check(&self) -> StateResult<bool> {
1072 let mut conn = self.pool.get().await.map_err(|e| StateError::StorageError {
1073 backend_type: "EnterpriseRedis".to_string(),
1074 details: format!("Failed to get connection: {}", e),
1075 })?;
1076
1077 let result: RedisResult<String> = redis::cmd("PING").query_async(&mut *conn).await;
1078 Ok(result.map(|r| r == "PONG").unwrap_or(false))
1079 }
1080
1081 pub fn metrics(&self) -> Option<Arc<RedisMetrics>> {
1083 self.metrics.clone()
1084 }
1085}
1086
1087impl Clone for EnterpriseRedisBackend {
1089 fn clone(&self) -> Self {
1090 Self {
1091 config: self.config.clone(),
1092 pool: self.pool.clone(),
1093 metrics: self.metrics.clone(),
1094 circuit_breaker: self.circuit_breaker.clone(),
1095 scripts: self.scripts.clone(),
1096 stats: self.stats.clone(),
1097 }
1098 }
1099}
1100
1101#[async_trait]
1102impl StateBackend for EnterpriseRedisBackend {
1103 async fn get(&self, key: &[u8]) -> StateResult<Option<Vec<u8>>> {
1104 self.execute_with_resilience("get", || async {
1105 let full_key = self.build_key(key);
1106
1107 let mut conn = self.pool.get().await.map_err(|e| StateError::StorageError {
1108 backend_type: "EnterpriseRedis".to_string(),
1109 details: format!("Failed to get connection: {}", e),
1110 })?;
1111
1112 let value: Option<Vec<u8>> = conn.get(&full_key).await.map_err(|e| StateError::StorageError {
1113 backend_type: "EnterpriseRedis".to_string(),
1114 details: format!("GET failed: {}", e),
1115 })?;
1116
1117 if let Some(compressed) = value {
1118 let decompressed = self.decompress(&compressed)?;
1119
1120 let mut stats = self.stats.write().await;
1121 stats.cache_hits += 1;
1122 stats.bytes_read += decompressed.len() as u64;
1123
1124 Ok(Some(decompressed))
1125 } else {
1126 let mut stats = self.stats.write().await;
1127 stats.cache_misses += 1;
1128 Ok(None)
1129 }
1130 }).await
1131 }
1132
1133 async fn put(&self, key: &[u8], value: &[u8]) -> StateResult<()> {
1134 self.execute_with_resilience("put", || async {
1135 let full_key = self.build_key(key);
1136 let compressed = self.compress(value)?;
1137
1138 let mut conn = self.pool.get().await.map_err(|e| StateError::StorageError {
1139 backend_type: "EnterpriseRedis".to_string(),
1140 details: format!("Failed to get connection: {}", e),
1141 })?;
1142
1143 if let Some(ttl) = self.config.default_ttl {
1144 conn.set_ex(&full_key, &compressed, ttl.as_secs() as u64)
1145 .await
1146 .map_err(|e| StateError::StorageError {
1147 backend_type: "EnterpriseRedis".to_string(),
1148 details: format!("SET failed: {}", e),
1149 })?;
1150 } else {
1151 conn.set(&full_key, &compressed)
1152 .await
1153 .map_err(|e| StateError::StorageError {
1154 backend_type: "EnterpriseRedis".to_string(),
1155 details: format!("SET failed: {}", e),
1156 })?;
1157 }
1158
1159 let mut stats = self.stats.write().await;
1160 stats.bytes_written += value.len() as u64;
1161 stats.bytes_compressed += compressed.len() as u64;
1162
1163 if let Some(ref metrics) = self.metrics {
1165 if compressed.len() > 0 {
1166 let ratio = value.len() as f64 / compressed.len() as f64;
1167 metrics.compression_ratio.set(ratio as i64);
1168 }
1169 }
1170
1171 Ok(())
1172 }).await
1173 }
1174
1175 async fn delete(&self, key: &[u8]) -> StateResult<()> {
1176 self.execute_with_resilience("delete", || async {
1177 let full_key = self.build_key(key);
1178
1179 let mut conn = self.pool.get().await.map_err(|e| StateError::StorageError {
1180 backend_type: "EnterpriseRedis".to_string(),
1181 details: format!("Failed to get connection: {}", e),
1182 })?;
1183
1184 conn.del(&full_key).await.map_err(|e| StateError::StorageError {
1185 backend_type: "EnterpriseRedis".to_string(),
1186 details: format!("DEL failed: {}", e),
1187 })
1188 }).await
1189 }
1190
1191 async fn list_keys(&self, prefix: &[u8]) -> StateResult<Vec<Vec<u8>>> {
1192 self.execute_with_resilience("list_keys", || async {
1193 let pattern = if prefix.is_empty() {
1194 format!("{}*", self.config.key_prefix).into_bytes()
1195 } else {
1196 let mut p = self.config.key_prefix.as_bytes().to_vec();
1197 p.extend_from_slice(prefix);
1198 p.push(b'*');
1199 p
1200 };
1201
1202 let mut conn = self.pool.get().await.map_err(|e| StateError::StorageError {
1203 backend_type: "EnterpriseRedis".to_string(),
1204 details: format!("Failed to get connection: {}", e),
1205 })?;
1206
1207 let mut keys = Vec::new();
1208 let mut cursor = 0u64;
1209
1210 loop {
1211 let (new_cursor, batch): (u64, Vec<Vec<u8>>) = redis::cmd("SCAN")
1212 .arg(cursor)
1213 .arg("MATCH")
1214 .arg(&pattern)
1215 .arg("COUNT")
1216 .arg(100)
1217 .query_async(&mut *conn)
1218 .await
1219 .map_err(|e| StateError::StorageError {
1220 backend_type: "EnterpriseRedis".to_string(),
1221 details: format!("SCAN failed: {}", e),
1222 })?;
1223
1224 keys.extend(batch);
1225 cursor = new_cursor;
1226
1227 if cursor == 0 {
1228 break;
1229 }
1230 }
1231
1232 let stripped: Vec<Vec<u8>> = keys.into_iter()
1234 .map(|k| self.strip_prefix(&k))
1235 .collect();
1236
1237 Ok(stripped)
1238 }).await
1239 }
1240
1241 async fn clear(&self) -> StateResult<()> {
1242 self.execute_with_resilience("clear", || async {
1243 let pattern = format!("{}*", self.config.key_prefix).into_bytes();
1244 let keys = self.list_keys(&[]).await?;
1245
1246 if keys.is_empty() {
1247 return Ok(());
1248 }
1249
1250 for chunk in keys.chunks(self.config.pipeline_batch_size) {
1251 let full_keys: Vec<Vec<u8>> = chunk.iter()
1252 .map(|k| self.build_key(k))
1253 .collect();
1254
1255 let mut conn = self.pool.get().await.map_err(|e| StateError::StorageError {
1256 backend_type: "EnterpriseRedis".to_string(),
1257 details: format!("Failed to get connection: {}", e),
1258 })?;
1259
1260 redis::cmd("DEL")
1261 .arg(&full_keys)
1262 .query_async(&mut *conn)
1263 .await
1264 .map_err(|e| StateError::StorageError {
1265 backend_type: "EnterpriseRedis".to_string(),
1266 details: format!("DEL failed: {}", e),
1267 })?;
1268 }
1269
1270 Ok(())
1271 }).await
1272 }
1273
1274 async fn count(&self) -> StateResult<usize> {
1275 let keys = self.list_keys(&[]).await?;
1276 Ok(keys.len())
1277 }
1278
1279 async fn contains(&self, key: &[u8]) -> StateResult<bool> {
1280 self.execute_with_resilience("contains", || async {
1281 let full_key = self.build_key(key);
1282
1283 let mut conn = self.pool.get().await.map_err(|e| StateError::StorageError {
1284 backend_type: "EnterpriseRedis".to_string(),
1285 details: format!("Failed to get connection: {}", e),
1286 })?;
1287
1288 conn.exists(&full_key).await.map_err(|e| StateError::StorageError {
1289 backend_type: "EnterpriseRedis".to_string(),
1290 details: format!("EXISTS failed: {}", e),
1291 })
1292 }).await
1293 }
1294}
1295
1296#[cfg(test)]
1297mod tests {
1298 use super::*;
1299
1300 async fn create_test_backend() -> Option<EnterpriseRedisBackend> {
1301 let config = EnterpriseRedisConfig::builder()
1302 .mode(RedisMode::Standalone {
1303 url: "redis://localhost:6379".to_string(),
1304 })
1305 .key_prefix("test:enterprise:")
1306 .compression(CompressionAlgorithm::Lz4)
1307 .enable_metrics(true)
1308 .build()
1309 .ok()?;
1310
1311 EnterpriseRedisBackend::new(config).await.ok()
1312 }
1313
1314 #[tokio::test]
1315 async fn test_basic_operations() {
1316 if let Some(backend) = create_test_backend().await {
1317 backend.clear().await.unwrap();
1318
1319 backend.put(b"key1", b"value1").await.unwrap();
1320 assert_eq!(backend.get(b"key1").await.unwrap(), Some(b"value1".to_vec()));
1321 assert!(backend.contains(b"key1").await.unwrap());
1322
1323 backend.delete(b"key1").await.unwrap();
1324 assert_eq!(backend.get(b"key1").await.unwrap(), None);
1325 }
1326 }
1327
1328 #[tokio::test]
1329 async fn test_compression() {
1330 if let Some(backend) = create_test_backend().await {
1331 backend.clear().await.unwrap();
1332
1333 let large_value = vec![b'x'; 10000];
1334 backend.put(b"large", &large_value).await.unwrap();
1335
1336 let retrieved = backend.get(b"large").await.unwrap().unwrap();
1337 assert_eq!(retrieved, large_value);
1338
1339 let stats = backend.stats().await;
1340 assert!(stats.bytes_compressed < stats.bytes_written);
1341 }
1342 }
1343
1344 #[tokio::test]
1345 async fn test_batch_operations() {
1346 if let Some(backend) = create_test_backend().await {
1347 backend.clear().await.unwrap();
1348
1349 let pairs = vec![
1350 (b"batch1".as_ref(), b"value1".as_ref()),
1351 (b"batch2".as_ref(), b"value2".as_ref()),
1352 (b"batch3".as_ref(), b"value3".as_ref()),
1353 ];
1354 backend.batch_put_optimized(&pairs).await.unwrap();
1355
1356 let keys = vec![b"batch1".as_ref(), b"batch2".as_ref(), b"batch3".as_ref()];
1357 let values = backend.batch_get_optimized(&keys).await.unwrap();
1358
1359 assert_eq!(values.len(), 3);
1360 assert_eq!(values[0], Some(b"value1".to_vec()));
1361 }
1362 }
1363
1364 #[tokio::test]
1365 async fn test_distributed_lock() {
1366 if let Some(backend) = create_test_backend().await {
1367 let guard1 = backend.acquire_lock("resource1", Duration::from_secs(10)).await;
1368 assert!(guard1.is_ok());
1369
1370 let guard2 = backend.try_acquire_lock("resource1", Duration::from_secs(10)).await.unwrap();
1371 assert!(guard2.is_none());
1372
1373 drop(guard1);
1374 tokio::time::sleep(Duration::from_millis(100)).await;
1375
1376 let guard3 = backend.try_acquire_lock("resource1", Duration::from_secs(10)).await.unwrap();
1377 assert!(guard3.is_some());
1378 }
1379 }
1380
1381 #[tokio::test]
1382 async fn test_health_check() {
1383 if let Some(backend) = create_test_backend().await {
1384 assert!(backend.health_check().await.unwrap());
1385 }
1386 }
1387}