tinylfu_cached/cache/config/
mod.rs

1use std::collections::hash_map::DefaultHasher;
2use std::hash::{Hash, Hasher};
3use std::time::Duration;
4
5use crate::cache::clock::{ClockType, SystemClock};
6use crate::cache::config::weight_calculation::Calculation;
7use crate::cache::errors::Errors;
8use crate::cache::expiration::config::TTLConfig;
9use crate::cache::policy::config::CacheWeightConfig;
10use crate::cache::pool::{BufferSize, PoolSize};
11use crate::cache::types::{IsTimeToLiveSpecified, KeyHash, TotalCapacity, TotalCounters, TotalShards, Weight};
12pub(crate) mod weight_calculation;
13
14/// Defines the function for calculating the hash of the incoming key. This hash is used to put the key in `crate::cache::policy::cache_weight::CacheWeight`.
15/// By default, DefaultHasher is used that uses SipHasher13 as the hash function.
16pub type HashFn<Key> = dyn Fn(&Key) -> KeyHash + Send + Sync;
17
18/// Defines the function for calculating the weight of the incoming key/value pair.
19/// Default is the `perform` function defined in `crate::cache::config::weight_calculation::Calculation`.
20pub type WeightCalculationFn<Key, Value> = dyn Fn(&Key, &Value, IsTimeToLiveSpecified) -> Weight + Send + Sync;
21
22/// Each put, put_or_update, delete results in a command to `crate::cache::command::command_executor::CommandExecutor`.
23/// CommandExecutor reads from an mpsc channel and COMMAND_BUFFER_SIZE defines the size (/buffer) of the command channel that
24/// is used by CommandExecutor.
25const COMMAND_BUFFER_SIZE: usize = 32 * 1024;
26
27/// Pool represents a ring-buffer that is used to buffer the gets for various keys.
28/// Default pool size is 32
29const ACCESS_POOL_SIZE: PoolSize = PoolSize(32);
30
31/// Each buffer inside the Pool is a Vec<KeyHash>.
32/// Default capacity of the buffer is 64
33const ACCESS_BUFFER_SIZE: BufferSize = BufferSize(64);
34
35/// Determines the number of shards to use in the [`dashmap::DashMap`] inside `crate::cache::store::Store`.
36/// Default is 256
37const SHARDS: usize = 256;
38
39/// Determines the frequency at which the `crate::cache::expiration::TTLTicker` runs.
40/// Default is every 5 seconds.
41const TTL_TICK_DURATION: Duration = Duration::from_secs(5);
42
43/// Defines the config parameters for Cached.
44pub struct Config<Key, Value>
45    where Key: Hash + 'static,
46          Value: 'static {
47    pub key_hash_fn: Box<HashFn<Key>>,
48    pub weight_calculation_fn: Box<WeightCalculationFn<Key, Value>>,
49    pub clock: ClockType,
50    pub counters: TotalCounters,
51    pub command_buffer_size: usize,
52    pub total_cache_weight: Weight,
53
54    pub(crate) access_pool_size: PoolSize,
55    pub(crate) access_buffer_size: BufferSize,
56    pub(crate) capacity: TotalCapacity,
57    pub(crate) shards: TotalShards,
58
59    ttl_tick_duration: Duration,
60}
61
62impl<Key, Value> Config<Key, Value>
63    where Key: Hash + 'static,
64          Value: 'static {
65    /// Creates a new instance of TTLConfig.
66    pub(crate) fn ttl_config(&self) -> TTLConfig {
67        TTLConfig::new(self.shards, self.ttl_tick_duration, self.clock.clone_box())
68    }
69
70    /// Creates a new instance of CacheWeightConfig.
71    pub(crate) fn cache_weight_config(&self) -> CacheWeightConfig {
72        CacheWeightConfig::new(self.capacity, self.shards, self.total_cache_weight)
73    }
74}
75
76/// Convenient builder that allows creating an instance of Config.
77pub struct ConfigBuilder<Key, Value>
78    where Key: Hash + 'static,
79          Value: 'static {
80    key_hash_fn: Box<HashFn<Key>>,
81    weight_calculation_fn: Box<WeightCalculationFn<Key, Value>>,
82    clock: ClockType,
83    counters: TotalCounters,
84    capacity: TotalCapacity,
85    command_buffer_size: usize,
86    access_pool_size: PoolSize,
87    access_buffer_size: BufferSize,
88    total_cache_weight: Weight,
89    shards: TotalShards,
90    ttl_tick_duration: Duration,
91}
92
93impl<Key, Value> ConfigBuilder<Key, Value>
94    where Key: Hash + 'static,
95          Value: 'static {
96
97    /// Create a new instance of ConfigBuilder with `counters`, `capacity` and `cache_weight`.
98    ///
99    /// `counters` are used to determine the number of counters to keep that hold the access frequency of each key.
100    ///
101    /// If you expect your cache to hold `10_000` elements, then counters should be 10 times (`100_000`) to
102    /// get a close estimate of the access frequency.
103    ///
104    /// `capacity` is used as a parameter for [`dashmap::DashMap`] inside `crate::cache::store::Store`.
105    ///  It defines the number of items that the cache may store.
106    ///
107    /// `cache_weight` defines the total cache size. `cache_weight` is treated as the cache size in bytes.
108    /// If `cache_weight` is set to `1024`, that means the cache should take only `1024 bytes` of memory.
109    /// After the cache size is full, any further `put` operation will result in either of the following:
110        /// - rejection of the incoming key
111        ///
112        /// - admission of the incoming key by causing eviction of some existing keys
113    pub fn new(counters: TotalCounters, capacity: TotalCapacity, cache_weight: Weight) -> Self {
114        assert!(counters > 0, "{}", Errors::TotalCountersGtZero);
115        assert!(capacity > 0, "{}", Errors::TotalCapacityGtZero);
116        assert!(cache_weight > 0, "{}", Errors::TotalCacheWeightGtZero);
117
118        let key_hash_fn = |key: &Key| -> KeyHash {
119            let mut hasher = DefaultHasher::new();
120            key.hash(&mut hasher);
121            hasher.finish()
122        };
123
124        ConfigBuilder {
125            key_hash_fn: Box::new(key_hash_fn),
126            weight_calculation_fn: Box::new(Calculation::perform),
127            clock: SystemClock::boxed(),
128            access_pool_size: ACCESS_POOL_SIZE,
129            access_buffer_size: ACCESS_BUFFER_SIZE,
130            command_buffer_size: COMMAND_BUFFER_SIZE,
131            counters,
132            capacity,
133            total_cache_weight: cache_weight,
134            shards: SHARDS,
135            ttl_tick_duration: TTL_TICK_DURATION,
136        }
137    }
138
139    /// Sets the key hash function.
140    ///
141    /// By default, DefaultHasher is used that uses SipHasher13 as the hash function.
142    pub fn key_hash_fn(mut self, key_hash: Box<HashFn<Key>>) -> ConfigBuilder<Key, Value> {
143        self.key_hash_fn = key_hash;
144        self
145    }
146
147    /// Sets the weight calculation function.
148    ///
149    /// Weight calculation function calculates the weight of the incoming key/value pair.
150    ///
151    /// Default is the `perform` function defined in `crate::cache::config::weight_calculation::Calculation`.
152    pub fn weight_calculation_fn(mut self, weight_calculation: Box<WeightCalculationFn<Key, Value>>) -> ConfigBuilder<Key, Value> {
153        self.weight_calculation_fn = weight_calculation;
154        self
155    }
156
157    /// Sets the clock to be used to get the current time. By default [`crate::cache::clock::SystemClock`] is used.
158    pub fn clock(mut self, clock: ClockType) -> ConfigBuilder<Key, Value> {
159        self.clock = clock;
160        self
161    }
162
163    /// Sets the pool size.
164    ///
165    /// Pool represents a ring-buffer that is used to buffer the gets for various keys.
166    ///
167    /// Default pool size is `32`.
168    pub fn access_pool_size(mut self, pool_size: usize) -> ConfigBuilder<Key, Value> {
169        assert!(pool_size > 0, "{}", Errors::PoolSizeGtZero);
170        self.access_pool_size = PoolSize(pool_size);
171        self
172    }
173
174    /// Sets the size of each buffer inside Pool.
175    ///
176    /// Default capacity of the buffer is `64`.
177    pub fn access_buffer_size(mut self, buffer_size: usize) -> ConfigBuilder<Key, Value> {
178        assert!(buffer_size > 0, "{}", Errors::BufferSizeGtZero);
179        self.access_buffer_size = BufferSize(buffer_size);
180        self
181    }
182
183    /// Each put, put_or_update, delete results in a command to `crate::cache::command::command_executor::CommandExecutor`.
184    ///
185    /// CommandExecutor reads from a channel and the default channel size is `32 * 1024`.
186    pub fn command_buffer_size(mut self, command_buffer_size: usize) -> ConfigBuilder<Key, Value> {
187        assert!(command_buffer_size > 0, "{}", Errors::CommandBufferSizeGtZero);
188        self.command_buffer_size = command_buffer_size;
189        self
190    }
191
192    /// Sets the number of shards to use in the DashMap inside `crate::cache::store::Store`.
193    ///
194    /// `shards` must be a power of `2` and greater than `1`.
195    pub fn shards(mut self, shards: TotalShards) -> ConfigBuilder<Key, Value> {
196        assert!(shards > 1, "{}", Errors::TotalShardsGtOne);
197        assert!(shards.is_power_of_two(), "{}", Errors::TotalShardsPowerOf2);
198        self.shards = shards;
199        self
200    }
201
202    /// Sets the duration of the `crate::cache::expiration::TTLTicker`.
203    ///
204    /// Default is every `5 seconds`.
205    pub fn ttl_tick_duration(mut self, duration: Duration) -> ConfigBuilder<Key, Value> {
206        self.ttl_tick_duration = duration;
207        self
208    }
209
210    // Builds an instance of Config with the supplied values.
211    pub fn build(self) -> Config<Key, Value> {
212        Config {
213            key_hash_fn: self.key_hash_fn,
214            weight_calculation_fn: self.weight_calculation_fn,
215            clock: self.clock,
216            access_pool_size: self.access_pool_size,
217            access_buffer_size: self.access_buffer_size,
218            command_buffer_size: self.command_buffer_size,
219            counters: self.counters,
220            capacity: self.capacity,
221            total_cache_weight: self.total_cache_weight,
222            shards: self.shards,
223            ttl_tick_duration: self.ttl_tick_duration,
224        }
225    }
226}
227
228#[cfg(test)]
229mod tests {
230    use std::time::{Duration, SystemTime};
231
232    use crate::cache::clock::ClockType;
233    use crate::cache::config::{Config, ConfigBuilder};
234    use crate::cache::config::tests::setup::UnixEpochClock;
235    use crate::cache::pool::{BufferSize, PoolSize};
236    use crate::cache::types::IsTimeToLiveSpecified;
237
238    mod setup {
239        use std::time::SystemTime;
240
241        use crate::cache::clock::Clock;
242
243        #[derive(Clone)]
244        pub(crate) struct UnixEpochClock;
245
246        impl Clock for UnixEpochClock {
247            fn now(&self) -> SystemTime {
248                SystemTime::UNIX_EPOCH
249            }
250        }
251    }
252
253    fn test_config_builder() -> ConfigBuilder<&'static str, &'static str>{
254        ConfigBuilder::new(100, 10, 100)
255    }
256
257    #[test]
258    fn key_hash_function() {
259        let builder: ConfigBuilder<&str, &str> = test_config_builder();
260
261        let key_hash_fn = Box::new(|_key: &&str| 1);
262        let config = builder.key_hash_fn(key_hash_fn).build();
263
264        let key = "topic";
265        let hash = (config.key_hash_fn)(&key);
266
267        assert_eq!(1, hash);
268    }
269
270    #[test]
271    fn weight_calculation_function() {
272        let builder: ConfigBuilder<&str, &str> = test_config_builder();
273
274        let weight_calculation_fn = Box::new(|_key: &&str, _value: &&str, _is_time_to_live_specified: IsTimeToLiveSpecified| 10);
275        let config = builder.weight_calculation_fn(weight_calculation_fn).build();
276
277        let key = "topic";
278        let value = "microservices";
279        let weight = (config.weight_calculation_fn)(&key, &value, false);
280
281        assert_eq!(10, weight);
282    }
283
284    #[test]
285    fn clock() {
286        let builder: ConfigBuilder<&str, &str> = test_config_builder();
287        let clock: ClockType = Box::new(UnixEpochClock {});
288
289        let config = builder.clock(clock).build();
290        assert_eq!(SystemTime::UNIX_EPOCH, config.clock.now());
291    }
292
293    #[test]
294    fn access_pool_size() {
295        let builder: ConfigBuilder<&str, &str> = test_config_builder();
296        let config = builder.access_pool_size(32).build();
297
298        assert_eq!(PoolSize(32), config.access_pool_size);
299    }
300
301    #[test]
302    fn access_buffer_size() {
303        let builder: ConfigBuilder<&str, &str> = test_config_builder();
304        let config = builder.access_buffer_size(64).build();
305
306        assert_eq!(BufferSize(64), config.access_buffer_size);
307    }
308
309    #[test]
310    fn command_buffer_size() {
311        let builder: ConfigBuilder<&str, &str> = test_config_builder();
312        let config = builder.command_buffer_size(1024).build();
313
314        assert_eq!(1024, config.command_buffer_size);
315    }
316
317    #[test]
318    fn counters() {
319        let config: Config<&str, &str> = ConfigBuilder::new(4096, 400, 100).build();
320
321        assert_eq!(4096, config.counters);
322    }
323
324    #[test]
325    fn total_cache_weight() {
326        let builder: ConfigBuilder<&str, &str> = ConfigBuilder::new(100, 10, 1048576);
327        let config = builder.build();
328
329        assert_eq!(1048576, config.total_cache_weight);
330    }
331
332    #[test]
333    fn shards() {
334        let builder: ConfigBuilder<&str, &str> = test_config_builder();
335        let config = builder.shards(16).build();
336
337        assert_eq!(16, config.shards);
338    }
339
340    #[test]
341    fn ttl_tick_duration() {
342        let builder: ConfigBuilder<&str, &str> = test_config_builder();
343        let config = builder.ttl_tick_duration(Duration::from_secs(5)).build();
344
345        assert_eq!(Duration::from_secs(5), config.ttl_tick_duration);
346    }
347
348    #[test]
349    fn ttl_config() {
350        let builder: ConfigBuilder<&str, &str> = test_config_builder();
351        let config = builder.ttl_tick_duration(Duration::from_secs(5)).shards(16).build();
352
353        let ttl_config = config.ttl_config();
354        assert_eq!(16, ttl_config.shards());
355        assert_eq!(Duration::from_secs(5), ttl_config.tick_duration());
356    }
357
358    #[test]
359    fn cache_weight_config() {
360        let builder: ConfigBuilder<&str, &str> = ConfigBuilder::new(100, 10, 200).shards(4);
361        let config = builder.build();
362
363        let cache_weight_config = config.cache_weight_config();
364        assert_eq!(10, cache_weight_config.capacity());
365        assert_eq!(4, cache_weight_config.shards());
366        assert_eq!(200, cache_weight_config.total_cache_weight());
367    }
368
369    #[test]
370    #[should_panic]
371    fn access_pool_size_must_be_greater_than_zero() {
372        let _: Config<&str, &str> = test_config_builder().access_pool_size(0).build();
373    }
374
375    #[test]
376    #[should_panic]
377    fn access_buffer_size_must_be_greater_than_zero() {
378        let _: Config<&str, &str> = test_config_builder().access_buffer_size(0).build();
379    }
380
381    #[test]
382    #[should_panic]
383    fn command_buffer_size_must_be_greater_than_zero() {
384        let _: Config<&str, &str> = test_config_builder().command_buffer_size(0).build();
385    }
386
387    #[test]
388    #[should_panic]
389    fn total_counters_must_be_greater_than_zero() {
390        let _: Config<&str, &str> = ConfigBuilder::new(0, 10, 10).build();
391    }
392
393    #[test]
394    #[should_panic]
395    fn total_capacity_must_be_greater_than_zero() {
396        let _: Config<&str, &str> = ConfigBuilder::new(10, 0, 10).build();
397    }
398
399    #[test]
400    #[should_panic]
401    fn total_cache_weight_must_be_greater_than_zero() {
402        let _: Config<&str, &str> = ConfigBuilder::new(100, 100, 0).build();
403    }
404
405    #[test]
406    #[should_panic]
407    fn shards_must_be_greater_than_one() {
408        let _: Config<&str, &str> = test_config_builder().shards(1).build();
409    }
410
411    #[test]
412    #[should_panic]
413    fn shards_must_be_power_of_2() {
414        let _: Config<&str, &str> = test_config_builder().shards(3).build();
415    }
416}