iqdb_cache/config.rs
1//! Cache configuration.
2
3use core::time::Duration;
4
5/// Default cache capacity: the number of distinct recent searches whose results
6/// are kept resident when no explicit capacity is given.
7pub(crate) const DEFAULT_CAPACITY: usize = 1024;
8
9/// Which entry an eviction discards when the cache is full.
10///
11/// All four policies keep the cache within its capacity; they differ only in
12/// *which* entry they sacrifice to make room. The default is [`Lru`](Self::Lru),
13/// the best general-purpose choice for search workloads.
14///
15/// # Examples
16///
17/// ```
18/// use iqdb_cache::EvictionPolicy;
19///
20/// assert_eq!(EvictionPolicy::default(), EvictionPolicy::Lru);
21/// ```
22#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
23#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
24#[non_exhaustive]
25pub enum EvictionPolicy {
26 /// **Least Recently Used.** Discards the entry untouched for the longest.
27 /// The default: it adapts to shifting query hot-sets and is the strongest
28 /// general default for search.
29 #[default]
30 Lru,
31 /// **Least Frequently Used.** Discards the entry with the fewest lifetime
32 /// hits, breaking ties by least-recently-used. Favors stable, skewed
33 /// workloads where a few queries dominate.
34 Lfu,
35 /// **First In, First Out.** Discards the oldest *inserted* entry regardless
36 /// of access. The cheapest policy; a good fit when reuse is uniform.
37 Fifo,
38 /// **Adaptive Replacement Cache.** Balances recency and frequency
39 /// automatically using ghost lists, tuning itself between LRU- and
40 /// LFU-like behavior as the workload shifts.
41 Arc,
42}
43
44/// Tuning for a [`CachedIndex`](crate::CachedIndex) — the Tier-2 configured path.
45///
46/// Build one with [`CacheConfig::new`] and the chaining setters, then hand it to
47/// [`CachedIndex::with_config`](crate::CachedIndex::with_config). Every setting
48/// has a sensible default, so `CacheConfig::new()` alone is a valid config.
49///
50/// | Setting | Default | Meaning |
51/// |---|---|---|
52/// | [`capacity`](CacheConfig::capacity) | `1024` | Max distinct searches cached; `0` disables caching. |
53/// | [`ttl`](CacheConfig::ttl) | none | Optional per-entry time-to-live; expired results are recomputed. |
54/// | [`policy`](CacheConfig::policy) | `Lru` | Which entry to evict when full. |
55///
56/// # Examples
57///
58/// ```
59/// use std::time::Duration;
60///
61/// use iqdb_cache::{CacheConfig, CachedIndex, EvictionPolicy};
62///
63/// let config = CacheConfig::new()
64/// .capacity(4096)
65/// .ttl(Duration::from_secs(30))
66/// .policy(EvictionPolicy::Lfu);
67///
68/// let cached = CachedIndex::with_config(iqdb_cache::doc_stub::stub_index(), config);
69/// assert_eq!(cached.capacity(), 4096);
70/// assert_eq!(cached.ttl(), Some(Duration::from_secs(30)));
71/// assert_eq!(cached.policy(), EvictionPolicy::Lfu);
72/// ```
73#[derive(Clone, Debug, PartialEq, Eq)]
74pub struct CacheConfig {
75 /// Maximum number of distinct cached searches.
76 pub(crate) capacity: usize,
77 /// Optional per-entry time-to-live.
78 pub(crate) ttl: Option<Duration>,
79 /// Eviction policy applied when the cache is full.
80 pub(crate) policy: EvictionPolicy,
81}
82
83impl CacheConfig {
84 /// A configuration with the default capacity (1024), no TTL, and the LRU
85 /// policy.
86 ///
87 /// # Examples
88 ///
89 /// ```
90 /// use iqdb_cache::CacheConfig;
91 ///
92 /// let config = CacheConfig::new();
93 /// // Equivalent to `CachedIndex::new(..)`'s defaults.
94 /// # let _ = config;
95 /// ```
96 #[must_use]
97 pub fn new() -> Self {
98 Self {
99 capacity: DEFAULT_CAPACITY,
100 ttl: None,
101 policy: EvictionPolicy::Lru,
102 }
103 }
104
105 /// Sets the eviction policy applied when the cache is full.
106 ///
107 /// # Examples
108 ///
109 /// ```
110 /// use iqdb_cache::{CacheConfig, EvictionPolicy};
111 ///
112 /// let config = CacheConfig::new().policy(EvictionPolicy::Arc);
113 /// # let _ = config;
114 /// ```
115 #[must_use]
116 pub fn policy(mut self, policy: EvictionPolicy) -> Self {
117 self.policy = policy;
118 self
119 }
120
121 /// Sets the maximum number of distinct cached searches.
122 ///
123 /// A `capacity` of `0` disables caching: searches pass straight through.
124 ///
125 /// # Examples
126 ///
127 /// ```
128 /// use iqdb_cache::CacheConfig;
129 ///
130 /// let config = CacheConfig::new().capacity(256);
131 /// # let _ = config;
132 /// ```
133 #[must_use]
134 pub fn capacity(mut self, capacity: usize) -> Self {
135 self.capacity = capacity;
136 self
137 }
138
139 /// Sets a per-entry time-to-live: a cached result older than `ttl` is
140 /// treated as a miss and recomputed.
141 ///
142 /// TTL bounds staleness from changes the wrapper cannot observe (for
143 /// example, the wrapped index mutated through another handle). Mutations
144 /// *through* the wrapper already invalidate exactly, independent of TTL.
145 ///
146 /// # Examples
147 ///
148 /// ```
149 /// use std::time::Duration;
150 ///
151 /// use iqdb_cache::CacheConfig;
152 ///
153 /// let config = CacheConfig::new().ttl(Duration::from_secs(60));
154 /// # let _ = config;
155 /// ```
156 #[must_use]
157 pub fn ttl(mut self, ttl: Duration) -> Self {
158 self.ttl = Some(ttl);
159 self
160 }
161
162 /// Clears any previously set TTL, so cached results never expire on time
163 /// (only on mutation).
164 ///
165 /// # Examples
166 ///
167 /// ```
168 /// use std::time::Duration;
169 ///
170 /// use iqdb_cache::CacheConfig;
171 ///
172 /// let config = CacheConfig::new().ttl(Duration::from_secs(60)).no_ttl();
173 /// # let _ = config;
174 /// ```
175 #[must_use]
176 pub fn no_ttl(mut self) -> Self {
177 self.ttl = None;
178 self
179 }
180}
181
182impl Default for CacheConfig {
183 fn default() -> Self {
184 Self::new()
185 }
186}