valence_core/
read_cache.rs1use std::sync::{Arc, OnceLock};
11
12use quick_cache::sync::Cache;
13use serde_json::Value;
14
15use crate::backend::DatabaseBackend;
16use crate::error::Result;
17use crate::instrumentation;
18use crate::ownership::{
19 ownership_unified_fetch_enabled, OwnershipGateStatus, OwnershipService, RecordOwnershipBundle,
20};
21
22fn cache_key(table: &str, id: &str) -> (String, String) {
23 (table.to_string(), id.to_string())
24}
25
26pub fn read_cache_enabled() -> bool {
28 static ENABLED: OnceLock<bool> = OnceLock::new();
29 *ENABLED.get_or_init(|| {
30 !matches!(
31 std::env::var("VALENCE_READ_CACHE").as_deref(),
32 Ok("0") | Ok("false") | Ok("FALSE")
33 )
34 })
35}
36
37fn read_cache_capacity() -> usize {
38 static CAPACITY: OnceLock<usize> = OnceLock::new();
39 *CAPACITY.get_or_init(|| {
40 std::env::var("VALENCE_READ_CACHE_MAX")
41 .ok()
42 .and_then(|v| v.parse().ok())
43 .unwrap_or(10_000)
44 .max(1)
45 })
46}
47
48fn global_cache() -> &'static Cache<(String, String), RecordOwnershipBundle> {
49 static CACHE: OnceLock<Cache<(String, String), RecordOwnershipBundle>> = OnceLock::new();
50 CACHE.get_or_init(|| Cache::new(read_cache_capacity()))
51}
52
53pub async fn get_record_via_cache(
55 backend: &Arc<dyn DatabaseBackend>,
56 table: &str,
57 id: &str,
58) -> Result<Option<Value>> {
59 if read_cache_enabled() {
60 let key = cache_key(table, id);
61 if let Some(cached) = global_cache().get(&key) {
62 instrumentation::record_ownership_fetch_mode("cache_hit_row");
63 return Ok(cached.row);
64 }
65 let row = backend.get_record(table, id).await?;
66 if row.is_some() || ownership_unified_fetch_enabled() {
67 global_cache().insert(
68 key,
69 RecordOwnershipBundle {
70 row: row.clone(),
71 ownership_status: OwnershipGateStatus::NotFetched,
72 },
73 );
74 }
75 return Ok(row);
76 }
77 backend.get_record(table, id).await
78}
79
80pub async fn get_record_with_ownership_bundle_via_cache(
82 backend: &Arc<dyn DatabaseBackend>,
83 table: &str,
84 id: &str,
85 valence_model: &str,
86 v: &crate::runtime::Valence,
87) -> Result<RecordOwnershipBundle> {
88 if read_cache_enabled() {
89 let key = cache_key(table, id);
90 if let Some(cached) = global_cache().get(&key) {
91 if cached.ownership_status != OwnershipGateStatus::NotFetched {
92 instrumentation::record_ownership_fetch_mode("cache_hit");
93 return Ok(cached);
94 }
95 }
96 }
97
98 let bundle = OwnershipService::fetch_record_with_ownership_gate_uncached(
99 backend,
100 table,
101 id,
102 valence_model,
103 v,
104 )
105 .await?;
106 instrumentation::record_ownership_fetch_mode("unified");
107
108 if read_cache_enabled() {
109 global_cache().insert(cache_key(table, id), bundle.clone());
110 }
111 Ok(bundle)
112}
113
114pub fn invalidate(table: &str, id: &str) {
116 if read_cache_enabled() {
117 global_cache().remove(&cache_key(table, id));
118 }
119}
120
121#[doc(hidden)]
123pub fn clear_for_test() {
124 global_cache().clear();
125}
126
127#[cfg(test)]
128mod tests {
129 use super::*;
130
131 #[test]
132 fn read_cache_enabled_by_default() {
133 assert!(read_cache_enabled());
134 }
135
136 #[test]
137 fn read_cache_capacity_defaults_to_10k() {
138 assert_eq!(read_cache_capacity(), 10_000);
139 }
140}