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
123 | StorageError
124 | InternalError
125 | Deferred
126 | CacheLockGiveUp
127 | CacheLockTimeout
128 | DeclinedToUpstream
129 | UpstreamError
130 | PredictedResponseTooLarge => {
131 return None;
132 }
133 Custom(reason) if self.skip_custom_reasons_fn.is_some_and(|f| f(reason)) => {
135 return None;
136 }
137 Custom(_) | OriginNotCache | ResponseTooLarge => { }
139 }
140
141 let hash = key.primary_bin();
144 let key = u128::from_be_bytes(hash);
145
146 let mut cache = self.uncacheable_keys.get(key).write();
147 let new_key = cache.put(key, ()).is_none();
149 if new_key {
150 debug!("request marked uncacheable");
151 }
152 Some(new_key)
153 }
154}
155
156#[cfg(test)]
157mod tests {
158 use super::*;
159 #[test]
160 fn test_mark_cacheability() {
161 let predictor = Predictor::<1>::new(10, None);
162 let key = CacheKey::new("a", "b", "c");
163 assert!(predictor.cacheable_prediction(&key));
165
166 predictor.mark_uncacheable(&key, NoCacheReason::InternalError);
168 assert!(predictor.cacheable_prediction(&key));
169 predictor.mark_uncacheable(&key, NoCacheReason::StorageError);
170 assert!(predictor.cacheable_prediction(&key));
171
172 predictor.mark_uncacheable(&key, NoCacheReason::OriginNotCache);
174 assert!(!predictor.cacheable_prediction(&key));
175
176 predictor.mark_cacheable(&key);
178 assert!(predictor.cacheable_prediction(&key));
179 }
180
181 #[test]
182 fn test_custom_skip_predicate() {
183 let predictor = Predictor::<1>::new(
184 10,
185 Some(|custom_reason| matches!(custom_reason, "Skipping")),
186 );
187 let key = CacheKey::new("a", "b", "c");
188 assert!(predictor.cacheable_prediction(&key));
190
191 predictor.mark_uncacheable(&key, NoCacheReason::InternalError);
193 assert!(predictor.cacheable_prediction(&key));
194
195 predictor.mark_uncacheable(&key, NoCacheReason::Custom("DontCacheMe"));
197 assert!(!predictor.cacheable_prediction(&key));
198
199 let key = CacheKey::new("a", "c", "d");
200 assert!(predictor.cacheable_prediction(&key));
201 predictor.mark_uncacheable(&key, NoCacheReason::Custom("Skipping"));
203 assert!(predictor.cacheable_prediction(&key));
204 }
205
206 #[test]
207 fn test_mark_uncacheable_lru() {
208 let predictor = Predictor::<1>::new(3, None);
209 let key1 = CacheKey::new("a", "b", "c");
210 predictor.mark_uncacheable(&key1, NoCacheReason::OriginNotCache);
211 assert!(!predictor.cacheable_prediction(&key1));
212
213 let key2 = CacheKey::new("a", "bc", "c");
214 predictor.mark_uncacheable(&key2, NoCacheReason::OriginNotCache);
215 assert!(!predictor.cacheable_prediction(&key2));
216
217 let key3 = CacheKey::new("a", "cd", "c");
218 predictor.mark_uncacheable(&key3, NoCacheReason::OriginNotCache);
219 assert!(!predictor.cacheable_prediction(&key3));
220
221 predictor.mark_uncacheable(&key1, NoCacheReason::OriginNotCache);
223
224 let key4 = CacheKey::new("a", "de", "c");
225 predictor.mark_uncacheable(&key4, NoCacheReason::OriginNotCache);
226 assert!(!predictor.cacheable_prediction(&key4));
227
228 assert!(!predictor.cacheable_prediction(&key1));
230 assert!(predictor.cacheable_prediction(&key2));
232 assert!(!predictor.cacheable_prediction(&key3));
233 assert!(!predictor.cacheable_prediction(&key4));
234 }
235}