[ 
https://issues.apache.org/jira/browse/HDDS-17579?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
 ]

Siyao Meng updated HDDS-17579:
------------------------------
    Attachment: TestBugMC2NewLeaderStaleRead.java

> New OM leader reports ready while previous term entries are still unapplied, 
> so default reads miss acknowledged writes
> ----------------------------------------------------------------------------------------------------------------------
>
>                 Key: HDDS-17579
>                 URL: https://issues.apache.org/jira/browse/HDDS-17579
>             Project: Apache Ozone
>          Issue Type: Bug
>    Affects Versions: 1.4.0, 2.0.0, 1.4.1, 2.1.0, 2.2.0, 2.1.1, 2.1.2, 2.2.1
>            Reporter: Siyao Meng
>            Priority: Major
>         Attachments: 
> MC-2-new-om-leader-ready-only-after-previous-term-applied.patch, 
> TestBugMC2NewLeaderStaleRead.java
>
>
> h3. Mechanism
> {{OzoneManagerRatisServer.getLeaderStatus}} returns {{LEADER_AND_READY}} as 
> soon as Ratis reports the division as leader ready. Ratis (3.3.1) sets that 
> when its state machine updater reaches the leader's startup configuration 
> entry ({{LeaderStateImpl.checkReady}}), and the updater counts an entry as 
> applied once {{applyTransaction}} has returned its future. 
> {{OzoneManagerStateMachine.applyTransaction}} only queues the entry on the 
> single OM apply executor ({{CompletableFuture.supplyAsync}}), and 
> {{notifyLeaderReady}} does not wait for that queue. So a new leader can be 
> ready while entries from the previous term, including writes already 
> acknowledged to their clients, are still queued and not in the OM table cache 
> or DB.
> On {{LEADER_AND_READY}} the default read path 
> ({{OzoneManagerProtocolServerSideTranslatorPB.submitReadRequestToOmDefault}}) 
> and the leader local branches of the other read paths answer from local 
> state. Background services that check {{OzoneManager.isLeaderReady}} start in 
> the same window (HDDS-16795 is one consequence).
> h3. Trigger
> Leadership moves to an OM whose apply executor has a backlog, or an OM 
> restarts with committed entries that were not flushed before it stopped, and 
> a read arrives before the backlog is applied.
> # Leader change (run): in a 3 OM ring, a follower's apply executor is held up 
> (in the test by holding a bucket lock on that follower). A key create in that 
> bucket, then a create of {{key1}} in another bucket, are acknowledged by the 
> leader and replicated. Leadership is transferred to the lagging follower. 
> Ratis logs "is ready since appliedIndex == startIndex", {{getLeaderStatus}} 
> returns {{LEADER_AND_READY}}, and a client {{getKey(key1)}} returns 
> {{KEY_NOT_FOUND}}, {{listKeys}} returns nothing. After the queue drains the 
> key is there.
> # Single OM restart (run during the analysis, not part of the attached test): 
> an OM stopped after acknowledging writes but before the double buffer flushed 
> them replays them in a new term after restart, and gives the same result. The 
> unflushed stop was simulated by pausing the double buffer before the stop; 
> the replay backlog was natural in one run (1500 key commits, Ratis ready at 
> index 4507 while OM had applied up to 1953) and came from a held bucket lock 
> in the other.
> The window lasts until the executor has caught up, at most 
> {{ozone.om.unflushed.transaction.max.count}} (default 10000) entries behind. 
> In the leader change case, a follower that keeps up has no backlog and is not 
> affected.
> h3. Impact
> * A client reading through the default path (and the leader local branches of 
> the follower read paths) gets {{KEY_NOT_FOUND}}, an empty listing or other 
> stale metadata for objects whose write was already acknowledged. The answer 
> is final for the client; nothing retries or corrects it.
> * Services gated by {{isLeaderReady}} (key and directory deletion, open key 
> and multipart cleanup, lifecycle, Ranger sync, upgrade finalization) can run 
> against state that is missing committed entries.
> * Linearizable reads served by the new leader through Ratis are affected the 
> same way. Ratis 3.3.1 ({{StateMachineUpdater.applyLog}}) passes a 
> configuration entry's index to the ReadIndex wait as applied right away, 
> while the futures of earlier OM entries can still be pending, and with no new 
> writes the read index is the startup index. On unmodified source with 
> {{ozone.om.ratis.server.read.option=LINEARIZABLE}}, 
> {{ozone.om.allow.leader.skip.linearizable.read=false}} and the leader lease 
> off, reads of {{key1}} with {{LINEARIZABLE_LEADER_ONLY}} and with 
> {{LINEARIZABLE_ALLOW_FOLLOWER}}, both served by the new leader through Ratis, 
> returned {{KEY_NOT_FOUND}} (run during the review of this report).
> h3. Reproduction
> PASS on unmodified source. [^TestBugMC2NewLeaderStaleRead.java] 
> ({{hadoop-ozone/integration-test}}) runs a 3 OM mini cluster, holds the 
> bucket lock of {{bucket2}} on a follower, writes a key in {{bucket2}} and 
> {{key1}} in {{bucket1}} through the client, transfers leadership to that 
> follower with the Ratis admin API and reads {{key1}} through the client. It 
> asserts {{LEADER_AND_READY}}, {{KEY_NOT_FOUND}} and an empty listing, then 
> that {{key1}} is readable once the lock is released. A passing test means the 
> defect is present. The held lock stands in for a slow apply; the restart 
> variant was shown earlier with a natural replay backlog.
> h3. Patch
> [^MC-2-new-om-leader-ready-only-after-previous-term-applied.patch], against 
> 424482eb9cfc4b835e1bf73bcdaa9e7d70df59f0. It also applies to master at 
> fef9cde752a.
> {{getLeaderStatus}} now also requires that the term of the OM state machine's 
> last applied index equals the current term. OM advances that index after the 
> double buffer has flushed an entry, and moves it onto a following Ratis 
> configuration or metadata entry once everything before it is flushed. Every 
> term starts with the leader's startup configuration entry, so the index is in 
> the current term exactly when everything committed in earlier terms is 
> applied and flushed. A leader with no backlog is not held back: the index 
> moves onto the startup entry right after Ratis applies it. With a backlog the 
> leader reports {{LEADER_AND_NOT_READY}} and clients retry. Writes are 
> rejected during the backlog too, so clients retry after 
> {{ozone.client.wait.between.retries.millis}} instead of queueing behind it. 
> That also keeps {{createClientRequest}} and {{preExecute}} from running 
> against the same stale local state, where a write could fail with 
> {{BUCKET_NOT_FOUND}} for a bucket created just before the leader change, or 
> be built for the wrong bucket layout (argued from code, not run). No new 
> state or config.
> On the new leader this closes the no hint read path, {{DEFAULT}} and 
> {{LINEARIZABLE_LEADER_ONLY}}, which now reject with 
> {{OMLeaderNotReadyException}} until the backlog is applied (with the patch, a 
> {{LINEARIZABLE_LEADER_ONLY}} read kept retrying while the backlog was held, 
> run during the review). It does not close {{LINEARIZABLE_ALLOW_FOLLOWER}} 
> landing on a not ready leader, which still goes to the Ratis ReadIndex wait 
> described above (argued from code), {{LOCAL_LEASE}} reads on a not ready 
> leader, covered by HDDS-17550, or linearizable reads on a follower, whose own 
> ReadIndex wait passes configuration entries in the same way (argued from 
> code). Rejecting {{LEADER_AND_NOT_READY}} in the 
> {{LINEARIZABLE_ALLOW_FOLLOWER}} branch, as the {{LINEARIZABLE_LEADER_ONLY}} 
> branch already does, is a possible follow up.
> Related: this is not a duplicate of HDDS-16795, which reports double purges 
> and quota drift in {{KeyDeletingService}} with the same root cause. 
> {{KeyDeletingService}} and {{DirectoryDeletingService}} check 
> {{isLeaderReady}} before both the active object store and the snapshot 
> branches, so this patch also closes the window of HDDS-16795 for runs that 
> start after the leader change or restart, including the snapshot path, and 
> the separate check proposed there is not needed if this change lands. 
> HDDS-16794 (a run already in progress across the change) is not covered. 
> RATIS-2313 asked Ratis to wait for pending state machine futures before 
> marking the leader ready and was resolved Not A Bug: a state machine that 
> needs this should wait itself, so the check belongs in Ozone. HDDS-13357 
> records the same assumption for {{resetInFlightSnapshotCount}}. Before Ozone 
> 1.4.0, Ratis 2.x readiness required the state machine's own applied index 
> ({{LeaderStateImpl.isReady}}); the window opened with the move to Ratis 3 
> (HDDS-9192), and this patch restores that meaning in OM. HDDS-10546 made OM 
> move that index onto the following configuration entries once earlier entries 
> are flushed, which this check relies on.
> Covered by {{testNewLeaderNotReadyUntilPreviousTermApplied}} in the existing 
> {{TestOzoneManagerHAWithStoppedNodes}}: the same leader change with a held 
> lock, asserting {{LEADER_AND_NOT_READY}} while Ratis already reports the 
> division ready, and that the key is readable once the backlog drains. Without 
> the change to {{OzoneManagerRatisServer}} it fails with "expected: 
> LEADER_AND_NOT_READY but was: LEADER_AND_READY". With it, 
> {{TestOzoneManagerHAWithStoppedNodes}} and 
> {{TestOzoneManagerHAWithAllRunning}} (44 tests), the OM state machine, Ratis 
> server, double buffer, background service and key manager unit suites (566 
> tests), {{TestTransferLeadershipShell}}, {{TestAddRemoveOzoneManager}}, 
> {{TestOzoneManagerHAFollowerReadWithAllRunning}}, {{TestOzoneManagerRestart}} 
> and {{TestOMUpgradeFinalization}} (34 tests) pass, checkstyle is clean. With 
> the patch the reproduction fails at its {{LEADER_AND_READY}} assertion, 
> because the new leader reports {{LEADER_AND_NOT_READY}} until the backlog is 
> applied. The single OM restart variant was not rerun with the patch.
> Found by TLA+ model checking and code review of the OM and SCM Ratis state 
> machine integration (leader readiness, snapshot install and log failure 
> handling) under HDDS-15926, on commit 
> 424482eb9cfc4b835e1bf73bcdaa9e7d70df59f0. Checked against HDDS issues and 
> apache/ozone pull requests for duplicates before filing. 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]

Reply via email to