use std::collections::{BTreeMap, BTreeSet};
use std::fmt;
use serde::{Deserialize, Serialize};
use crate::cluster::{ClusterEpoch, ClusterNodeId};
use crate::grid::active_active::HybridLogicalClock;
use crate::grid::hardening::{ReplicatedValueRecord, ValueVersion};
pub trait ConflictFreeValue: Clone + Send + Sync {
fn merge(&mut self, other: &Self);
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct GCounter {
per_node: BTreeMap<ClusterNodeId, u64>,
}
impl GCounter {
pub fn new() -> Self {
Self::default()
}
pub fn increment(&mut self, node: impl Into<ClusterNodeId>, by: u64) {
let entry = self.per_node.entry(node.into()).or_default();
*entry = entry.saturating_add(by);
}
pub fn component(&self, node: &ClusterNodeId) -> u64 {
self.per_node.get(node).copied().unwrap_or_default()
}
pub fn value(&self) -> u64 {
self.per_node.values().copied().sum()
}
pub fn metadata_bytes(&self) -> u64 {
self.per_node
.keys()
.map(|node| node.as_str().len() as u64 + std::mem::size_of::<u64>() as u64)
.sum()
}
}
impl ConflictFreeValue for GCounter {
fn merge(&mut self, other: &Self) {
for (node, value) in &other.per_node {
let entry = self.per_node.entry(node.clone()).or_default();
*entry = (*entry).max(*value);
}
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct PnCounter {
inc: GCounter,
dec: GCounter,
}
impl PnCounter {
pub fn new() -> Self {
Self::default()
}
pub fn increment(&mut self, node: impl Into<ClusterNodeId>, by: u64) {
self.inc.increment(node, by);
}
pub fn decrement(&mut self, node: impl Into<ClusterNodeId>, by: u64) {
self.dec.increment(node, by);
}
pub fn value(&self) -> i128 {
i128::from(self.inc.value()) - i128::from(self.dec.value())
}
pub fn metadata_bytes(&self) -> u64 {
self.inc
.metadata_bytes()
.saturating_add(self.dec.metadata_bytes())
}
}
impl ConflictFreeValue for PnCounter {
fn merge(&mut self, other: &Self) {
self.inc.merge(&other.inc);
self.dec.merge(&other.dec);
}
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
pub struct OrSetTag {
pub node: ClusterNodeId,
pub counter: u64,
}
impl OrSetTag {
pub fn new(node: impl Into<ClusterNodeId>, counter: u64) -> Self {
Self {
node: node.into(),
counter,
}
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
#[serde(bound(
serialize = "T: Serialize + Ord",
deserialize = "T: Deserialize<'de> + Ord"
))]
pub struct OrSet<T> {
adds: BTreeMap<T, BTreeSet<OrSetTag>>,
removes: BTreeSet<OrSetTag>,
}
impl<T> OrSet<T>
where
T: Clone + Ord,
{
pub fn new() -> Self {
Self {
adds: BTreeMap::new(),
removes: BTreeSet::new(),
}
}
pub fn add(&mut self, value: T, tag: OrSetTag) {
self.adds.entry(value).or_default().insert(tag);
}
pub fn remove(&mut self, value: &T) {
if let Some(tags) = self.adds.get(value) {
self.removes.extend(tags.iter().cloned());
}
}
pub fn contains(&self, value: &T) -> bool {
self.adds
.get(value)
.map(|tags| tags.iter().any(|tag| !self.removes.contains(tag)))
.unwrap_or(false)
}
pub fn values(&self) -> Vec<T> {
self.adds
.keys()
.filter(|value| self.contains(value))
.cloned()
.collect()
}
pub fn metadata_bytes(&self) -> u64 {
let adds = self
.adds
.values()
.map(|tags| tags.len() as u64 * or_set_tag_bytes())
.sum::<u64>();
let removes = self.removes.len() as u64 * or_set_tag_bytes();
adds.saturating_add(removes)
}
}
impl<T> ConflictFreeValue for OrSet<T>
where
T: Clone + Ord + Send + Sync,
{
fn merge(&mut self, other: &Self) {
for (value, tags) in &other.adds {
self.adds
.entry(value.clone())
.or_default()
.extend(tags.iter().cloned());
}
self.removes.extend(other.removes.iter().cloned());
}
}
fn or_set_tag_bytes() -> u64 {
std::mem::size_of::<u64>() as u64 * 2
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct LwwRegister<T> {
value: T,
hlc: HybridLogicalClock,
writer: ClusterNodeId,
}
impl<T> LwwRegister<T>
where
T: Clone,
{
pub fn new(value: T, hlc: HybridLogicalClock, writer: impl Into<ClusterNodeId>) -> Self {
Self {
value,
hlc,
writer: writer.into(),
}
}
pub fn value(&self) -> &T {
&self.value
}
pub fn hlc(&self) -> HybridLogicalClock {
self.hlc
}
pub fn writer(&self) -> &ClusterNodeId {
&self.writer
}
}
impl<T> ConflictFreeValue for LwwRegister<T>
where
T: Clone + Send + Sync,
{
fn merge(&mut self, other: &Self) {
if (other.hlc, &other.writer) > (self.hlc, &self.writer) {
*self = other.clone();
}
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct CrdtMergeStats {
pub merge_total: BTreeMap<String, u64>,
pub conflict_resolved_total: BTreeMap<String, u64>,
pub metadata_bytes: BTreeMap<String, u64>,
}
impl CrdtMergeStats {
pub fn record_merge(&mut self, kind: &'static str, conflict: bool, metadata_bytes: u64) {
*self.merge_total.entry(kind.to_owned()).or_default() += 1;
if conflict {
*self
.conflict_resolved_total
.entry(kind.to_owned())
.or_default() += 1;
}
self.metadata_bytes.insert(kind.to_owned(), metadata_bytes);
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum TombstoneCrdtDecision {
ApplyUpdate,
KeepTombstone,
}
pub fn tombstone_crdt_decision(
tombstone: &ReplicatedValueRecord,
update_version: ValueVersion,
update_epoch: ClusterEpoch,
) -> TombstoneCrdtDecision {
if tombstone.is_tombstone()
&& (tombstone.version, tombstone.epoch) >= (update_version, update_epoch)
{
TombstoneCrdtDecision::KeepTombstone
} else {
TombstoneCrdtDecision::ApplyUpdate
}
}
impl fmt::Display for TombstoneCrdtDecision {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str(match self {
Self::ApplyUpdate => "apply_update",
Self::KeepTombstone => "keep_tombstone",
})
}
}