tibba_cache/
two_level_store.rs1use super::{Error, Expired, RedisCache, TtlLruStore};
16use serde::{Serialize, de::DeserializeOwned};
17use std::num::NonZeroUsize;
18use std::time::{Duration, SystemTime, UNIX_EPOCH};
19
20type Result<T> = std::result::Result<T, Error>;
21
22#[inline]
23fn now_secs() -> u64 {
24 SystemTime::now()
25 .duration_since(UNIX_EPOCH)
26 .unwrap_or_default()
27 .as_secs()
28}
29
30#[inline]
33fn get_ttl_by_unit(unit: Duration) -> Duration {
34 let secs = unit.as_secs();
35 if secs == 0 {
36 return Duration::ZERO;
37 }
38 let remaining = secs - (now_secs() % secs);
40 if remaining < secs / 10 {
42 return Duration::from_secs(secs + remaining);
43 }
44 Duration::from_secs(remaining)
45}
46
47#[derive(Clone)]
49struct ExpiredCache<T> {
50 data: T,
52 expired_at: u64,
54}
55
56impl<T> Expired for ExpiredCache<T> {
57 fn is_expired(&self) -> bool {
59 now_secs() >= self.expired_at
60 }
61}
62
63pub struct TwoLevelStore<T> {
66 lru: TtlLruStore<ExpiredCache<T>>,
68 ttl: Duration,
70 redis: RedisCache,
72}
73
74impl<T: Clone + Serialize + DeserializeOwned> TwoLevelStore<T> {
75 pub fn new(redis: RedisCache, size: NonZeroUsize, ttl: Duration) -> Self {
78 Self {
79 lru: TtlLruStore::new(size),
80 ttl,
81 redis,
82 }
83 }
84
85 async fn fill_lru(&self, key: &str, value: T, ttl: Duration) {
86 if ttl.is_zero() {
87 return;
88 }
89 self.lru
90 .set(
91 key,
92 ExpiredCache {
93 data: value,
94 expired_at: now_secs() + ttl.as_secs(),
95 },
96 )
97 .await;
98 }
99
100 pub async fn set(&self, key: &str, value: T) -> Result<()> {
103 let ttl = get_ttl_by_unit(self.ttl);
105
106 self.redis.set_struct(key, &value, Some(ttl)).await?;
108 self.fill_lru(key, value, ttl).await;
109
110 Ok(())
111 }
112
113 pub async fn get(&self, key: &str) -> Result<Option<T>> {
116 if let Some(value) = self.lru.get(key).await {
118 return Ok(Some(value.data));
119 }
120 let result: Option<T> = self.redis.get_struct(key).await?;
122 if let Some(value) = &result {
123 let ttl = get_ttl_by_unit(self.ttl);
124 if ttl <= self.ttl {
126 self.fill_lru(key, value.clone(), ttl).await;
127 }
128 }
129 Ok(result)
130 }
131
132 pub async fn del(&self, key: &str) -> Result<()> {
134 self.lru.del(key).await;
135 self.redis.del(key).await
136 }
137
138 pub async fn purge_expired(&self) {
141 self.lru.purge_expired().await;
142 }
143}