# How the PreVote Mechanism in Dragonboat Prevents Split-Brain Scenarios

> Learn how Dragonboats PreVote mechanism prevents split-brain scenarios. This dry-run election ensures quorum feasibility, keeping your cluster stable and avoiding leader failures.

- Repository: [lni/dragonboat](https://github.com/lni/dragonboat)
- Tags: internals
- Published: 2026-03-06

---

**The PreVote mechanism in Dragonboat acts as a dry-run election that verifies quorum feasibility without incrementing the term, preventing stale or partitioned nodes from forcing the current leader to step down and causing split-brain.**

Dragonboat is a high-performance Raft consensus implementation in Go. The **PreVote mechanism in dragonboat** extends the standard Raft algorithm to add a preliminary election phase that protects cluster stability. This mechanism, derived from the Raft thesis §5.2, ensures that nodes cannot disrupt an active leader unless they first prove they can reach a majority of the cluster.

## How PreVote Works in Dragonboat

### Triggering a PreVote Campaign

When a follower’s election timeout expires, the node invokes `handleNodeElection` in [`internal/raft/raft.go`](https://github.com/lni/dragonboat/blob/main/internal/raft/raft.go). If the configuration has `PreVote` enabled and the node is not currently a leader-transfer target, it initiates a pre-vote campaign instead of a normal election.

```go
// internal/raft/raft.go
// L1081-L1087
if r.preVote && !r.isLeaderTransferTarget {
    plog.Debugf("%s will start a preVote campaign", r.describe())
    return r.preVoteCampaign()
}

```

This check ensures that leadership transfers are not delayed by the pre-vote phase.

### Becoming a PreVote Candidate

The `preVoteCampaign` method transitions the node to the `preVoteCandidate` state. Crucially, this transition does **not** increment the term. The node resets its election timer and clears any prior vote, but its term remains unchanged.

```go
// internal/raft/raft.go
// L1001-L1018
r.state = preVoteCandidate
r.reset(r.term, true)   // term stays the same
r.setLeaderID(NoLeader)
plog.Warningf("%s became PreVote candidate", r.describe())

```

This temporary state allows the node to test its eligibility without affecting the cluster’s term.

### Broadcasting RequestPreVote Messages

The pre-vote candidate broadcasts `RequestPreVote` messages to all voting members. These messages include the candidate’s last log index and term, allowing recipients to verify the candidate’s log is at least as up-to-date as their own. Notably, the message term is set to `term + 1` for the request only, but the sender’s local term remains unchanged.

```go
// internal/raft/raft.go
// L1150-L1174
r.send(pb.Message{
    Term:     term + 1,               // term is *one* higher only for the request
    To:       k,
    Type:     pb.RequestPreVote,
    LogIndex: index,
    LogTerm:  lastTerm,
})
plog.Warningf("%s sent RequestPreVote to %s", r.describe(), ReplicaID(k))

```

### Evaluating PreVote Requests

When a peer receives a `RequestPreVote`, it executes `handleNodeRequestPreVote`. The peer grants the pre-vote only if two conditions are met: the incoming term is greater than the local term, and the candidate’s log is up-to-date (`r.log.upToDate`). If either check fails, the peer rejects the request without changing its own term.

```go
// internal/raft/raft.go
// L1670-L1686
if m.Term > r.term && isUpToDate {
    resp.Term = m.Term
    // grant pre-vote
} else {
    resp.Term = r.term
    resp.Reject = true
}
r.send(resp)

```

This validation ensures that only viable candidates—those with fresh logs and higher terms—can proceed.

### Transitioning to Real Election

The pre-vote candidate collects responses via `handlePreVoteCandidateRequestPreVoteResp`. If it receives a quorum of positive pre-votes, it invokes `campaign()` to start the actual election, which increments the term and sends `RequestVote` messages. If it fails to reach a quorum, it reverts to the follower state.

```go
// internal/raft/raft.go
// L2260-L2274
count := r.handleVoteResp(m.From, m.Reject, true)
if count == r.quorum() {
    if err := r.campaign(); err != nil { return err }
} else if len(r.votes)-count == r.quorum() {
    r.becomeFollower(r.term, NoLeader)
}

```

This gated transition prevents term inflation and ensures that only nodes capable of reaching a majority can disturb the current leader.

## Why PreVote Prevents Split-Brain

Split-brain occurs when two nodes simultaneously believe they are the leader, often because a partitioned or stale node increments its term and forces the legitimate leader to step down. Dragonboat’s pre-vote eliminates this risk through a dry-run validation phase.

| Split-brain cause | How pre-vote stops it |
|-------------------|----------------------|
| A stale follower increments its term, forces the current leader to step down, and wins a separate election → two leaders. | The stale node **does not** increment its term during pre-vote. Its `RequestPreVote` is rejected because its log is not up-to-date, so it never becomes a candidate. |
| Multiple partitioned nodes each start an election at the same time, each gathers a minority of votes, and all increment terms → rapid term churn and possible split. | Pre-vote ensures a node only proceeds to the costly term-increment step after it *knows* it can obtain a majority, thus avoiding unnecessary term changes that would disrupt the existing leader. |
| A node with a higher term but no quorum may keep sending `RequestVote`, causing other nodes to step down repeatedly. | `onMessageTermNotMatched` (see `handleNodeElection`) drops `RequestVote` from a higher-term node if the local node still has a valid leader and the election timeout has not elapsed, further protecting the cluster from oscillations. |
| Leader transfer can be interfered with by a concurrent election. | The pre-vote is *skipped* when a leadership transfer is in progress (`r.isLeaderTransferTarget`), so the transfer can complete without being pre-empted. |

By requiring a node to prove it can reach a quorum before incrementing its term, Dragonboat ensures that only viable candidates can trigger leadership changes, maintaining single-leader safety even during network partitions.

## Enabling and Configuring PreVote

PreVote is controlled via the `Config` struct. By default, Dragonboat enables this feature, but you can explicitly configure it when initializing a `NodeHost`.

```go
cfg := config.Config{
    // Other configuration options...
    PreVote: true,   // Enable the pre-vote mechanism
}
nh, err := dragonboat.NewNodeHost(cfg)

```

*(Source: [`config/config.go`](https://github.com/lni/dragonboat/blob/main/config/config.go))*

When enabled, the Raft state machine automatically uses the pre-vote protocol for all election timeouts, unless the node is the target of a leader transfer.

## Summary

- The **PreVote mechanism in dragonboat** adds a preliminary dry-run election phase before actual term increments.
- Nodes in the `preVoteCandidate` state broadcast `RequestPreVote` messages without increasing their term, testing quorum feasibility.
- Peers reject pre-vote requests from stale or partitioned nodes via `handleNodeRequestPreVote`, preventing term inflation.
- Only after receiving a quorum of positive pre-votes does the node call `campaign()` to increment its term and start a real election.
- This protocol eliminates split-brain scenarios by ensuring only nodes with up-to-date logs and reachable quorums can disturb the current leader.

## Frequently Asked Questions

### What is the difference between PreVote and a regular Raft election?

A regular Raft election immediately increments the candidate’s term and sends `RequestVote` messages. In contrast, the **PreVote mechanism in dragonboat** first enters a `preVoteCandidate` state where it sends `RequestPreVote` messages using a tentative term (local term + 1) without actually incrementing its local term. Only if it receives a quorum of positive responses does it proceed to the real election. This prevents stale nodes from disrupting the cluster with spurious term increments.

### How does PreVote interact with leader transfer operations?

Dragonboat explicitly disables PreVote when processing a leader transfer. In `handleNodeElection`, the code checks `r.isLeaderTransferTarget` before initiating a pre-vote campaign. If a leader transfer is in progress, the node skips the pre-vote phase and proceeds directly to the regular election mechanism. This ensures that leadership transfers complete quickly without being delayed by the preliminary quorum check.

### Can PreVote be disabled, and when should you consider disabling it?

Yes, you can disable PreVote by setting `PreVote: false` in the `config.Config` struct when creating the `NodeHost`. However, this is generally not recommended for production deployments. Disabling PreVote may be useful in specific testing scenarios where you need to observe classic Raft behavior, or in extremely small clusters (e.g., two nodes) where the additional network round-trip of pre-vote adds unnecessary latency. For most production clusters of three or more nodes, leaving PreVote enabled provides essential protection against split-brain scenarios.

### Does the PreVote mechanism affect write latency or throughput?

The PreVote mechanism adds one network round-trip (the exchange of `RequestPreVote` and `RequestPreVoteResp` messages) only during the election phase. Since elections are rare events in a stable cluster—occurring only when the leader fails or network partitions occur—PreVote has negligible impact on steady-state write latency and throughput. The trade-off of one additional round-trip during elections is overwhelmingly beneficial compared to the potential downtime and inconsistency caused by a split-brain scenario.