use of com.hazelcast.cp.internal.raft.impl.log.RaftLog in project hazelcast by hazelcast.
the class RaftNodeImpl method canReplicateNewEntry.
/**
* Returns true if a new entry with the operation is currently allowed to
* be replicated. This method can be invoked only when the local Raft node
* is the leader.
* <p>
* Replication is not allowed, when;
* <ul>
* <li>Node is terminating, terminated or stepped down. See {@link RaftNodeStatus}.</li>
* <li>Raft log contains max allowed uncommitted entry count.
* See {@link RaftAlgorithmConfig#getUncommittedEntryCountToRejectNewAppends()}.</li>
* <li>The operation is a {@link RaftGroupCmd} and there's an ongoing membership change in group.</li>
* <li>The operation is a membership change operation and there's no committed entry in this term yet.
* See {@link RaftIntegration#getAppendedEntryOnLeaderElection()}.</li>
* <li>There is an ongoing leadership transfer.</li>
* </ul>
*/
public boolean canReplicateNewEntry(Object operation) {
if (isTerminatedOrSteppedDown()) {
return false;
}
RaftLog log = state.log();
long lastLogIndex = log.lastLogOrSnapshotIndex();
long commitIndex = state.commitIndex();
if (lastLogIndex - commitIndex >= maxUncommittedEntryCount) {
return false;
}
if (status == TERMINATING) {
return false;
} else if (status == UPDATING_GROUP_MEMBER_LIST) {
return state.lastGroupMembers().isKnownMember(getLocalMember()) && !(operation instanceof RaftGroupCmd);
}
if (operation instanceof UpdateRaftGroupMembersCmd) {
// the leader must have committed an entry in its term to make a membership change
// https://groups.google.com/forum/#!msg/raft-dev/t4xj6dJTP6E/d2D9LrWRza8J
// last committed entry is either in the last snapshot or still in the log
LogEntry lastCommittedEntry = commitIndex == log.snapshotIndex() ? log.snapshot() : log.getLogEntry(commitIndex);
assert lastCommittedEntry != null;
return lastCommittedEntry.term() == state.term();
}
return state.leadershipTransferState() == null;
}
use of com.hazelcast.cp.internal.raft.impl.log.RaftLog in project hazelcast by hazelcast.
the class RaftNodeImpl method applyLogEntries.
/**
* Applies committed log entries between {@code lastApplied} and {@code commitIndex}, if there's any available.
* If new entries are applied, {@link RaftState}'s {@code lastApplied} field is updated.
*
* @see RaftState#lastApplied()
* @see RaftState#commitIndex()
*/
public void applyLogEntries() {
// Reject logs we've applied already
long commitIndex = state.commitIndex();
long lastApplied = state.lastApplied();
if (commitIndex == lastApplied) {
return;
}
// If commitIndex > lastApplied: increment lastApplied, apply log[lastApplied] to state machine (§5.3)
assert commitIndex > lastApplied : "commit index: " + commitIndex + " cannot be smaller than last applied: " + lastApplied;
// Apply all the preceding logs
RaftLog raftLog = state.log();
for (long idx = state.lastApplied() + 1; idx <= commitIndex; idx++) {
LogEntry entry = raftLog.getLogEntry(idx);
if (entry == null) {
String msg = "Failed to get log entry at index: " + idx;
logger.severe(msg);
throw new AssertionError(msg);
}
applyLogEntry(entry);
// Update the lastApplied index
state.lastApplied(idx);
}
assert status != TERMINATED || commitIndex == raftLog.lastLogOrSnapshotIndex() : "commit index: " + commitIndex + " must be equal to " + raftLog.lastLogOrSnapshotIndex() + " on termination.";
if (state.role() == LEADER || state.role() == FOLLOWER) {
takeSnapshotIfCommitIndexAdvanced();
}
}
use of com.hazelcast.cp.internal.raft.impl.log.RaftLog in project hazelcast by hazelcast.
the class RaftNodeImpl method tryAdvanceCommitIndex.
public boolean tryAdvanceCommitIndex() {
// 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)
long quorumMatchIndex = findQuorumMatchIndex();
long commitIndex = state.commitIndex();
RaftLog raftLog = state.log();
for (; quorumMatchIndex > commitIndex; quorumMatchIndex--) {
// Only log entries from the leader’s current term are committed by counting replicas; once an entry
// from the current term has been committed in this way, then all prior entries are committed indirectly
// because of the Log Matching Property.
LogEntry entry = raftLog.getLogEntry(quorumMatchIndex);
if (entry.term() == state.term()) {
commitEntries(quorumMatchIndex);
return true;
} else if (logger.isFineEnabled()) {
logger.fine("Cannot commit " + entry + " since an entry from the current term: " + state.term() + " is needed.");
}
}
return false;
}
use of com.hazelcast.cp.internal.raft.impl.log.RaftLog in project hazelcast by hazelcast.
the class RaftStateTest method toLeader_fromCandidate.
@Test
public void toLeader_fromCandidate() {
state.toCandidate(false);
int term = state.term();
RaftLog log = state.log();
log.appendEntries(new LogEntry(term, 1, null), new LogEntry(term, 2, null), new LogEntry(term, 3, null));
long lastLogIndex = log.lastLogOrSnapshotIndex();
state.toLeader();
assertEquals(RaftRole.LEADER, state.role());
assertEquals(localMember, state.leader());
assertNull(state.candidateState());
LeaderState leaderState = state.leaderState();
assertNotNull(leaderState);
for (RaftEndpoint endpoint : state.remoteMembers()) {
FollowerState followerState = leaderState.getFollowerState(endpoint);
assertEquals(0, followerState.matchIndex());
assertEquals(lastLogIndex + 1, followerState.nextIndex());
}
long[] matchIndices = leaderState.matchIndices();
assertEquals(state.remoteMembers().size() + 1, matchIndices.length);
for (long index : matchIndices) {
assertEquals(0, index);
}
}
use of com.hazelcast.cp.internal.raft.impl.log.RaftLog in project hazelcast by hazelcast.
the class RaftNodeImpl method takeSnapshotIfCommitIndexAdvanced.
/**
* Takes a snapshot if the advance in {@code commitIndex} is equal to
* {@link RaftAlgorithmConfig#getCommitIndexAdvanceCountToSnapshot()}.
* <p>
* Snapshot is not created if the Raft group is being destroyed.
*/
@SuppressWarnings("checkstyle:npathcomplexity")
private void takeSnapshotIfCommitIndexAdvanced() {
long commitIndex = state.commitIndex();
if ((commitIndex - state.log().snapshotIndex()) < commitIndexAdvanceCountToSnapshot) {
return;
}
if (isTerminatedOrSteppedDown()) {
// If the status is TERMINATED or STEPPED_DOWN, then there will not be any new appends.
return;
}
RaftLog log = state.log();
Object snapshot = raftIntegration.takeSnapshot(commitIndex);
if (snapshot instanceof Throwable) {
Throwable t = (Throwable) snapshot;
logger.severe("Could not take snapshot at commit index: " + commitIndex, t);
return;
}
int snapshotTerm = log.getLogEntry(commitIndex).term();
RaftGroupMembers members = state.committedGroupMembers();
SnapshotEntry snapshotEntry = new SnapshotEntry(snapshotTerm, commitIndex, snapshot, members.index(), members.members());
long highestLogIndexToTruncate = commitIndex - maxNumberOfLogsToKeepAfterSnapshot;
LeaderState leaderState = state.leaderState();
if (leaderState != null) {
long[] matchIndices = leaderState.matchIndices();
// Last slot is reserved for leader index and always zero.
// If there is at least one follower with unknown match index,
// its log can be close to the leader's log so we are keeping the old log entries.
boolean allMatchIndicesKnown = Arrays.stream(matchIndices, 0, matchIndices.length - 1).noneMatch(i -> i == 0);
if (allMatchIndicesKnown) {
// Otherwise, we will keep the log entries until the minimum match index
// that is bigger than (commitIndex - maxNumberOfLogsToKeepAfterSnapshot).
// If there is no such follower (all of the minority followers are far behind),
// then there is no need to keep the old log entries.
highestLogIndexToTruncate = Arrays.stream(matchIndices).filter(i -> i < commitIndex).filter(i -> i > commitIndex - maxNumberOfLogsToKeepAfterSnapshot).map(i -> i - 1).sorted().findFirst().orElse(commitIndex);
}
}
int truncatedEntryCount = log.setSnapshot(snapshotEntry, highestLogIndexToTruncate);
if (logger.isFineEnabled()) {
logger.fine(snapshotEntry + " is taken, " + truncatedEntryCount + " entries are truncated.");
}
}
Aggregations