pingora_cache/
predictor.rs1use crate::hashtable::{ConcurrentLruCache, LruShard};
18
19pub type CustomReasonPredicate = fn(&'static str) -> bool;
20
21pub struct Predictor<const N_SHARDS: usize> {
31 uncacheable_keys: ConcurrentLruCache<(), N_SHARDS>,
32 skip_custom_reasons_fn: Option<CustomReasonPredicate>,
33}
34
35use crate::{key::CacheHashKey, CacheKey, NoCacheReason};
36use log::debug;
37
38pub trait CacheablePredictor {
42 fn cacheable_prediction(&self, key: &CacheKey) -> bool;
44
45 fn mark_cacheable(&self, key: &CacheKey) -> bool;
48
49 fn mark_uncacheable(&self, key: &CacheKey, reason: NoCacheReason) -> Option<bool>;
54}
55
56impl<const N_SHARDS: usize> Predictor<N_SHARDS>
61where
62 [LruShard<()>; N_SHARDS]: Default,
63{
64 pub fn new(
73 shard_capacity: usize,
74 skip_custom_reasons_fn: Option<CustomReasonPredicate>,
75 ) -> Predictor<N_SHARDS> {
76 Predictor {
77 uncacheable_keys: ConcurrentLruCache::<(), N_SHARDS>::new(shard_capacity),
78 skip_custom_reasons_fn,
79 }
80 }
81}
82
83impl<const N_SHARDS: usize> CacheablePredictor for Predictor<N_SHARDS>
84where
85 [LruShard<()>; N_SHARDS]: Default,
86{
87 fn cacheable_prediction(&self, key: &CacheKey) -> bool {
88 let hash = key.primary_bin();
90 let key = u128::from_be_bytes(hash); !self.uncacheable_keys.read(key).contains(&key)
95 }
96
97 fn mark_cacheable(&self, key: &CacheKey) -> bool {
98 let hash = key.primary_bin();
101 let key = u128::from_be_bytes(hash);
102
103 let cache = self.uncacheable_keys.get(key);
104 if !cache.read().contains(&key) {
105 return true;
107 }
108
109 let mut cache = cache.write();
110 cache.pop(&key);
111 debug!("bypassed request became cacheable");
112 false
113 }
114
115 fn mark_uncacheable(&self, key: &CacheKey, reason: NoCacheReason) -> Option<bool> {
116 use NoCacheReason::*;
119 match reason {
120 NeverEnabled | StorageError | InternalError | Deferred | CacheLockGiveUp
123 | CacheLockTimeout | DeclinedToUpstream | UpstreamError => {
124 return None;
125 }
126 Custom(reason) if self.skip_custom_reasons_fn.is_some_and(|f| f(reason)) => {
128 return None;
129 }
130 Custom(_) | OriginNotCache | ResponseTooLarge => { }
132 }
133
134 let hash = key.primary_bin();
137 let key = u128::from_be_bytes(hash);
138
139 let mut cache = self.uncacheable_keys.get(key).write();
140 let new_key = cache.put(key, ()).is_none();
142 if new_key {
143 debug!("request marked uncacheable");
144 }
145 Some(new_key)
146 }
147}
148
149#[cfg(test)]
150mod tests {
151 use super::*;
152 #[test]
153 fn test_mark_cacheability() {
154 let predictor = Predictor::<1>::new(10, None);
155 let key = CacheKey::new("a", "b", "c");
156 assert!(predictor.cacheable_prediction(&key));
158
159 predictor.mark_uncacheable(&key, NoCacheReason::InternalError);
161 assert!(predictor.cacheable_prediction(&key));
162 predictor.mark_uncacheable(&key, NoCacheReason::StorageError);
163 assert!(predictor.cacheable_prediction(&key));
164
165 predictor.mark_uncacheable(&key, NoCacheReason::OriginNotCache);
167 assert!(!predictor.cacheable_prediction(&key));
168
169 predictor.mark_cacheable(&key);
171 assert!(predictor.cacheable_prediction(&key));
172 }
173
174 #[test]
175 fn test_custom_skip_predicate() {
176 let predictor = Predictor::<1>::new(
177 10,
178 Some(|custom_reason| matches!(custom_reason, "Skipping")),
179 );
180 let key = CacheKey::new("a", "b", "c");
181 assert!(predictor.cacheable_prediction(&key));
183
184 predictor.mark_uncacheable(&key, NoCacheReason::InternalError);
186 assert!(predictor.cacheable_prediction(&key));
187
188 predictor.mark_uncacheable(&key, NoCacheReason::Custom("DontCacheMe"));
190 assert!(!predictor.cacheable_prediction(&key));
191
192 let key = CacheKey::new("a", "c", "d");
193 assert!(predictor.cacheable_prediction(&key));
194 predictor.mark_uncacheable(&key, NoCacheReason::Custom("Skipping"));
196 assert!(predictor.cacheable_prediction(&key));
197 }
198
199 #[test]
200 fn test_mark_uncacheable_lru() {
201 let predictor = Predictor::<1>::new(3, None);
202 let key1 = CacheKey::new("a", "b", "c");
203 predictor.mark_uncacheable(&key1, NoCacheReason::OriginNotCache);
204 assert!(!predictor.cacheable_prediction(&key1));
205
206 let key2 = CacheKey::new("a", "bc", "c");
207 predictor.mark_uncacheable(&key2, NoCacheReason::OriginNotCache);
208 assert!(!predictor.cacheable_prediction(&key2));
209
210 let key3 = CacheKey::new("a", "cd", "c");
211 predictor.mark_uncacheable(&key3, NoCacheReason::OriginNotCache);
212 assert!(!predictor.cacheable_prediction(&key3));
213
214 predictor.mark_uncacheable(&key1, NoCacheReason::OriginNotCache);
216
217 let key4 = CacheKey::new("a", "de", "c");
218 predictor.mark_uncacheable(&key4, NoCacheReason::OriginNotCache);
219 assert!(!predictor.cacheable_prediction(&key4));
220
221 assert!(!predictor.cacheable_prediction(&key1));
223 assert!(predictor.cacheable_prediction(&key2));
225 assert!(!predictor.cacheable_prediction(&key3));
226 assert!(!predictor.cacheable_prediction(&key4));
227 }
228}