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

Siyao Meng updated HDDS-17356:
------------------------------
    Attachment: TestBugMC3ContainerCacheReopenAfterErrorClose.java

> Data scan marks a container UNHEALTHY when its DB reopen fails on the lock 
> still held after a RocksDB error close
> -----------------------------------------------------------------------------------------------------------------
>
>                 Key: HDDS-17356
>                 URL: https://issues.apache.org/jira/browse/HDDS-17356
>             Project: Apache Ozone
>          Issue Type: Bug
>    Affects Versions: 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: Minor
>         Attachments: 
> MC-3-treat-db-lock-held-by-previous-instance-as-transient-scan-error.patch, 
> TestBugMC3ContainerCacheReopenAfterErrorClose.java
>
>
> h3. Mechanism
> When a read or write on a container DB fails with {{Corruption}} or 
> {{IOError}}, {{RocksDatabase.closeOnError}} marks the DB closed and starts a 
> {{DBCloser}} thread. That thread frees the native DB, and releases its LOCK 
> file, only when the last reference is gone; since HDDS-14800 an open iterator 
> is such a reference. {{ContainerCache.getDB}} sees the closed store, removes 
> it ({{removeDB}} calls {{ReferenceCountedDB.cleanup}} and {{stop}}, whose 
> {{RocksDatabase.close}} returns at once because the close is already in 
> progress) and reopens the same path. RocksDB rejects the open with "lock hold 
> by current process" while the old instance still exists.
> {{BlockUtils.getDB}} turns that into a {{StorageContainerException}} with 
> {{UNABLE_TO_READ_METADATA_DB}} and drops the cause. A data scan that gets 
> this exception records {{INACCESSIBLE_DB}}, and {{ContainerScanHelper}} 
> cannot recognise it as transient (the check added by HDDS-15150 walks the 
> cause chain), so it marks the container UNHEALTHY and triggers a volume scan.
> h3. Trigger
> # A schema V1 or V2 container DB is open in {{ContainerCache}} and a thread 
> iterates it (for example the container data scanner, which keeps its block 
> iterator open while it scans a block).
> # Another operation on the same DB fails with {{Corruption}} or {{IOError}} 
> (I used real corruption of the SST files).
> # A data scan of that container runs before the iterator is closed. This is 
> likely: {{HddsDispatcher}} queues an on demand data scan after every failed 
> request, including the original Corruption or IOError and each failed request 
> in the window (subject to the minimum scan gap).
> Schema V3 is not affected: {{DatanodeStoreCache}} does not reopen a closed 
> store.
> h3. Impact
> * The data scan marks a replica UNHEALTHY although the reopen failed only 
> because the old instance had not been freed yet; SCM then replaces the 
> replica. After a Corruption the mark may be justified anyway, after a 
> transient IOError it is not. Only data scans open the DB; the metadata scan 
> does not.
> * Client requests for the container fail with {{UNABLE_TO_READ_METADATA_DB}} 
> until the iterator on the old instance is closed, and each schedules a volume 
> check. The volume check does not open container DBs, so it does not fail a 
> healthy volume. After the iterator is closed, {{getDB}} succeeds again, also 
> on the same corrupted files. Clients retry on another replica.
> h3. Reproduction
> [^TestBugMC3ContainerCacheReopenAfterErrorClose.java] 
> ({{hadoop-hdds/container-service}}) builds a schema V2 container DB, opens it 
> through {{BlockUtils.getDB}} with an iterator, overwrites the start of each 
> SST file, and reads another table, which fails with Corruption. A second 
> {{BlockUtils.getDB}} then fails with {{UNABLE_TO_READ_METADATA_DB}} ("lock 
> hold by current process" in the log). That exception is given to 
> {{ContainerScanHelper.scanData}} the way {{KeyValueContainerCheck.scanData}} 
> reports it. After the iterator is closed, the same {{getDB}} succeeds. The 
> test asserts the correct behaviour, so on unfixed code it fails at its last 
> assertion: the container was marked UNHEALTHY. With the patch it passes.
> h3. Patch
> [^MC-3-treat-db-lock-held-by-previous-instance-as-transient-scan-error.patch],
>  against bf3829e6c8ac69ae17585585703f91426dcdf6b3. It also applies to master 
> at 4fb68e96831. The open pull request for HDDS-17261 changes adjacent lines 
> in {{BlockUtils.getDB}} and 
> {{ContainerScanHelper.handleUnhealthyScanResult}}, so whichever lands second 
> needs a small rebase.
> {{BlockUtils.getDB}} keeps the original exception as the cause, and 
> {{ScanTransientIOUtil}} also treats "lock hold by current process" in the 
> cause chain as transient ({{scanErrorsAreOnlyTooManyOpenFiles}} becomes 
> {{scanErrorsAreOnlyTransient}}). A data scan in the window is then logged as 
> incomplete and retried on a later pass instead of marking the container 
> UNHEALTHY. The match is narrow: a LOCK held by another process is still an 
> error. Requests in the window still fail transiently as described above; 
> making them wait for the old instance is not proposed, because 
> {{ContainerCache}} closes it under its cache wide lock and the holder can be 
> a scanner that keeps its iterator for a whole block. As a side effect of 
> keeping the cause, the HDDS-15150 "too many open files" check also works when 
> the DB open itself fails on file descriptor exhaustion (from the code, not 
> run). The two scan log messages that named only file descriptor exhaustion 
> are reworded.
> Covered by {{testGetDBFailsWithLockCauseAfterErrorCloseWithOpenIterator}} in 
> the existing {{TestContainerCache}} (real window, the failure must carry the 
> RocksDB lock error, and {{getDB}} succeeds after the iterator is closed), 
> {{testDataScanOnlyDbLockHeldByPreviousInstanceDoesNotMarkUnhealthy}} in 
> {{TestBackgroundContainerDataScanner}}, and two cases in 
> {{TestScanTransientIOUtil}} (lock held by this process is transient, lock 
> held by another process is not). The first two fail without the change. With 
> it the container cache, scanner, container check, dispatcher, handler, 
> container and volume suites (420 tests) pass, checkstyle is clean.
> Found by TLA+ model checking and code review of the RocksDB handle close and 
> shutdown paths under HDDS-15926, on commit 
> bf3829e6c8ac69ae17585585703f91426dcdf6b3. 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