use std::fmt;
use serde::{Deserialize, Serialize};
use crate::grid::consistency_level::ConsistencyLevel;
use crate::grid::session_context::{PartitionKey, SessionWatermark, VersionStamp};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ReadEscalation {
ServeLocal,
TryHigherLevel(ConsistencyLevel),
ReadRepair,
WaitThenFail,
FailUnmet,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub struct SessionReadBudget {
pub allow_higher_level: bool,
pub allow_read_repair: bool,
pub wait_budget_millis: u64,
}
impl SessionReadBudget {
pub const fn strict() -> Self {
Self {
allow_higher_level: true,
allow_read_repair: true,
wait_budget_millis: 1,
}
}
pub const fn fail_fast() -> Self {
Self {
allow_higher_level: false,
allow_read_repair: false,
wait_budget_millis: 0,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SessionGuaranteeUnmet {
pub key: PartitionKey,
pub required: VersionStamp,
pub candidate: VersionStamp,
}
impl fmt::Display for SessionGuaranteeUnmet {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
formatter,
"session guarantee unmet for partition {}: candidate {:?} below required {:?}",
self.key.partition.value(),
self.candidate,
self.required
)
}
}
impl std::error::Error for SessionGuaranteeUnmet {}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct SessionRywMetrics {
pub session_ryw_escalations_total: u64,
pub session_guarantee_unmet_total: u64,
}
pub fn resolve_session_read(
watermark: &SessionWatermark,
key: &PartitionKey,
replica_stamp: VersionStamp,
budget: SessionReadBudget,
) -> ReadEscalation {
let Some(required) = watermark.highest_seen(key) else {
return ReadEscalation::ServeLocal;
};
if replica_stamp >= required {
return ReadEscalation::ServeLocal;
}
if budget.allow_higher_level {
return ReadEscalation::TryHigherLevel(ConsistencyLevel::Quorum);
}
if budget.allow_read_repair {
return ReadEscalation::ReadRepair;
}
if budget.wait_budget_millis > 0 {
return ReadEscalation::WaitThenFail;
}
ReadEscalation::FailUnmet
}
pub fn serve_session_read(
watermark: &mut SessionWatermark,
key: PartitionKey,
replica_stamp: VersionStamp,
budget: SessionReadBudget,
) -> Result<ReadEscalation, SessionGuaranteeUnmet> {
match resolve_session_read(watermark, &key, replica_stamp, budget) {
ReadEscalation::ServeLocal => {
watermark.observe(key, replica_stamp);
Ok(ReadEscalation::ServeLocal)
}
ReadEscalation::FailUnmet => {
let required = watermark.highest_seen(&key).unwrap_or(replica_stamp);
Err(SessionGuaranteeUnmet {
key,
required,
candidate: replica_stamp,
})
}
escalation => Ok(escalation),
}
}