diff --git a/src/failure_domain.rs b/src/failure_domain.rs index 1b1dbd36eda16e112d79c62a55f500f307dac708..40f9f14cd371dbaff88e6de41eb910aed0ae2790 100644 --- a/src/failure_domain.rs +++ b/src/failure_domain.rs @@ -3,8 +3,34 @@ use crate::traft::Distance; use crate::util::Uppercase; use std::collections::{HashMap, HashSet}; -//////////////////////////////////////////////////////////////////////////////// -/// Failure domains of a given instance. +/// Failure domain of a given instance. +/// +/// A failure domain is a set of `KEY=VALUE` pairs that we call +/// components. Specifying failure domains provides a user a way to mark +/// two instances as being a single point of failure. Both key and value +/// can be anything meaningful to a user, like `"region=eu"`, or +/// `"dc=msk"`. +/// +/// If two different failure domains have at least one common component +/// (both key and value), it is assumed they both may encounter outage +/// at once. +/// +/// There might be more than one component in a failure domain, e.g. +/// `"dc=msk,srv=msk-1"`. Yet Picodata doesn't make any assumptions +/// about their meaning or hierarchy, it only matches components. +/// +/// Picodata relies on failure domains to enhance fault tolerance of a +/// cluster: +/// +/// - Picodata will avoid forming a replicaset of instances with at +/// least one common failure domain component. +/// +/// - Picodata tends to keep raft voters as distant from each other as +/// possible. +/// +/// Failure domains are case-insensitive. Components are converted to +/// upprcase implicitly. +/// #[derive(Default, PartialEq, Eq, Clone, serde::Deserialize, serde::Serialize)] pub struct FailureDomain { #[serde(flatten)] @@ -20,8 +46,23 @@ impl FailureDomain { self.data.keys() } - /// Empty `FailureDomain` doesn't intersect with any other `FailureDomain` - /// even with another empty one. + /// Checks whether `self` and `other` failure domains have at least + /// one common component. + /// + /// Empty failure domain doesn't intersect with any other even with + /// another empty one. + /// + /// # Example + /// + /// ``` + /// let msk_1 = FailureDomain::from([("dc", "msk"), ("srv", "msk-1")]); + /// let msk_2 = FailureDomain::from([("dc", "msk"), ("srv", "msk-2")]); + /// let spb = FailureDomain::from([("dc", "spb")]); + /// + /// assert_eq!(msk_1.intersects(&msk_2), true); + /// assert_eq!(msk_1.intersects(&spb), false); + /// ``` + /// pub fn intersects(&self, other: &Self) -> bool { for (name, value) in &self.data { match other.data.get(name) { @@ -34,8 +75,21 @@ impl FailureDomain { false } - /// Calculate distance between two `FailureDomain`. - /// `Distance` is property of metric space implicitly set by `FailureDomain` keys (axis) and values + /// Calculates a distance between `self` and `other` failure domains. + /// + /// The distance is a number of components differ. + /// + /// # Example + /// + /// ``` + /// let msk_1 = FailureDomain::from([("dc", "msk"), ("srv", "msk-1")]); + /// let msk_2 = FailureDomain::from([("dc", "msk"), ("srv", "msk-2")]); + /// let spb = FailureDomain::from([("dc", "spb")]); + /// + /// assert_eq!(msk_1.distance(&msk_1), 0); + /// assert_eq!(msk_1.distance(&msk_2), 1); + /// assert_eq!(msk_1.distance(&spb), 2); + /// ``` pub fn distance(&self, other: &Self) -> Distance { let mut keys: HashSet<&Uppercase> = HashSet::new(); keys.extend(self.names()); diff --git a/src/governor/cc.rs b/src/governor/cc.rs index 20da77e9044e3e0153f809d7a625f30147395a4f..a1305320d478bbd5c441947482e5a6c9257932df 100644 --- a/src/governor/cc.rs +++ b/src/governor/cc.rs @@ -46,7 +46,7 @@ impl<'a> RaftConf<'a> { } } -/// Sum of failure domain distances between `a` and each of `bs` +/// Sum of failure domain distances between `a` and each of `bs`. fn sum_distance(all: &BTreeMap<RaftId, &Instance>, a: &RaftId, bs: &BTreeSet<RaftId>) -> Distance { let Some(a) = all.get(a) else { return 0 }; bs.iter() @@ -55,7 +55,7 @@ fn sum_distance(all: &BTreeMap<RaftId, &Instance>, a: &RaftId, bs: &BTreeSet<Raf .sum() } -/// From `candidates` find one with maximum total distance to `voters` +/// Among `candidates` find one with maximum total distance to `voters`. fn find_farthest( all: &BTreeMap<RaftId, &Instance>, voters: &BTreeSet<RaftId>,