Skip to content

Commit 32a5349

Browse files
authored
Merge pull request #20 from Bootstrap-Academy/health-cache
feat(health): cache health items independently of each other
2 parents cf143f4 + 5982467 commit 32a5349

File tree

5 files changed

+99
-50
lines changed

5 files changed

+99
-50
lines changed

academy/src/environment/mod.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,9 @@ impl ConfigProvider {
184184
};
185185

186186
let health_feature_config = HealthFeatureConfig {
187-
cache_ttl: config.health.cache_ttl.into(),
187+
database_cache_ttl: config.health.database_cache_ttl.into(),
188+
cache_cache_ttl: config.health.cache_cache_ttl.into(),
189+
email_cache_ttl: config.health.email_cache_ttl.into(),
188190
};
189191

190192
let session_feature_config = SessionFeatureConfig {

academy_config/src/lib.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,9 @@ pub struct InternalConfig {
143143

144144
#[derive(Debug, Deserialize)]
145145
pub struct HealthConfig {
146-
pub cache_ttl: Duration,
146+
pub database_cache_ttl: Duration,
147+
pub cache_cache_ttl: Duration,
148+
pub email_cache_ttl: Duration,
147149
}
148150

149151
#[derive(Debug, Deserialize)]
Lines changed: 85 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use std::{sync::Arc, time::Duration};
1+
use std::{future::Future, sync::Arc, time::Duration};
22

33
use academy_cache_contracts::CacheService;
44
use academy_core_health_contracts::{HealthFeatureService, HealthStatus};
@@ -24,17 +24,21 @@ pub struct HealthFeatureServiceImpl<Time, Db, Cache, Email> {
2424

2525
#[derive(Debug, Clone)]
2626
pub struct HealthFeatureConfig {
27-
pub cache_ttl: Duration,
27+
pub database_cache_ttl: Duration,
28+
pub cache_cache_ttl: Duration,
29+
pub email_cache_ttl: Duration,
2830
}
2931

3032
#[derive(Debug, Default)]
3133
struct State {
32-
cache: RwLock<Option<CachedStatus>>,
34+
database_cache: RwLock<Option<CachedStatusItem>>,
35+
cache_cache: RwLock<Option<CachedStatusItem>>,
36+
email_cache: RwLock<Option<CachedStatusItem>>,
3337
}
3438

3539
#[derive(Debug, Clone, Copy)]
36-
struct CachedStatus {
37-
status: HealthStatus,
40+
struct CachedStatusItem {
41+
status: bool,
3842
timestamp: DateTime<Utc>,
3943
}
4044

@@ -48,63 +52,98 @@ where
4852
{
4953
#[trace_instrument(skip(self))]
5054
async fn get_status(&self) -> HealthStatus {
55+
let database = self.ping_cached(
56+
"database",
57+
&self.state.database_cache,
58+
self.config.database_cache_ttl,
59+
|| async {
60+
self.db
61+
.ping()
62+
.await
63+
.inspect_err(|err| error!("Failed to ping database: {err}"))
64+
.is_ok()
65+
},
66+
);
67+
68+
let cache = self.ping_cached(
69+
"cache",
70+
&self.state.cache_cache,
71+
self.config.cache_cache_ttl,
72+
|| async {
73+
self.cache
74+
.ping()
75+
.await
76+
.inspect_err(|err| error!("Failed to ping cache: {err}"))
77+
.is_ok()
78+
},
79+
);
80+
81+
let email = self.ping_cached(
82+
"email",
83+
&self.state.email_cache,
84+
self.config.email_cache_ttl,
85+
|| async {
86+
self.email
87+
.ping()
88+
.await
89+
.inspect_err(|err| error!("Failed to ping smtp server: {err}"))
90+
.is_ok()
91+
},
92+
);
93+
94+
let (database, cache, email) = tokio::join!(database, cache, email);
95+
96+
HealthStatus {
97+
database,
98+
cache,
99+
email,
100+
}
101+
}
102+
}
103+
104+
impl<Time, Db, Cache, Email> HealthFeatureServiceImpl<Time, Db, Cache, Email>
105+
where
106+
Time: TimeService,
107+
{
108+
#[trace_instrument(skip(self, f))]
109+
async fn ping_cached<F>(
110+
&self,
111+
_item: &'static str,
112+
cache: &RwLock<Option<CachedStatusItem>>,
113+
ttl: Duration,
114+
f: impl FnOnce() -> F,
115+
) -> bool
116+
where
117+
F: Future<Output = bool>,
118+
{
51119
let now = self.time.now();
52120

53-
let status_if_not_expired = |cached: Option<CachedStatus>| {
54-
let CachedStatus { status, timestamp } = cached?;
55-
let ttl = timestamp + self.config.cache_ttl - now;
121+
let status_if_not_expired = |cached: Option<CachedStatusItem>| {
122+
let CachedStatusItem { status, timestamp } = cached?;
123+
let ttl = timestamp + ttl - now;
56124
(ttl > TimeDelta::zero())
57125
.then_some(status)
58126
.inspect(|_| trace!(%ttl, "use cache"))
59127
};
60128

61-
if let Some(status) = status_if_not_expired(*self.state.cache.read().await) {
129+
if let Some(status) = status_if_not_expired(*cache.read().await) {
62130
return status;
63131
}
64132

65133
trace!("cache miss, acquire write lock");
66-
let mut cache_guard = self.state.cache.write().await;
134+
let mut cache_guard = cache.write().await;
67135
if let Some(status) = status_if_not_expired(*cache_guard) {
68136
return status;
69137
}
70138

71-
let database = async {
72-
self.db
73-
.ping()
74-
.await
75-
.inspect_err(|err| error!("Failed to ping database: {err}"))
76-
.is_ok()
77-
};
78-
79-
let cache = async {
80-
self.cache
81-
.ping()
82-
.await
83-
.inspect_err(|err| error!("Failed to ping cache: {err}"))
84-
.is_ok()
85-
};
86-
87-
let email = async {
88-
self.email
89-
.ping()
90-
.await
91-
.inspect_err(|err| error!("Failed to ping smtp server: {err}"))
92-
.is_ok()
93-
};
139+
trace!("ping");
140+
let status = f().await;
94141

95-
let (database, cache, email) = tokio::join!(database, cache, email);
96-
97-
let status = HealthStatus {
98-
database,
99-
cache,
100-
email,
101-
};
142+
*cache_guard = Some(CachedStatusItem {
143+
status,
144+
timestamp: now,
145+
});
102146

103-
cache_guard
104-
.insert(CachedStatus {
105-
status,
106-
timestamp: now,
107-
})
108-
.status
147+
status
109148
}
110149
}

config.toml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,9 @@ jwt_ttl = "10s"
3030
# shop_url = ""
3131

3232
[health]
33-
cache_ttl = "10s"
33+
database_cache_ttl = "10s"
34+
cache_cache_ttl = "10s"
35+
email_cache_ttl = "10s"
3436

3537
[user]
3638
name_change_rate_limit = "30d"

nix/tests/default.nix

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,11 @@
4949
5050
};
5151
internal.shop_url = "http://127.0.0.1:8004/shop/";
52-
health.cache_ttl = "2s";
52+
health = {
53+
database_cache_ttl = "2s";
54+
cache_cache_ttl = "2s";
55+
email_cache_ttl = "2s";
56+
};
5357
contact.email = "contact@academy";
5458
recaptcha = {
5559
enable = lib.mkDefault true;

0 commit comments

Comments
 (0)