1use serde::{Deserialize, Serialize};
9
10pub const MAX_OPTIMIZATION_DIAGNOSTICS: usize = 8;
13
14#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
16#[serde(deny_unknown_fields)]
17pub struct OptimizationPolicy {
18 pub key_schema_version: u32,
19 pub cache_namespace_version: u64,
20 pub admission: OptimizationAdmission,
21 pub expiry: ExpiryBasis,
22 #[serde(default, skip_serializing_if = "Option::is_none")]
23 pub batch: Option<BatchPolicy>,
24 #[serde(default, skip_serializing_if = "Option::is_none")]
25 pub memory_cache: Option<MemoryCachePolicy>,
26 #[serde(default, skip_serializing_if = "Option::is_none")]
28 pub shared_calls: Option<SharedCallPolicy>,
29 #[serde(default, skip_serializing_if = "Option::is_none")]
32 pub persistent_cache: Option<PersistentCachePolicy>,
33 #[serde(default, skip_serializing_if = "Option::is_none")]
36 pub decision_cache: Option<DecisionCachePolicy>,
37}
38
39#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
41#[serde(deny_unknown_fields)]
42pub struct SharedCallPolicy {
43 pub max_shared_calls: u32,
45 pub max_waiters_per_call: u32,
47 pub allocation: SharedChargeAllocation,
48}
49
50#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
51#[serde(rename_all = "snake_case")]
52pub enum SharedChargeAllocation {
53 FinalOwner,
56}
57
58#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
61#[serde(deny_unknown_fields)]
62pub struct PersistentCachePolicy {
63 pub store_contract: StoreContract,
64 pub max_entry_bytes: u64,
65 pub max_scope_bytes: u64,
66 pub max_namespace_bytes: u64,
67 pub ttl_ms: u64,
68 pub on_store_failure: StoreFailurePolicy,
69}
70
71#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
75#[serde(deny_unknown_fields)]
76pub struct StoreContract {
77 pub schema: String,
78 pub namespace: String,
79 pub durability: String,
80 pub encryption: String,
81 pub atomicity: String,
82 pub conflict: String,
83 pub expiry: String,
84 pub invalidation: String,
85 pub erasure: String,
86}
87
88impl StoreContract {
89 pub fn terms(&self) -> [(&'static str, &str); 9] {
91 [
92 ("schema", &self.schema),
93 ("namespace", &self.namespace),
94 ("durability", &self.durability),
95 ("encryption", &self.encryption),
96 ("atomicity", &self.atomicity),
97 ("conflict", &self.conflict),
98 ("expiry", &self.expiry),
99 ("invalidation", &self.invalidation),
100 ("erasure", &self.erasure),
101 ]
102 }
103}
104
105#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
106#[serde(rename_all = "snake_case")]
107pub enum StoreFailurePolicy {
108 FailClosed,
111 ContinueWithoutCache,
113}
114
115#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
118#[serde(deny_unknown_fields)]
119pub struct DecisionCachePolicy {
120 pub tiers: Vec<DecisionCacheTier>,
123 pub max_entry_bytes: u64,
124 #[serde(default, skip_serializing_if = "Option::is_none")]
126 pub max_entries: Option<u64>,
127 pub ttl_ms: u64,
128 pub volatile: Vec<String>,
131 pub key_dimensions: Vec<String>,
133 #[serde(default)]
135 pub on_refresh_failure: RefreshFailurePolicy,
136}
137
138#[derive(
139 Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, schemars::JsonSchema,
140)]
141#[serde(rename_all = "snake_case")]
142pub enum DecisionCacheTier {
143 Memory,
144 Persistent,
145}
146
147#[derive(
150 Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema,
151)]
152#[serde(rename_all = "snake_case")]
153pub enum RefreshFailurePolicy {
154 #[default]
156 Unresolved,
157 Recompute,
159 ServeCachedWithinFreshness,
162}
163
164#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
165#[serde(rename_all = "snake_case")]
166pub enum OptimizationAdmission {
167 Refuse,
169 ExecuteIndependent,
171}
172
173#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
174#[serde(rename_all = "snake_case")]
175pub enum ExpiryBasis {
176 InjectedRuntimeTime,
179}
180
181#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
182#[serde(deny_unknown_fields)]
183pub struct BatchPolicy {
184 pub max_members: u32,
185 pub max_canonical_bytes: u64,
186 pub max_members_per_scope: u32,
187 pub max_queue_delay_ms: u64,
190 pub max_backend_work: u64,
191 pub allocation: BatchChargeAllocation,
192}
193
194#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
197#[serde(rename_all = "snake_case")]
198pub enum BatchChargeAllocation {
199 EvenRemainderByMemberIndex,
204}
205
206#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
209#[serde(deny_unknown_fields)]
210pub struct MemoryCachePolicy {
211 #[serde(
214 default,
215 skip_serializing_if = "std::ops::Not::not",
216 deserialize_with = "acquisition_pages"
217 )]
218 #[schemars(schema_with = "crate::acquisition_record::constructed_schema")]
221 pub acquisition_pages: bool,
222 pub max_bytes: u64,
223 pub max_entries: u64,
224 pub max_entry_bytes: u64,
225 pub max_scope_bytes: u64,
226 pub ttl_ms: u64,
227 pub eviction: EvictionOrder,
228}
229
230#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
231#[serde(rename_all = "snake_case")]
232pub enum EvictionOrder {
233 Fifo,
236}
237
238#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
239#[serde(rename_all = "snake_case")]
240pub enum OptimizationMechanism {
241 Independent,
242 Batch,
243 MemoryCache,
244 SharedCall,
247 PersistentCache,
250 DecisionCache,
253}
254
255#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
256#[serde(rename_all = "snake_case")]
257pub enum OptimizationOutcome {
258 Bypassed,
259 Miss,
260 Hit,
261 Batched,
262 Refused,
263}
264
265#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
268#[serde(deny_unknown_fields)]
269pub struct OptimizationRecord {
270 pub mechanism: OptimizationMechanism,
271 pub key_schema_version: u32,
272 pub outcome: OptimizationOutcome,
273 #[serde(default, skip_serializing_if = "Option::is_none")]
274 pub entry_id: Option<String>,
275 #[serde(default, skip_serializing_if = "Option::is_none")]
276 pub batch_id: Option<String>,
277 #[serde(default, skip_serializing_if = "Option::is_none")]
278 pub batch_member: Option<u32>,
279 #[serde(default, skip_serializing_if = "Option::is_none")]
283 pub source_attempt_id: Option<String>,
284 pub age_ms: u64,
285 pub invalidation_generation: u64,
286 pub charge_owner: bool,
287 pub allocated_charge_units: u64,
288 pub shared_liability_units: u64,
289 #[serde(default, skip_serializing_if = "Vec::is_empty")]
292 pub diagnostics: Vec<String>,
293 #[serde(default, skip_serializing_if = "Option::is_none")]
295 pub shared: Option<SharedRecord>,
296}
297
298#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
302#[serde(deny_unknown_fields)]
303pub struct SharedRecord {
304 pub shared_call_id: String,
305 pub attempt_id: String,
306 pub waiter_id: String,
307 pub role: SharedRole,
308 #[serde(default, skip_serializing_if = "Option::is_none")]
309 pub left: Option<SharedLeave>,
310 #[serde(default, skip_serializing_if = "Option::is_none")]
311 pub handed_off_to: Option<String>,
312 #[serde(default, skip_serializing_if = "Option::is_none")]
313 pub received_from: Option<String>,
314 pub final_owner: bool,
315}
316
317#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
318#[serde(rename_all = "snake_case")]
319pub enum SharedRole {
320 Led,
321 Joined,
322}
323
324#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
326#[serde(rename_all = "snake_case")]
327pub enum SharedLeave {
328 Cancelled,
329 Deadline,
330}
331
332#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, schemars::JsonSchema)]
336#[serde(deny_unknown_fields)]
337pub struct DecisionCacheRecord {
338 pub outcome: DecisionCacheOutcome,
339 #[serde(default, skip_serializing_if = "Option::is_none")]
341 pub entry_id: Option<String>,
342 #[serde(default, skip_serializing_if = "Option::is_none")]
344 pub source_run_record: Option<crate::ids::ContentDigest>,
345 #[serde(default, skip_serializing_if = "Option::is_none")]
347 pub age_ms: Option<u64>,
348 #[serde(default, skip_serializing_if = "Option::is_none")]
350 pub generations: Option<crate::cache_entry::EntryGenerations>,
351 #[serde(default, skip_serializing_if = "Vec::is_empty")]
353 pub reused: Vec<u32>,
354 #[serde(default, skip_serializing_if = "Vec::is_empty")]
356 pub refreshed: Vec<u32>,
357 #[serde(default, skip_serializing_if = "Vec::is_empty")]
359 pub mismatched: Vec<u32>,
360 #[serde(default, skip_serializing_if = "Vec::is_empty")]
363 pub substituted: Vec<SubstitutedInput>,
364 #[serde(default, skip_serializing_if = "Option::is_none")]
366 pub on_refresh_failure: Option<RefreshFailurePolicy>,
367 #[serde(default, skip_serializing_if = "Option::is_none")]
370 pub abandoned: Option<AbandonedRun>,
371 #[serde(default, skip_serializing_if = "Vec::is_empty")]
373 pub diagnostics: Vec<String>,
374}
375
376#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
377#[serde(rename_all = "snake_case")]
378pub enum DecisionCacheOutcome {
379 Hit,
381 Refreshed,
383 Miss,
385 Bypassed,
387 Refused,
389}
390
391#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, schemars::JsonSchema)]
395#[serde(deny_unknown_fields)]
396pub struct AbandonedRun {
397 #[serde(default, skip_serializing_if = "Vec::is_empty")]
398 pub requests: Vec<crate::run::RequestRecord>,
399 #[serde(default, skip_serializing_if = "Vec::is_empty")]
400 pub acquisitions: Vec<crate::acquisition_record::AcquisitionRecord>,
401}
402
403#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
406#[serde(deny_unknown_fields)]
407pub struct SubstitutedInput {
408 pub input: String,
409 pub entry_digest: crate::ids::ContentDigest,
410}
411
412impl OptimizationRecord {
413 pub fn diagnose(&mut self, diagnostic: impl Into<String>) {
415 match self.diagnostics.len() {
416 n if n + 1 < MAX_OPTIMIZATION_DIAGNOSTICS => self.diagnostics.push(diagnostic.into()),
417 n if n + 1 == MAX_OPTIMIZATION_DIAGNOSTICS => {
418 self.diagnostics.push("further diagnostics dropped".into());
419 }
420 _ => {}
421 }
422 }
423}
424
425fn acquisition_pages<'de, D: serde::Deserializer<'de>>(d: D) -> Result<bool, D::Error> {
426 if !bool::deserialize(d)? {
427 return Err(serde::de::Error::custom(
428 "omit acquisition_pages when disabled",
429 ));
430 }
431 Ok(true)
432}