use std::collections::{BTreeMap, BTreeSet};
use serde::{Deserialize, Serialize};
use crate::cluster::{ClusterNodeId, PartitionId};
use crate::grid::hardening::ReplicatedValueRecord;
use crate::grid::ReplicatedSlot;
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
pub struct KeyRange {
pub start: String,
pub end: String,
}
impl KeyRange {
pub fn single(key: impl Into<String>) -> Self {
let key = key.into();
Self {
start: key.clone(),
end: key,
}
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct RepairToken {
pub last_key: Option<String>,
}
impl RepairToken {
pub fn covers(&self, key: &str) -> bool {
self.last_key
.as_deref()
.map(|last| key <= last)
.unwrap_or(false)
}
fn advance_to(&mut self, key: &str) {
if self
.last_key
.as_deref()
.map(|last| key > last)
.unwrap_or(true)
{
self.last_key = Some(key.to_owned());
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct MerkleTree {
pub partition: PartitionId,
leaves: BTreeMap<String, u64>,
}
impl MerkleTree {
pub fn from_records(
partition: PartitionId,
records: &BTreeMap<String, ReplicatedValueRecord>,
) -> Self {
let leaves = records
.iter()
.filter(|(_, record)| record.partition == partition)
.map(|(key, record)| (key.clone(), hash_record(key, record)))
.collect();
Self { partition, leaves }
}
pub fn empty(partition: PartitionId) -> Self {
Self {
partition,
leaves: BTreeMap::new(),
}
}
pub fn len(&self) -> usize {
self.leaves.len()
}
pub fn is_empty(&self) -> bool {
self.leaves.is_empty()
}
pub fn diff(&self, other: &Self) -> Vec<KeyRange> {
self.diff_after(other, &RepairToken::default())
}
pub fn diff_after(&self, other: &Self, watermark: &RepairToken) -> Vec<KeyRange> {
let keys = self
.leaves
.keys()
.chain(other.leaves.keys())
.filter(|key| !watermark.covers(key))
.cloned()
.collect::<BTreeSet<_>>();
keys.into_iter()
.filter(|key| self.leaves.get(key) != other.leaves.get(key))
.map(KeyRange::single)
.collect()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum RepairKind {
ForegroundReadRepair,
ScheduledIncremental,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct RepairSession {
pub partition: PartitionId,
pub peers: Vec<ClusterNodeId>,
pub repaired_watermark: RepairToken,
}
impl RepairSession {
pub fn new(partition: PartitionId, peers: Vec<ClusterNodeId>) -> Self {
Self {
partition,
peers,
repaired_watermark: RepairToken::default(),
}
}
pub fn run(&mut self, left: &MerkleTree, right: &MerkleTree, kind: RepairKind) -> RepairReport {
let before = self.repaired_watermark.clone();
let ranges = left.diff_after(right, &self.repaired_watermark);
for range in &ranges {
self.repaired_watermark.advance_to(&range.end);
}
let skipped_repaired_ranges = left
.diff(right)
.into_iter()
.filter(|range| before.covers(&range.end))
.count();
RepairReport {
kind,
partition: self.partition,
ranges,
repaired_watermark: self.repaired_watermark.clone(),
skipped_repaired_ranges,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct RepairReport {
pub kind: RepairKind,
pub partition: PartitionId,
pub ranges: Vec<KeyRange>,
pub repaired_watermark: RepairToken,
pub skipped_repaired_ranges: usize,
}
impl RepairReport {
pub fn ranges_exchanged(&self) -> usize {
self.ranges.len()
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ForegroundReadRepairOutcome {
pub served: Option<ReplicatedValueRecord>,
pub repairs: Vec<ReplicatedValueRecord>,
}
pub fn foreground_read_repair<I>(records: I) -> ForegroundReadRepairOutcome
where
I: IntoIterator<Item = Option<ReplicatedValueRecord>>,
{
let present = records.into_iter().flatten().collect::<Vec<_>>();
let served = present
.iter()
.cloned()
.reduce(|left, right| left.merge(right));
let repairs = served
.as_ref()
.map(|fresh| {
present
.iter()
.filter(|record| *record != fresh)
.map(|_| fresh.clone())
.collect::<Vec<_>>()
})
.unwrap_or_default();
ForegroundReadRepairOutcome { served, repairs }
}
fn hash_record(key: &str, record: &ReplicatedValueRecord) -> u64 {
let mut hash = Fnv64::new();
hash.bytes(key.as_bytes());
hash.u32(record.partition.value());
hash.u64(record.version);
hash.u64(record.epoch.value());
match &record.state {
ReplicatedSlot::Value { value, version } => {
hash.u8(1);
hash.u64(*version);
hash.bytes(value);
}
ReplicatedSlot::Tombstone {
version,
gc_eligible_after,
} => {
hash.u8(2);
hash.u64(*version);
hash.u64(
gc_eligible_after
.map(|epoch| epoch.value())
.unwrap_or(u64::MAX),
);
}
}
hash.finish()
}
#[derive(Debug, Clone, Copy)]
struct Fnv64(u64);
impl Fnv64 {
const OFFSET: u64 = 0xcbf2_9ce4_8422_2325;
const PRIME: u64 = 0x0000_0100_0000_01b3;
fn new() -> Self {
Self(Self::OFFSET)
}
fn u8(&mut self, value: u8) {
self.0 ^= u64::from(value);
self.0 = self.0.wrapping_mul(Self::PRIME);
}
fn u32(&mut self, value: u32) {
self.bytes(&value.to_le_bytes());
}
fn u64(&mut self, value: u64) {
self.bytes(&value.to_le_bytes());
}
fn bytes(&mut self, value: &[u8]) {
for byte in value {
self.u8(*byte);
}
}
fn finish(self) -> u64 {
self.0
}
}