private boolean updateFollowerLogInformation( FollowerLogInformation followerLogInformation, AppendEntriesReply appendEntriesReply) { boolean updated = followerLogInformation.setMatchIndex(appendEntriesReply.getLogLastIndex()); updated = followerLogInformation.setNextIndex(appendEntriesReply.getLogLastIndex() + 1) || updated; if (updated && LOG.isDebugEnabled()) { LOG.debug( "{}: handleAppendEntriesReply - FollowerLogInformation for {} updated: matchIndex: {}, nextIndex: {}", logName(), followerLogInformation.getId(), followerLogInformation.getMatchIndex(), followerLogInformation.getNextIndex()); } return updated; }
@Override protected RaftActorBehavior handleAppendEntriesReply( ActorRef sender, AppendEntriesReply appendEntriesReply) { if (LOG.isTraceEnabled()) { LOG.trace("{}: handleAppendEntriesReply: {}", logName(), appendEntriesReply); } // Update the FollowerLogInformation String followerId = appendEntriesReply.getFollowerId(); FollowerLogInformation followerLogInformation = followerToLog.get(followerId); if (followerLogInformation == null) { LOG.error("{}: handleAppendEntriesReply - unknown follower {}", logName(), followerId); return this; } if (followerLogInformation.timeSinceLastActivity() > context.getConfigParams().getElectionTimeOutInterval().toMillis()) { LOG.warn( "{} : handleAppendEntriesReply delayed beyond election timeout, " + "appendEntriesReply : {}, timeSinceLastActivity : {}, lastApplied : {}, commitIndex : {}", logName(), appendEntriesReply, followerLogInformation.timeSinceLastActivity(), context.getLastApplied(), context.getCommitIndex()); } followerLogInformation.markFollowerActive(); followerLogInformation.setPayloadVersion(appendEntriesReply.getPayloadVersion()); boolean updated = false; if (appendEntriesReply.isSuccess()) { updated = updateFollowerLogInformation(followerLogInformation, appendEntriesReply); } else { LOG.debug( "{}: handleAppendEntriesReply: received unsuccessful reply: {}", logName(), appendEntriesReply); long followerLastLogIndex = appendEntriesReply.getLogLastIndex(); ReplicatedLogEntry followersLastLogEntry = context.getReplicatedLog().get(followerLastLogIndex); if (appendEntriesReply.isForceInstallSnapshot()) { // Reset the followers match and next index. This is to signal that this follower has // nothing // in common with this Leader and so would require a snapshot to be installed followerLogInformation.setMatchIndex(-1); followerLogInformation.setNextIndex(-1); // Force initiate a snapshot capture initiateCaptureSnapshot(followerId); } else if (followerLastLogIndex < 0 || (followersLastLogEntry != null && followersLastLogEntry.getTerm() == appendEntriesReply.getLogLastTerm())) { // The follower's log is empty or the last entry is present in the leader's journal // and the terms match so the follower is just behind the leader's journal from // the last snapshot, if any. We'll catch up the follower quickly by starting at the // follower's last log index. updated = updateFollowerLogInformation(followerLogInformation, appendEntriesReply); } else { // TODO: When we find that the follower is out of sync with the // Leader we simply decrement that followers next index by 1. // Would it be possible to do better than this? The RAFT spec // does not explicitly deal with it but may be something for us to // think about. followerLogInformation.decrNextIndex(); } } // Now figure out if this reply warrants a change in the commitIndex // If there exists an N such that N > commitIndex, a majority // of matchIndex[i] ≥ N, and log[N].term == currentTerm: // set commitIndex = N (§5.3, §5.4). for (long N = context.getCommitIndex() + 1; ; N++) { int replicatedCount = 1; for (FollowerLogInformation info : followerToLog.values()) { if (info.getMatchIndex() >= N) { replicatedCount++; } } if (replicatedCount >= minReplicationCount) { ReplicatedLogEntry replicatedLogEntry = context.getReplicatedLog().get(N); if (replicatedLogEntry != null && replicatedLogEntry.getTerm() == currentTerm()) { context.setCommitIndex(N); } else { break; } } else { break; } } // Apply the change to the state machine if (context.getCommitIndex() > context.getLastApplied()) { if (LOG.isDebugEnabled()) { LOG.debug( "{}: handleAppendEntriesReply from {}: applying to log - commitIndex: {}, lastAppliedIndex: {}", logName(), followerId, context.getCommitIndex(), context.getLastApplied()); } applyLogToStateMachine(context.getCommitIndex()); } if (!context.getSnapshotManager().isCapturing()) { purgeInMemoryLog(); } // Send the next log entry immediately, if possible, no need to wait for heartbeat to trigger // that event sendUpdatesToFollower(followerId, followerLogInformation, false, !updated); return this; }