{
  "slug": "raft-joint-consensus",
  "term": "Raft joint consensus",
  "aliases": [
    "Joint consensus (Raft)"
  ],
  "definition": "Raft joint consensus is a transitional membership configuration requiring separate majorities of the old and new voting configurations.",
  "note": "The original transition commits a joint configuration before recording the final configuration. Elections and commitment in the joint phase require both majorities, not merely a majority of their union. This is the paper's membership mechanism. [raft-paper]\n\nThe raft v3.6.0 library exposes joint changes through ConfChangeV2 and requires compatible nodes. For an accepted committed configuration change, the application calls ApplyConfChange and records the returned configuration state in snapshots. Library support does not mean every service uses the same membership workflow. [raft-api]\n\nConstructed example: old {A, B, C}, new {C, D, E}. Count affirmative votes for the same candidate in the same term. C, D, E give old 1/3 and new 3/3: a 3/5 union majority is insufficient. The election is not necessarily lost; more votes may arrive. A, C, D give 2/3 in each; A, B, D, E also satisfy both counts without C. This is a tally example, not a complete election or cluster test. [raft-joint]",
  "status": "shipping",
  "advantages": [],
  "limitations": [
    {
      "text": "For fixed active old {A, B, C} and new {C, D, E}, constructed matched-prefix indices A=4, B=4, C=8, D=8, E=8 give group bounds 4 and 8. JointConfig.CommittedIndex takes their minimum: candidate 4. Counting the union yields 8. Assume prefix matches against the same leader log, not arbitrary local log lengths; this is not a cluster experiment.",
      "citationIds": [
        "raft-joint",
        "raft-majority"
      ]
    },
    {
      "text": "The helper alone does not make the final commit decision. The leader's current term is passed to raftLog.maybeCommit, which advances only beyond the existing commit index with a matching nonzero term. An older-term candidate alone cannot establish new commitment by replica counting. Entries already committed remain committed; a later current-term commitment covers its older prefix indirectly.",
      "citationIds": [
        "raft-leader",
        "raft-log",
        "raft-paper"
      ]
    }
  ],
  "supersedes": [],
  "supersededBy": [],
  "firstSeen": null,
  "lastSeen": null,
  "updated": "2026-11-04",
  "related": [
    "raft",
    "quorum-intersection"
  ],
  "citations": [
    {
      "id": "raft-paper",
      "title": "Ongaro and Ousterhout: In Search of an Understandable Consensus Algorithm, extended version (20 May 2014), sections 5.4, 6 and 8.",
      "url": "https://raft.github.io/raft.pdf",
      "accessed": "2026-11-04"
    },
    {
      "id": "raft-api",
      "title": "raft v3.6.0: Node configuration-change and application interfaces",
      "url": "https://raw.githubusercontent.com/etcd-io/raft/v3.6.0/node.go",
      "accessed": "2026-11-04"
    },
    {
      "id": "hesela-consistency",
      "title": "Hesela: storage consistency, Raft commitment and read safety",
      "url": "https://hesela.com/analyses/storage-consistency-ordering/#raft-reads",
      "accessed": "2026-11-04"
    },
    {
      "id": "raft-joint",
      "title": "raft v3.6.0: JointConfig.VoteResult requires both majorities and distinguishes pending from lost",
      "url": "https://raw.githubusercontent.com/etcd-io/raft/v3.6.0/quorum/joint.go",
      "accessed": "2026-11-04"
    },
    {
      "id": "raft-log",
      "title": "raft v3.6.0: raftLog.maybeCommit and matchTerm, advancement and current-term guard",
      "url": "https://raw.githubusercontent.com/etcd-io/raft/v3.6.0/log.go",
      "accessed": "2026-11-04"
    },
    {
      "id": "raft-majority",
      "title": "raft v3.6.0: MajorityConfig.CommittedIndex, majority-supported replicated prefix",
      "url": "https://raw.githubusercontent.com/etcd-io/raft/v3.6.0/quorum/majority.go",
      "accessed": "2026-11-04"
    },
    {
      "id": "raft-leader",
      "title": "raft v3.6.0: raft.maybeCommit passes the leader term and tracker candidate to the log",
      "url": "https://raw.githubusercontent.com/etcd-io/raft/v3.6.0/raft.go",
      "accessed": "2026-11-04"
    }
  ],
  "provenance": [
    "Original mechanism: section 6 and Figure 11. Library integration: versioned Node API. Configuration activation timing is implementation-specific; the paper is not an operational reconfiguration runbook. [raft-paper] [raft-api]"
  ]
}
