[
https://issues.apache.org/jira/browse/HDDS-16795?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
]
Siyao Meng updated HDDS-16795:
------------------------------
Attachment: TestBugMC1PurgeRepeatedAfterRestart.java
> KeyDeletingService can purge keys twice after a leader change to an OM still
> applying a backlog, or after an OM restart
> -----------------------------------------------------------------------------------------------------------------------
>
> Key: HDDS-16795
> URL: https://issues.apache.org/jira/browse/HDDS-16795
> Project: Apache Ozone
> Issue Type: Bug
> Reporter: Siyao Meng
> Priority: Major
> Attachments:
> MC-1-wait-for-flush-of-earlier-terms-before-aos-purge.patch,
> TestBugMC1PurgeRepeatedAfterRestart.java
>
>
> h3. Mechanism
> Before a run on the active object store, {{KeyDeletingService}} and
> {{DirectoryDeletingService}} call
> {{AbstractKeyDeletingService.isPreviousPurgeTransactionFlushed}}
> (HDDS-13760). It compares the last purge transaction, kept in memory in
> {{DeletingServiceMetrics}}, with the transaction info flushed to the DB. The
> in-memory value is set when this OM process applies a purge. After a restart
> it is empty, and on an OM that has just become leader it does not include a
> purge that is committed in the Ratis log but not applied yet.
> The OM reports leader ready as soon as the Ratis state machine updater has
> passed the first entry of the new term. The updater hands entries to the
> single OM executor and does not wait for them, so earlier entries, including
> a {{PurgeKeys}}, can still be queued at that time. A run that starts then
> passes {{shouldRun}} and the flush check, finds the same keys in
> {{deletedTable}} and submits a second {{PurgeKeys}}. {{OMKeyPurgeRequest}}
> subtracts the sizes in {{bucketPurgeKeysSize}} from {{snapshotUsedBytes}} and
> {{snapshotUsedNamespace}} for both requests. Deleting the already deleted
> rows is a no-op and nothing rejects the second request.
> h3. Trigger
> # A key is deleted and the delete is flushed.
> # A KeyDeletingService run submits {{PurgeKeys}} for it. The request is
> committed in the Ratis log.
> # Before the purge is applied on another OM that OM becomes leader, or before
> the purge is flushed the OM restarts.
> # The (new) leader reports leader ready while the purge is still waiting
> behind earlier entries in its executor.
> # A KeyDeletingService run starts in that window and purges the key again.
> The window is short, from leader ready until the executor has caught up,
> which is bounded by {{ozone.om.unflushed.transaction.max.count}} entries.
> With the default service interval of 60s a run rarely starts inside it. I
> expect the leader change onto an OM that is still applying a backlog (for
> example during a rolling restart under load) to be the realistic form, and
> the restart of a single OM to be practically unreachable at defaults, because
> its first run comes before leader ready and the next one 60s later (both
> argued, not run). The reproduction uses a restart and keeps the replay
> waiting.
> By history the defect is present since 2.1.0: {{ozone-2.1.0}} is the first
> release tag that has both the flush check (HDDS-13760) and the usage
> accounting in the purge (HDDS-13764). Not run on older code.
> h3. Impact
> * The pending deletion bytes and namespace of the bucket become too low,
> negative for a bucket with nothing else pending.
> {{OmBucketInfo.getTotalBucketSpace}} and {{getTotalBucketNamespace}} include
> them without a lower bound, so clients see wrong (in the reproduction
> negative) used bytes and namespace, and the quota checks in {{OMKeyRequest}}
> admit more than the quota. In the reproduction a bucket with a namespace
> quota of 1 accepted two keys. The space quota check reads the same total
> (code reading, not run).
> * The second request repeats the whole batch of the run, not a single key.
> * The wrong values are flushed to the bucket table and replicated, and
> nothing corrects them automatically. The quota repair recomputes these
> counters when an operator starts it (code reading, not run).
> * {{DirectoryDeletingService}} uses the same check and
> {{OMDirectoriesPurgeRequestWithFSO}} also reduces the bucket usage without a
> presence check, so it has the same exposure (code reading, not run).
> h3. Reproduction
> PASS, unmodified source. [^TestBugMC1PurgeRepeatedAfterRestart.java]
> ({{hadoop-ozone/ozone-manager}}) runs one real OM with a recording SCM block
> client and no datanodes. The KeyDeletingService gets a long interval and is
> run by hand. A key is deleted and flushed, then the double buffer is paused,
> a key is created in a second bucket and the service purges the deleted key,
> and the OM is stopped. A new {{OzoneManager}} is created on the same storage
> while the test holds the lock of the second bucket, so the replayed key
> create and the replayed purge behind it wait while the OM is already ready
> leader. The service run in that state sends the blocks to SCM again and
> submits a second purge. Afterwards the bucket row has {{snapshotUsedBytes}}
> of minus three times the key size and {{snapshotUsedNamespace = -1}} in
> RocksDB, the client reads the same negative usage, and with a namespace quota
> of 1 the bucket accepts two keys and rejects the third with
> {{QUOTA_EXCEEDED}}. A passing test means the defect is present.
> Note that {{OzoneManager.restart}} on the same object keeps the in-memory
> value and therefore does not show the problem.
> h3. Patch
> [^MC-1-wait-for-flush-of-earlier-terms-before-aos-purge.patch], against
> a6332fb59879b80615b9233e953d6813d03ae7a8. It also applies to master at
> 3a22acfaa21.
> {{isPreviousPurgeTransactionFlushed}} now also returns false while the last
> applied term index of the OM state machine is from an earlier term than the
> current term of the Ratis server. In OM that index is advanced by the double
> buffer after a flush, so once it is in the current term everything committed
> before this OM became leader is applied and flushed, and the scan sees it.
> After that the existing in-memory value covers the purges of the current
> term. A new leader with nothing to do is not held back: the first entry of
> every term is a Ratis configuration entry, and the state machine moves the
> last applied index onto it as soon as everything before it is flushed, so the
> check opens without any OM transaction in the new term. No new state is
> added, and both services are covered for the active object store. A run that
> starts in the window is skipped and logged at INFO.
> Subtracting only for keys that are still in {{deletedTable}}, or flooring the
> counters at 0, was considered and not taken: {{deletedTable}} has no table
> cache entries and the DB lags the apply by the double buffer, so an earlier
> purge that is not flushed would still look present, and OMs could decide
> differently.
> Covered by {{testNoPurgeUntilReplayedPurgeIsFlushed}} in a new nested class
> of the existing {{TestKeyDeletingService}}. It asserts that the check is
> closed for both services while the replayed purge is not applied, and that
> the key is then purged once. Without the change to
> {{AbstractKeyDeletingService}} it fails at the first of these assertions,
> because the check is open. {{OmTestManagers}} gets a small static helper so
> that a test can create a second OM on the same storage. With the patch 131
> unit tests of {{hadoop-ozone/ozone-manager}} (deleting service, purge
> request, key manager, snapshot manager and quota repair suites) and 31 mini
> cluster tests (key purging, OM HA with stopped nodes, snapshot and directory
> deleting service suites) pass, checkstyle is clean. With the patch the
> reproduction fails at its first assertion of the buggy outcome (SCM gets the
> blocks of the key once, not twice), because the run after the restart is
> skipped.
> Not covered by the patch, on purpose: the snapshot tasks of the two services
> use a different check, {{OmSnapshotManager.areSnapshotChangesFlushedToDB}},
> which reads the last transaction recorded in the snapshot info when a purge
> is applied. It has the same weakness, because on a new leader a snapshot
> purge that is committed but not applied is not recorded yet (code reading,
> not run). That check needs its own change and test and is left for a follow
> up.
> A run that has already passed the check when the leadership moves away and
> comes back is a different defect, filed separately as HDDS-16794. Neither fix
> covers the other: this patch covers a run that starts after the leader
> change, the other one a run that started before it.
> Found by TLA+ model checking and code review of the OM key deletion service
> and key purge paths under HDDS-15926, on commit
> a6332fb59879b80615b9233e953d6813d03ae7a8. 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
(v8.20.10#820010)
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]