pingora_cache/
predictor.rs1use crate::hashtable::ConcurrentLruCache;
18
19pub type CustomReasonPredicate = fn(&'static str) -> bool;
20
21pub struct Predictor<const N_SHARDS: usize> {
31 uncacheable_keys: ConcurrentLruCache<NoCacheReason, N_SHARDS>,
34 skip_custom_reasons_fn: Option<CustomReasonPredicate>,
35}
36
37use crate::{key::CacheHashKey, CacheKey, NoCacheReason};
38use log::debug;
39
40pub trait CacheablePredictor {
44 fn cacheable_prediction(&self, key: &CacheKey) -> bool;
46
47 fn predicted_uncacheable_reason(&self, _key: &CacheKey) -> Option<NoCacheReason> {
55 None
56 }
57
58 fn mark_cacheable(&self, key: &CacheKey) -> bool;
61
62 fn mark_uncacheable(&self, key: &CacheKey, reason: NoCacheReason) -> Option<bool>;
67}
68
69impl<const N_SHARDS: usize> Predictor<N_SHARDS> {
70 pub fn new(
79 shard_capacity: usize,
80 skip_custom_reasons_fn: Option<CustomReasonPredicate>,
81 ) -> Predictor<N_SHARDS> {
82 Predictor {
83 uncacheable_keys: ConcurrentLruCache::<NoCacheReason, N_SHARDS>::new(shard_capacity),
84 skip_custom_reasons_fn,
85 }
86 }
87}
88
89impl<const N_SHARDS: usize> CacheablePredictor for Predictor<N_SHARDS> {
90 fn cacheable_prediction(&self, key: &CacheKey) -> bool {
91 self.predicted_uncacheable_reason(key).is_none()
92 }
93
94 fn predicted_uncacheable_reason(&self, key: &CacheKey) -> Option<NoCacheReason> {
95 let hash = key.primary_bin();
97 let key = u128::from_be_bytes(hash); self.uncacheable_keys.read(key).peek(&key).copied()
103 }
104
105 fn mark_cacheable(&self, key: &CacheKey) -> bool {
106 let hash = key.primary_bin();
109 let key = u128::from_be_bytes(hash);
110
111 let cache = self.uncacheable_keys.get(key);
112 if !cache.read().contains(&key) {
113 return true;
115 }
116
117 let mut cache = cache.write();
118 cache.pop(&key);
119 debug!("bypassed request became cacheable");
120 false
121 }
122
123 fn mark_uncacheable(&self, key: &CacheKey, reason: NoCacheReason) -> Option<bool> {
124 use NoCacheReason::*;
127 match reason {
128 NeverEnabled
131 | StorageError
132 | InternalError
133 | Deferred
134 | CacheLockGiveUp
135 | CacheLockTimeout
136 | CacheLockRetryLimit
137 | DeclinedToUpstream
138 | UpstreamError
139 | PredictedResponseTooLarge => {
140 return None;
141 }
142 Custom(reason) if self.skip_custom_reasons_fn.is_some_and(|f| f(reason)) => {
144 return None;
145 }
146 Custom(_) | OriginNotCache | ResponseTooLarge => { }
148 }
149
150 let hash = key.primary_bin();
153 let key = u128::from_be_bytes(hash);
154
155 let mut cache = self.uncacheable_keys.get(key).write();
156 let new_key = cache.put(key, reason).is_none();
159 if new_key {
160 debug!("request marked uncacheable");
161 }
162 Some(new_key)
163 }
164}
165
166#[cfg(test)]
167mod tests {
168 use super::*;
169 #[test]
170 fn test_mark_cacheability() {
171 let predictor = Predictor::<1>::new(10, None);
172 let key = CacheKey::new("b", "c");
173 assert!(predictor.cacheable_prediction(&key));
175
176 predictor.mark_uncacheable(&key, NoCacheReason::InternalError);
178 assert!(predictor.cacheable_prediction(&key));
179 predictor.mark_uncacheable(&key, NoCacheReason::StorageError);
180 assert!(predictor.cacheable_prediction(&key));
181
182 predictor.mark_uncacheable(&key, NoCacheReason::OriginNotCache);
184 assert!(!predictor.cacheable_prediction(&key));
185
186 predictor.mark_cacheable(&key);
188 assert!(predictor.cacheable_prediction(&key));
189 }
190
191 #[test]
192 fn test_remembers_uncacheable_reason() {
193 let predictor = Predictor::<1>::new(10, None);
194 let key = CacheKey::new("reason", "tag");
195 assert_eq!(predictor.predicted_uncacheable_reason(&key), None);
196
197 predictor.mark_uncacheable(&key, NoCacheReason::Custom("AuthorizationHeader"));
198 assert_eq!(
199 predictor.predicted_uncacheable_reason(&key),
200 Some(NoCacheReason::Custom("AuthorizationHeader"))
201 );
202
203 predictor.mark_uncacheable(&key, NoCacheReason::ResponseTooLarge);
205 assert_eq!(
206 predictor.predicted_uncacheable_reason(&key),
207 Some(NoCacheReason::ResponseTooLarge)
208 );
209
210 predictor.mark_uncacheable(&key, NoCacheReason::InternalError);
212 assert_eq!(
213 predictor.predicted_uncacheable_reason(&key),
214 Some(NoCacheReason::ResponseTooLarge)
215 );
216
217 predictor.mark_cacheable(&key);
218 assert_eq!(predictor.predicted_uncacheable_reason(&key), None);
219 }
220
221 #[test]
222 fn test_custom_skip_predicate() {
223 let predictor = Predictor::<1>::new(
224 10,
225 Some(|custom_reason| matches!(custom_reason, "Skipping")),
226 );
227 let key = CacheKey::new("b", "c");
228 assert!(predictor.cacheable_prediction(&key));
230
231 predictor.mark_uncacheable(&key, NoCacheReason::InternalError);
233 assert!(predictor.cacheable_prediction(&key));
234
235 predictor.mark_uncacheable(&key, NoCacheReason::Custom("DontCacheMe"));
237 assert!(!predictor.cacheable_prediction(&key));
238
239 let key = CacheKey::new("c", "d");
240 assert!(predictor.cacheable_prediction(&key));
241 predictor.mark_uncacheable(&key, NoCacheReason::Custom("Skipping"));
243 assert!(predictor.cacheable_prediction(&key));
244 }
245
246 #[test]
247 fn test_mark_uncacheable_lru() {
248 let predictor = Predictor::<1>::new(3, None);
249 let key1 = CacheKey::new("b", "c");
250 predictor.mark_uncacheable(&key1, NoCacheReason::OriginNotCache);
251 assert!(!predictor.cacheable_prediction(&key1));
252
253 let key2 = CacheKey::new("bc", "c");
254 predictor.mark_uncacheable(&key2, NoCacheReason::OriginNotCache);
255 assert!(!predictor.cacheable_prediction(&key2));
256
257 let key3 = CacheKey::new("cd", "c");
258 predictor.mark_uncacheable(&key3, NoCacheReason::OriginNotCache);
259 assert!(!predictor.cacheable_prediction(&key3));
260
261 predictor.mark_uncacheable(&key1, NoCacheReason::OriginNotCache);
263
264 let key4 = CacheKey::new("de", "c");
265 predictor.mark_uncacheable(&key4, NoCacheReason::OriginNotCache);
266 assert!(!predictor.cacheable_prediction(&key4));
267
268 assert!(!predictor.cacheable_prediction(&key1));
270 assert!(predictor.cacheable_prediction(&key2));
272 assert!(!predictor.cacheable_prediction(&key3));
273 assert!(!predictor.cacheable_prediction(&key4));
274 }
275
276 #[test]
277 fn test_shard_count_above_32() {
278 let predictor = Predictor::<64>::new(10, None);
282 let key = CacheKey::new("b", "c");
283 assert!(predictor.cacheable_prediction(&key));
284
285 predictor.mark_uncacheable(&key, NoCacheReason::OriginNotCache);
286 assert!(!predictor.cacheable_prediction(&key));
287
288 predictor.mark_cacheable(&key);
289 assert!(predictor.cacheable_prediction(&key));
290 }
291}