Andrey Yarovoy created HDDS-16297:
-------------------------------------

             Summary: Bound per-directory purge size so a single dense 
directory doesn't hold the bucket write lock for the full per-task budget
                 Key: HDDS-16297
                 URL: https://issues.apache.org/jira/browse/HDDS-16297
             Project: Apache Ozone
          Issue Type: Improvement
            Reporter: Andrey Yarovoy


*Problem*

HDDS-16220 reduced OM apply-thread bucket-write-lock contention for the 
_multi-bucket_ directory-purge regime by grouping {{{}PurgePathRequest{}}}s per 
bucket ({{{}VolumeBucketId{}}}) so a single purge transaction no longer 
acquires several buckets' write locks together. It does not address the 
orthogonal _single dense directory_ case: one directory with a large fan-out of 
sub-files/sub-dirs.

In {{{}DirectoryDeletingService.prepareDeleteDirRequest{}}}, a directory's 
sub-entries are gathered up to the remaining per-task budget 
({{{}remainNum{}}}, seeded from {{{}ozone.path.deleting.limit.per.task{}}}) and 
{{wrapPurgeRequest}} bundles them into {*}one indivisible 
{{PurgePathRequest}}{*}. On the apply side, 
{{OMDirectoriesPurgeRequestWithFSO.validateAndUpdateCache}} acquires the bucket 
write lock once ({{{}acquireWriteLocks(BUCKET_LOCK, …){}}}) and applies every 
prepared entry under that single hold. So a single dense directory can consume 
the whole per-task budget worth of entries under one uninterrupted write-lock 
acquisition — freezing readers on that hot bucket for the duration.

*Why not split on the apply side*

{{validateAndUpdateCache}} applies *one already-committed Ratis transaction* 
and produces *one {{OMClientResponse}}* flushed atomically by the double buffer 
at {{{}context.getIndex(){}}}. The apply path receives a committed log entry 
and cannot re-split it into multiple transactions. Cycling the bucket write 
lock between chunks _within_ a single apply would:
 * not reduce the single apply thread's total serial work (the thing that 
bounds commit throughput), and
 * expose a partially-tombstoned subtree under one transaction index, departing 
from how every other OM request handler holds its lock once and releases in 
{{{}finally{}}}.

*Proposed approach (DDS-side)*

Introduce a per-{{{}PurgePathRequest{}}} (per-directory) entry cap that is 
distinct from the existing per-{_}round{_} 
{{{}ozone.path.deleting.limit.per.task{}}}. When a directory's pending 
sub-entries exceed the cap, emit them across *multiple* purge transactions 
instead of one. The normal per-transaction apply loop then releases and 
re-acquires the bucket write lock between chunks for free, bounding each 
individual lock hold — without any bespoke intra-apply lock cycling.

*Correctness surface to cover*
 * *Per-transaction atomicity of a directory purged in pieces* — a half-purged 
directory becomes observable between transactions. Note this already happens 
across DDS rounds today (a recursive delete is drained over many transactions), 
so the new behavior is a finer-grained instance of an existing property, not a 
new one — but it should be stated and tested.
 * *{{deletedTable}} move ordering / idempotency on apply-retry* — chunked 
emission must remain replay-safe if a transaction is re-applied.
 * *Snapshot chain ownership* — 
{{fromSnapshotInfo.setLastTransactionInfo(...)}} records the purge point on the 
snapshot; when a directory spans multiple transactions, define which 
transaction owns that update so the snapshot's {{lastTransactionInfo}} stays 
consistent.
 * *Progress/termination* — ensure the per-directory cap can't stall drain 
progress for a pathological single directory.

*Value axis*

This is a *read-availability* improvement on hot buckets (bounding the 
write-lock hold seen by readers), not a write-throughput one — the single apply 
thread still does the same total work. Same distinction called out on the 
write-lock-scope work; state it on the JIRA so the two aren't read as 
contradictory.

*Testing*
 * DDS unit test: a directory with sub-entries above the cap emits N 
{{{}PurgePathRequest{}}}s across N transactions rather than one.
 * Apply-side re-validation / idempotency test for chunked emission.
 * Snapshot-deep-clean coverage (extend 
{{{}TestSnapshotDirectoryCleaningService{}}}) for a dense directory spanning 
transactions.



--
This message was sent by Atlassian Jira
(v8.20.10#820010)

---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to