use std::{ collections::{BTreeMap, HashSet}, sync::{Arc, Mutex}, time::{Instant, Duration}, }; /// In-process cache avoids a round-trip to redis for each post #[derive(Clone)] pub struct PostsCache { inner: Arc>, } struct PostsCacheInner { cache: HashSet>, ages: BTreeMap>, size: usize, } impl PostsCache { #[must_use] pub fn new(size: usize) -> Self { PostsCache { inner: Arc::new(Mutex::new(PostsCacheInner { cache: HashSet::new(), ages: BTreeMap::new(), size, })), } } // returns true if already exists #[must_use] pub fn insert(&self, k: String) -> bool { let k = Arc::new(k); let mut inner = self.inner.lock().expect("lock"); if inner.cache.contains(&k) { metrics::increment_counter!("posts_cache_hits", "type" => "hit"); return true; } let mut now = Instant::now(); while inner.ages.get(&now).is_some() { now += Duration::from_millis(1); } inner.ages.insert(now, k.clone()); inner.cache.insert(k); while inner.cache.len() > inner.size { let oldest = inner.ages.keys().copied().next().expect("ages first"); let oldest_k = inner.ages.remove(&oldest).expect("remove oldest"); inner.cache.remove(&oldest_k); } metrics::increment_counter!("posts_cache_hits", "type" => "miss"); false } }