Siyao Meng created HDDS-17020:
---------------------------------

             Summary: Datanode volume failure closes the failed volume's 
RocksDB while holding the volume set write lock, stalling container creates and 
node reports
                 Key: HDDS-17020
                 URL: https://issues.apache.org/jira/browse/HDDS-17020
             Project: Apache Ozone
          Issue Type: Bug
            Reporter: Siyao Meng
         Attachments: TestBugCR1FailVolumeHoldsVolumeSetLock.java

h3. Mechanism
{{MutableVolumeSet.handleVolumeFailures}} and {{MutableVolumeSet.failVolume}} 
call {{HddsVolume.failVolume}} while holding the volume set write lock, and 
with schema V3 that closes the volume's per disk RocksDB in place: 
{{RDBStore.close}} flushes the DB, then {{RocksDatabase.close}} runs 
{{cancelAllBackgroundWork(true)}} and {{waitAndClose}} waits, without a 
timeout, until every open reference to the DB is released. Since HDDS-14800 a 
RocksDB iterator holds a DB reference until it is closed, and the comment in 
{{waitAndClose}} expects the wait to be short. The data scanner keeps its block 
iterator open while it scans a block, so it is the long holder: one block at 
{{hdds.container.scrub.volume.bytes.per.second}} (default 5 MB/s), about 51 s 
for a 256 MB block.

Until the close returns, everything that takes the volume set read lock waits: 
{{MutableVolumeSet.getStorageReport}} (node report), 
{{KeyValueContainer.create}} on every volume, and 
{{KeyValueHandler.populateContainerPathFields}} (container import, including 
disk balancer moves). The fatal volume failure check and the failed volume 
listener ({{ContainerSet.handleVolumeFailures}}) only run after the close.

h3. Trigger
# The data scanner is scanning a block of a container on volume V, holding its 
block iterator.
# The volume checker fails V, and {{handleVolumeFailures}} closes V's DB under 
the write lock.

No client action is involved.

h3. Impact
* While the close waits, container creates and imports on all volumes of the 
datanode wait. This includes the create that {{HddsDispatcher}} runs for the 
first WriteChunk, PutBlock or PutSmallFile of a new container, so client writes 
to new containers stall on every volume of the datanode. Reads and writes on 
existing containers do not take the lock.
* Node report generation waits; heartbeats continue with the last published 
node report.
* The failed volume listener does not run until the close returns, so the 
containers of the failed volume stay in the container set and in container 
reports, and SCM keeps counting those replicas and does not re-replicate them. 
The failed volume tolerance check is not applied either.
* Measured: the stall lasts as long as the reference is held, here 2 s. The 
bound is the longest of the reference hold, the flush and the background work 
cancel; for the scanner see above (arithmetic, not run).
* Argued, not reproduced: for as long as the scanner's read or the flush blocks 
on the failing disk (indefinitely on a hung device), all of the above persist. 
A hung disk is the case a disk check timeout fails a volume for.

h3. Reproduction
PASS, deterministic, unmodified source. 
[^TestBugCR1FailVolumeHoldsVolumeSetLock.java] 
({{hadoop-hdds/container-service}}) uses two schema V3 data volumes, keeps a 
block iterator on the first volume's DB open as the data scanner does during a 
block scan, and calls {{MutableVolumeSet.failVolume}} on that volume. For the 2 
s the iterator is held, {{failVolume}}, {{getStorageReport}} and a container 
create with the round robin policy (which then lands on the healthy volume) all 
stay unfinished; after the iterator is closed all three complete. It shows the 
lock being held across the close, not a hung disk read. A passing test means 
the defect is present.

h3. Suggested fix
No patch is attached; the order of the failure handling steps is for review. 
Under the write lock, only mark the volume failed and move it to the failed 
volume map. Close its DB after the fatal check and the failed volume listener 
have run, or asynchronously, so that a hanging close does not hold back the 
container sweep and the full container report. Other paths need the same 
treatment:
* {{MutableVolumeSet.failVolume}} is public and some direct callers already 
hold the write lock ({{VersionEndpointTask.checkVolumeSet}}, 
{{ScmHAFinalizeUpgradeActionDatanode}}); others call it outside 
{{handleVolumeFailures}} ({{ContainerReader}}, {{HddsDatanodeService}}). Moving 
the close only inside {{handleVolumeFailures}} leaves those closing under the 
lock.
* {{DbVolume.failVolume}} closes every DB on a DB volume under the DB volume 
set write lock, and {{OzoneContainer.getNodeReport}} also reads that set's 
storage report, so the same stall applies there for each DB on the DB volume.

Found by code review of the datanode volume and per-disk RocksDB lifecycle 
paths, as part of the TLA+ verification effort under HDDS-15926, on commit 
cce1ca28944886218132f01c0e271c1fd0700078. Checked against HDDS issues and 
apache/ozone pull requests for duplicates before filing. Affects 2.2.0 and 
later (the iterator reference dates from HDDS-14800; the flush under the lock 
is older). Related but not duplicates: HDDS-14800, and HDDS-16119, which 
removed the volume set lock from the metrics path for a similar reason. 
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]

Reply via email to