Andrey Yarovoy created HDDS-16289:
-------------------------------------

             Summary: Narrow the FSO create/commit apply-path bucket write-lock 
to the cache-mutation tail so it does not gate readers of a hot bucket
                 Key: HDDS-16289
                 URL: https://issues.apache.org/jira/browse/HDDS-16289
             Project: Apache Ozone
          Issue Type: Improvement
            Reporter: Andrey Yarovoy


*Problem*

On the FSO write-apply path, {{validateAndUpdateCache}} acquires the 
{{BUCKET_LOCK}} *write* lock and holds it across a block of work that is mostly 
{_}reads of already-committed state{_}, not cache mutation. Because a hot 
bucket hashes to a single lock stripe and the OM applies transactions on a 
*single* Ratis apply thread, the duration of this write-lock hold is exactly 
the window during which every read RPC on that bucket ({{{}getBucketInfo{}}}, 
{{{}getBucketOwner{}}}, {{{}readKeyInfo{}}}) is blocked — Java's 
{{ReentrantReadWriteLock}} blocks incoming readers whenever a writer is queued 
at the head, in both fair and non-fair (default) mode.

OM {{/stacks}} analysis showed this as recurring flares in handler threads sit 
in {{OzoneManagerLock.acquireLock}} (read side) behind the single apply 
thread's {{{}acquireWriteLock{}}}, with only a handful of readers actually 
inside the critical section. The critical section is not slow because of a 
contended mutation — it is slow because read-heavy work runs under the write 
lock.

*Where the time goes* 
({{{}OMFileCreateRequestWithFSO.validateAndUpdateCache{}}}, same shape in 
{{{}OMKeyCommitRequestWithFSO{}}})

Held under the write lock today:
 * {{verifyDirectoryKeysInPath(...)}} — the FSO path walk: one RocksDB point 
lookup per path segment ({{{}directoryTable.get{}}}) plus a {{fileTable.get}} 
for the leaf. This is the dominant cost and it only *reads* committed/cache 
state.
 * {{getOmKeyInfoFromFileTable(...)}} — another RocksDB read.
 * {{{}checkDirectoryResult{}}}, {{{}checkAllParentsExist{}}}, 
{{{}getAllMissingParentDirInfo{}}}, quota checks — validation over the values 
just read.
 * *Only the tail* genuinely needs write exclusivity: 
{{{}addOpenFileTableCacheEntry{}}}, {{{}addDirectoryTableCacheEntries{}}}, and 
the in-place bucket quota update ({{{}incrUsedNamespace{}}}).

The code already hints at this — the comment above 
{{addOpenFileTableCacheEntry}} notes the open-key cache add "can be done 
outside of lock."

*Proposed change (investigation + implementation)*

Reorder so the exclusive section covers only the cache mutations that require 
it:
 # Resolve the path and read existing entries / build {{missingParentInfos}} 
and the prepared {{OmKeyInfo}} _before_ taking the write lock (or under the 
shared read lock).
 # Acquire the bucket write lock and, under it, *re-validate the invariants 
that could have changed* (existence/overwrite, quota) against the current 
cache, then apply the cache entries and quota update.
 # Release.

Because writes are applied by a single thread, this does not change write 
throughput — the prior finding that write-lock _scope_ reduction is low-value 
for write p99 (single apply thread bounds write commit rate) still holds. The 
value here is on a {*}different axis: read availability/latency on hot 
buckets{*}, which the current wide critical section directly harms.

*Testing*
 * Concurrency test: drive many read RPCs at one bucket while a create/commit 
is applied to that bucket; assert readers block only for the (short) mutation 
tail, not the path-resolution phase.
 * Correctness: existing {{TestOMFileCreateRequestWithFSO}} / 
{{TestOMKeyCommitRequestWithFSO}} plus new cases for the 
re-validation-under-lock path (concurrent overwrite, quota-exceeded discovered 
at re-check).



--
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