use serde::{Deserialize, Serialize};
use crate::grid::hardening::{MergePolicy, ReplicatedValueRecord};
use crate::grid::session_context::{PartitionKey, SessionWatermark, VersionStamp};
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct StalenessBound {
pub max_version_lag: u64,
}
impl StalenessBound {
pub const fn versions(max_version_lag: u64) -> Self {
Self { max_version_lag }
}
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum SessionReadMode {
#[default]
Causal,
BoundedStaleness {
max: StalenessBound,
},
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum StalenessEscalationReason {
BelowCausalFloor,
BelowSessionWatermark,
BeyondBound,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum StalenessDecision {
ServeCausal {
observed_version_lag: u64,
},
ServeFast {
observed_version_lag: u64,
},
Escalate {
reason: StalenessEscalationReason,
observed_version_lag: u64,
},
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct BoundedStalenessMetrics {
pub bounded_staleness_fast_serves_total: u64,
pub bounded_staleness_escalations_total: u64,
}
pub fn converge_replicated_values<P, I>(policy: &P, records: I) -> Option<ReplicatedValueRecord>
where
P: MergePolicy + ?Sized,
I: IntoIterator<Item = ReplicatedValueRecord>,
{
let mut winner = None;
for record in records {
winner = policy.merge(winner.as_ref(), &record);
}
winner
}
pub fn resolve_session_read_mode(
watermark: &SessionWatermark,
key: &PartitionKey,
causal_floor: Option<VersionStamp>,
candidate: VersionStamp,
mode: SessionReadMode,
) -> StalenessDecision {
let observed_version_lag = watermark
.highest_seen(key)
.map(|seen| seen.version_distance_from(candidate))
.unwrap_or_default();
if causal_floor.is_some_and(|floor| candidate < floor) {
return StalenessDecision::Escalate {
reason: StalenessEscalationReason::BelowCausalFloor,
observed_version_lag,
};
}
match mode {
SessionReadMode::Causal => {
if watermark
.highest_seen(key)
.is_some_and(|required| candidate < required)
{
StalenessDecision::Escalate {
reason: StalenessEscalationReason::BelowSessionWatermark,
observed_version_lag,
}
} else {
StalenessDecision::ServeCausal {
observed_version_lag,
}
}
}
SessionReadMode::BoundedStaleness { max }
if observed_version_lag <= max.max_version_lag =>
{
StalenessDecision::ServeFast {
observed_version_lag,
}
}
SessionReadMode::BoundedStaleness { .. } => StalenessDecision::Escalate {
reason: StalenessEscalationReason::BeyondBound,
observed_version_lag,
},
}
}
pub fn within_staleness_bound(
watermark: &SessionWatermark,
key: &PartitionKey,
causal_floor: Option<VersionStamp>,
candidate: VersionStamp,
mode: SessionReadMode,
) -> bool {
matches!(
resolve_session_read_mode(watermark, key, causal_floor, candidate, mode),
StalenessDecision::ServeCausal { .. } | StalenessDecision::ServeFast { .. }
)
}