All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.permazen.kv.raft.Follower Maven / Gradle / Ivy


/*
 * Copyright (C) 2015 Archie L. Cobbs. All rights reserved.
 */

package io.permazen.kv.raft;

import io.permazen.kv.raft.msg.AppendResponse;

import java.util.Comparator;
import java.util.HashSet;
import java.util.NavigableSet;
import java.util.Set;
import java.util.TreeSet;

import javax.annotation.concurrent.GuardedBy;

/**
 * Contains information maintained by leaders about followers.
 */
public class Follower {

    /**
     * Sorts instances by their {@linkplain Follower#getIdentity identities}.
     */
    public static final Comparator SORT_BY_IDENTITY = Comparator.comparing(Follower::getIdentity);

    private final RaftKVDatabase raft;
    private final String identity;                      // follower's unique identity
    private final String address;                       // follower's network address
    private final Service updateService;

    @GuardedBy("raft")
    private final Timer updateTimer;                    // heartbeat/update timer

    // Used to avoid sending data for log entry back to the follower if the follower, as the originator, already has the data
    @GuardedBy("raft")
    private final HashSet skipDataLogEntries = new HashSet<>();

    // Used to keep track of which leader lease timeouts this follower is waiting on to commit (read-only) transactions.
    // This is so when the leaderLeaseTimeout exceeds one of these values, we know to immediately notify the follower.
    @GuardedBy("raft")
    private final TreeSet commitLeaseTimeouts = new TreeSet<>();

    @GuardedBy("raft")
    private long nextIndex;                             // index of the next log entry to send to peer
    @GuardedBy("raft")
    private long matchIndex;                            // index of highest log entry in follower that we know matches ours
    @GuardedBy("raft")
    private long leaderCommit;                          // highest leaderCommit we have sent to this follower
    @GuardedBy("raft")
    private Timestamp leaderTimestamp;                  // most recent leaderTimestamp received in any AppendResponse
    @GuardedBy("raft")
    private Timestamp snapshotTimestamp;                // timestamp of the most recent snapshot install
    @GuardedBy("raft")
    private boolean synced;                             // if previous AppendEntryRequest was successful
    @GuardedBy("raft")
    private SnapshotTransmit snapshotTransmit;          // in-progress snapshot transfer, if any

// Construtors

    Follower(LeaderRole leader, String identity, String address, long lastLogIndex) {
        assert leader != null;
        assert identity != null;
        assert address != null;
        assert lastLogIndex >= 0;
        assert Thread.holdsLock(leader.raft);
        this.raft = leader.raft;
        this.identity = identity;
        this.address = address;
        this.nextIndex = lastLogIndex + 1;
        this.updateService = new Service(leader, "update follower \"" + this.identity + "\"", () -> leader.updateFollower(this));
        this.updateTimer = new Timer(this.raft, "update timer for \"" + this.identity + "\"", this.updateService);
    }

// Status

    /**
     * Get the identity of this follower.
     *
     * @return follower identity
     */
    public String getIdentity() {
        synchronized (this.raft) {
            return this.identity;
        }
    }

    /**
     * Get the address of this follower.
     *
     * @return follower address
     */
    public String getAddress() {
        synchronized (this.raft) {
            return this.address;
        }
    }

    /**
     * Get the index of the next log entry to send to this follower.
     *
     * @return follower next index
     */
    public long getNextIndex() {
        synchronized (this.raft) {
            return this.nextIndex;
        }
    }
    void setNextIndex(final long nextIndex) {
        assert Thread.holdsLock(this.raft);
        this.nextIndex = nextIndex;
    }

    /**
     * Get the index of the highest log entry in the follower's log known to match the leader's log.
     *
     * @return follower match index; will be zero if unknown
     */
    public long getMatchIndex() {
        synchronized (this.raft) {
            return this.matchIndex;
        }
    }
    void setMatchIndex(final long matchIndex) {
        assert Thread.holdsLock(this.raft);
        this.matchIndex = matchIndex;
    }

