oxistore_cache/lib.rs
1#![forbid(unsafe_code)]
2#![warn(missing_docs)]
3
4//! `oxistore-cache` — Pure-Rust LRU, ARC, LFU, and W-TinyLFU eviction primitives.
5//!
6//! This crate provides four cache implementations behind a unified [`Cache`]
7//! trait:
8//!
9//! - [`LruCache`] — classic Least-Recently-Used cache backed by a
10//! `hashlink::LinkedHashMap`. O(1) amortised operations.
11//!
12//! - [`ArcCache`] — Adaptive Replacement Cache (Megiddo & Modha, FAST'03),
13//! which balances recency and frequency by maintaining four lists (T1, T2,
14//! B1, B2) and an adaptive target `p`. ARC is scan-resistant.
15//!
16//! - [`LfuCache`] — Least-Frequently-Used cache with O(1) operations using
17//! the Shah, Mitra & Matani (2010) algorithm.
18//!
19//! - [`WTinyLfuCache`] — Window TinyLFU with Count-Min Sketch frequency
20//! estimation and a doorkeeper bloom filter, providing near-optimal hit rates
21//! on skewed (Zipfian) workloads.
22//!
23//! All cache implementations support per-entry TTL (time-to-live) via the
24//! [`Cache::put_with_ttl`] method. Expiry is checked lazily on access.
25//!
26//! # Example
27//!
28//! ```rust
29//! use oxistore_cache::{LruCache, ArcCache, LfuCache, WTinyLfuCache, Cache};
30//!
31//! let mut lru = LruCache::new(3);
32//! lru.put(1u32, "a");
33//! lru.put(2u32, "b");
34//! lru.put(3u32, "c");
35//! lru.put(4u32, "d"); // evicts 1 (LRU)
36//! assert!(lru.get(&1u32).is_none());
37//! assert_eq!(lru.get(&2u32), Some(&"b"));
38//!
39//! let mut arc = ArcCache::new(3);
40//! arc.put(1u32, "a");
41//! arc.put(2u32, "b");
42//! arc.put(3u32, "c");
43//!
44//! let mut lfu = LfuCache::new(3);
45//! lfu.put(1u32, "a");
46//! lfu.put(2u32, "b");
47//! lfu.put(3u32, "c");
48//!
49//! let mut wtlfu = WTinyLfuCache::new(3);
50//! wtlfu.put(1u32, "a");
51//! wtlfu.put(2u32, "b");
52//! wtlfu.put(3u32, "c");
53//! ```
54
55/// Adaptive Replacement Cache (ARC) — balances recency and frequency.
56pub mod arc;
57/// Bounded-memory cache wrapper — enforces a hard byte-budget cap.
58pub mod bounded;
59/// Cache builder — ergonomic constructor for all cache policies.
60pub mod builder;
61/// Least-Frequently-Used (LFU) cache — O(1) frequency-based eviction.
62pub mod lfu;
63/// Least-Recently-Used (LRU) cache — evicts the least recently accessed entry.
64pub mod lru;
65/// Sharded concurrent cache — N LRU shards behind a Mutex for low contention.
66pub mod sharded;
67/// Count-Min Sketch and Doorkeeper bloom filter for W-TinyLFU.
68pub mod sketch;
69/// Cache hit/miss statistics tracking.
70pub mod stats;
71/// Thread-safe cache wrapper — wraps any `Cache` impl behind a `Mutex`.
72pub mod sync;
73/// Window TinyLFU — state-of-the-art admission policy with CMS frequency estimator.
74pub mod tinylfu;
75/// Write-through and write-back cache adapters.
76pub mod write_adapter;
77
78/// Caching wrapper for `BlobStore` backends (see `oxistore_blob::BlobStore`).
79#[cfg(feature = "blob")]
80pub mod blob_cache;
81
82pub use arc::ArcCache;
83pub use bounded::BoundedCache;
84pub use builder::{CacheBuilder, CachePolicy};
85pub use lfu::LfuCache;
86pub use lru::LruCache;
87pub use sharded::ShardedCache;
88pub use stats::{CacheStats, StatsCache};
89pub use sync::SyncCache;
90pub use tinylfu::WTinyLfuCache;
91pub use write_adapter::{CacheableKvStore, WriteBackCache, WriteThroughCache};
92
93#[cfg(feature = "blob")]
94pub use blob_cache::BlobCache;
95
96/// A cache entry that optionally expires at a given instant.
97///
98/// Used internally by all cache implementations to support per-entry TTL.
99/// Callers interact with the value type `V` via the [`Cache`] trait methods;
100/// `CacheEntry` is exposed for advanced use cases (e.g. custom wrappers).
101pub struct CacheEntry<V> {
102 /// The stored value.
103 pub value: V,
104 /// Optional expiry time. `None` means the entry never expires.
105 pub expires_at: Option<std::time::Instant>,
106}
107
108impl<V> CacheEntry<V> {
109 /// Create a non-expiring entry.
110 #[must_use]
111 pub fn new(value: V) -> Self {
112 CacheEntry {
113 value,
114 expires_at: None,
115 }
116 }
117
118 /// Create an entry that expires after `ttl` from now.
119 #[must_use]
120 pub fn with_ttl(value: V, ttl: std::time::Duration) -> Self {
121 CacheEntry {
122 value,
123 expires_at: Some(std::time::Instant::now() + ttl),
124 }
125 }
126
127 /// Return `true` if this entry has expired (i.e. its deadline has passed).
128 #[must_use]
129 pub fn is_expired(&self) -> bool {
130 match self.expires_at {
131 Some(deadline) => std::time::Instant::now() >= deadline,
132 None => false,
133 }
134 }
135}
136
137/// Unified interface for key-value caches with bounded capacity.
138///
139/// All four implementations ([`LruCache`], [`ArcCache`], [`LfuCache`],
140/// [`WTinyLfuCache`]) implement this trait, allowing callers to write generic
141/// code against the cache interface.
142pub trait Cache<K, V> {
143 /// Look up `key`, returning a reference to the value if present.
144 ///
145 /// Implementations update internal bookkeeping (e.g. promoting the entry
146 /// to MRU or incrementing frequency counts) as a side effect.
147 ///
148 /// If the entry exists but has expired (TTL), it is removed and `None` is
149 /// returned without updating recency or frequency.
150 fn get(&mut self, key: &K) -> Option<&V>;
151
152 /// Insert or update `key` -> `value` without a TTL.
153 ///
154 /// If inserting a new key would exceed the cache's capacity, the
155 /// implementation evicts one entry (per its policy) and returns the
156 /// evicted value. On a key update, the old value is not returned.
157 fn put(&mut self, key: K, value: V) -> Option<V>;
158
159 /// Insert or update `key` -> `value` with a time-to-live.
160 ///
161 /// After `ttl` has elapsed, any access to `key` will treat it as a miss
162 /// and remove the entry lazily.
163 ///
164 /// Concrete implementations in this crate override this to actually store
165 /// the expiry. External impls that don't need TTL may rely on the default
166 /// which falls back to a plain `put` (no expiry).
167 fn put_with_ttl(&mut self, key: K, value: V, ttl: std::time::Duration) -> Option<V> {
168 let _ = ttl;
169 self.put(key, value)
170 }
171
172 /// Return the number of live entries currently in the cache.
173 fn len(&self) -> usize;
174
175 /// Return the maximum number of entries the cache can hold.
176 fn cap(&self) -> usize;
177
178 /// Return `true` if the cache holds no entries.
179 fn is_empty(&self) -> bool {
180 self.len() == 0
181 }
182
183 /// Remove the entry associated with `key`, returning its value if present.
184 ///
185 /// Unlike eviction, this is an explicit removal requested by the caller.
186 fn remove(&mut self, key: &K) -> Option<V>;
187
188 /// Remove all entries from the cache.
189 fn clear(&mut self);
190
191 /// Look up `key` without updating access metadata (no promotion).
192 ///
193 /// If the entry has expired (TTL), it is removed and `None` is returned.
194 /// Returns `None` if the key is not present or has expired.
195 fn peek(&self, key: &K) -> Option<&V>;
196
197 /// Return `true` if `key` is present in the cache (without promotion).
198 ///
199 /// Expired entries are treated as absent.
200 fn contains_key(&self, key: &K) -> bool;
201
202 /// Dynamically resize the cache capacity.
203 ///
204 /// If `new_cap` is smaller than the current length, excess entries are
205 /// evicted according to the cache's eviction policy.
206 fn resize(&mut self, new_cap: usize);
207
208 /// Return `&V` for `key`, inserting `default()` if the key is absent.
209 ///
210 /// The closure `default` is called at most once. If the key is already
211 /// present the closure is never invoked.
212 ///
213 /// Implementations may override this for efficiency (e.g. to avoid a
214 /// second hash lookup). The default implementation uses [`Cache::peek`]
215 /// after ensuring the key exists.
216 fn get_or_insert(&mut self, key: K, default: impl FnOnce() -> V) -> &V
217 where
218 K: Clone,
219 {
220 if !self.contains_key(&key) {
221 let v = default();
222 self.put(key.clone(), v);
223 }
224 // peek is &self and won't panic since we just ensured the key exists.
225 self.peek(&key).expect("key was just inserted")
226 }
227
228 /// Return all live (non-expired) values currently stored in the cache.
229 ///
230 /// The default implementation returns an empty `Vec`. Concrete
231 /// implementations override this to return actual values.
232 fn values(&self) -> Vec<&V> {
233 Vec::new()
234 }
235
236 /// Pre-populate the cache with `(key, value)` pairs from an iterator.
237 ///
238 /// Each pair is inserted via [`Cache::put`], respecting the cache's
239 /// eviction policy. Pairs that exceed capacity cause earlier entries to
240 /// be evicted according to the policy.
241 fn warm(&mut self, iter: impl IntoIterator<Item = (K, V)>) {
242 for (k, v) in iter {
243 self.put(k, v);
244 }
245 }
246}