RaftEx.Server.Election (raft_ex v0.1.0)

View Source

Raft leader election implementation with pre-vote support.

This module handles the election process including:

  • Pre-vote phase: Checks if an election would succeed before incrementing term
  • Candidate phase: Actual voting with term increment
  • Vote request/reply handling
  • Quorum evaluation
  • Election timeout management

Election Flow

  1. Pre-Vote: Candidate probes peers without incrementing term
  2. Vote Request: If pre-vote succeeds, increment term and request votes
  3. Vote Reply: Peers grant vote if candidate's log is up-to-date
  4. Win Condition: Candidate wins if it receives votes from majority

Pre-Vote Benefits

Pre-vote prevents a partitioned node from disrupting the cluster by:

  • Not incrementing its term until it knows it can win
  • Avoiding unnecessary term increases that would force leader re-election
  • Allowing the node to catch up before starting a real election

Summary

Functions

Check if it's still possible to win the election.

Evaluate election result based on current votes.

Handle an incoming pre-vote request.

Handle an incoming vote request.

Start an election (pre-vote or candidate phase).

Get the number of votes needed to win.

Types

election_context()

@type election_context() :: %{
  state: election_state(),
  term: RaftEx.Types.term_num(),
  votes_received: MapSet.t(RaftEx.Types.server_id()),
  total_voters: non_neg_integer(),
  last_log_index: RaftEx.Types.index(),
  last_log_term: RaftEx.Types.term_num()
}

election_result()

@type election_result() :: :won | :lost | :ongoing

election_state()

@type election_state() :: :pre_vote | :candidate

Functions

can_still_win?(votes_received, total_voters)

@spec can_still_win?(non_neg_integer(), non_neg_integer()) :: boolean()

Check if it's still possible to win the election.

Returns true if there are enough outstanding votes to reach majority.

evaluate_election_result(map)

@spec evaluate_election_result(election_context()) :: election_result()

Evaluate election result based on current votes.

Returns :won, :lost, or :ongoing.

handle_pre_vote_reply(pre_vote_result, context, state, peer_id)

@spec handle_pre_vote_reply(
  RaftEx.Types.PreVoteResult.t(),
  election_context(),
  map(),
  RaftEx.Types.server_id()
) :: {boolean(), election_context(), [term()]}

Handle a pre-vote reply from a peer.

Returns {should_start_election, updated_context, effects}.

handle_pre_vote_request(pre_vote_rpc, state)

@spec handle_pre_vote_request(RaftEx.Types.PreVoteRpc.t(), map()) ::
  {boolean(), [term()]}

Handle an incoming pre-vote request.

Returns {vote_granted, effects}.

handle_vote_reply(request_vote_result, context, state, peer_id)

@spec handle_vote_reply(
  RaftEx.Types.RequestVoteResult.t(),
  election_context(),
  map(),
  RaftEx.Types.server_id()
) :: {election_result(), election_context(), [term()]}

Handle a vote reply from a peer.

Returns {election_result, updated_context, effects}.

handle_vote_request(request_vote_rpc, state)

@spec handle_vote_request(RaftEx.Types.RequestVoteRpc.t(), map()) ::
  {boolean(), map(), [term()]}

Handle an incoming vote request.

Returns {vote_granted, updated_state, effects}.

start_election(state, map)

@spec start_election(election_state(), map()) :: {election_context(), [term()]}

Start an election (pre-vote or candidate phase).

Returns {election_context, effects} where effects include RPCs to send to peers.

votes_needed(total_voters)

@spec votes_needed(non_neg_integer()) :: non_neg_integer()

Get the number of votes needed to win.

Returns the majority count.