Initial commit

This commit is contained in:
redclawsystems
2026-03-04 00:08:42 +00:00
commit 4d88dc0584
4449 changed files with 1556714 additions and 0 deletions
@@ -0,0 +1,128 @@
//! State backend trait and related types for state management.
use std::time::SystemTime;
use serde::{Deserialize, Serialize};
use super::{CompressionAlgorithm, StreamingResult};
/// State backend trait for pluggable state storage
#[async_trait::async_trait]
pub trait StateBackend: Send + Sync + std::fmt::Debug {
/// Store state
async fn store(&self, key: &str, value: &[u8]) -> StreamingResult<()>;
/// Retrieve state
async fn retrieve(&self, key: &str) -> StreamingResult<Option<Vec<u8>>>;
/// Delete state
async fn delete(&self, key: &str) -> StreamingResult<()>;
/// List keys with prefix
async fn list_keys(&self, prefix: &str) -> StreamingResult<Vec<String>>;
/// Batch operations
async fn batch_operation(&self, operations: Vec<StateOperation>) -> StreamingResult<()>;
/// Create snapshot
async fn create_snapshot(&self, snapshot_id: &str) -> StreamingResult<SnapshotMetadata>;
/// Restore from snapshot
async fn restore_snapshot(&self, snapshot_id: &str) -> StreamingResult<()>;
/// Get statistics
async fn get_statistics(&self) -> StreamingResult<StateBackendStatistics>;
/// Health check
async fn health_check(&self) -> StreamingResult<HealthStatus>;
}
/// State operations for batching
#[derive(Debug, Clone)]
pub enum StateOperation {
/// Store operation
Store { key: String, value: Vec<u8> },
/// Delete operation
Delete { key: String },
/// Conditional store (compare-and-swap)
ConditionalStore {
key: String,
expected_value: Option<Vec<u8>>,
new_value: Vec<u8>,
},
}
/// Snapshot metadata
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SnapshotMetadata {
/// Snapshot ID
pub snapshot_id: String,
/// Creation timestamp
pub created_at: SystemTime,
/// Size in bytes
pub size_bytes: u64,
/// Checksum
pub checksum: String,
/// Compression info
pub compression_info: Option<CompressionInfo>,
/// Version info
pub version: u64,
}
/// Compression information
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CompressionInfo {
/// Algorithm used
pub algorithm: CompressionAlgorithm,
/// Compression ratio
pub compression_ratio: f64,
/// Compressed size
pub compressed_size_bytes: u64,
/// Uncompressed size
pub uncompressed_size_bytes: u64,
}
/// State backend statistics
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StateBackendStatistics {
/// Total keys
pub total_keys: u64,
/// Total size in bytes
pub total_size_bytes: u64,
/// Read operations per second
pub read_ops_per_sec: f64,
/// Write operations per second
pub write_ops_per_sec: f64,
/// Average read latency (microseconds)
pub avg_read_latency_micros: u64,
/// Average write latency (microseconds)
pub avg_write_latency_micros: u64,
/// Cache hit ratio
pub cache_hit_ratio: f64,
/// Background operations
pub background_operations: BackgroundOperationStats,
}
/// Background operation statistics
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BackgroundOperationStats {
/// Compaction operations
pub compaction_ops: u64,
/// Garbage collection operations
pub gc_ops: u64,
/// Cleanup operations
pub cleanup_ops: u64,
}
/// Health status
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum HealthStatus {
/// Healthy
Healthy,
/// Degraded performance
Degraded,
/// Unhealthy
Unhealthy,
/// Critical condition
Critical,
}
@@ -0,0 +1,388 @@
//! State Backend Implementations
//!
//! This module provides concrete implementations of the `StateBackend` trait
//! for different storage backends including RocksDB and in-memory storage.
use std::path::PathBuf;
use std::sync::Arc;
use std::time::SystemTime;
use dashmap::DashMap;
use super::{
BackgroundOperationStats, HealthStatus, SnapshotMetadata, StateBackend, StateBackendStatistics,
StateOperation,
};
use crate::{StreamingError, StreamingResult};
/// RocksDB-based state backend for persistent state storage.
///
/// This implementation uses DashMap as an in-memory mock for the actual
/// RocksDB storage. In a production environment, this would be replaced
/// with actual RocksDB operations.
#[derive(Debug)]
pub struct RocksDbStateBackend {
/// Path to the RocksDB database directory
path: PathBuf,
/// In-memory data store (mock for RocksDB)
data: Arc<DashMap<String, Vec<u8>>>,
/// Snapshot storage
snapshots: Arc<DashMap<String, Vec<(String, Vec<u8>)>>>,
}
impl RocksDbStateBackend {
/// Creates a new RocksDB state backend at the specified path.
pub async fn new(path: PathBuf) -> StreamingResult<Self> {
Ok(Self {
path,
data: Arc::new(DashMap::new()),
snapshots: Arc::new(DashMap::new()),
})
}
/// Returns the database path.
pub fn path(&self) -> &PathBuf {
&self.path
}
}
#[async_trait::async_trait]
impl StateBackend for RocksDbStateBackend {
async fn store(&self, key: &str, value: &[u8]) -> StreamingResult<()> {
self.data.insert(key.to_string(), value.to_vec());
Ok(())
}
async fn retrieve(&self, key: &str) -> StreamingResult<Option<Vec<u8>>> {
Ok(self.data.get(key).map(|entry| entry.value().clone()))
}
async fn delete(&self, key: &str) -> StreamingResult<()> {
self.data.remove(key);
Ok(())
}
async fn list_keys(&self, prefix: &str) -> StreamingResult<Vec<String>> {
Ok(self
.data
.iter()
.filter(|entry| entry.key().starts_with(prefix))
.map(|entry| entry.key().clone())
.collect())
}
async fn batch_operation(&self, operations: Vec<StateOperation>) -> StreamingResult<()> {
for op in operations {
match op {
StateOperation::Store { key, value } => self.store(&key, &value).await?,
StateOperation::Delete { key } => self.delete(&key).await?,
StateOperation::ConditionalStore {
key,
expected_value,
new_value,
} => {
// Check if current value matches expected value
let current = self.retrieve(&key).await?;
let should_store = match (current, expected_value) {
(None, None) => true,
(Some(cur), Some(exp)) if cur == exp => true,
_ => false,
};
if should_store {
self.store(&key, &new_value).await?;
}
}
}
}
Ok(())
}
async fn create_snapshot(&self, snapshot_id: &str) -> StreamingResult<SnapshotMetadata> {
let snapshot_data: Vec<(String, Vec<u8>)> = self
.data
.iter()
.map(|entry| (entry.key().clone(), entry.value().clone()))
.collect();
let size_bytes: u64 = snapshot_data
.iter()
.map(|(k, v)| (k.len() + v.len()) as u64)
.sum();
let metadata = SnapshotMetadata {
snapshot_id: snapshot_id.to_string(),
created_at: std::time::SystemTime::now(),
size_bytes,
checksum: format!("{size_bytes:x}"), // Simple checksum for now
compression_info: None,
version: 1,
};
// Store snapshot (in real implementation would persist to disk)
self.snapshots
.insert(snapshot_id.to_string(), snapshot_data);
Ok(metadata)
}
async fn restore_snapshot(&self, snapshot_id: &str) -> StreamingResult<()> {
if let Some(snapshot_data) = self.snapshots.get(snapshot_id) {
self.data.clear();
for (key, value) in snapshot_data.value() {
self.data.insert(key.clone(), value.clone());
}
Ok(())
} else {
Err(StreamingError::StateError(format!(
"Snapshot {snapshot_id} not found"
)))
}
}
async fn get_statistics(&self) -> StreamingResult<StateBackendStatistics> {
let total_size: usize = self
.data
.iter()
.map(|entry| entry.key().len() + entry.value().len())
.sum();
Ok(StateBackendStatistics {
total_keys: self.data.len() as u64,
total_size_bytes: total_size as u64,
read_ops_per_sec: 0.0,
write_ops_per_sec: 0.0,
avg_read_latency_micros: 0,
avg_write_latency_micros: 0,
cache_hit_ratio: 0.0,
background_operations: BackgroundOperationStats {
compaction_ops: 0,
gc_ops: 0,
cleanup_ops: 0,
},
})
}
async fn health_check(&self) -> StreamingResult<HealthStatus> {
// Basic health check - ensure we can read/write
let test_key = "__health_check_test__";
let test_value = b"test";
match self.store(test_key, test_value).await {
Ok(()) => {
let retrieved = self.retrieve(test_key).await?;
self.delete(test_key).await?;
if retrieved.is_some() {
Ok(HealthStatus::Healthy)
} else {
Ok(HealthStatus::Degraded)
}
}
Err(_) => Ok(HealthStatus::Unhealthy),
}
}
}
/// In-memory state backend for fast, ephemeral state storage.
///
/// This backend stores all state in memory using a concurrent hashmap.
/// It's suitable for testing, development, or scenarios where persistence
/// is not required.
#[derive(Debug)]
pub struct MemoryStateBackend {
/// In-memory data store
data: Arc<DashMap<String, Vec<u8>>>,
/// Maximum memory usage limit in bytes
max_memory: usize,
}
impl MemoryStateBackend {
/// Creates a new in-memory state backend with the specified memory limit.
pub async fn new(max_memory: usize) -> StreamingResult<Self> {
Ok(Self {
data: Arc::new(DashMap::new()),
max_memory,
})
}
/// Returns the maximum memory limit.
pub fn max_memory(&self) -> usize {
self.max_memory
}
/// Returns the current number of stored keys.
pub fn key_count(&self) -> usize {
self.data.len()
}
}
#[async_trait::async_trait]
impl StateBackend for MemoryStateBackend {
async fn store(&self, key: &str, value: &[u8]) -> StreamingResult<()> {
self.data.insert(key.to_string(), value.to_vec());
Ok(())
}
async fn retrieve(&self, key: &str) -> StreamingResult<Option<Vec<u8>>> {
Ok(self.data.get(key).map(|entry| entry.value().clone()))
}
async fn delete(&self, key: &str) -> StreamingResult<()> {
self.data.remove(key);
Ok(())
}
async fn list_keys(&self, prefix: &str) -> StreamingResult<Vec<String>> {
Ok(self
.data
.iter()
.filter(|entry| entry.key().starts_with(prefix))
.map(|entry| entry.key().clone())
.collect())
}
async fn batch_operation(&self, operations: Vec<StateOperation>) -> StreamingResult<()> {
for op in operations {
match op {
StateOperation::Store { key, value } => {
self.store(&key, &value).await?;
}
StateOperation::Delete { key } => {
self.delete(&key).await?;
}
StateOperation::ConditionalStore {
key,
expected_value: _,
new_value,
} => {
self.store(&key, &new_value).await?;
}
}
}
Ok(())
}
async fn create_snapshot(&self, snapshot_id: &str) -> StreamingResult<SnapshotMetadata> {
Ok(SnapshotMetadata {
snapshot_id: snapshot_id.to_string(),
created_at: SystemTime::now(),
size_bytes: 0,
checksum: "mock_checksum".to_string(),
compression_info: None,
version: 1,
})
}
async fn restore_snapshot(&self, _snapshot_id: &str) -> StreamingResult<()> {
Ok(())
}
async fn get_statistics(&self) -> StreamingResult<StateBackendStatistics> {
Ok(StateBackendStatistics {
total_keys: self.data.len() as u64,
total_size_bytes: 0,
read_ops_per_sec: 0.0,
write_ops_per_sec: 0.0,
avg_read_latency_micros: 0,
avg_write_latency_micros: 0,
cache_hit_ratio: 1.0,
background_operations: BackgroundOperationStats {
compaction_ops: 0,
gc_ops: 0,
cleanup_ops: 0,
},
})
}
async fn health_check(&self) -> StreamingResult<HealthStatus> {
Ok(HealthStatus::Healthy)
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::path::PathBuf;
#[tokio::test]
async fn test_memory_backend_store_and_retrieve() {
let backend = MemoryStateBackend::new(1024 * 1024).await.unwrap();
backend.store("key1", b"value1").await.unwrap();
let retrieved = backend.retrieve("key1").await.unwrap();
assert_eq!(retrieved, Some(b"value1".to_vec()));
}
#[tokio::test]
async fn test_memory_backend_delete() {
let backend = MemoryStateBackend::new(1024 * 1024).await.unwrap();
backend.store("key1", b"value1").await.unwrap();
backend.delete("key1").await.unwrap();
let retrieved = backend.retrieve("key1").await.unwrap();
assert_eq!(retrieved, None);
}
#[tokio::test]
async fn test_memory_backend_list_keys() {
let backend = MemoryStateBackend::new(1024 * 1024).await.unwrap();
backend.store("prefix:key1", b"value1").await.unwrap();
backend.store("prefix:key2", b"value2").await.unwrap();
backend.store("other:key3", b"value3").await.unwrap();
let keys = backend.list_keys("prefix:").await.unwrap();
assert_eq!(keys.len(), 2);
}
#[tokio::test]
async fn test_rocksdb_backend_store_and_retrieve() {
let backend = RocksDbStateBackend::new(PathBuf::from("/tmp/test_db"))
.await
.unwrap();
backend.store("key1", b"value1").await.unwrap();
let retrieved = backend.retrieve("key1").await.unwrap();
assert_eq!(retrieved, Some(b"value1".to_vec()));
}
#[tokio::test]
async fn test_rocksdb_backend_snapshot() {
let backend = RocksDbStateBackend::new(PathBuf::from("/tmp/test_db"))
.await
.unwrap();
backend.store("key1", b"value1").await.unwrap();
backend.store("key2", b"value2").await.unwrap();
let metadata = backend.create_snapshot("snapshot1").await.unwrap();
assert_eq!(metadata.snapshot_id, "snapshot1");
// Clear and restore
backend.delete("key1").await.unwrap();
backend.delete("key2").await.unwrap();
backend.restore_snapshot("snapshot1").await.unwrap();
let retrieved = backend.retrieve("key1").await.unwrap();
assert_eq!(retrieved, Some(b"value1".to_vec()));
}
#[tokio::test]
async fn test_health_check() {
let memory_backend = MemoryStateBackend::new(1024 * 1024).await.unwrap();
let rocksdb_backend = RocksDbStateBackend::new(PathBuf::from("/tmp/test_db"))
.await
.unwrap();
assert!(matches!(
memory_backend.health_check().await.unwrap(),
HealthStatus::Healthy
));
assert!(matches!(
rocksdb_backend.health_check().await.unwrap(),
HealthStatus::Healthy
));
}
}
@@ -0,0 +1,232 @@
//! Checkpoint management for stream state persistence and recovery.
use crate::StreamingResult;
use crate::types_metrics::CheckpointMetrics;
use crate::types_processing::{
CheckpointExecutor, CheckpointScheduler, CheckpointStorage, CheckpointValidator,
};
use dashmap::DashMap;
use serde::{Deserialize, Serialize};
use std::{
collections::HashMap,
sync::Arc,
time::{Duration, SystemTime},
};
use uuid::Uuid;
use super::CheckpointConfig;
/// Checkpoint manager for state persistence
#[derive(Debug)]
pub struct CheckpointManager {
/// Active checkpoints
active_checkpoints: Arc<DashMap<String, CheckpointInfo>>,
/// Checkpoint executor
executor: Arc<CheckpointExecutor>,
/// Checkpoint storage
storage: Arc<CheckpointStorage>,
/// Checkpoint validator
validator: Arc<CheckpointValidator>,
/// Configuration
config: CheckpointConfig,
/// Checkpoint scheduler
scheduler: Arc<CheckpointScheduler>,
/// Metrics
metrics: Arc<CheckpointMetrics>,
}
impl CheckpointManager {
pub fn new(config: CheckpointConfig) -> Self {
let storage = Arc::new(CheckpointStorage::new(100));
let executor = Arc::new(CheckpointExecutor::new(Arc::clone(&storage)));
Self {
active_checkpoints: Arc::new(DashMap::new()),
executor,
storage,
validator: Arc::new(CheckpointValidator::new()),
config,
scheduler: Arc::new(CheckpointScheduler::new()),
metrics: Arc::new(CheckpointMetrics::new()),
}
}
/// Create a new checkpoint
pub async fn create_checkpoint(
&self,
stream_id: &str,
state: Vec<u8>,
) -> StreamingResult<String> {
let checkpoint_id = Uuid::new_v4().to_string();
let checkpoint_info = CheckpointInfo {
checkpoint_id: checkpoint_id.clone(),
stream_id: stream_id.to_string(),
start_time: SystemTime::now(),
status: CheckpointStatus::Completed,
progress: CheckpointProgress {
total_items: 1,
completed_items: 1,
bytes_processed: state.len() as u64,
estimated_completion: None,
},
metadata: CheckpointMetadata {
version: 1,
created_at: SystemTime::now(),
stream_position: StreamPosition {
stream_id: stream_id.to_string(),
partition: Some(0),
offset: 0,
timestamp: SystemTime::now(),
},
state_snapshot: StateSnapshot {
snapshot_id: checkpoint_id.clone(),
entries: HashMap::new(),
total_size_bytes: state.len() as u64,
checksum: String::new(), // In production, calculate actual checksum
},
dependencies: vec![],
custom_metadata: HashMap::new(),
},
created_at: SystemTime::now(),
state_size: state.len(),
state_hash: 0, // In production, calculate actual hash
};
self.active_checkpoints
.insert(checkpoint_id.clone(), checkpoint_info);
self.storage.store(&checkpoint_id, state).await?;
Ok(checkpoint_id)
}
/// Get checkpoint statistics
pub fn get_statistics(&self) -> CheckpointStats {
CheckpointStats {
active_checkpoints: self.active_checkpoints.len(),
total_checkpoints: self.metrics.get_total_checkpoints(),
}
}
}
/// Checkpoint statistics
#[derive(Debug, Clone)]
pub struct CheckpointStats {
pub active_checkpoints: usize,
pub total_checkpoints: u64,
}
/// Checkpoint information
#[derive(Debug, Clone)]
pub struct CheckpointInfo {
/// Checkpoint ID
pub checkpoint_id: String,
/// Stream ID
pub stream_id: String,
/// Start time
pub start_time: SystemTime,
/// Status
pub status: CheckpointStatus,
/// Progress
pub progress: CheckpointProgress,
/// Metadata
pub metadata: CheckpointMetadata,
/// Created at timestamp
pub created_at: SystemTime,
/// State size in bytes
pub state_size: usize,
/// State hash for verification
pub state_hash: u64,
}
/// Checkpoint status
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum CheckpointStatus {
/// Preparing checkpoint
Preparing,
/// In progress
InProgress,
/// Completed successfully
Completed,
/// Failed
Failed { error: String },
/// Cancelled
Cancelled,
}
/// Checkpoint progress
#[derive(Debug, Clone)]
pub struct CheckpointProgress {
/// Total items to checkpoint
pub total_items: usize,
/// Items completed
pub completed_items: usize,
/// Bytes processed
pub bytes_processed: u64,
/// Estimated completion time
pub estimated_completion: Option<SystemTime>,
}
/// Checkpoint metadata
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CheckpointMetadata {
/// Checkpoint version
pub version: u64,
/// Creation timestamp
pub created_at: SystemTime,
/// Stream position at checkpoint
pub stream_position: StreamPosition,
/// State snapshot
pub state_snapshot: StateSnapshot,
/// Dependencies
pub dependencies: Vec<String>,
/// Custom metadata
pub custom_metadata: HashMap<String, String>,
}
/// Stream position
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StreamPosition {
/// Stream identifier
pub stream_id: String,
/// Offset in stream
pub offset: u64,
/// Partition (if applicable)
pub partition: Option<u32>,
/// Timestamp
pub timestamp: SystemTime,
}
/// State snapshot
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StateSnapshot {
/// Snapshot ID
pub snapshot_id: String,
/// State entries
pub entries: HashMap<String, StateEntry>,
/// Total size
pub total_size_bytes: u64,
/// Checksum
pub checksum: String,
}
/// State entry
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StateEntry {
/// Entry key
pub key: String,
/// Entry value
pub value: Vec<u8>,
/// Version
pub version: u64,
/// Last modified
pub last_modified: SystemTime,
/// TTL (time to live)
pub ttl: Option<Duration>,
}
@@ -0,0 +1,629 @@
//! Configuration types for stream state management.
//!
//! This module contains all configuration structs and enums for the state management system,
//! including backend configuration, checkpointing, recovery, exactly-once processing,
//! distributed coordination, and performance tuning.
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::path::PathBuf;
use std::time::{Duration, SystemTime};
/// Store condition for conditional storage operations
#[derive(Debug, Clone)]
pub enum StoreCondition {
/// Store only if key doesn't exist
IfNotExists,
/// Store only if version matches
IfVersion(u64),
}
/// State management configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StateManagementConfig {
/// Backend configuration
pub backend_config: StateBackendConfig,
/// Checkpointing configuration
pub checkpoint_config: CheckpointConfig,
/// Recovery configuration
pub recovery_config: RecoveryConfig,
/// Exactly-once configuration
pub exactly_once_config: ExactlyOnceConfig,
/// Distributed coordination configuration
pub coordination_config: CoordinationConfig,
/// Failure detection configuration
pub failure_detection_config: FailureDetectionConfig,
/// Performance tuning
pub performance_config: StatePerformanceConfig,
}
impl Default for StateManagementConfig {
fn default() -> Self {
Self {
backend_config: StateBackendConfig::Memory {
max_memory_bytes: 1024 * 1024 * 1024, // 1 GB
persist_to_disk: false,
persistence_path: None,
},
checkpoint_config: CheckpointConfig::default(),
recovery_config: RecoveryConfig::default(),
exactly_once_config: ExactlyOnceConfig::default(),
coordination_config: CoordinationConfig::default(),
failure_detection_config: FailureDetectionConfig::default(),
performance_config: StatePerformanceConfig::default(),
}
}
}
/// State backend configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum StateBackendConfig {
/// In-memory state backend
Memory {
/// Maximum memory usage (bytes)
max_memory_bytes: usize,
/// Enable persistence to disk
persist_to_disk: bool,
/// Persistence path
persistence_path: Option<PathBuf>,
},
/// RocksDB state backend
RocksDb {
/// Database path
path: PathBuf,
/// RocksDB configuration
rocksdb_config: RocksDbConfig,
},
/// Sled state backend
Sled {
/// Database path
path: PathBuf,
/// Sled configuration
sled_config: SledConfig,
},
/// Redis state backend
Redis {
/// Connection URL
url: String,
/// Redis configuration
redis_config: RedisStateConfig,
},
/// Custom state backend
Custom {
/// Backend name
name: String,
/// Configuration parameters
config: HashMap<String, String>,
},
}
/// RocksDB configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RocksDbConfig {
/// Write buffer size
pub write_buffer_size: usize,
/// Maximum write buffers
pub max_write_buffers: usize,
/// Block cache size
pub block_cache_size: usize,
/// Compression type
pub compression_type: String,
/// Enable statistics
pub enable_statistics: bool,
}
/// Sled configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SledConfig {
/// Cache capacity
pub cache_capacity: u64,
/// Flush every n operations
pub flush_every_ms: Option<u64>,
/// Use compression
pub use_compression: bool,
}
/// Redis state configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RedisStateConfig {
/// Connection pool size
pub pool_size: usize,
/// Command timeout
pub timeout_ms: u64,
/// Key prefix
pub key_prefix: String,
/// Use clustering
pub use_clustering: bool,
}
/// Checkpoint configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CheckpointConfig {
/// Checkpoint interval
pub interval: Duration,
/// Checkpoint timeout
pub timeout: Duration,
/// Minimum pause between checkpoints
pub min_pause_between: Duration,
/// Maximum concurrent checkpoints
pub max_concurrent: usize,
/// Enable incremental checkpointing
pub incremental: bool,
/// Checkpoint retention
pub retention_policy: CheckpointRetentionPolicy,
/// Compression configuration
pub compression: CheckpointCompressionConfig,
/// Checkpoint directory
pub checkpoint_dir: PathBuf,
}
impl Default for CheckpointConfig {
fn default() -> Self {
Self {
interval: Duration::from_secs(60),
timeout: Duration::from_secs(30),
min_pause_between: Duration::from_secs(5),
max_concurrent: 1,
incremental: false,
retention_policy: CheckpointRetentionPolicy::default(),
compression: CheckpointCompressionConfig::default(),
checkpoint_dir: PathBuf::from("/tmp/checkpoints"),
}
}
}
/// Checkpoint retention policy
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CheckpointRetentionPolicy {
/// Maximum checkpoints to keep
pub max_checkpoints: usize,
/// Retention time
pub retention_time: Duration,
/// Keep checkpoints based on size
pub max_total_size_bytes: Option<usize>,
}
impl Default for CheckpointRetentionPolicy {
fn default() -> Self {
Self {
max_checkpoints: 10,
retention_time: Duration::from_secs(86400), // 24 hours
max_total_size_bytes: None,
}
}
}
/// Checkpoint compression configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CheckpointCompressionConfig {
/// Enable compression
pub enabled: bool,
/// Compression algorithm
pub algorithm: CompressionAlgorithm,
/// Compression level
pub level: u8,
}
impl Default for CheckpointCompressionConfig {
fn default() -> Self {
Self {
enabled: true,
algorithm: CompressionAlgorithm::Gzip,
level: 6,
}
}
}
/// Compression algorithms
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum CompressionAlgorithm {
/// No compression
None,
/// Gzip compression
Gzip,
/// Zstd compression
Zstd,
/// LZ4 compression
Lz4,
/// Snappy compression
Snappy,
}
/// Recovery configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RecoveryConfig {
/// Recovery strategy
pub strategy: RecoveryStrategy,
/// Recovery timeout
pub timeout: Duration,
/// Parallel recovery workers
pub parallel_workers: usize,
/// Enable partial recovery
pub allow_partial_recovery: bool,
/// Recovery validation
pub validation_config: RecoveryValidationConfig,
}
impl Default for RecoveryConfig {
fn default() -> Self {
Self {
strategy: RecoveryStrategy::FullRecovery,
timeout: Duration::from_secs(300),
parallel_workers: 4,
allow_partial_recovery: false,
validation_config: RecoveryValidationConfig::default(),
}
}
}
/// Recovery strategies
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, Hash)]
pub enum RecoveryStrategy {
/// Full recovery from latest checkpoint
FullRecovery,
/// Recovery from specific checkpoint
FromCheckpoint,
/// Incremental recovery
IncrementalRecovery,
/// Point-in-time recovery
PointInTimeRecovery { target_time: SystemTime },
/// Best-effort recovery
BestEffortRecovery,
/// No recovery (restart from scratch)
NoRecovery,
}
/// Recovery validation configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RecoveryValidationConfig {
/// Enable validation
pub enabled: bool,
/// Validation timeout
pub timeout: Duration,
/// Checksum validation
pub validate_checksums: bool,
/// Data consistency checks
pub consistency_checks: bool,
}
impl Default for RecoveryValidationConfig {
fn default() -> Self {
Self {
enabled: true,
timeout: Duration::from_secs(60),
validate_checksums: true,
consistency_checks: true,
}
}
}
/// Exactly-once processing configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ExactlyOnceConfig {
/// Enable exactly-once processing
pub enabled: bool,
/// Idempotency key generation
pub idempotency_config: IdempotencyConfig,
/// Deduplication configuration
pub deduplication_config: DeduplicationConfig,
/// Transaction configuration
pub transaction_config: TransactionConfig,
}
impl Default for ExactlyOnceConfig {
fn default() -> Self {
Self {
enabled: true,
idempotency_config: IdempotencyConfig::default(),
deduplication_config: DeduplicationConfig::default(),
transaction_config: TransactionConfig::default(),
}
}
}
/// Idempotency configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct IdempotencyConfig {
/// Key generation strategy
pub key_generation_strategy: IdempotencyKeyStrategy,
/// Key lifetime
pub key_lifetime: Duration,
/// Maximum keys to track
pub max_keys: usize,
}
impl Default for IdempotencyConfig {
fn default() -> Self {
Self {
key_generation_strategy: IdempotencyKeyStrategy::EventId,
key_lifetime: Duration::from_secs(3600),
max_keys: 10000,
}
}
}
/// Idempotency key generation strategies
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum IdempotencyKeyStrategy {
/// Event ID based
EventId,
/// Content hash based
ContentHash,
/// Custom field based
CustomField { field_name: String },
/// Composite key
Composite { fields: Vec<String> },
}
/// Deduplication configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DeduplicationConfig {
/// Window size for deduplication
pub window_size: Duration,
/// Maximum duplicates to track
pub max_duplicates: usize,
/// Bloom filter configuration
pub bloom_filter_config: BloomFilterConfig,
}
impl Default for DeduplicationConfig {
fn default() -> Self {
Self {
window_size: Duration::from_secs(300),
max_duplicates: 10000,
bloom_filter_config: BloomFilterConfig::default(),
}
}
}
/// Bloom filter configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BloomFilterConfig {
/// Expected number of elements
pub expected_elements: usize,
/// False positive probability
pub false_positive_probability: f64,
/// Enable counting bloom filter
pub counting_filter: bool,
}
impl Default for BloomFilterConfig {
fn default() -> Self {
Self {
expected_elements: 10000,
false_positive_probability: 0.01,
counting_filter: false,
}
}
}
/// Transaction configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TransactionConfig {
/// Transaction timeout
pub timeout: Duration,
/// Maximum concurrent transactions
pub max_concurrent_transactions: usize,
/// Two-phase commit enabled
pub two_phase_commit: bool,
/// Isolation level
pub isolation_level: IsolationLevel,
}
impl Default for TransactionConfig {
fn default() -> Self {
Self {
timeout: Duration::from_secs(30),
max_concurrent_transactions: 100,
two_phase_commit: false,
isolation_level: IsolationLevel::ReadCommitted,
}
}
}
/// Transaction isolation levels
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum IsolationLevel {
/// Read uncommitted
ReadUncommitted,
/// Read committed
ReadCommitted,
/// Repeatable read
RepeatableRead,
/// Serializable
Serializable,
}
/// Distributed coordination configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CoordinationConfig {
/// Enable distributed coordination
pub enabled: bool,
/// Consensus algorithm
pub consensus_algorithm: ConsensusAlgorithm,
/// Leader election configuration
pub leader_election: LeaderElectionConfig,
/// Membership management
pub membership_config: MembershipConfig,
}
impl Default for CoordinationConfig {
fn default() -> Self {
Self {
enabled: false,
consensus_algorithm: ConsensusAlgorithm::Raft {
election_timeout_ms: (150, 300),
heartbeat_interval_ms: 50,
},
leader_election: LeaderElectionConfig::default(),
membership_config: MembershipConfig::default(),
}
}
}
/// Consensus algorithms
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum ConsensusAlgorithm {
/// Raft consensus
Raft {
/// Election timeout range
election_timeout_ms: (u64, u64),
/// Heartbeat interval
heartbeat_interval_ms: u64,
},
/// PBFT (Practical Byzantine Fault Tolerance)
Pbft {
/// View timeout
view_timeout_ms: u64,
},
/// Custom consensus
Custom { algorithm_name: String },
}
/// Leader election configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LeaderElectionConfig {
/// Election timeout
pub timeout: Duration,
/// Term duration
pub term_duration: Duration,
/// Maximum retries
pub max_retries: usize,
}
impl Default for LeaderElectionConfig {
fn default() -> Self {
Self {
timeout: Duration::from_secs(10),
term_duration: Duration::from_secs(300),
max_retries: 3,
}
}
}
/// Membership management configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MembershipConfig {
/// Join timeout
pub join_timeout: Duration,
/// Leave timeout
pub leave_timeout: Duration,
/// Failure detection interval
pub failure_detection_interval: Duration,
/// Maximum cluster size
pub max_cluster_size: usize,
}
impl Default for MembershipConfig {
fn default() -> Self {
Self {
join_timeout: Duration::from_secs(30),
leave_timeout: Duration::from_secs(10),
failure_detection_interval: Duration::from_secs(5),
max_cluster_size: 100,
}
}
}
/// Failure detection configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FailureDetectionConfig {
/// Heartbeat interval
pub heartbeat_interval: Duration,
/// Failure timeout
pub failure_timeout: Duration,
/// Recovery timeout
pub recovery_timeout: Duration,
/// Maximum failures before permanent failure
pub max_failures: usize,
}
impl Default for FailureDetectionConfig {
fn default() -> Self {
Self {
heartbeat_interval: Duration::from_secs(5),
failure_timeout: Duration::from_secs(30),
recovery_timeout: Duration::from_secs(60),
max_failures: 3,
}
}
}
/// State performance configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StatePerformanceConfig {
/// Background compaction enabled
pub background_compaction: bool,
/// Compaction interval
pub compaction_interval: Duration,
/// Memory pressure thresholds
pub memory_pressure_thresholds: MemoryPressureThresholds,
/// I/O optimization
pub io_optimization: IoOptimizationConfig,
}
impl Default for StatePerformanceConfig {
fn default() -> Self {
Self {
background_compaction: true,
compaction_interval: Duration::from_secs(3600),
memory_pressure_thresholds: MemoryPressureThresholds::default(),
io_optimization: IoOptimizationConfig::default(),
}
}
}
/// Memory pressure thresholds
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MemoryPressureThresholds {
/// Warning threshold (percentage)
pub warning_threshold: f64,
/// Critical threshold (percentage)
pub critical_threshold: f64,
/// Emergency threshold (percentage)
pub emergency_threshold: f64,
}
impl Default for MemoryPressureThresholds {
fn default() -> Self {
Self {
warning_threshold: 0.7,
critical_threshold: 0.85,
emergency_threshold: 0.95,
}
}
}
/// I/O optimization configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct IoOptimizationConfig {
/// Batch writes
pub batch_writes: bool,
/// Write batch size
pub write_batch_size: usize,
/// Async I/O
pub async_io: bool,
/// I/O threads
pub io_threads: usize,
}
impl Default for IoOptimizationConfig {
fn default() -> Self {
Self {
batch_writes: true,
write_batch_size: 1000,
async_io: true,
io_threads: 4,
}
}
}
@@ -0,0 +1,186 @@
//! Exactly-once processing semantics for streaming workloads.
//!
//! This module provides idempotency tracking, deduplication, and exactly-once
//! processing guarantees for stream processing pipelines.
use super::config::{DeduplicationConfig, ExactlyOnceConfig, IdempotencyConfig};
use crate::StreamingResult;
use crate::types_processing::{ProcessingState, TransactionManager};
use crate::types_remaining::{BloomFilter, DuplicateDetector};
use dashmap::DashMap;
use parking_lot::Mutex as ParkingMutex;
use std::{collections::HashMap, sync::Arc, time::SystemTime};
/// Exactly-once processor with idempotency and deduplication
#[derive(Debug)]
pub struct ExactlyOnceProcessor {
/// Idempotency tracker
idempotency_tracker: Arc<IdempotencyTracker>,
/// Deduplication engine
deduplication_engine: Arc<DeduplicationEngine>,
/// Transaction manager
transaction_manager: Arc<TransactionManager>,
/// Configuration
config: ExactlyOnceConfig,
/// Processing state
processing_state: Arc<ProcessingState>,
}
impl ExactlyOnceProcessor {
pub fn new(config: ExactlyOnceConfig) -> Self {
Self {
idempotency_tracker: Arc::new(IdempotencyTracker::new(
config.idempotency_config.clone(),
)),
deduplication_engine: Arc::new(DeduplicationEngine::new(
config.deduplication_config.clone(),
)),
transaction_manager: Arc::new(TransactionManager::new()),
config,
processing_state: Arc::new(ProcessingState::new()),
}
}
/// Check if an event has already been processed
pub async fn is_already_processed(&self, event_id: &str) -> StreamingResult<bool> {
self.idempotency_tracker.is_processed(event_id).await
}
/// Mark an event as processed
pub async fn mark_as_processed(&self, event_id: &str) -> StreamingResult<()> {
self.idempotency_tracker.mark_processed(event_id).await
}
/// Get processor statistics
pub fn get_statistics(&self) -> ExactlyOnceStats {
ExactlyOnceStats {
events_processed: self.idempotency_tracker.get_processed_count(),
duplicates_detected: self.deduplication_engine.get_duplicate_count(),
}
}
}
/// Exactly-once statistics
#[derive(Debug, Clone)]
pub struct ExactlyOnceStats {
pub events_processed: usize,
pub duplicates_detected: usize,
}
/// Idempotency tracker
#[derive(Debug)]
pub struct IdempotencyTracker {
/// Processed event IDs
processed_events: Arc<DashMap<String, ProcessedEventInfo>>,
/// Bloom filter for fast lookups
bloom_filter: Arc<ParkingMutex<BloomFilter>>,
/// Configuration
config: IdempotencyConfig,
}
impl IdempotencyTracker {
pub fn new(config: IdempotencyConfig) -> Self {
Self {
processed_events: Arc::new(DashMap::new()),
bloom_filter: Arc::new(ParkingMutex::new(BloomFilter::new(10000, 3))),
config,
}
}
/// Check if event is processed
pub async fn is_processed(&self, event_id: &str) -> StreamingResult<bool> {
// First check bloom filter for fast negative
let bloom = self.bloom_filter.lock();
if !bloom.contains(event_id.as_bytes()) {
return Ok(false);
}
drop(bloom);
// Then check actual processed events
Ok(self.processed_events.contains_key(event_id))
}
/// Mark event as processed
pub async fn mark_processed(&self, event_id: &str) -> StreamingResult<()> {
let bloom = self.bloom_filter.lock();
bloom.insert(event_id.as_bytes());
drop(bloom);
self.processed_events.insert(
event_id.to_string(),
ProcessedEventInfo {
event_id: event_id.to_string(),
processed_at: SystemTime::now(),
result_hash: String::from("0"),
metadata: HashMap::new(),
},
);
Ok(())
}
/// Get processed event count
pub fn get_processed_count(&self) -> usize {
self.processed_events.len()
}
}
/// Processed event information
#[derive(Debug, Clone)]
pub struct ProcessedEventInfo {
/// Event ID
pub event_id: String,
/// Processing timestamp
pub processed_at: SystemTime,
/// Result hash
pub result_hash: String,
/// Processing metadata
pub metadata: HashMap<String, String>,
}
/// Deduplication engine
#[derive(Debug)]
pub struct DeduplicationEngine {
/// Event signatures
event_signatures: Arc<DashMap<String, EventSignature>>,
/// Duplicate detector
duplicate_detector: Arc<DuplicateDetector>,
/// Configuration
config: DeduplicationConfig,
}
impl DeduplicationEngine {
pub fn new(config: DeduplicationConfig) -> Self {
Self {
event_signatures: Arc::new(DashMap::new()),
duplicate_detector: Arc::new(DuplicateDetector::new(10000)),
config,
}
}
/// Get duplicate count
pub fn get_duplicate_count(&self) -> usize {
self.duplicate_detector.get_duplicate_count()
}
}
/// Event signature for deduplication
#[derive(Debug, Clone)]
pub struct EventSignature {
/// Signature hash
pub signature: String,
/// First occurrence timestamp
pub first_seen: SystemTime,
/// Occurrence count
pub count: usize,
/// Last occurrence timestamp
pub last_seen: SystemTime,
}
@@ -0,0 +1,778 @@
//! Stream State Manager
//!
//! Core manager for stream state with fault tolerance, checkpointing,
//! recovery, and exactly-once processing guarantees.
use crate::{StreamingError, StreamingResult, realtime_pipeline::StreamEvent};
use dashmap::DashMap;
use std::{
collections::HashMap,
sync::{
Arc,
atomic::{AtomicU64, Ordering},
},
time::{Duration, Instant, SystemTime},
};
use uuid::Uuid;
use super::{
BackgroundOperationStats, CheckpointManager, ExactlyOnceProcessor, FailureDetector,
HealthStatus, IdempotencyKeyStrategy, LogEntry, OperationType, RecoveryManager, RecoveryResult,
RecoveryStatistics, SnapshotMetadata, StateBackend, StateBackendConfig, StateBackendStatistics,
StateCoordinator, StateHandle, StateManagementConfig, StateMetrics, StateOperation,
StateSnapshot, StreamPosition, TransactionLog, TransactionLogConfig,
};
/// Stream state manager with fault tolerance
#[derive(Debug)]
pub struct StreamStateManager {
/// State backends
state_backends: Arc<DashMap<String, Arc<dyn StateBackend>>>,
/// Checkpoint manager
checkpoint_manager: Arc<CheckpointManager>,
/// Recovery manager
recovery_manager: Arc<RecoveryManager>,
/// Exactly-once processor
exactly_once_processor: Arc<ExactlyOnceProcessor>,
/// State coordinator (for distributed setups)
state_coordinator: Arc<StateCoordinator>,
/// Failure detector
failure_detector: Arc<FailureDetector>,
/// State metrics
state_metrics: Arc<StateMetrics>,
/// Configuration
config: StateManagementConfig,
/// Active state handles
active_states: Arc<DashMap<String, StateHandle>>,
/// Transaction log
transaction_log: Arc<TransactionLog>,
}
/// State store result
#[derive(Debug, Clone)]
pub struct StateStoreResult {
/// Operation success
pub success: bool,
/// Whether duplicate was detected
pub duplicate_detected: bool,
/// Operation identifier
pub operation_id: String,
/// Operation latency
pub latency: Duration,
}
/// State failure types
#[derive(Debug, Clone)]
pub enum StateFailureType {
/// Backend failure
BackendFailure { backend_name: String },
/// Checkpoint failure
CheckpointFailure { checkpoint_id: String },
/// Recovery failure
RecoveryFailure { recovery_id: String },
}
/// Comprehensive state statistics
#[derive(Debug, Clone)]
pub struct StateStatistics {
/// Backend statistics
pub backend_statistics: StateBackendStatistics,
/// Checkpoint statistics
pub checkpoint_statistics: CheckpointStatistics,
/// Recovery statistics
pub recovery_statistics: RecoveryStatistics,
/// Exactly-once processing statistics
pub exactly_once_statistics: ExactlyOnceStatistics,
/// Overall health status
pub overall_health: HealthStatus,
}
/// Checkpoint statistics
#[derive(Debug, Clone, Default)]
pub struct CheckpointStatistics {
pub total_checkpoints: u64,
pub successful_checkpoints: u64,
pub failed_checkpoints: u64,
pub avg_checkpoint_time_ms: u64,
}
/// Exactly-once processing statistics
#[derive(Debug, Clone, Default)]
pub struct ExactlyOnceStatistics {
pub processed_events: u64,
pub duplicate_events: u64,
pub deduplication_rate: f64,
}
// Mock implementations for compilation (full implementations would be separate)
#[derive(Debug)]
struct MemoryStateBackend {
data: Arc<DashMap<String, Vec<u8>>>,
#[allow(dead_code)]
max_memory: usize,
}
#[derive(Debug)]
struct RocksDbStateBackend {
#[allow(dead_code)]
path: std::path::PathBuf,
data: Arc<DashMap<String, Vec<u8>>>,
snapshots: Arc<DashMap<String, Vec<(String, Vec<u8>)>>>,
}
impl RocksDbStateBackend {
async fn new(path: std::path::PathBuf) -> StreamingResult<Self> {
Ok(Self {
path,
data: Arc::new(DashMap::new()),
snapshots: Arc::new(DashMap::new()),
})
}
}
impl MemoryStateBackend {
async fn new(max_memory: usize) -> StreamingResult<Self> {
Ok(Self {
data: Arc::new(DashMap::new()),
max_memory,
})
}
}
#[async_trait::async_trait]
impl StateBackend for RocksDbStateBackend {
async fn store(&self, key: &str, value: &[u8]) -> StreamingResult<()> {
self.data.insert(key.to_string(), value.to_vec());
Ok(())
}
async fn retrieve(&self, key: &str) -> StreamingResult<Option<Vec<u8>>> {
Ok(self.data.get(key).map(|entry| entry.value().clone()))
}
async fn delete(&self, key: &str) -> StreamingResult<()> {
self.data.remove(key);
Ok(())
}
async fn list_keys(&self, prefix: &str) -> StreamingResult<Vec<String>> {
Ok(self
.data
.iter()
.filter(|entry| entry.key().starts_with(prefix))
.map(|entry| entry.key().clone())
.collect())
}
async fn batch_operation(&self, operations: Vec<StateOperation>) -> StreamingResult<()> {
for op in operations {
match op {
StateOperation::Store { key, value } => self.store(&key, &value).await?,
StateOperation::Delete { key } => self.delete(&key).await?,
StateOperation::ConditionalStore {
key,
expected_value,
new_value,
} => {
// Check if current value matches expected value
let current = self.retrieve(&key).await?;
let should_store = match (current, expected_value) {
(None, None) => true,
(Some(cur), Some(exp)) if cur == exp => true,
_ => false,
};
if should_store {
self.store(&key, &new_value).await?;
}
}
}
}
Ok(())
}
async fn create_snapshot(&self, snapshot_id: &str) -> StreamingResult<SnapshotMetadata> {
let snapshot_data: Vec<(String, Vec<u8>)> = self
.data
.iter()
.map(|entry| (entry.key().clone(), entry.value().clone()))
.collect();
let size_bytes: u64 = snapshot_data
.iter()
.map(|(k, v)| (k.len() + v.len()) as u64)
.sum();
let metadata = SnapshotMetadata {
snapshot_id: snapshot_id.to_string(),
created_at: std::time::SystemTime::now(),
size_bytes,
checksum: format!("{size_bytes:x}"), // Simple checksum for now
compression_info: None,
version: 1,
};
// Store snapshot (in real implementation would persist to disk)
self.snapshots
.insert(snapshot_id.to_string(), snapshot_data);
Ok(metadata)
}
async fn restore_snapshot(&self, snapshot_id: &str) -> StreamingResult<()> {
if let Some(snapshot_data) = self.snapshots.get(snapshot_id) {
self.data.clear();
for (key, value) in snapshot_data.value() {
self.data.insert(key.clone(), value.clone());
}
Ok(())
} else {
Err(StreamingError::StateError(format!(
"Snapshot {snapshot_id} not found"
)))
}
}
async fn get_statistics(&self) -> StreamingResult<StateBackendStatistics> {
let total_size: usize = self
.data
.iter()
.map(|entry| entry.key().len() + entry.value().len())
.sum();
Ok(StateBackendStatistics {
total_keys: self.data.len() as u64,
total_size_bytes: total_size as u64,
read_ops_per_sec: 0.0,
write_ops_per_sec: 0.0,
avg_read_latency_micros: 0,
avg_write_latency_micros: 0,
cache_hit_ratio: 0.0,
background_operations: BackgroundOperationStats {
compaction_ops: 0,
gc_ops: 0,
cleanup_ops: 0,
},
})
}
async fn health_check(&self) -> StreamingResult<HealthStatus> {
// Basic health check - ensure we can read/write
let test_key = "__health_check_test__";
let test_value = b"test";
match self.store(test_key, test_value).await {
Ok(()) => {
let retrieved = self.retrieve(test_key).await?;
self.delete(test_key).await?;
if retrieved.is_some() {
Ok(HealthStatus::Healthy)
} else {
Ok(HealthStatus::Degraded)
}
}
Err(_) => Ok(HealthStatus::Unhealthy),
}
}
}
#[async_trait::async_trait]
impl StateBackend for MemoryStateBackend {
async fn store(&self, key: &str, value: &[u8]) -> StreamingResult<()> {
self.data.insert(key.to_string(), value.to_vec());
Ok(())
}
async fn retrieve(&self, key: &str) -> StreamingResult<Option<Vec<u8>>> {
Ok(self.data.get(key).map(|entry| entry.value().clone()))
}
async fn delete(&self, key: &str) -> StreamingResult<()> {
self.data.remove(key);
Ok(())
}
async fn list_keys(&self, prefix: &str) -> StreamingResult<Vec<String>> {
Ok(self
.data
.iter()
.filter(|entry| entry.key().starts_with(prefix))
.map(|entry| entry.key().clone())
.collect())
}
async fn batch_operation(&self, operations: Vec<StateOperation>) -> StreamingResult<()> {
for op in operations {
match op {
StateOperation::Store { key, value } => {
self.store(&key, &value).await?;
}
StateOperation::Delete { key } => {
self.delete(&key).await?;
}
StateOperation::ConditionalStore {
key,
expected_value: _,
new_value,
} => {
self.store(&key, &new_value).await?;
}
}
}
Ok(())
}
async fn create_snapshot(&self, snapshot_id: &str) -> StreamingResult<SnapshotMetadata> {
Ok(SnapshotMetadata {
snapshot_id: snapshot_id.to_string(),
created_at: SystemTime::now(),
size_bytes: 0,
checksum: "mock_checksum".to_string(),
compression_info: None,
version: 1,
})
}
async fn restore_snapshot(&self, _snapshot_id: &str) -> StreamingResult<()> {
Ok(())
}
async fn get_statistics(&self) -> StreamingResult<StateBackendStatistics> {
Ok(StateBackendStatistics {
total_keys: self.data.len() as u64,
total_size_bytes: 0,
read_ops_per_sec: 0.0,
write_ops_per_sec: 0.0,
avg_read_latency_micros: 0,
avg_write_latency_micros: 0,
cache_hit_ratio: 1.0,
background_operations: BackgroundOperationStats {
compaction_ops: 0,
gc_ops: 0,
cleanup_ops: 0,
},
})
}
async fn health_check(&self) -> StreamingResult<HealthStatus> {
Ok(HealthStatus::Healthy)
}
}
// Core implementation
impl StreamStateManager {
/// Create a new stream state manager
pub async fn new(config: StateManagementConfig) -> StreamingResult<Self> {
let state_backends = Arc::new(DashMap::new());
let checkpoint_manager = Arc::new(CheckpointManager::new(config.checkpoint_config.clone()));
let recovery_manager = Arc::new(RecoveryManager::new(config.recovery_config.clone()));
let exactly_once_processor = Arc::new(ExactlyOnceProcessor::new(
config.exactly_once_config.clone(),
));
let state_coordinator = Arc::new(StateCoordinator::new());
let failure_detector = Arc::new(FailureDetector::new(
config.failure_detection_config.heartbeat_interval,
));
let state_metrics = Arc::new(StateMetrics::new());
let active_states = Arc::new(DashMap::new());
let transaction_log = Arc::new(TransactionLog::new(TransactionLogConfig {
log_path: config
.checkpoint_config
.checkpoint_dir
.join("transaction.log"),
max_log_size_bytes: 100 * 1024 * 1024, // 100MB
sync_frequency_ms: 1000,
compression_enabled: true,
}));
// Initialize backend
let backend = Self::create_backend(&config.backend_config).await?;
state_backends.insert("default".to_string(), backend);
Ok(Self {
state_backends,
checkpoint_manager,
recovery_manager,
exactly_once_processor,
state_coordinator,
failure_detector,
state_metrics,
config,
active_states,
transaction_log,
})
}
/// Store state with exactly-once semantics
pub async fn store_state(
&self,
key: &str,
value: &[u8],
stream_id: &str,
) -> StreamingResult<StateStoreResult> {
let start_time = Instant::now();
// Check for duplicates if exactly-once is enabled
if self.config.exactly_once_config.enabled {
let idempotency_key = self.generate_idempotency_key(key, value, stream_id).await?;
if self
.exactly_once_processor
.is_already_processed(&idempotency_key)
.await?
{
return Ok(StateStoreResult {
success: true,
duplicate_detected: true,
operation_id: idempotency_key,
latency: start_time.elapsed(),
});
}
}
// Get backend
let backend = self.get_backend("default").await?;
// Store state
backend.store(key, value).await?;
// Update transaction log
self.transaction_log
.append(LogEntry {
entry_id: self.generate_log_entry_id(),
timestamp: SystemTime::now(),
operation_type: OperationType::StateUpdate {
key: key.to_string(),
},
data: value.to_vec(),
checksum: self.calculate_checksum(value),
})
.await?;
// Record metrics
let latency = start_time.elapsed();
self.state_metrics
.record_operation_latency("store", latency.as_millis() as u64);
// Mark as processed for exactly-once
if self.config.exactly_once_config.enabled {
let idempotency_key = self.generate_idempotency_key(key, value, stream_id).await?;
self.exactly_once_processor
.mark_as_processed(&idempotency_key)
.await?;
}
Ok(StateStoreResult {
success: true,
duplicate_detected: false,
operation_id: Uuid::new_v4().to_string(),
latency,
})
}
/// Retrieve state
pub async fn retrieve_state(&self, key: &str) -> StreamingResult<Option<Vec<u8>>> {
let start_time = Instant::now();
// Get backend
let backend = self.get_backend("default").await?;
// Retrieve state
let result = backend.retrieve(key).await?;
// Record metrics
let latency = start_time.elapsed();
self.state_metrics
.record_operation_latency("retrieve", latency.as_millis() as u64);
Ok(result)
}
/// Create checkpoint
pub async fn create_checkpoint(&self, stream_id: &str) -> StreamingResult<String> {
// Get current state for the stream
let state_data = if let Some(_state) = self.active_states.get(stream_id) {
// Serialize the state (placeholder implementation)
vec![1, 2, 3, 4] // Would serialize actual state in production
} else {
vec![]
};
self.checkpoint_manager
.create_checkpoint(stream_id, state_data)
.await
}
/// Recover from checkpoint
pub async fn recover_from_checkpoint(
&self,
checkpoint_id: &str,
) -> StreamingResult<RecoveryResult> {
self.recovery_manager
.recover_from_checkpoint(checkpoint_id)
.await?;
Ok(RecoveryResult {
recovery_id: checkpoint_id.to_string(),
recovered_state: StateSnapshot {
snapshot_id: checkpoint_id.to_string(),
entries: HashMap::new(),
total_size_bytes: 0,
checksum: String::new(),
},
stream_position: StreamPosition {
stream_id: "default".to_string(),
offset: 0,
partition: Some(0),
timestamp: std::time::SystemTime::now(),
},
statistics: RecoveryStatistics::default(),
validation_results: vec![],
})
}
/// Process events with exactly-once guarantees
pub async fn process_exactly_once(
&self,
events: Vec<StreamEvent>,
) -> StreamingResult<Vec<StreamEvent>> {
if !self.config.exactly_once_config.enabled {
return Ok(events);
}
let mut processed_events = Vec::new();
for event in events {
let idempotency_key = self.generate_event_idempotency_key(&event).await?;
if !self
.exactly_once_processor
.is_already_processed(&idempotency_key)
.await?
{
processed_events.push(event);
self.exactly_once_processor
.mark_as_processed(&idempotency_key)
.await?;
}
}
Ok(processed_events)
}
/// Handle state failures with automatic recovery
pub async fn handle_state_failure(
&self,
failure_type: StateFailureType,
) -> StreamingResult<()> {
match failure_type {
StateFailureType::BackendFailure { backend_name } => {
// Attempt to recover backend
self.recover_backend(&backend_name).await?;
}
StateFailureType::CheckpointFailure { checkpoint_id } => {
// Retry checkpoint or fall back to previous checkpoint
self.retry_checkpoint(&checkpoint_id).await?;
}
StateFailureType::RecoveryFailure { recovery_id } => {
// Attempt alternative recovery strategy
self.fallback_recovery(&recovery_id).await?;
}
}
Ok(())
}
/// Get comprehensive state statistics
pub async fn get_state_statistics(&self) -> StreamingResult<StateStatistics> {
let backend_stats = self.get_backend("default").await?.get_statistics().await?;
let checkpoint_stats = self.checkpoint_manager.get_statistics();
let recovery_stats = self.recovery_manager.get_statistics();
Ok(StateStatistics {
backend_statistics: backend_stats,
checkpoint_statistics: CheckpointStatistics {
total_checkpoints: checkpoint_stats.total_checkpoints,
successful_checkpoints: checkpoint_stats.total_checkpoints, // Assuming all are successful for now
failed_checkpoints: 0,
avg_checkpoint_time_ms: 0,
},
recovery_statistics: recovery_stats,
exactly_once_statistics: {
let stats = self.exactly_once_processor.get_statistics();
ExactlyOnceStatistics {
processed_events: stats.events_processed as u64,
duplicate_events: stats.duplicates_detected as u64,
deduplication_rate: if stats.events_processed > 0 {
stats.duplicates_detected as f64 / stats.events_processed as f64
} else {
0.0
},
}
},
overall_health: self.calculate_overall_health().await?,
})
}
// Helper methods
async fn create_backend(config: &StateBackendConfig) -> StreamingResult<Arc<dyn StateBackend>> {
match config {
StateBackendConfig::Memory {
max_memory_bytes,
persist_to_disk: _,
persistence_path: _,
} => Ok(Arc::new(MemoryStateBackend::new(*max_memory_bytes).await?)),
StateBackendConfig::RocksDb {
path,
rocksdb_config: _,
} => Ok(Arc::new(RocksDbStateBackend::new(path.clone()).await?)),
_ => Err(StreamingError::Config(
"Unsupported backend type".to_string(),
)),
}
}
async fn get_backend(&self, name: &str) -> StreamingResult<Arc<dyn StateBackend>> {
self.state_backends
.get(name)
.map(|entry| Arc::clone(entry.value()))
.ok_or_else(|| StreamingError::Config(format!("Backend '{name}' not found")))
}
async fn generate_idempotency_key(
&self,
key: &str,
value: &[u8],
stream_id: &str,
) -> StreamingResult<String> {
match &self
.config
.exactly_once_config
.idempotency_config
.key_generation_strategy
{
IdempotencyKeyStrategy::ContentHash => {
let mut hasher = std::collections::hash_map::DefaultHasher::new();
std::hash::Hasher::write(&mut hasher, key.as_bytes());
std::hash::Hasher::write(&mut hasher, value);
std::hash::Hasher::write(&mut hasher, stream_id.as_bytes());
Ok(format!("{:x}", std::hash::Hasher::finish(&hasher)))
}
_ => Ok(format!("{}:{}:{}", stream_id, key, Uuid::new_v4())),
}
}
async fn generate_event_idempotency_key(&self, event: &StreamEvent) -> StreamingResult<String> {
Ok(event.event_id.to_string())
}
fn generate_log_entry_id(&self) -> u64 {
static COUNTER: AtomicU64 = AtomicU64::new(1);
COUNTER.fetch_add(1, Ordering::Relaxed)
}
fn calculate_checksum(&self, data: &[u8]) -> u32 {
use std::hash::{Hash, Hasher};
let mut hasher = std::collections::hash_map::DefaultHasher::new();
data.hash(&mut hasher);
hasher.finish() as u32
}
async fn calculate_overall_health(&self) -> StreamingResult<HealthStatus> {
// Simplified health calculation
Ok(HealthStatus::Healthy)
}
async fn recover_backend(&self, _backend_name: &str) -> StreamingResult<()> {
// Implementation for backend recovery
Ok(())
}
async fn retry_checkpoint(&self, _checkpoint_id: &str) -> StreamingResult<()> {
// Implementation for checkpoint retry
Ok(())
}
async fn fallback_recovery(&self, _recovery_id: &str) -> StreamingResult<()> {
// Implementation for fallback recovery
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::time::Duration;
#[tokio::test]
async fn test_state_manager_creation() {
let config = StateManagementConfig::default();
let manager = StreamStateManager::new(config).await;
assert!(manager.is_ok());
}
#[tokio::test]
async fn test_state_store_and_retrieve() {
let config = StateManagementConfig::default();
let manager = StreamStateManager::new(config).await.unwrap();
let key = "test_key";
let value = b"test_value";
let stream_id = "test_stream";
// Store state
let store_result = manager.store_state(key, value, stream_id).await;
assert!(store_result.is_ok());
// Retrieve state
let retrieved = manager.retrieve_state(key).await;
assert!(retrieved.is_ok());
assert_eq!(retrieved.unwrap().unwrap(), value);
}
#[tokio::test]
async fn test_exactly_once_processing() {
let mut config = StateManagementConfig::default();
config.exactly_once_config.enabled = true;
let manager = StreamStateManager::new(config).await.unwrap();
let event_id = Uuid::new_v4();
let event_time = SystemTime::now();
let events = vec![StreamEvent {
event_id,
event_time,
processing_time: SystemTime::now(),
stream_id: "test_stream".to_string(),
data: crate::realtime_pipeline::EventData::Text {
content: "test".to_string(),
tokens: None,
},
metadata: HashMap::new(),
watermark: None,
id: event_id.to_string(),
timestamp: event_time,
partition_key: Some("test_partition".to_string()),
sequence_number: 0,
}];
let result = manager.process_exactly_once(events).await;
assert!(result.is_ok());
}
#[tokio::test]
async fn test_sub_millisecond_state_operations() {
let config = StateManagementConfig::default();
let manager = StreamStateManager::new(config).await.unwrap();
let start = Instant::now();
let _result = manager.retrieve_state("non_existent_key").await.unwrap();
let latency = start.elapsed();
assert!(latency < Duration::from_millis(1));
}
}
@@ -0,0 +1,99 @@
//! # Stream State Management and Fault Tolerance
//!
//! Production-grade state management with checkpointing, recovery, exactly-once
//! processing guarantees, and distributed state coordination for streaming workloads.
// ============================================================================
// Submodule Declarations
// ============================================================================
pub mod backend;
pub mod backends;
pub mod checkpoint;
pub mod config;
pub mod exactly_once;
pub mod manager;
pub mod recovery;
pub mod state_handle;
// ============================================================================
// External Imports
// ============================================================================
use crate::types_final::FailureDetector;
use crate::types_processing::StateCoordinator;
use crate::StreamingResult;
// ============================================================================
// Re-exports from config module
// ============================================================================
pub use config::{
BloomFilterConfig, CheckpointCompressionConfig, CheckpointConfig, CheckpointRetentionPolicy,
CompressionAlgorithm, ConsensusAlgorithm, CoordinationConfig, DeduplicationConfig,
ExactlyOnceConfig, FailureDetectionConfig, IdempotencyConfig, IdempotencyKeyStrategy,
IoOptimizationConfig, IsolationLevel, LeaderElectionConfig, MembershipConfig,
MemoryPressureThresholds, RecoveryConfig, RecoveryStrategy, RecoveryValidationConfig,
RedisStateConfig, RocksDbConfig, SledConfig, StateBackendConfig, StateManagementConfig,
StatePerformanceConfig, StoreCondition, TransactionConfig,
};
// ============================================================================
// Re-exports from backend module
// ============================================================================
pub use backend::{
BackgroundOperationStats, CompressionInfo, HealthStatus, SnapshotMetadata, StateBackend,
StateBackendStatistics, StateOperation,
};
// ============================================================================
// Re-exports from checkpoint module
// ============================================================================
pub use checkpoint::{
CheckpointInfo, CheckpointManager, CheckpointMetadata, CheckpointProgress, CheckpointStats,
CheckpointStatus, StateEntry, StateSnapshot, StreamPosition,
};
// ============================================================================
// Re-exports from recovery module
// ============================================================================
pub use recovery::{
RecoveryAttempt, RecoveryExecutor, RecoveryManager, RecoveryResult, RecoveryStatistics,
RecoveryStatus, ValidationResult, ValidationType,
};
// ============================================================================
// Re-exports from exactly_once module
// ============================================================================
pub use exactly_once::{
DeduplicationEngine, EventSignature, ExactlyOnceProcessor, ExactlyOnceStats,
IdempotencyTracker, ProcessedEventInfo,
};
// ============================================================================
// Re-exports from state_handle module
// ============================================================================
pub use state_handle::{
LatencyHistogram, LogEntry, OperationType, SizeMetrics, StateHandle, StateMetrics,
TransactionLog, TransactionLogConfig,
};
// ============================================================================
// Re-exports from manager module
// ============================================================================
pub use manager::{
CheckpointStatistics, ExactlyOnceStatistics, StateFailureType, StateStatistics,
StateStoreResult, StreamStateManager,
};
// ============================================================================
// Re-exports from backends module
// ============================================================================
pub use backends::{MemoryStateBackend, RocksDbStateBackend};
@@ -0,0 +1,198 @@
//! Recovery management for stream state fault tolerance.
//!
//! This module provides recovery capabilities including checkpoint-based recovery,
//! recovery strategies, and validation of recovered state.
use dashmap::DashMap;
use std::{
collections::HashMap,
sync::{Arc, atomic::Ordering},
time::SystemTime,
};
use tokio::sync::Mutex;
use super::checkpoint::{StateSnapshot, StreamPosition};
use super::config::{RecoveryConfig, RecoveryStrategy};
use crate::types_processing::{RecoveryCoordinator, RecoveryMetrics};
use crate::{StreamingError, StreamingResult};
/// Recovery manager for stream state
#[derive(Debug)]
pub struct RecoveryManager {
/// Recovery strategies
strategies: Arc<DashMap<RecoveryStrategy, Arc<dyn RecoveryExecutor>>>,
/// Recovery history
recovery_history: Arc<Mutex<Vec<RecoveryAttempt>>>,
/// Recovery coordinator
coordinator: Arc<RecoveryCoordinator>,
/// Configuration
config: RecoveryConfig,
/// Metrics
metrics: Arc<RecoveryMetrics>,
}
impl RecoveryManager {
pub fn new(config: RecoveryConfig) -> Self {
Self {
strategies: Arc::new(DashMap::new()),
recovery_history: Arc::new(Mutex::new(Vec::new())),
coordinator: Arc::new(RecoveryCoordinator::new()),
config,
metrics: Arc::new(RecoveryMetrics::new()),
}
}
pub async fn recover_from_checkpoint(&self, checkpoint_id: &str) -> StreamingResult<()> {
let attempt = RecoveryAttempt {
attempt_id: uuid::Uuid::new_v4().to_string(),
strategy: RecoveryStrategy::FromCheckpoint,
start_time: SystemTime::now(),
end_time: None,
status: RecoveryStatus::Starting,
statistics: RecoveryStatistics::default(),
};
{
let mut history = self.recovery_history.lock().await;
history.push(attempt.clone());
}
// Start recovery process
self.coordinator
.start_recovery(checkpoint_id.to_string())
.await
.map_err(|e| StreamingError::State(format!("Recovery failed: {e}")))?;
// Update metrics
self.metrics
.recoveries_initiated
.fetch_add(1, Ordering::SeqCst);
// Complete recovery
self.coordinator.complete_recovery();
self.metrics
.recoveries_completed
.fetch_add(1, Ordering::SeqCst);
Ok(())
}
pub fn get_statistics(&self) -> RecoveryStatistics {
RecoveryStatistics {
total_recoveries: self.metrics.recoveries_completed.load(Ordering::SeqCst),
successful_recoveries: self.metrics.recoveries_completed.load(Ordering::SeqCst),
failed_recoveries: self.metrics.recoveries_failed.load(Ordering::SeqCst),
average_recovery_time_ms: self.metrics.recovery_time_ms.load(Ordering::SeqCst),
last_recovery_time: None,
}
}
}
/// Recovery attempt
#[derive(Debug, Clone)]
pub struct RecoveryAttempt {
/// Attempt ID
pub attempt_id: String,
/// Recovery strategy used
pub strategy: RecoveryStrategy,
/// Start time
pub start_time: SystemTime,
/// End time
pub end_time: Option<SystemTime>,
/// Status
pub status: RecoveryStatus,
/// Recovery statistics
pub statistics: RecoveryStatistics,
}
/// Recovery status
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum RecoveryStatus {
/// Starting recovery
Starting,
/// Recovery in progress
InProgress,
/// Recovery completed successfully
Completed,
/// Recovery failed
Failed { error: String },
/// Recovery partially completed
PartialSuccess { recovered_count: usize },
}
/// Recovery statistics
#[derive(Debug, Clone, Default)]
pub struct RecoveryStatistics {
/// Total recoveries performed
pub total_recoveries: u64,
/// Successful recoveries
pub successful_recoveries: u64,
/// Failed recoveries
pub failed_recoveries: u64,
/// Average recovery time in milliseconds
pub average_recovery_time_ms: u64,
/// Last recovery timestamp
pub last_recovery_time: Option<SystemTime>,
}
/// Recovery executor trait
#[async_trait::async_trait]
pub trait RecoveryExecutor: Send + Sync + std::fmt::Debug {
/// Execute recovery
async fn execute_recovery(
&self,
checkpoint_id: &str,
target_position: Option<StreamPosition>,
) -> StreamingResult<RecoveryResult>;
/// Validate recovery
async fn validate_recovery(&self, recovery_result: &RecoveryResult) -> StreamingResult<bool>;
/// Cleanup after recovery
async fn cleanup(&self, recovery_id: &str) -> StreamingResult<()>;
}
/// Recovery result
#[derive(Debug, Clone)]
pub struct RecoveryResult {
/// Recovery ID
pub recovery_id: String,
/// Recovered state
pub recovered_state: StateSnapshot,
/// Stream position after recovery
pub stream_position: StreamPosition,
/// Recovery statistics
pub statistics: RecoveryStatistics,
/// Validation results
pub validation_results: Vec<ValidationResult>,
}
/// Validation result
#[derive(Debug, Clone)]
pub struct ValidationResult {
/// Validation type
pub validation_type: ValidationType,
/// Success status
pub success: bool,
/// Error message (if failed)
pub error_message: Option<String>,
/// Validation details
pub details: HashMap<String, String>,
}
/// Validation types
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ValidationType {
/// Checksum validation
Checksum,
/// Data consistency validation
DataConsistency,
/// Schema validation
Schema,
/// Custom validation
Custom(String),
}
@@ -0,0 +1,250 @@
//! State handle, transaction log, and metrics types for state management.
use crate::types_remaining::{LogReader, LogWriter};
use dashmap::DashMap;
use serde::{Deserialize, Serialize};
use std::{
collections::VecDeque,
path::PathBuf,
sync::{
Arc,
atomic::{AtomicU64, AtomicUsize, Ordering},
},
time::SystemTime,
};
use tokio::sync::{Mutex, mpsc};
/// State handle for accessing stream state
#[derive(Debug, Clone)]
pub struct StateHandle {
/// Handle ID
pub handle_id: String,
/// Stream ID
pub stream_id: String,
/// State key
pub state_key: String,
/// Current version
pub version: Arc<AtomicU64>,
/// Last accessed
pub last_accessed: Arc<AtomicU64>,
/// Reference count
pub ref_count: Arc<AtomicUsize>,
}
/// Transaction log for durability
#[derive(Debug)]
pub struct TransactionLog {
/// Log entries
entries: Arc<Mutex<VecDeque<LogEntry>>>,
/// Log writer
writer: Arc<Mutex<LogWriter>>,
/// Log reader
reader: Arc<LogReader>,
/// Configuration
config: TransactionLogConfig,
}
impl TransactionLog {
pub fn new(config: TransactionLogConfig) -> Self {
let (writer, _rx) = LogWriter::new();
// LogReader doesn't have a constructor that pairs with LogWriter
// Creating a dummy receiver for now
let (_tx, rx) = mpsc::unbounded_channel();
Self {
entries: Arc::new(Mutex::new(VecDeque::new())),
writer: Arc::new(Mutex::new(writer)),
reader: Arc::new(LogReader::new(rx)),
config,
}
}
pub async fn append(&self, entry: LogEntry) -> crate::StreamingResult<()> {
let mut entries = self.entries.lock().await;
// Check max log size
let entry_size = entry.data.len() as u64;
let current_size: u64 = entries.iter().map(|e| e.data.len() as u64).sum();
if current_size + entry_size > self.config.max_log_size_bytes {
// Rotate log by removing oldest entries
while !entries.is_empty() && current_size + entry_size > self.config.max_log_size_bytes
{
entries.pop_front();
}
}
entries.push_back(entry);
Ok(())
}
}
/// Transaction log configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TransactionLogConfig {
/// Log file path
pub log_path: PathBuf,
/// Maximum log size
pub max_log_size_bytes: u64,
/// Sync frequency
pub sync_frequency_ms: u64,
/// Compression enabled
pub compression_enabled: bool,
}
/// Log entry
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LogEntry {
/// Entry ID
pub entry_id: u64,
/// Timestamp
pub timestamp: SystemTime,
/// Operation type
pub operation_type: OperationType,
/// Data
pub data: Vec<u8>,
/// Checksum
pub checksum: u32,
}
/// Operation types for transaction log
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum OperationType {
/// State update
StateUpdate { key: String },
/// State delete
StateDelete { key: String },
/// Checkpoint start
CheckpointStart { checkpoint_id: String },
/// Checkpoint complete
CheckpointComplete { checkpoint_id: String },
/// Recovery start
RecoveryStart { recovery_id: String },
/// Recovery complete
RecoveryComplete { recovery_id: String },
}
/// State metrics
#[derive(Debug)]
pub struct StateMetrics {
/// Operation counters
pub operation_counters: Arc<DashMap<String, AtomicU64>>,
/// Latency histograms
pub latency_histograms: Arc<DashMap<String, LatencyHistogram>>,
/// Size metrics
pub size_metrics: Arc<SizeMetrics>,
/// Error counters
pub error_counters: Arc<DashMap<String, AtomicU64>>,
}
impl Default for StateMetrics {
fn default() -> Self {
Self::new()
}
}
impl StateMetrics {
pub fn new() -> Self {
Self {
operation_counters: Arc::new(DashMap::new()),
latency_histograms: Arc::new(DashMap::new()),
size_metrics: Arc::new(SizeMetrics::new()),
error_counters: Arc::new(DashMap::new()),
}
}
pub fn record_operation_latency(&self, operation: &str, latency_us: u64) {
// Record operation count
self.operation_counters
.entry(operation.to_string())
.or_insert_with(|| AtomicU64::new(0))
.fetch_add(1, Ordering::SeqCst);
// Record latency in histogram
self.latency_histograms
.entry(operation.to_string())
.or_default()
.record(latency_us);
}
}
/// Size metrics
#[derive(Debug)]
pub struct SizeMetrics {
/// Total state size
pub total_size_bytes: Arc<AtomicU64>,
/// Key count
pub key_count: Arc<AtomicU64>,
/// Average value size
pub avg_value_size_bytes: Arc<AtomicU64>,
}
impl Default for SizeMetrics {
fn default() -> Self {
Self::new()
}
}
impl SizeMetrics {
pub fn new() -> Self {
Self {
total_size_bytes: Arc::new(AtomicU64::new(0)),
key_count: Arc::new(AtomicU64::new(0)),
avg_value_size_bytes: Arc::new(AtomicU64::new(0)),
}
}
}
/// Latency histogram for performance tracking
#[derive(Debug)]
pub struct LatencyHistogram {
/// Histogram buckets
pub buckets: Vec<AtomicU64>,
/// Bucket boundaries (microseconds)
pub boundaries: Vec<u64>,
/// Total samples
pub total_samples: AtomicU64,
/// Sum of all latencies
pub total_latency_micros: AtomicU64,
}
impl Default for LatencyHistogram {
fn default() -> Self {
Self::new()
}
}
impl LatencyHistogram {
pub fn new() -> Self {
let boundaries = vec![10, 50, 100, 500, 1000, 5000, 10000];
let buckets = boundaries.iter().map(|_| AtomicU64::new(0)).collect();
Self {
buckets,
boundaries,
total_samples: AtomicU64::new(0),
total_latency_micros: AtomicU64::new(0),
}
}
pub fn record(&self, latency_us: u64) {
self.total_samples.fetch_add(1, Ordering::SeqCst);
self.total_latency_micros
.fetch_add(latency_us, Ordering::SeqCst);
// Find the appropriate bucket
for (i, boundary) in self.boundaries.iter().enumerate() {
if latency_us <= *boundary {
self.buckets[i].fetch_add(1, Ordering::SeqCst);
return;
}
}
// If we get here, it's in the overflow bucket
if let Some(last) = self.buckets.last() {
last.fetch_add(1, Ordering::SeqCst);
}
}
}