Class RaftLog

java.lang.Object
com.loomcache.server.consensus.RaftLog

public class RaftLog extends Object
The Raft replicated log.

Entries are 1-indexed (index 0 is a sentinel). The log supports: - append: add new entries at the end - truncate: remove entries from a given index (for conflict resolution) - getEntry: retrieve by index - getEntries: retrieve a range (for AppendEntries batching) - snapshot/compaction: trim prefix when snapshot is taken

Thread-safe via ReadWriteLock.

  • Nested Class Summary

    Nested Classes
    Modifier and Type
    Class
    Description
    static final record 
    Immutable pair of (index, term) from the Raft log.
  • Constructor Summary

    Constructors
    Constructor
    Description
    RaftLog(String nodeId)
    Creates a new RaftLog for a given node.
  • Method Summary

    Modifier and Type
    Method
    Description
    long
    append(long term, byte[] command)
    Append a new entry to the log.
    void
    appendAll(List<LogEntry> newEntries)
    Append multiple entries to the log (batch append).
    long
    Append a pre-built LogEntry to the log.
    void
    compactUpTo(long snapshotIndex, long snapshotTerm)
    Compact the log by removing all entries up to and including snapshotIndex.
    boolean
    containsEntry(long index, long term)
    Check if an entry with the given index and term exists in the log.
    long
    Estimate the memory usage of the log in bytes.
    long
    Find the first conflicting entry when applying a list of new entries.
    getEntriesFrom(long startIndex)
    Get all entries from a starting index to the end of the log (inclusive).
    @Nullable LogEntry
    getEntry(long index)
    Get a log entry at a specific index.
    getEntryRange(long fromIndex, long toIndex)
    Retrieve a range of entries from the log.
    long
    Get the index of the last entry in the log.
    Atomically get the last index and term as a pair.
    long
    Get the term of the last entry in the log.
    Get a snapshot of current log statistics.
    long
    Get the index of the last snapshot.
    long
    Get the term of the last snapshot.
    long
    getTermAt(long index)
    Get the term at a specific index.
    void
    removeFrom(long fromIndex)
    Remove all entries from the given index onwards (inclusive).
    void
    Restore a log entry during WAL recovery, preserving its persisted index exactly.
    void
    restoreSnapshotState(long snapshotIndex, long snapshotTerm)
    Restore snapshot state during recovery.
    int
    Get the total number of entries in the log.
    void
    truncateFrom(long fromIndex)
    Truncate log from the given index onwards (inclusive).

    Methods inherited from class Object

    clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
  • Constructor Details

    • RaftLog

      public RaftLog(String nodeId)
      Creates a new RaftLog for a given node.
      Parameters:
      nodeId - the ID of the node (non-null, used for logging)
      Throws:
      NullPointerException - if nodeId is null
  • Method Details

    • append

      public long append(long term, byte[] command)
      Append a new entry to the log.

      This method is used by the leader to add new committed entries, and also by followers to reconstruct their log during recovery from the WAL.

      The new entry is assigned a monotonically increasing index and added to the end of the log. All log indices are 1-based (index 0 is a sentinel).

      Parameters:
      term - The Raft term when this entry is created (non-negative)
      command - The serialized state machine command (may be null or empty for NO_OP)
      Returns:
      The index assigned to the new entry (positive)
      Throws:
      IllegalStateException - If entry index monotonicity would be violated
      IllegalArgumentException - If term is negative
    • appendEntry

      public long appendEntry(LogEntry entry)
      Append a pre-built LogEntry to the log.

      This is used for entries that have a specific type (e.g., CONFIG_CHANGE, NO_OP) where the caller has already constructed the LogEntry with the correct type.

      Thread-safe via write lock.

      Parameters:
      entry - The LogEntry to append (non-null)
      Returns:
      The index assigned to the entry
      Throws:
      NullPointerException - If entry is null
      IllegalStateException - If entry index monotonicity would be violated
      IllegalArgumentException - If term is negative
    • restoreEntry

      public void restoreEntry(LogEntry entry)
      Restore a log entry during WAL recovery, preserving its persisted index exactly.

      Unlike appendEntry(LogEntry) which reassigns index to lastIndex + 1, this method trusts the entry's original index from the WAL. It validates that the entry index is contiguous with the current log tail and aborts on gaps or duplicates.

      Thread-safe via write lock. Must only be called during recovery before the node starts accepting new operations.

      Parameters:
      entry - The LogEntry to restore with its persisted index
      Throws:
      IllegalStateException - If the entry index is not exactly lastIndex + 1 (gap or duplicate)
      IllegalArgumentException - If term is negative
      NullPointerException - If entry is null
    • appendAll

      public void appendAll(List<LogEntry> newEntries)
      Append multiple entries to the log (batch append).

      This is used by followers during AppendEntries RPC from the leader. If an entry conflicts with an existing entry (same index, different term), the conflicting entry and all subsequent entries are truncated before appending.

      Thread-safe via write lock. Entries are added in order.

      Parameters:
      newEntries - List of entries to append (non-null, may be empty)
      Throws:
      NullPointerException - If newEntries is null
    • getEntry

      public @Nullable LogEntry getEntry(long index)
      Get a log entry at a specific index.

      Returns the entry at the given index if it exists in the log. The log is 1-indexed; index 0 is a sentinel and cannot be retrieved.

      Thread-safe via read lock.

      Parameters:
      index - The log index (1-based)
      Returns:
      The LogEntry at the given index, or null if the index is out of bounds or before the snapshot index
    • getEntriesFrom

      public List<LogEntry> getEntriesFrom(long startIndex)
      Get all entries from a starting index to the end of the log (inclusive).

      This is used by the leader when replicating entries to followers via AppendEntries RPC. The entries are returned as a copy to prevent external mutations.

      Thread-safe via read lock.

      Parameters:
      startIndex - The starting index (1-based). If startIndex is beyond the log end, returns an empty list
      Returns:
      An immutable copy of entries from startIndex to the end of the log (inclusive), or an empty list if startIndex is out of bounds
    • getLastIndex

      public long getLastIndex()
      Get the index of the last entry in the log.

      This returns the highest index in the replicated log, accounting for snapshots. If the log is empty but a snapshot exists, returns the snapshot index.

      Thread-safe via read lock.

      Returns:
      The last log index (1-based), or the last snapshot index if log is empty, or 0 if both log and snapshot are empty
    • getLastTerm

      public long getLastTerm()
      Get the term of the last entry in the log.

      Thread-safe via read lock.

      Returns:
      The term of the last log entry, or the last snapshot term if log is empty, or 0 if both log and snapshot are empty
    • getLastIndexAndTerm

      public RaftLog.IndexTermPair getLastIndexAndTerm()
      Atomically get the last index and term as a pair.

      This avoids TOCTOU issues when callers need both values to correspond to the same log state (e.g., RequestVote RPCs where index and term must match).

      Returns:
      a record containing the last index and last term, read under a single lock
    • getTermAt

      public long getTermAt(long index)
      Get the term at a specific index.

      Note: This method acquires the read lock via withReadLock, then internally calls getEntry() which also acquires the read lock. This is safe because ReentrantReadWriteLock is reentrant.

    • removeFrom

      public void removeFrom(long fromIndex)
      Remove all entries from the given index onwards (inclusive).

      This is a convenience method used to roll back entries that failed WAL persistence. Delegates to truncateFrom(long).

      Parameters:
      fromIndex - The index from which to start removing (1-based, inclusive)
    • truncateFrom

      public void truncateFrom(long fromIndex)
      Truncate log from the given index onwards (inclusive).

      Removes all entries at or after fromIndex. This is used when a follower's log conflicts with the leader's log during AppendEntries RPC to restore consistency.

      Thread-safe via write lock.

      Parameters:
      fromIndex - The index from which to start truncating (1-based, inclusive). If fromIndex is beyond the log end, this is a no-op
    • compactUpTo

      public void compactUpTo(long snapshotIndex, long snapshotTerm)
      Compact the log by removing all entries up to and including snapshotIndex.

      This is called after the state machine is snapshotted to bound memory usage. Entries before snapshotIndex are discarded since they are no longer needed (the state machine state is captured in the snapshot).

      If snapshotIndex is older than the current snapshot, this operation is a no-op. Thread-safe via write lock.

      Parameters:
      snapshotIndex - The index of the last entry included in the snapshot (1-based)
      snapshotTerm - The term of the entry at snapshotIndex
    • restoreSnapshotState

      public void restoreSnapshotState(long snapshotIndex, long snapshotTerm)
      Restore snapshot state during recovery. Resets the in-memory log to start from the snapshot index (entries will be replayed from WAL afterward). This differs from compactUpTo which expects entries to already exist.
      Parameters:
      snapshotIndex - The snapshot's last included index
      snapshotTerm - The snapshot's last included term
    • size

      public int size()
      Get the total number of entries in the log.

      This count includes the sentinel entry at index 0, so it is always >= 1. Does not include entries removed by compaction (snapshots).

      Thread-safe via read lock.

      Returns:
      The number of log entries in memory (including sentinel)
    • getSnapshotIndex

      public long getSnapshotIndex()
      Get the index of the last snapshot.

      The snapshot index represents the last entry that was included in the most recent snapshot of the state machine. Entries up to and including this index have been removed from the log.

      Thread-safe via read lock.

      Returns:
      The snapshot index, or 0 if no snapshot exists
    • getSnapshotTerm

      public long getSnapshotTerm()
      Get the term of the last snapshot.

      This is the Raft term of the entry at the snapshot index.

      Thread-safe via read lock.

      Returns:
      The snapshot term, or 0 if no snapshot exists
    • getLogStats

      public LogStats getLogStats()
      Get a snapshot of current log statistics.

      Returns a point-in-time view of log metrics for monitoring and diagnostics. Thread-safe via read lock.

      Returns:
      A LogStats record with current log state
    • getEntryRange

      public List<LogEntry> getEntryRange(long fromIndex, long toIndex)
      Retrieve a range of entries from the log.

      This method returns entries within the specified range [fromIndex, toIndex]. Returns entries in order, or an empty list if the range is invalid or out of bounds. Thread-safe via read lock.

      Parameters:
      fromIndex - The starting index (1-based, inclusive)
      toIndex - The ending index (1-based, inclusive)
      Returns:
      A list copy of entries in the range, or empty list if range is invalid
    • containsEntry

      public boolean containsEntry(long index, long term)
      Check if an entry with the given index and term exists in the log.

      This is used to verify log consistency during replication. Thread-safe via read lock.

      Parameters:
      index - The entry index (1-based)
      term - The expected term of the entry
      Returns:
      true if an entry at index with the given term exists, false otherwise
    • findConflict

      public long findConflict(List<LogEntry> entries)
      Find the first conflicting entry when applying a list of new entries.

      Compares entries in the provided list against the log to find the first index where the terms differ. Returns the index of the first conflict, or -1 if no conflict. This is used during replication to determine where the follower's log diverges. Thread-safe via read lock.

      Parameters:
      entries - List of entries to check for conflicts (non-null)
      Returns:
      The index (1-based) of the first conflicting entry, or -1 if no conflict
      Throws:
      NullPointerException - if entries is null
    • estimateSizeInBytes

      public long estimateSizeInBytes()
      Estimate the memory usage of the log in bytes.

      Provides an approximate size calculation including entry overhead. Formula: ~56 bytes per LogEntry object + command size. Thread-safe via read lock.

      Returns:
      Approximate size in bytes (non-negative)