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