NuRaft Documentation

repository·master·Indexed 22 days ago

https://github.com/ebay/nuraft

A lightweight, high-performance C++ implementation of the Raft consensus algorithm. NuRaft provides production-ready features including SSL/TLS, pre-vote protocols, custom quorum sizes, asynchronous replication, and support for dynamic membership and configuration changes.

Tokens
16.3K
Snippets
40
Records
67
Agent score
78%

What's inside NuRaft

  1. Overview of NuRaft features

    master

    NuRaft is a C++ Raft implementation derived from the cornerstone project. It provides a lightweight implementation with minimum dependencies, enhanced with several real-world features required for production use cases.

    Core Raft Capabilities

    • Log replication & compaction
    • Leader election
    • Snapshotting
    • Dynamic membership & configuration changes
    • Group commit & pipelined write
    • User-defined log store & state machine support

    Advanced Features

    • Pre-vote protocol: Prevents disruptive elections.
    • Leadership expiration: Handles leader timeouts.
    • Priority-based semi-deterministic leader election: Influences which nodes become leaders.
    • Read-only member (learner): Allows nodes to join the cluster without participating in quorums.
    • Object-based logical snapshot: For efficient snapshot transmission.
    • Custom quorum size: Separate quorum sizes for commit and leader election.
    • Asynchronous replication: Improves performance via async operations.
    • SSL/TLS support: Secures communication.
    • Parallel Log Appending: Optimizes log write performance.
    • Custom Commit Policy: Allows fine-grained control over when entries are committed.
    • Streaming Mode: Optimized for high-throughput streaming.
  2. What is a Read-Only Member (Learner)

    master

    A Read-Only Member (also referred to as a Learner) is a node that receives data updates from the leader but does not participate in leader elections or contribute to the quorum size.

    Key Characteristics:

    • Quorum Exclusion: Read-only members are not counted when calculating the quorum size. For example, in a cluster of 4 nodes where 3 are normal members and 1 is a read-only member, the quorum size is calculated based on the 3 normal members (quorum = 2).
    • Election Behavior: They do not initiate elections and do not receive or respond to vote requests.
    • Use Case: They are ideal for geo-distributed replication. By using read-only members in remote datacenters, you can maintain a low-latency quorum within a single local datacenter while allowing remote nodes to catch up with the leader asynchronously.
  3. Implement required State Manager and State Machine APIs during initialization

    master

    When the Raft server initializes, it invokes specific methods on your custom modules to recover state. You must implement these correctly to ensure durability:

    State Manager (state_mgr)

    • load_log_store(): Must return your log_store instance.
    • load_config(): Must return the last committed srv_config containing membership info. On the very first launch, return a config containing only the local server.
    • read_state(): Must return the last srv_state (containing term and voting info).

    State Machine (state_machine)

    • last_commit_index(): Must return the last committed log number. If not implemented/durable, the server will attempt to replay logs from the beginning.
    • last_snapshot(): Must return the handle of the last durable snapshot.
  4. Implement State Machine for Asynchronous Replication

    master

    When using asynchronous replication mode, the standard lifecycle of state machine operations changes. You must implement the following logic:

    1. Execution: Move the actual execution logic from state_machine::commit() to state_machine::pre_commit(). This ensures the result is available immediately when append_entries() returns.
    2. Conflict Resolution: You must correctly implement state_machine::rollback() to revert any changes made during pre_commit() if a conflict is detected during background replication.

    Comparison of Modes:

    FeatureAsynchronous Replication ModeSynchronous Replication with async_handler
    Execution TimingHappens in pre_commit() (before replication)Happens in commit() (after consensus)
    append_entries() ReturnReturns immediately with the execution resultReturns immediately, but data is NOT yet committed
    NotificationNo later notification providedUses a user-defined async_handler to notify when commit() is eventually set
  5. Understand the Pre-Vote Protocol in NuRaft

    master

    The Pre-Vote protocol is a mechanism used to prevent unnecessary leader elections and term disruptions caused by network partitions.

    In standard Raft, a node that loses contact with the leader will increment its term and initiate an election. If this node is partitioned but can still reach a quorum, it can force the current leader to step down by increasing the global term, even if the leader is still healthy and serving the rest of the cluster. This causes continuous disruption.

    How Pre-Vote works: Before a node increments its term and starts a formal election, it first sends a pre-vote request to other nodes.

    • If voters have recently received heartbeats from the current leader, they will reject the pre-vote request. The initiator will not increment its term, preventing disruption.
    • If voters' election timers have expired (indicating the leader is likely dead), they will accept the pre-vote request. Once the initiator receives a majority of acceptances, it proceeds to increment its term and initiate a formal request_vote phase.

    Trade-off: When a leader actually fails, the Pre-Vote protocol can slightly increase the time required to elect a new leader, as a majority of servers must first experience an election timeout before the pre-vote phase succeeds.

    Initiator   Voter(s)
    |           |
    X           |   raft_server::handle_election_timeout()
    X           |   raft_server::request_prevote()
    X---------->|   Send pre-vote request
    |           X   raft_server::handle_prevote_req()
    |<----------X   Send response
    X           |   raft_server::handle_prevote_resp()
    X           |   raft_server::initiate_vote()
    X           |   raft_server::request_vote()
    X---------->|   Send vote request
    |           X   raft_server::handle_vote_req()
    |<----------X   Send response
    X           |   raft_server::handle_vote_resp()
    X           |   raft_server::become_leader()
    |           |
  6. Use Full Consensus Mode for strong consistency

    master

    NuRaft supports a Full Consensus Mode where the leader only commits a log when all healthy members have received it. This ensures that once a log is committed, the latest data can be read from any member, providing strong consistency.

    To prevent a single unreachable node from halting the entire protocol, NuRaft dynamically excludes unhealthy members from the quorum. A member is considered unhealthy if it fails to respond for longer than response_limit_ (from raft_server::limits) multiplied by the heartbeat period. Members automatically become healthy again once they respond to the leader.

    Note on Availability: If the number of unhealthy members constitutes a majority, the leader will be unable to commit logs, similar to standard quorum-based consensus.

  7. How to use the buffer class

    master

    The buffer class represents a raw memory blob. It reserves a small amount of metadata at the beginning (4 bytes if size < 32KB, 8 bytes otherwise). User data must be written starting from the user section, not the absolute start of the memory blob.

    Key operations:

    • Allocation: Use buffer::alloc(size) to create a new buffer.
    • Accessing User Data: Use data() to get the pointer to the start of the user data section, or data_begin() to get the start of the user section regardless of the current cursor position.
    • Position Management: The buffer maintains an internal cursor. Use pos() to get or set the current position.
    // Allocate memory
    ptr<buffer> b = buffer::alloc( size_to_allocate );
    
    // Get pointer to user data section
    void* ptr = (void*)b->data();
    
    // Get/Set internal cursor position
    size_t current_position = b->pos();
    b->pos( new_position );
    
    // Get start of user section regardless of current position
    void* ptr_begin = (void*)b->data_begin();
  8. Understand Parallel Log Appending behavior and safety

    master

    When parallel_log_appending_ is enabled, the leader's behavior changes regarding how it handles the quorum and disk writes:

    • Commit Logic: The leader commits the log as soon as a quorum of servers has received it. Because replication and disk writing happen in parallel, the leader might reach a quorum before its own local disk write is complete.
    • Safety: The protocol remains safe because at least a majority of servers have the log at the moment it is committed, even if the leader itself is not yet part of that quorum due to a pending disk write.
    • Execution Scenarios:
      • If the disk write completes before replication: Behavior is identical to the standard sequential protocol.
      • If replication completes before the disk write: The leader can apply the log to the state machine immediately after the quorum is reached, potentially before the local disk write finishes.

    Note: This optimization applies only to the leader. Followers will always wait for the notify_log_append_completion call before responding to the leader.

  9. Understand the NuRaft threading model

    master

    NuRaft operations are distributed across two main thread categories. Developers must ensure that APIs called by the User/Asio group are lightweight to avoid blocking the Raft engine.

    1. User and Asio Thread Pool

    These threads execute active Raft operations. Operations called here must be fast and non-blocking. This group invokes:

    • log_store operations.
    • state_machine::pre_commit() and state_machine::rollback().
    • Snapshot chunk I/O via state_machine::read_logical_snp_obj and state_machine::save_logical_snp_obj.

    2. Background Commit Thread

    This thread runs continuously to handle long-running or periodic tasks. This group invokes:

    • log_store operations.
    • state_machine::commit().
    • Snapshot creation via state_machine::create_snapshot.
    • Log compaction via log_store::compact.

    Thread Safety Requirement

    Because log_store operations can be called by different threads (User/Asio and Background Commit) in parallel, your implementation of the log store must be thread-safe.

  10. Configure Server State and Cluster Membership

    master

    NuRaft uses a hierarchical configuration model:

    • cluster_config: A cluster-wide configuration containing a list of srv_config objects (one for each server).
    • srv_config: Defines the configuration for an individual server.
    • state_mgr: A class used to manage the configuration and srv_state.

    You must override the base state_mgr class with your own implementation to handle how configuration and server states are managed. See in_memory_state_mgr.hxx for an example.

  11. Avoid multiple leaders using leadership_expiry_ and pre-vote

    master

    To minimize the window where two leaders might coexist (the previous stale leader and the newly elected leader), you can align the expiration time with the election timeout.

    If you set leadership_expiry_ to be equal to or smaller than election_timeout_lower_bound_, and use the pre-vote protocol, there will be no overlapping time between the previous leader and the new leader.

    Warning: Setting the expiration time too low can make the Raft group overly sensitive to network jitters or hiccups, potentially leading to system instability.

  12. Understand the NuRaft architecture and module responsibilities

    master

    NuRaft is composed of five primary modules. To use the library, you must implement three of these modules (Log Store, State Machine, and State Manager) while leveraging the provided Raft Server and Asio layer.

    Provided by NuRaft

    • Raft server: Coordinates all incoming requests and responses from users and other nodes.
    • Asio layer: Handles network communication, timers, and thread pool management.

    User-Implemented Modules

    • Log store: Manages read, write, and compact operations of Raft logs.
      • Interface: libnuraft/log_store.hxx
    • State machine: Executes commits (including optional pre-commit and rollback) and manages snapshots.
      • Interface: libnuraft/state_machine.hxx
    • State manager: Responsible for saving and loading cluster configuration and status.
      • Interface: libnuraft/state_mgr.hxx

    Optional Modules

    • Debugging logger: Used for system logging.
      • Interface: libnuraft/logger.hxx