README

Lfan-ke/raft-moonbit/confchange does not have a README file

#
Changer

pub struct Changer {
cfg : ChangerConfig
prs : Map[String,
Progress
]
last_index : UInt64
max_inflight : Int
max_inflight_bytes : UInt64
}

The Changer drives a configuration through single and joint changes. Each public operation is transactional: on error the configuration is left untouched (etcd's checkAndCopy semantics).

#
Changer::advance_index

fn Changer::advance_index(self : Changer) -> Unit

Advance the index a newly-added follower's progress is anchored at. The datadriven harness bumps this once per command so next reveals which "round" a progress was created in (proving a demoted voter's progress is preserved, not recreated, across a joint transition).

#
Changer::conf_state

The configuration as a ConfState (etcd's ProgressTracker.ConfState): the incoming voters, the outgoing half, the learners and the staged learners, plus the auto-leave flag. Used to round-trip a configuration through a snapshot.

#
Changer::describe

fn Changer::describe(self : Changer) -> String

The configuration and its per-follower progress, in etcd's datadriven format: voters=(…)[&&(…)] [learners=(…)] [learners_next=(…)] [autoleave], then one line per follower.

#
Changer::enter_joint

fn Changer::enter_joint(self : Changer, auto_leave : Bool, changes : Array[(String, String)]) -> String?

Enter joint consensus C(new,old): rotate the incoming voters into the outgoing half, then apply the batch to the incoming half.

#
Changer::leave_joint

fn Changer::leave_joint(self : Changer) -> String?

Leave joint consensus: promote any staged learners_next to learners, preserving their progress, and drop the outgoing half.

#
Changer::new

fn Changer::new(last_index? : UInt64, max_inflight? : Int, max_inflight_bytes? : UInt64) -> Changer

A Changer over an empty configuration, with progress next-indices anchored at last_index, a per-follower window of max_inflight messages and, when non-zero, max_inflight_bytes bytes (etcd's MakeProgressTracker (maxInflight, maxBytes); 0 = no byte limit).

#
Changer::restore

Rebuild a configuration from a ConfState (etcd's Restore), running the same sequence of changes the state describes. Returns an error message on an inconsistent state.

#
Changer::simple

fn Changer::simple(self : Changer, changes : Array[(String, String)]) -> String?

A simple (non-joint) change: it may mutate the incoming voter set by at most one, and may not run while joint.

#
ChangerConfig

pub struct ChangerConfig {
incoming : Array[String]
outgoing : Array[String]
learners : Array[String]
learners_next : Array[String]
auto_leave : Bool
}

The full configuration the Changer maintains: the incoming and outgoing voter halves (outgoing non-empty iff joint), the learners, and the learners that will become learners once a joint configuration is left (learners_next).

#
ConfChange

pub(all) struct ConfChange {
change_type : ConfChangeType
node_id : String
} derive(Eq)

A configuration change carried by a ConfChange log entry: which server is joining or leaving. It is serialized into the entry's command so every server applies the same change at the same log position.

#
ConfChange::add

fn ConfChange::add(id : String) -> ConfChange

A change that adds id to the cluster.

#
ConfChange::add_learner

fn ConfChange::add_learner(id : String) -> ConfChange

A change that adds id as a learner (a non-voting member).

#
ConfChange::apply_to

fn ConfChange::apply_to(self : ConfChange, config : Membership) -> Unit

Apply this change to a configuration in place.

#
ConfChange::decode

fn ConfChange::decode(command : Bytes) -> ConfChange?

Recover a configuration change from a log-entry command, or None if the bytes are not a well-formed change.

#
ConfChange::encode

fn ConfChange::encode(self : ConfChange) -> Bytes

Serialize this change into a log-entry command. The encoding is a one- character tag ('+' add voter, '-' remove, 'L' add learner) followed by the server id, which keeps it human-readable in dumps and trivially reversible.

#
ConfChange::remove

fn ConfChange::remove(id : String) -> ConfChange

A change that removes id from the cluster.

#
ConfChangeTransition

pub(all) enum ConfChangeTransition {
Auto
JointImplicit
JointExplicit
} derive(Eq)

How a ConfChangeV2 transitions the configuration (etcd's ConfChangeTransition): Auto applies a batch simply when it safely can (at most one voter changed) and otherwise enters an auto-leaving joint; JointImplicit always enters joint and auto-leaves; JointExplicit always enters joint and waits for an explicit leave.

#
ConfChangeType

pub(all) enum ConfChangeType {
AddNode
RemoveNode
AddLearnerNode
} derive(Eq)

The kind of a single-server configuration change (Raft §6, §4.2.1).

#
ConfChangeV2

pub(all) struct ConfChangeV2 {
changes : Array[ConfChange]
transition : ConfChangeTransition
} derive(Eq)

A batch configuration change (etcd's ConfChangeV2): several single changes applied atomically. An empty batch leaves joint consensus. Whether a non-empty batch is applied simply or via a joint transition — and whether that joint auto-leaves — is governed by transition (Raft §4.3, joint consensus).

#
ConfChangeV2::auto

fn ConfChangeV2::auto(changes : Array[ConfChange]) -> ConfChangeV2

A batch with the Auto transition: applied simply when it can be (at most one voter changed), otherwise as an auto-leaving joint change — etcd's default.

#
ConfChangeV2::auto_leave

fn ConfChangeV2::auto_leave(self : ConfChangeV2) -> Bool

Whether this joint change auto-leaves once committed.

#
ConfChangeV2::decode

fn ConfChangeV2::decode(command : Bytes) -> ConfChangeV2?

Recover a batch change, or None if the bytes are not a well-formed batch.

#
ConfChangeV2::encode

fn ConfChangeV2::encode(self : ConfChangeV2) -> Bytes

Serialize as V + transition tag (a/i/e) + ;-separated <tag><id> changes. The V prefix distinguishes a batch from a single change on decode.

#
ConfChangeV2::enter_joint

fn ConfChangeV2::enter_joint(changes : Array[ConfChange], auto_leave? : Bool) -> ConfChangeV2

Enter joint consensus with changes. auto_leave selects the implicit (auto-leaving) or explicit joint transition — the historical API.

#
ConfChangeV2::enters_joint

fn ConfChangeV2::enters_joint(self : ConfChangeV2) -> (Bool, Bool)

Whether this batch enters a joint configuration, and if so whether that joint auto-leaves (etcd's ConfChangeV2.EnterJoint). Auto with at most one change is applied simply — no joint; anything else is joint, auto-leaving unless the transition is explicit.

#
ConfChangeV2::is_leave

fn ConfChangeV2::is_leave(self : ConfChangeV2) -> Bool

Whether this change leaves a joint configuration (etcd's ConfChangeV2.LeaveJoint). This is the case only for the Auto transition with no changes: an explicit joint transition carrying no changes still enters an (empty) joint config and must not be mistaken for a leave, which is exactly the complement of enters_joint.

#
ConfChangeV2::leave_joint

fn ConfChangeV2::leave_joint() -> ConfChangeV2

Leave joint consensus (an empty batch).

#
Membership

pub(all) struct Membership {
members : Array[String]
outgoing : Array[String]
joint : Bool
learners : Array[String]
learners_next : Array[String]
auto_leave : Bool
}

The set of servers that currently form the cluster. Raft changes membership through the log so every server adopts each change at the same point in the sequence (Raft §6). Two disciplines are supported: single-server changes, where the old and new majorities always overlap; and joint consensus, where the cluster passes through a transitional configuration C(old,new) that needs a majority of both the old and the new voter sets to agree.

members is the incoming configuration C(new). outgoing holds the old configuration C(old) and is non-empty only while joint is true.

#
Membership::add

fn Membership::add(self : Membership, id : String) -> Unit

Add a server as a voter, unless it already is one. A learner being added as a voter is promoted: it leaves the learner set (Raft §4.2.1).

#
Membership::add_learner

fn Membership::add_learner(self : Membership, id : String) -> Unit

Add id as a learner (non-voting member). If it is currently a voter this demotes it (etcd's l<id>). A voter demoted while a joint config is active and still present in the outgoing half is staged in learners_next: it leaves the incoming voters but keeps voting through the outgoing half until the config is left, so its removal from the quorum is atomic with the leave (Raft §4.3). Otherwise it becomes a learner right away.

#
Membership::begin_joint

fn Membership::begin_joint(self : Membership, outgoing : Array[String]) -> Unit

Enter joint consensus keeping the already-updated incoming set, recording outgoing as the old half. Used when the incoming voters have been mutated in place by a batch of changes (etcd's ConfChangeV2 EnterJoint).

#
Membership::committed_index

fn Membership::committed_index(self : Membership, acked : Map[String, UInt64]) -> UInt64

The committed index this configuration agrees on, given the acked indices, accounting for the joint transition when one is in progress.

#
Membership::contains

fn Membership::contains(self : Membership, id : String) -> Bool

Whether id is a voter in the current configuration — in the incoming set, or, during a joint transition, in either half.

#
Membership::enter_joint

fn Membership::enter_joint(self : Membership, new_members : Array[String]) -> Unit

Enter joint consensus, moving to the target voter set new_members while keeping the current set as the outgoing half. Until the transition is committed and leave_joint is called, decisions need both majorities.

#
Membership::has_majority

fn Membership::has_majority(self : Membership, granted : Array[String]) -> Bool

Whether granted (the ids that agreed, e.g. voted or acknowledged) forms a majority. A simple configuration needs a majority of the incoming set; a joint configuration needs a majority of the incoming set and a majority of the outgoing set, which is what makes joint consensus safe against split decisions during a change (Raft §6).

#
Membership::is_joint

fn Membership::is_joint(self : Membership) -> Bool

Whether the configuration is currently in the joint (transitional) state.

#
Membership::is_learner

fn Membership::is_learner(self : Membership, id : String) -> Bool

Whether id is a learner (a non-voting member).

#
Membership::leave_joint

fn Membership::leave_joint(self : Membership) -> Unit

Leave joint consensus once C(old,new) is committed: the outgoing half is dropped and the cluster runs on the incoming configuration alone.

#
Membership::new

fn Membership::new(members : Array[String]) -> Membership

Create a simple (non-joint) cluster configuration from an initial voter set.

#
Membership::nodes

fn Membership::nodes(self : Membership) -> Array[String]

Every server that participates in replication: voters and learners alike.

#
Membership::quorum

fn Membership::quorum(self : Membership) -> Int

The majority size of the current (incoming) configuration. Meaningful for a simple configuration; in a joint configuration use has_majority, which accounts for both halves.

#
Membership::remove

fn Membership::remove(self : Membership, id : String) -> Unit

Remove a server from the configuration entirely — voter or learner.

#
Membership::size

fn Membership::size(self : Membership) -> Int

The number of servers in the incoming configuration.

#
Membership::vote_result

fn Membership::vote_result(self : Membership, votes : Map[String, Bool]) ->
VoteState

The vote outcome for this configuration, accounting for a joint transition.

#
Membership::voters

fn Membership::voters(self : Membership) -> Array[String]

The voters of the current configuration, de-duplicated across both halves.