1use crate::error::Result;
10use crate::value::Value;
11use chrono::{DateTime, Utc};
12use serde::{Deserialize, Serialize};
13use sha2::{Digest, Sha256};
14use std::fmt;
15
16#[derive(Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
18pub struct CacheKey(pub [u8; 32]);
19
20impl CacheKey {
21 pub fn from_parts(parts: &[&[u8]]) -> Self {
23 let mut hasher = Sha256::new();
24 for part in parts {
25 hasher.update((part.len() as u64).to_le_bytes());
28 hasher.update(part);
29 }
30 Self(hasher.finalize().into())
31 }
32
33 pub fn for_state(config_hash: &CacheKey, x_hash: &CacheKey, y_hash: Option<&CacheKey>) -> Self {
41 match y_hash {
42 Some(y) => Self::from_parts(&[&config_hash.0, &x_hash.0, b"y", &y.0]),
43 None => Self::from_parts(&[&config_hash.0, &x_hash.0]),
44 }
45 }
46
47 pub fn for_output(
50 config_hash: &CacheKey,
51 state_hash: &CacheKey,
52 input_hash: &CacheKey,
53 ) -> Self {
54 Self::from_parts(&[&config_hash.0, &state_hash.0, &input_hash.0])
55 }
56
57 pub fn hash_data(data: &[u8]) -> Self {
59 Self::from_parts(&[data])
60 }
61
62 pub fn for_value(value: &Value) -> Self {
78 let mut hasher = Sha256::new();
79 Self::absorb(&mut hasher, value);
80 Self(hasher.finalize().into())
81 }
82
83 fn absorb(hasher: &mut Sha256, value: &Value) {
84 match value {
87 Value::Tensor { values, shape } => {
88 hasher.update([0u8]);
89 hasher.update((shape.len() as u64).to_le_bytes());
90 for dim in shape {
91 hasher.update((*dim as u64).to_le_bytes());
92 }
93 hasher.update((values.len() as u64).to_le_bytes());
94 for v in values.iter() {
95 hasher.update(v.to_bits().to_le_bytes());
96 }
97 }
98 Value::Text(text) => {
99 hasher.update([5u8]);
100 hasher.update((text.len() as u64).to_le_bytes());
101 hasher.update(text.as_bytes());
102 }
103 Value::Json(json) => {
104 hasher.update([1u8]);
105 let bytes = serde_json::to_vec(json.as_ref()).unwrap_or_default();
109 hasher.update((bytes.len() as u64).to_le_bytes());
110 hasher.update(&bytes);
111 }
112 Value::Bytes(bytes) => {
113 hasher.update([2u8]);
114 hasher.update((bytes.len() as u64).to_le_bytes());
115 hasher.update(bytes.as_slice());
116 }
117 Value::Object(bytes) => {
118 hasher.update([3u8]);
119 hasher.update((bytes.len() as u64).to_le_bytes());
120 hasher.update(bytes.as_slice());
121 }
122 Value::Empty => hasher.update([4u8]),
123 }
128 }
129
130 pub fn to_hex(&self) -> String {
132 self.0.iter().map(|b| format!("{b:02x}")).collect()
133 }
134}
135
136impl fmt::Debug for CacheKey {
137 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
138 write!(f, "CacheKey({}...)", &self.to_hex()[..12])
139 }
140}
141
142impl fmt::Display for CacheKey {
143 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
144 write!(f, "{}", &self.to_hex()[..16])
145 }
146}
147
148#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
150pub enum CacheTier {
151 Memory,
153 Local,
155 Remote,
157}
158
159#[derive(Debug, Clone, Serialize, Deserialize)]
161pub enum Origin {
162 Computed {
164 node_id: String,
166 run_id: String,
168 },
169 Ingested {
171 source: String,
173 },
174 Streamed {
176 window_start: DateTime<Utc>,
178 window_end: DateTime<Utc>,
180 },
181}
182
183#[derive(Debug, Clone, Serialize, Deserialize)]
185pub struct EntryMeta {
186 pub key: CacheKey,
188 pub size_bytes: u64,
190 pub created_at: DateTime<Utc>,
192 pub last_accessed: DateTime<Utc>,
194 pub ttl: Option<std::time::Duration>,
196 pub origin: Origin,
198}
199
200pub trait CacheStore: Send + Sync {
205 fn get(&self, key: &CacheKey) -> Result<Option<Value>>;
207
208 fn put(&self, key: &CacheKey, value: &Value) -> Result<()>;
210
211 fn exists(&self, key: &CacheKey) -> Result<bool>;
213
214 fn remove(&self, key: &CacheKey) -> Result<()>;
216
217 fn metadata(&self, key: &CacheKey) -> Result<Option<EntryMeta>>;
219
220 fn put_with_origin(&self, key: &CacheKey, value: &Value, origin: &Origin) -> Result<()> {
223 let _ = origin;
224 self.put(key, value)
225 }
226
227 fn put_computed(
233 &self,
234 key: &CacheKey,
235 value: &Value,
236 origin: &Origin,
237 compute: std::time::Duration,
238 deterministic: bool,
239 ) -> Result<()> {
240 let _ = (compute, deterministic);
241 self.put_with_origin(key, value, origin)
242 }
243
244 fn tier(&self) -> CacheTier {
250 CacheTier::Memory
251 }
252
253 fn get_located(&self, key: &CacheKey) -> Result<Option<(Value, CacheTier)>> {
260 Ok(self.get(key)?.map(|value| (value, self.tier())))
261 }
262}
263
264#[cfg(test)]
265mod tests {
266 use super::*;
267
268 #[test]
269 fn cache_key_deterministic() {
270 let k1 = CacheKey::from_parts(&[b"hello", b"world"]);
271 let k2 = CacheKey::from_parts(&[b"hello", b"world"]);
272 assert_eq!(k1, k2);
273 }
274
275 #[test]
276 fn cache_key_sensitive_to_content() {
277 let k1 = CacheKey::from_parts(&[b"hello", b"world"]);
278 let k2 = CacheKey::from_parts(&[b"hello", b"world!"]);
279 assert_ne!(k1, k2);
280 }
281
282 #[test]
283 fn cache_key_sensitive_to_part_boundaries() {
284 let k1 = CacheKey::from_parts(&[b"ab", b"c"]);
286 let k2 = CacheKey::from_parts(&[b"a", b"bc"]);
287 assert_ne!(k1, k2);
288 }
289
290 #[test]
291 fn cache_key_for_state() {
292 let config = CacheKey::hash_data(b"scaler_config");
293 let data = CacheKey::hash_data(b"training_data");
294 let state_key = CacheKey::for_state(&config, &data, None);
295
296 let state_key2 = CacheKey::for_state(&config, &data, None);
298 assert_eq!(state_key, state_key2);
299
300 let data2 = CacheKey::hash_data(b"different_data");
302 let state_key3 = CacheKey::for_state(&config, &data2, None);
303 assert_ne!(state_key, state_key3);
304 }
305
306 #[test]
307 fn cache_key_for_state_sensitive_to_labels() {
308 let config = CacheKey::hash_data(b"config");
309 let x = CacheKey::hash_data(b"features");
310 let y1 = CacheKey::hash_data(b"labels_a");
311 let y2 = CacheKey::hash_data(b"labels_b");
312
313 let unsupervised = CacheKey::for_state(&config, &x, None);
314 let supervised_a = CacheKey::for_state(&config, &x, Some(&y1));
315 let supervised_b = CacheKey::for_state(&config, &x, Some(&y2));
316
317 assert_ne!(unsupervised, supervised_a);
318 assert_ne!(supervised_a, supervised_b);
319 }
320
321 #[test]
322 fn for_value_deterministic_and_sensitive() {
323 let v1 = Value::tensor(vec![1.0, 2.0], vec![2]);
324 let v2 = Value::tensor(vec![1.0, 2.0], vec![2]);
325 let v3 = Value::tensor(vec![1.0, 2.0], vec![1, 2]);
326
327 assert_eq!(CacheKey::for_value(&v1), CacheKey::for_value(&v2));
328 assert_ne!(CacheKey::for_value(&v1), CacheKey::for_value(&v3));
330 }
331
332 #[test]
336 fn for_value_separates_non_finite_floats() {
337 let nan = Value::tensor(vec![f64::NAN], vec![1]);
338 let pos = Value::tensor(vec![f64::INFINITY], vec![1]);
339 let neg = Value::tensor(vec![f64::NEG_INFINITY], vec![1]);
340
341 assert_eq!(
342 serde_json::to_vec(&nan).unwrap(),
343 serde_json::to_vec(&pos).unwrap(),
344 "the premise: JSON really does flatten these together"
345 );
346
347 assert_ne!(CacheKey::for_value(&nan), CacheKey::for_value(&pos));
348 assert_ne!(CacheKey::for_value(&pos), CacheKey::for_value(&neg));
349 assert_ne!(
351 CacheKey::for_value(&Value::tensor(vec![0.0], vec![1])),
352 CacheKey::for_value(&Value::tensor(vec![-0.0], vec![1]))
353 );
354 }
355
356 #[test]
359 fn for_value_separates_variants_holding_the_same_bytes() {
360 assert_ne!(
361 CacheKey::for_value(&Value::Bytes(std::sync::Arc::new(b"x".to_vec()))),
362 CacheKey::for_value(&Value::Object(std::sync::Arc::new(b"x".to_vec())))
363 );
364 }
365
366 #[test]
367 fn cache_key_for_output() {
368 let config = CacheKey::hash_data(b"config");
369 let state = CacheKey::hash_data(b"state");
370 let input = CacheKey::hash_data(b"input");
371 let key = CacheKey::for_output(&config, &state, &input);
372
373 let state2 = CacheKey::hash_data(b"state2");
375 let key2 = CacheKey::for_output(&config, &state2, &input);
376 assert_ne!(key, key2);
377 }
378
379 #[test]
380 fn cache_key_hex_and_display() {
381 let key = CacheKey::hash_data(b"test");
382 let hex = key.to_hex();
383 assert_eq!(hex.len(), 64); let display = format!("{key}");
386 assert_eq!(display.len(), 16); let debug = format!("{key:?}");
389 assert!(debug.starts_with("CacheKey("));
390 }
391
392 #[test]
393 fn cache_key_serde_roundtrip() {
394 let key = CacheKey::hash_data(b"test_data");
395 let json = serde_json::to_string(&key).unwrap();
396 let deserialized: CacheKey = serde_json::from_str(&json).unwrap();
397 assert_eq!(key, deserialized);
398 }
399}