Skip to main content

cache_rs/config/
lfuda.rs

1//! Configuration for the Least Frequently Used with Dynamic Aging (LFUDA) cache.
2//!
3//! This module provides configuration for LFUDA 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//! # Examples
41//!
42//! ```
43//! use cache_rs::config::LfudaCacheConfig;
44//! use cache_rs::LfudaCache;
45//! use core::num::NonZeroUsize;
46//!
47//! // In-memory cache: 50MB budget for values, ~5KB average size
48//! let config = LfudaCacheConfig {
49//!     capacity: NonZeroUsize::new(10_000).unwrap(),
50//!     initial_age: 0,
51//!     max_size: 50 * 1024 * 1024,  // 50MB
52//! };
53//! let cache: LfudaCache<String, Vec<u8>> = LfudaCache::init(config, None);
54//!
55//! // Small fixed-size value cache with initial age
56//! let config = LfudaCacheConfig {
57//!     capacity: NonZeroUsize::new(1000).unwrap(),
58//!     initial_age: 100,
59//!     max_size: 1024 * 1024,  // 1MB is plenty for small values
60//! };
61//! let cache: LfudaCache<String, i32> = LfudaCache::init(config, None);
62//! ```
63
64use core::fmt;
65use core::num::NonZeroUsize;
66
67/// Configuration for an LFUDA (Least Frequently Used with Dynamic Aging) cache.
68///
69/// LFUDA enhances LFU by using a dynamic aging mechanism that prevents old
70/// frequently-accessed items from permanently blocking new items.
71///
72/// # Fields
73///
74/// - `capacity`: Maximum number of entries the cache can hold. Each entry has
75///   memory overhead (~64-128 bytes) for keys, pointers, and metadata.
76/// - `initial_age`: Initial global age value (default: 0)
77/// - `max_size`: Maximum total size in bytes for cached values. Set this based
78///   on your memory budget, not to `u64::MAX`. See module docs for sizing guidance.
79///
80/// # Sizing Recommendations
81///
82/// Always set meaningful values for both fields:
83///
84/// - **In-memory cache**: `max_size` = memory budget for values;
85///   `capacity` = `max_size` / average_value_size
86/// - **Disk-based cache**: `max_size` = disk space allocation;
87///   `capacity` = `max_size` / average_object_size
88///
89/// # Examples
90///
91/// ```
92/// use cache_rs::config::LfudaCacheConfig;
93/// use cache_rs::LfudaCache;
94/// use core::num::NonZeroUsize;
95///
96/// // 10MB cache for ~1KB average values → ~10,000 entries
97/// let config = LfudaCacheConfig {
98///     capacity: NonZeroUsize::new(10_000).unwrap(),
99///     initial_age: 0,
100///     max_size: 10 * 1024 * 1024,  // 10MB
101/// };
102/// let cache: LfudaCache<String, Vec<u8>> = LfudaCache::init(config, None);
103///
104/// // Small cache for tiny values with initial age
105/// let config = LfudaCacheConfig {
106///     capacity: NonZeroUsize::new(500).unwrap(),
107///     initial_age: 100,
108///     max_size: 64 * 1024,  // 64KB is ample for small values
109/// };
110/// let cache: LfudaCache<&str, i32> = LfudaCache::init(config, None);
111/// ```
112#[derive(Clone, Copy)]
113pub struct LfudaCacheConfig {
114    /// Maximum number of key-value pairs the cache can hold.
115    /// Account for ~64-128 bytes overhead per entry beyond value size.
116    pub capacity: NonZeroUsize,
117    /// Initial global age value
118    pub initial_age: usize,
119    /// Maximum total size in bytes for cached values.
120    /// Set based on your memory/disk budget. Avoid using `u64::MAX`.
121    pub max_size: u64,
122}
123
124impl fmt::Debug for LfudaCacheConfig {
125    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
126        f.debug_struct("LfudaCacheConfig")
127            .field("capacity", &self.capacity)
128            .field("initial_age", &self.initial_age)
129            .field("max_size", &self.max_size)
130            .finish()
131    }
132}
133
134#[cfg(test)]
135mod tests {
136    use super::*;
137
138    #[test]
139    fn test_lfuda_config_creation() {
140        // 10MB cache with ~10KB average values
141        let config = LfudaCacheConfig {
142            capacity: NonZeroUsize::new(1000).unwrap(),
143            initial_age: 0,
144            max_size: 10 * 1024 * 1024,
145        };
146        assert_eq!(config.capacity.get(), 1000);
147        assert_eq!(config.initial_age, 0);
148        assert_eq!(config.max_size, 10 * 1024 * 1024);
149    }
150
151    #[test]
152    fn test_lfuda_config_with_initial_age() {
153        // 1MB cache with initial age
154        let config = LfudaCacheConfig {
155            capacity: NonZeroUsize::new(500).unwrap(),
156            initial_age: 10,
157            max_size: 1024 * 1024,
158        };
159        assert_eq!(config.capacity.get(), 500);
160        assert_eq!(config.initial_age, 10);
161        assert_eq!(config.max_size, 1024 * 1024);
162    }
163}