diff --git a/ratis-docs/src/site/markdown/configurations.md b/ratis-docs/src/site/markdown/configurations.md index acd1cb9f9e..0f20540a20 100644 --- a/ratis-docs/src/site/markdown/configurations.md +++ b/ratis-docs/src/site/markdown/configurations.md @@ -226,6 +226,27 @@ if it fails to receive any RPC responses from this peer within this specified ti | **Type** | boolean | | **Default** | false | +| **Property** | `raft.server.read.leader.heartbeat-check.enabled` | +|:----------------|:--------------------------------------------------| +| **Description** | whether to check heartbeat for read index. | +| **Type** | boolean | +| **Default** | true | + +Note that the original read index algorithm requires heartbeat check +in order to guarantee linearizable read. +By setting this property to false, +it reduces the RTT by eliminating the heartbeat check. +However, it might cause the reads not to be linearizable in a split-brain case. +Without the heartbeat check, a leader might not be the latest leader +and, as a result, it might serve stale reads. +When there is a split brain, there might be a small period of time +that the (old) leader has lost majority heartbeats but have not yet detected it. +As the same time, a new leader is elected by a majority of peers. +Then, the old leader might serve stale data +since it does not have the transactions committed by the new leaders. +Since such split-brain case is supposed to be rare, +it might be an acceptable tradeoff for applications that +seek to improve the linearizable read performance. ### Write - Configurations related to write requests. diff --git a/ratis-server-api/src/main/java/org/apache/ratis/server/RaftServerConfigKeys.java b/ratis-server-api/src/main/java/org/apache/ratis/server/RaftServerConfigKeys.java index 2538a472a8..0786f7cf72 100644 --- a/ratis-server-api/src/main/java/org/apache/ratis/server/RaftServerConfigKeys.java +++ b/ratis-server-api/src/main/java/org/apache/ratis/server/RaftServerConfigKeys.java @@ -249,6 +249,17 @@ static void setLeaderLeaseTimeoutRatio(RaftProperties properties, double ratio) setDouble(properties::setDouble, LEADER_LEASE_TIMEOUT_RATIO_KEY, ratio); } + String LEADER_HEARTBEAT_CHECK_ENABLED_KEY = PREFIX + ".leader.heartbeat-check.enabled"; + boolean LEADER_HEARTBEAT_CHECK_ENABLED_DEFAULT = true; + static boolean leaderHeartbeatCheckEnabled(RaftProperties properties) { + return getBoolean(properties::getBoolean, LEADER_HEARTBEAT_CHECK_ENABLED_KEY, + LEADER_HEARTBEAT_CHECK_ENABLED_DEFAULT, getDefaultLog()); + } + + static void setLeaderHeartbeatCheckEnabled(RaftProperties properties, boolean enabled) { + setBoolean(properties::setBoolean, LEADER_HEARTBEAT_CHECK_ENABLED_KEY, enabled); + } + interface ReadAfterWriteConsistent { String PREFIX = Read.PREFIX + ".read-after-write-consistent"; diff --git a/ratis-server/src/main/java/org/apache/ratis/server/impl/LeaderStateImpl.java b/ratis-server/src/main/java/org/apache/ratis/server/impl/LeaderStateImpl.java index 90d0b76df5..ef0bb6b700 100644 --- a/ratis-server/src/main/java/org/apache/ratis/server/impl/LeaderStateImpl.java +++ b/ratis-server/src/main/java/org/apache/ratis/server/impl/LeaderStateImpl.java @@ -354,6 +354,7 @@ boolean isApplied() { private final ReadIndexHeartbeats readIndexHeartbeats; private final boolean readIndexAppliedIndexEnabled; + private final boolean leaderHeartbeatCheckEnabled; private final LeaderLease lease; LeaderStateImpl(RaftServerImpl server) { @@ -392,6 +393,8 @@ boolean isApplied() { } this.readIndexAppliedIndexEnabled = RaftServerConfigKeys.Read.ReadIndex .appliedIndexEnabled(properties); + this.leaderHeartbeatCheckEnabled = RaftServerConfigKeys.Read + .leaderHeartbeatCheckEnabled(properties); final RaftConfigurationImpl conf = state.getRaftConf(); Collection others = conf.getOtherPeers(server.getId()); @@ -1166,7 +1169,8 @@ CompletableFuture getReadIndex(Long readAfterWriteConsistentIndex) { } // if lease is enabled, check lease first - if (hasLease()) { + // if we allow leader to skip the leadership check heartbeat, we can return immediately + if (!leaderHeartbeatCheckEnabled || hasLease()) { return CompletableFuture.completedFuture(readIndex); }