pub mod cache;
pub mod config;
pub mod error;
pub mod types;
pub mod logging;
pub mod streaming_protocol;
mod compression;
mod l1_cache;
#[cfg(feature = "melange-storage")]
mod l2_cache;
#[cfg(feature = "melange-storage")]
mod melange_adapter;
mod ttl;
pub use cache::{RatMemCache, RatMemCacheBuilder, CacheOptions};
pub use error::{CacheError, CacheResult};
pub use types::{CacheValue, EvictionStrategy, CacheLayer, CacheOperation};
pub use config::{
CacheConfig, CacheConfigBuilder,
L1Config, TtlConfig,
PerformanceConfig, LoggingConfig
};
#[cfg(feature = "melange-storage")]
pub use config::{L2Config, CacheWarmupStrategy};
#[cfg(feature = "melange-storage")]
pub use melange_adapter::{MelangeAdapter, MelangeConfig, CompressionAlgorithm, BatchOperation};
pub use l1_cache::L1CacheStats;
#[cfg(feature = "melange-storage")]
pub use l2_cache::L2CacheStats;
pub use ttl::TtlStats;
pub const VERSION: &str = env!("CARGO_PKG_VERSION");
pub const NAME: &str = env!("CARGO_PKG_NAME");
pub const DESCRIPTION: &str = env!("CARGO_PKG_DESCRIPTION");
pub fn info() -> String {
format!("{} v{} - {}", NAME, VERSION, DESCRIPTION)
}
#[cfg(all(test, feature = "melange-storage"))]
mod tests {
use super::*;
use bytes::Bytes;
use tokio::time::{sleep, Duration};
use tempfile::TempDir;
#[tokio::test]
async fn test_library_info() {
let info = info();
assert!(info.contains("rat_memcache"));
assert!(info.contains(VERSION));
}
#[tokio::test]
async fn test_basic_usage() {
let temp_dir = TempDir::new().unwrap();
let cache = RatMemCacheBuilder::new()
.l1_config(L1Config {
max_memory: 1024 * 1024 * 1024, max_entries: 100_000,
eviction_strategy: EvictionStrategy::Lru,
})
.l2_config(L2Config {
enable_l2_cache: true,
data_dir: Some(temp_dir.path().to_path_buf()),
max_disk_size: 10 * 1024 * 1024,
write_buffer_size: 1024 * 1024,
max_write_buffer_number: 3,
block_cache_size: 512 * 1024,
enable_lz4: true,
compression_threshold: 128,
compression_max_threshold: 1024 * 1024,
compression_level: 6,
background_threads: 2,
clear_on_startup: false,
cache_size_mb: 256,
max_file_size_mb: 512,
smart_flush_enabled: true,
smart_flush_base_interval_ms: 100,
smart_flush_min_interval_ms: 20,
smart_flush_max_interval_ms: 500,
smart_flush_write_rate_threshold: 10000,
smart_flush_accumulated_bytes_threshold: 4 * 1024 * 1024,
cache_warmup_strategy: crate::config::CacheWarmupStrategy::Recent,
zstd_compression_level: None,
l2_write_strategy: "write_through".to_string(),
l2_write_threshold: 1024,
l2_write_ttl_threshold: 300,
})
.ttl_config(TtlConfig {
expire_seconds: Some(60),
cleanup_interval: 60,
max_cleanup_entries: 100,
lazy_expiration: true,
active_expiration: false,
})
.performance_config(PerformanceConfig {
worker_threads: 4,
enable_concurrency: true,
read_write_separation: true,
batch_size: 100,
enable_warmup: false,
large_value_threshold: 10240, })
.logging_config(LoggingConfig {
level: "debug".to_string(),
enable_colors: false,
show_timestamp: false,
enable_performance_logs: true,
enable_audit_logs: false,
enable_cache_logs: true,
enable_logging: true,
enable_async: false,
batch_size: 2048,
batch_interval_ms: 25,
buffer_size: 16384,
})
.build()
.await
.unwrap();
let key = "test_key".to_string();
let value = Bytes::from("test_value");
cache.set(key.clone(), value.clone()).await.unwrap();
let retrieved = cache.get(&key).await.unwrap();
assert!(retrieved.is_some());
assert_eq!(retrieved.unwrap(), value);
cache.shutdown().await.unwrap();
}
#[tokio::test]
async fn test_cache_options() {
let temp_dir = TempDir::new().unwrap();
let cache = RatMemCacheBuilder::new()
.l1_config(L1Config {
max_memory: 1024 * 1024 * 1024, max_entries: 100_000,
eviction_strategy: EvictionStrategy::Lru,
})
.l2_config(L2Config {
enable_l2_cache: true,
data_dir: Some(temp_dir.path().to_path_buf()),
max_disk_size: 10 * 1024 * 1024,
write_buffer_size: 1024 * 1024,
max_write_buffer_number: 3,
block_cache_size: 512 * 1024,
enable_lz4: true,
compression_threshold: 128,
compression_max_threshold: 1024 * 1024,
compression_level: 6,
background_threads: 2,
clear_on_startup: false,
cache_size_mb: 256,
max_file_size_mb: 512,
smart_flush_enabled: true,
smart_flush_base_interval_ms: 100,
smart_flush_min_interval_ms: 20,
smart_flush_max_interval_ms: 500,
smart_flush_write_rate_threshold: 10000,
smart_flush_accumulated_bytes_threshold: 4 * 1024 * 1024,
cache_warmup_strategy: crate::config::CacheWarmupStrategy::Recent,
zstd_compression_level: None,
l2_write_strategy: "write_through".to_string(),
l2_write_threshold: 1024,
l2_write_ttl_threshold: 300,
})
.ttl_config(TtlConfig {
expire_seconds: Some(60),
cleanup_interval: 60,
max_cleanup_entries: 100,
lazy_expiration: true,
active_expiration: false,
})
.performance_config(PerformanceConfig {
worker_threads: 4,
enable_concurrency: true,
read_write_separation: true,
batch_size: 100,
enable_warmup: false,
large_value_threshold: 10240, })
.logging_config(LoggingConfig {
level: "debug".to_string(),
enable_colors: false,
show_timestamp: false,
enable_performance_logs: true,
enable_audit_logs: false,
enable_cache_logs: true,
enable_logging: true,
enable_async: false,
batch_size: 2048,
batch_interval_ms: 25,
buffer_size: 16384,
})
.build()
.await
.unwrap();
let key = "options_key".to_string();
let value = Bytes::from("options_value");
let options = CacheOptions {
ttl_seconds: Some(300),
force_l2: true,
skip_l1: false,
enable_compression: Some(true),
};
cache.set_with_options(key.clone(), value.clone(), &options).await.unwrap();
let retrieved = cache.get(&key).await.unwrap();
assert!(retrieved.is_some());
assert_eq!(retrieved.unwrap(), value);
cache.shutdown().await.unwrap();
}
#[tokio::test]
async fn test_error_handling() {
let temp_dir = TempDir::new().unwrap();
let cache = RatMemCacheBuilder::new()
.l1_config(L1Config {
max_memory: 1024 * 1024 * 1024, max_entries: 100_000,
eviction_strategy: EvictionStrategy::Lru,
})
.l2_config(L2Config {
enable_l2_cache: true,
data_dir: Some(temp_dir.path().to_path_buf()),
max_disk_size: 10 * 1024 * 1024,
write_buffer_size: 1024 * 1024,
max_write_buffer_number: 3,
block_cache_size: 512 * 1024,
enable_lz4: true,
compression_threshold: 128,
compression_max_threshold: 1024 * 1024,
compression_level: 6,
background_threads: 2,
clear_on_startup: false,
cache_size_mb: 256,
max_file_size_mb: 512,
smart_flush_enabled: true,
smart_flush_base_interval_ms: 100,
smart_flush_min_interval_ms: 20,
smart_flush_max_interval_ms: 500,
smart_flush_write_rate_threshold: 10000,
smart_flush_accumulated_bytes_threshold: 4 * 1024 * 1024,
cache_warmup_strategy: crate::config::CacheWarmupStrategy::Recent,
zstd_compression_level: None,
l2_write_strategy: "write_through".to_string(),
l2_write_threshold: 1024,
l2_write_ttl_threshold: 300,
})
.ttl_config(TtlConfig {
expire_seconds: Some(60),
cleanup_interval: 60,
max_cleanup_entries: 100,
lazy_expiration: true,
active_expiration: false,
})
.performance_config(PerformanceConfig {
worker_threads: 4,
enable_concurrency: true,
read_write_separation: true,
batch_size: 100,
enable_warmup: false,
large_value_threshold: 10240, })
.logging_config(LoggingConfig {
level: "debug".to_string(),
enable_colors: false,
show_timestamp: false,
enable_performance_logs: true,
enable_audit_logs: false,
enable_cache_logs: true,
enable_logging: true,
enable_async: false,
batch_size: 2048,
batch_interval_ms: 25,
buffer_size: 16384,
})
.build()
.await
.unwrap();
let key = "test_key".to_string();
let value = Bytes::from("test_value");
let result = cache.set_with_ttl(key.clone(), value.clone(), 10000).await;
assert!(result.is_ok(), "设置大TTL值应该成功");
let retrieved = cache.get(&key).await.unwrap();
assert!(retrieved.is_some(), "设置的值应该能够获取到");
assert_eq!(retrieved.unwrap(), value, "获取的值应该与设置的值相同");
let result = cache.set_with_ttl(key.clone(), value.clone(), 0).await;
assert!(result.is_ok(), "设置TTL为0应该成功");
let short_ttl_key = "short_ttl";
cache.set_with_ttl(short_ttl_key.to_string(), value.clone(), 1).await.unwrap();
let retrieved = cache.get(short_ttl_key).await.unwrap();
assert!(retrieved.is_some(), "短TTL键应该能够立即获取");
sleep(Duration::from_millis(1500)).await;
let retrieved = cache.get(short_ttl_key).await.unwrap();
assert!(retrieved.is_none(), "短TTL键应该已经过期");
cache.shutdown().await.unwrap();
}
}