[
https://issues.apache.org/jira/browse/HDDS-17548?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
]
Siyao Meng updated HDDS-17548:
------------------------------
Attachment: TestBugMC4LocalLeaseNotReadyLeaderRetry.java
> OM client turns follower read off for the rest of its life, or keeps retrying
> a not ready leader, after an OM rejects a LOCAL_LEASE read
> ----------------------------------------------------------------------------------------------------------------------------------------
>
> Key: HDDS-17548
> URL: https://issues.apache.org/jira/browse/HDDS-17548
> Project: Apache Ozone
> Issue Type: Bug
> Affects Versions: 2.2.0, 2.2.1
> Reporter: Siyao Meng
> Priority: Major
> Attachments:
> MC-1-MC-4-local-lease-rejection-keeps-follower-read.patch,
> TestBugMC1LocalLeaseRejectionDisablesFollowerRead.java,
> TestBugMC4LocalLeaseNotReadyLeaderRetry.java
>
>
> h3. Mechanism
> {{OzoneManagerProtocolServerSideTranslatorPB.submitReadRequestToOmLocalLease}}
> rejects a LOCAL_LEASE read with {{createLeaderErrorException}} when local
> lease is disabled on the OM or the OM fails {{allowFollowerReadLocalLease}}
> (last leader RPC older than the lease time, applied index too far behind the
> leader's commit index, or no known leader with a finite log limit). On a
> follower that is {{OMNotLeaderException}}, on a leader that is not ready yet
> it is {{OMLeaderNotReadyException}}. Both client providers,
> {{HadoopRpcOMFollowerReadFailoverProxyProvider}} and
> {{GrpcOMFollowerReadFailoverProxyProvider}}, treat these answers as routing
> errors:
> * {{OMNotLeaderException}} from the follower read node calls
> {{disableFollowerRead}}. The flag is shared by the whole client and only the
> constructor sets it. It is meant for a cluster whose followers do not serve
> follower reads at all (for example a Ratis read option other than
> LINEARIZABLE, where a LINEARIZABLE_ALLOW_FOLLOWER read gets the same
> exception). Afterwards {{shouldUseFollowerRead}} is false for every request,
> and reads without a hint get the leader consistency
> ({{ozone.client.leader.read.default.consistency}}, DEFAULT) instead of the
> follower consistency.
> * {{OMLeaderNotReadyException}} is rethrown to the retry policy without
> moving the follower read node. The policy retries the OM it believes is the
> leader ({{setNextOmProxy(getCurrentProxyOMNodeId())}}), and
> {{selectFollowerReadNodeId}} skips that OM for a LOCAL_LEASE read, so it
> picks the same not ready OM again. The rethrow assumes that the not ready OM
> is the believed leader, which is not the case after an election the client
> has not seen yet.
> h3. Trigger
> Both need a LOCAL_LEASE read:
> {{ozone.client.follower.read.default.consistency=LOCAL_LEASE}}, a per request
> hint through {{ClientProtocol.setThreadLocalReadConsistency}}, or the S3
> header {{x-ozone-read-consistency: follower-stale}} (with an optional
> {{x-ozone-local-lease-log-limit}}). The S3 gateway honours the header only
> when security is off; with security on it sends every S3 request as
> LINEARIZABLE_LEADER_ONLY. Both providers are always installed, and a read
> with an explicit LOCAL_LEASE hint goes to a follower even with
> {{ozone.client.follower.read.enabled=false}} (the default). The consistency
> change in the first impact below needs
> {{ozone.client.follower.read.enabled=true}} with LINEARIZABLE_ALLOW_FOLLOWER
> as the follower default; with follower read off on the client, unhinted reads
> already go to the leader as DEFAULT, and a rejected hinted read only stops
> later hinted reads from using followers.
> * Follower read turned off: one LOCAL_LEASE read reaches a follower that
> fails the lease check. At default settings that happens during an election,
> after a gap of more than 5 s in leader RPCs, or more than 10000 entries
> behind the leader. With a small per request log limit (0 from the S3 header)
> a follower that is one entry behind under write load is enough. An OM with
> {{ozone.om.follower.read.local.lease.enabled=false}} (the default) rejects
> every LOCAL_LEASE read this way.
> * Retries pinned to a not ready leader: a new leader is elected before the
> client learns about it (it learns from the {{OMNotLeaderException}} redirect
> of a request sent to the old leader). The new leader rejects LOCAL_LEASE
> reads while it is not ready and its apply backlog is larger than the log
> limit (10000 by default, or the per request limit). The old leader, now a
> follower, and the third OM pass the same check and could serve.
> h3. Impact
> * Follower read stays off for the rest of the client's life. The S3 gateway
> uses one {{OzoneClient}} for all requests, so a single request can turn it
> off for every user of that gateway until it restarts. All reads then go to
> the leader. Reads without a hint that the client would send as
> LINEARIZABLE_ALLOW_FOLLOWER (the default follower consistency) are sent as
> DEFAULT, which the leader serves locally after checking its own Raft role,
> without a ReadIndex round. That is what a client without follower read gets,
> but it is weaker than the configured linearizable read: a leader that has
> lost leadership without noticing yet can return stale data (argued from the
> code, not shown). For a client whose default is LOCAL_LEASE the reads get
> stronger, and the effect is only the loss of follower read. On a cluster with
> local lease disabled, LOCAL_LEASE reads also switch off the
> LINEARIZABLE_ALLOW_FOLLOWER follower reads the cluster does serve.
> * The LOCAL_LEASE read is retried only on the not ready OM, with the growing
> wait of the retry policy, until that OM is ready or
> {{ozone.client.failover.max.attempts}} is used up, while the other OMs could
> serve it at once. At default settings (500 attempts, 2 s base wait) this is
> mostly a stall that can last past the not ready window; a failed read needs a
> smaller budget or a long window. Another request on the same client that
> corrects the believed leader ends it. Since HDDS-17448 the gRPC client (the
> S3 gateway path) does the same as the Hadoop RPC client; before it, the gRPC
> client fell back to the leader here.
> h3. Reproduction
> [^TestBugMC1LocalLeaseRejectionDisablesFollowerRead.java] runs in
> {{hadoop-ozone/integration-test}} on the shared three OM cluster of
> {{OzoneManagerHAFollowerReadTests}} (Ratis LINEARIZABLE read, local lease
> enabled), with the Hadoop RPC and the gRPC transport, through the client API
> plus the existing test hooks that pin the follower read node
> ({{changeInitialProxyForTest}}, {{changeLeaderProxyForTest}},
> {{changeFollowerReadInitialProxy}}). One LOCAL_LEASE read with a per request
> lease time of 0 ms is rejected by the follower (its
> {{NumFollowerReadLocalLeaseFailTime}} metric goes up) and served by the
> leader. Afterwards 5 reads without a hint, pointed at the same follower, did
> 0 linearizable reads on the follower and 0 on the leader with both
> transports. With the patch the follower served all 5 as linearizable reads.
> [^TestBugMC4LocalLeaseNotReadyLeaderRetry.java] runs in
> {{hadoop-ozone/common}}. The Hadoop RPC retry proxy and the gRPC transport
> run unmodified against three simulated OMs that answer like
> {{submitReadRequestToOmLocalLease}} does for their role: a write teaches the
> client that om1 is the leader, then om2 becomes the leader and stays not
> ready. With a budget of 6 failovers the LOCAL_LEASE read went to om2 on every
> attempt (7 with Hadoop RPC, 6 with gRPC) and failed, and om1 and om3 got no
> request. With the patch it is served at once. The pinned retries were not
> produced on a real cluster: the existing test hooks cannot hold a newly
> elected OM not ready with a backlog above the limit.
> h3. Patch
> [^MC-1-MC-4-local-lease-rejection-keeps-follower-read.patch], against
> efe0c7eba1527530d1001f6517b733576fdc6d61, which is also current master.
> In both providers, an {{OMNotLeaderException}} or
> {{OMLeaderNotReadyException}} answer to a LOCAL_LEASE read now moves the
> follower read node to the next OM and sends this read to the leader, as the
> {{OMNotLeaderException}} branch already did, but without
> {{disableFollowerRead}}. Other reads keep the current handling: a cluster
> that does not serve LINEARIZABLE_ALLOW_FOLLOWER still turns follower read off
> ({{TestOzoneManagerHAWithAllRunning.testOMFollowerReadWithClusterDisabled}}),
> and a not ready leader is still retried after a wait (HDDS-17448). There is
> no server, protocol or configuration change. A separate server change makes
> OMs reject more LOCAL_LEASE reads; this client fix should land first or
> together with it. One behaviour changes for a setup without the bug: a
> LOCAL_LEASE client against OMs with local lease disabled, or against OMs
> older than follower read support (2.1.x and older answer every read on a
> follower with {{OMNotLeaderException}}, which matters when clients are
> upgraded first), now sends each LOCAL_LEASE read to one follower and then to
> the leader (one extra RPC per read), where before the first rejection sent
> all later reads to the leader. That cost is acceptable because the rejection
> is a cheap role and configuration check on the OM, is logged at DEBUG only,
> and only hits clients that asked for LOCAL_LEASE, while the old behaviour
> silently changed the read consistency of every later read. Trying the
> remaining followers before the leader, as for {{ReadIndexException}}, would
> cost up to one RPC per OM per read in that setup, with a warning per failed
> follower on the Hadoop RPC path, so the patch goes to the leader right away.
> Covered by {{testLocalLeaseRejectionKeepsFollowerRead}} and
> {{testLocalLeaseReadOnNotReadyLeaderUnknownToClient}} in
> {{TestHadoopRpcOMFollowerReadFailoverProxyProvider}} (through the retry
> proxy) and {{testLocalLeaseRejectionFallsBackToLeader}} (explicit hint) and
> {{testLocalLeaseRejectionWithLocalLeaseDefault}} (no hint, client default
> LOCAL_LEASE) for both exceptions in
> {{TestGrpcOMFollowerReadFailoverProxyProvider}}. Without the change they fail
> with "expected: <true> but was: <false>" for the follower read flag and with
> the {{OMLeaderNotReadyException}} thrown to the caller. With it the client
> side follower read and failover suites in {{hadoop-ozone/common}} pass (98
> tests), as do {{TestOzoneManagerHAFollowerReadWithAllRunning}},
> {{TestOzoneManagerHAFollowerReadWithStoppedNodes}},
> {{TestOzoneShellHAWithFollowerRead}} and
> {{testOMFollowerReadWithClusterDisabled}} (26 tests), and checkstyle is clean.
> Found by TLA+ model checking and code review of the OM follower read and read
> consistency routing paths under HDDS-15926, on commit
> efe0c7eba1527530d1001f6517b733576fdc6d61. Checked against HDDS issues and
> apache/ozone pull requests for duplicates before filing. Related but not
> duplicates: HDDS-16435 (a superseded leader serving leader local reads, which
> is the consistency the first symptom falls back to), HDDS-17448 (resolved,
> retry of a not ready leader that is the believed leader) and HDDS-14509
> (resolved, added the client read consistency levels). The attached patch is a
> proposal for review. Generated with Specula (Claude Opus 5.5).
--
This message was sent by Atlassian Jira
(v10.3.26#10030026)
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]