    /**
     * Get the most recent (leader's) request timestamp returned by this follower in a response, if any.
     *
     * @return follower leader timestamp, or null if no response has been received yet from this follower
     */
    public Timestamp getLeaderTimestamp() {
        synchronized (this.raft) {
            return this.leaderTimestamp;
        }
    }
    void setLeaderTimestamp(final Timestamp leaderTimestamp) {
        assert Thread.holdsLock(this.raft);
        this.leaderTimestamp = leaderTimestamp;
    }

    /**
     * Get the (leader's) timestamp of the most recent snapshot install sent to this follower, if any.
     *
     * @return follower leader timestamp, or null if no response has been received yet from this follower
     */
    public Timestamp getSnapshotTimestamp() {
        synchronized (this.raft) {
            return this.snapshotTimestamp;
        }
    }
    void setSnapshotTimestamp(final Timestamp snapshotTimestamp) {
        assert Thread.holdsLock(this.raft);
        this.snapshotTimestamp = snapshotTimestamp;
    }

    /**
     * Get the leader commit index most recently sent to this follower.
     *
     * @return follower leader commit index
     */
    public long getLeaderCommit() {
        synchronized (this.raft) {
            return this.leaderCommit;
        }
    }
    void setLeaderCommit(final long leaderCommit) {
        assert Thread.holdsLock(this.raft);
        this.leaderCommit = leaderCommit;
    }

    /**
     * Determine whether we believe this follower is "synchronized".
     *
     * 

* By "synchronized" we mean the most recently received {@link AppendResponse} * indicated a successful match of the previous log entry. We only send "probes" to unsynchronized followers. * * @return true if synchronized */ public boolean isSynced() { synchronized (this.raft) { return this.synced; } } void setSynced(final boolean synced) { assert Thread.holdsLock(this.raft); if (this.synced != synced) { this.synced = synced; if (this.raft.isPerfLogEnabled()) this.raft.perfLog("sync status of \"" + this.identity + "\" changed -> " + (!synced ? "not " : "") + "synced"); } } /** * Determine whether this follower is currently being sent a whole database snapshot download. * * @return true if snapshot install is in progress */ public boolean isReceivingSnapshot() { synchronized (this.raft) { return this.snapshotTransmit != null; } } // Package-access methods boolean hasLogEntry(long index) { assert Thread.holdsLock(this.raft); return this.matchIndex >= index && this.raft.isClusterMember(this.identity); } SnapshotTransmit getSnapshotTransmit() { assert Thread.holdsLock(this.raft); return this.snapshotTransmit; } void setSnapshotTransmit(final SnapshotTransmit snapshotTransmit) { assert Thread.holdsLock(this.raft); this.snapshotTransmit = snapshotTransmit; } Set getSkipDataLogEntries() { assert Thread.holdsLock(this.raft); return this.skipDataLogEntries; } NavigableSet getCommitLeaseTimeouts() { assert Thread.holdsLock(this.raft); return this.commitLeaseTimeouts; } Timer getUpdateTimer() { assert Thread.holdsLock(this.raft); return this.updateTimer; } Service getUpdateService() { return this.updateService; } void updateNow() { assert Thread.holdsLock(this.raft); this.updateTimer.timeoutNow(); } void cancelSnapshotTransmit() { assert Thread.holdsLock(this.raft); if (this.snapshotTransmit != null) { this.matchIndex = Math.min(this.matchIndex, this.snapshotTransmit.getSnapshotIndex()); this.snapshotTransmit.close(); this.snapshotTransmit = null; this.setSynced(false); } } // Clean up this follower void cleanup() { assert Thread.holdsLock(this.raft); this.cancelSnapshotTransmit(); this.updateTimer.cancel(); } // Object @Override public String toString() { synchronized (this.raft) { return this.getClass().getSimpleName() + "[identity=\"" + this.identity + "\"" + ",nextIndex=" + this.nextIndex + ",matchIndex=" + this.matchIndex + ",leaderCommit=" + this.leaderCommit + (this.leaderTimestamp != null ? ",leaderTimestamp=" + String.format("%+dms", this.leaderTimestamp.offsetFromNow()) : "") + ",synced=" + this.synced + (!this.skipDataLogEntries.isEmpty() ? ",skipDataLogEntries=" + this.skipDataLogEntries : "") + (this.snapshotTransmit != null ? ",snapshotTransmit=" + this.snapshotTransmit : "") + "]"; } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy