57 lines
1.5 KiB
Rust
57 lines
1.5 KiB
Rust
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<Mutex<PostsCacheInner>>,
|
|
}
|
|
|
|
struct PostsCacheInner {
|
|
cache: HashSet<Arc<String>>,
|
|
ages: BTreeMap<Instant, Arc<String>>,
|
|
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
|
|
}
|
|
}
|