Skip to main content

cache_rs/config/
slru.rs

1//! Configuration for the Segmented Least Recently Used (SLRU) cache.
2//!
3//! This module provides configuration for SLRU caches.
4//!
5//! # Sizing Guidelines
6//!
7//! ## Understanding `max_size` and `capacity`
8//!
9//! - **`max_size`**: The maximum total size in bytes for cached *values*. This should reflect
10//!   your memory budget for the cache data itself.
11//! - **`capacity`**: The maximum number of entries. Each entry has memory overhead beyond
12//!   the value size (approximately 64-128 bytes per entry for keys, pointers, and metadata).
13//!
14//! ## For In-Memory Caches
15//!
16//! Set `max_size` to the amount of memory you want to allocate for cached values:
17//!
18//! ```text
19//! Total Memory ≈ max_size + (capacity × overhead_per_entry)
20//! overhead_per_entry ≈ 64-128 bytes (keys, pointers, metadata)
21//! ```
22//!
23//! **Example**: For a 100MB cache with ~10KB average values:
24//! - `max_size = 100 * 1024 * 1024` (100MB for values)
25//! - `capacity = 10_000` entries
26//! - Overhead ≈ 10,000 × 100 bytes = ~1MB additional
27//!
28//! ## For Disk-Based or External Caches
29//!
30//! When caching references to external storage, size based on your target cache size:
31//!
32//! ```text
33//! capacity = target_cache_size / average_object_size
34//! ```
35//!
36//! **Example**: For a 1GB disk cache with 50KB average objects:
37//! - `max_size = 1024 * 1024 * 1024` (1GB)
38//! - `capacity = 1GB / 50KB ≈ 20,000` entries
39//!
40//! ## SLRU-Specific Considerations
41//!
42//! SLRU divides the cache into **probationary** and **protected** segments.
43//! A common ratio is 20% protected (hot items) and 80% probationary (new items).
44//!
45//! # Examples
46//!
47//! ```
48//! use cache_rs::config::SlruCacheConfig;
49//! use cache_rs::SlruCache;
50//! use core::num::NonZeroUsize;
51//!
52//! // In-memory cache: 50MB budget, 20% protected segment
53//! let config = SlruCacheConfig {
54//!     capacity: NonZeroUsize::new(10_000).unwrap(),
55//!     protected_capacity: NonZeroUsize::new(2_000).unwrap(),  // 20%
56//!     max_size: 50 * 1024 * 1024,  // 50MB
57//! };
58//! let cache: SlruCache<String, Vec<u8>> = SlruCache::init(config, None);
59//!
60//! // Small cache for session data
61//! let config = SlruCacheConfig {
62//!     capacity: NonZeroUsize::new(1000).unwrap(),
63//!     protected_capacity: NonZeroUsize::new(200).unwrap(),
64//!     max_size: 10 * 1024 * 1024,  // 10MB
65//! };
66//! let cache: SlruCache<String, i32> = SlruCache::init(config, None);
67//! ```
68
69use core::fmt;
70use core::num::NonZeroUsize;
71
72/// Configuration for an SLRU (Segmented LRU) cache.
73///
74/// SLRU divides the cache into two segments: a probationary segment for new entries
75/// and a protected segment for frequently accessed entries.
76///
77/// # Fields
78///
79/// - `capacity`: Total number of entries the cache can hold. Each entry has
80///   memory overhead (~64-128 bytes) for keys, pointers, and metadata.
81/// - `protected_capacity`: Size of the protected segment (must be < capacity).
82///   Typically 20% of total capacity for hot items.
83/// - `max_size`: Maximum total size in bytes for cached values. Set this based
84///   on your memory budget, not to `u64::MAX`. See module docs for sizing guidance.
85///
86/// # Sizing Recommendations
87///
88/// Always set meaningful values for both fields:
89///
90/// - **In-memory cache**: `max_size` = memory budget for values;
91///   `capacity` = `max_size` / average_value_size
92/// - **Disk-based cache**: `max_size` = disk space allocation;
93///   `capacity` = `max_size` / average_object_size
94///
95/// # Examples
96///
97/// ```
98/// use cache_rs::config::SlruCacheConfig;
99/// use cache_rs::SlruCache;
100/// use core::num::NonZeroUsize;
101///
102/// // 10MB cache for ~1KB values, 20% protected
103/// let config = SlruCacheConfig {
104///     capacity: NonZeroUsize::new(10_000).unwrap(),
105///     protected_capacity: NonZeroUsize::new(2_000).unwrap(),
106///     max_size: 10 * 1024 * 1024,  // 10MB
107/// };
108/// let cache: SlruCache<String, Vec<u8>> = SlruCache::init(config, None);
109///
110/// // Small cache for config values, 20% protected
111/// let config = SlruCacheConfig {
112///     capacity: NonZeroUsize::new(500).unwrap(),
113///     protected_capacity: NonZeroUsize::new(100).unwrap(),
114///     max_size: 64 * 1024,  // 64KB is ample for small values
115/// };
116/// let cache: SlruCache<&str, i32> = SlruCache::init(config, None);
117/// ```
118#[derive(Clone, Copy)]
119pub struct SlruCacheConfig {
120    /// Total capacity of the cache (protected + probationary).
121    /// Account for ~64-128 bytes overhead per entry beyond value size.
122    pub capacity: NonZeroUsize,
123    /// Maximum size for the protected segment (must be < capacity).
124    /// Typically 20% of capacity for frequently accessed "hot" items.
125    pub protected_capacity: NonZeroUsize,
126    /// Maximum total size in bytes for cached values.
127    /// Set based on your memory/disk budget. Avoid using `u64::MAX`.
128    pub max_size: u64,
129}
130
131impl fmt::Debug for SlruCacheConfig {
132    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
133        f.debug_struct("SlruCacheConfig")
134            .field("capacity", &self.capacity)
135            .field("protected_capacity", &self.protected_capacity)
136            .field("max_size", &self.max_size)
137            .finish()
138    }
139}
140
141#[cfg(test)]
142mod tests {
143    use super::*;
144
145    #[test]
146    fn test_slru_config_creation() {
147        // 10MB cache with 20% protected segment
148        let config = SlruCacheConfig {
149            capacity: NonZeroUsize::new(1000).unwrap(),
150            protected_capacity: NonZeroUsize::new(200).unwrap(),
151            max_size: 10 * 1024 * 1024,
152        };
153        assert_eq!(config.capacity.get(), 1000);
154        assert_eq!(config.protected_capacity.get(), 200);
155        assert_eq!(config.max_size, 10 * 1024 * 1024);
156    }
157
158    #[test]
159    fn test_slru_config_with_size_limit() {
160        // 1MB cache with 20% protected segment
161        let config = SlruCacheConfig {
162            capacity: NonZeroUsize::new(1000).unwrap(),
163            protected_capacity: NonZeroUsize::new(200).unwrap(),
164            max_size: 1024 * 1024,
165        };
166        assert_eq!(config.capacity.get(), 1000);
167        assert_eq!(config.protected_capacity.get(), 200);
168        assert_eq!(config.max_size, 1024 * 1024);
169    }
170}