[
https://issues.apache.org/jira/browse/HDDS-17012?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
]
Siyao Meng updated HDDS-17012:
------------------------------
Attachment: MC-3-report-data-scan-cut-short-by-closed-db.patch
> Container data scan cut short by a closed RocksDB is accepted as complete, so
> a partial checksum is reported and the replica stays CLOSED
> -----------------------------------------------------------------------------------------------------------------------------------------
>
> Key: HDDS-17012
> URL: https://issues.apache.org/jira/browse/HDDS-17012
> Project: Apache Ozone
> Issue Type: Bug
> Reporter: Siyao Meng
> Priority: Minor
> Attachments: MC-3-report-data-scan-cut-short-by-closed-db.patch,
> TestBugMC3ScanTruncatedByDbClose.java
>
>
> h3. Mechanism
> Since HDDS-14800, {{RDBStoreAbstractIterator.hasNext}} returns false once the
> RocksDB is closed, so a scanner stops without an exception. The datanode
> block iterator ({{AbstractDatanodeStore.KeyValueBlockIterator}}) passes that
> on, so {{KeyValueContainerCheck.scanData}} takes the end of iteration as the
> end of the container's blocks, and a scan whose per-disk DB is closed partway
> through ends with no error. {{ContainerScanHelper.scanData}} then treats it
> as a complete healthy scan: {{updateContainerChecksum}} writes the merkle
> tree built from the blocks scanned so far to the {{.tree}} file, sets the
> container data checksum from it and sends an ICR, the data scan timestamp
> moves forward, and the replica stays CLOSED.
> A closed DB stays closed until the datanode restarts: {{DatanodeStoreCache}}
> keeps the closed store, and the volume check does not look at it
> ({{HddsVolume.checkDbHealth}} also ignores NoSpace errors). The metadata
> scanner does not read the DB.
> h3. Trigger
> # A CLOSED container is being scanned by the background or on demand data
> scanner.
> # The per-disk RocksDB of its volume is closed by RocksDB error handling
> ({{RocksDatabase.closeOnError}}), for example when a write or flush for
> another container on the same disk fails with an IOError such as a full disk.
> # The scanner's next {{hasNext}} returns false and the partial result is
> saved.
> No client action is involved; the close comes from a disk or RocksDB error on
> the datanode.
> h3. Impact
> * Only the container being scanned on that disk when the DB closes is
> affected (normally one). Every other container on the disk is already marked
> UNHEALTHY with checksum 0 when the scanner or a failed request reaches it,
> because its scan finds the DB closed at the start.
> * The affected replica stays CLOSED although its DB can no longer be read,
> and reports a data checksum computed from part of its blocks. That checksum
> is visible to operators ({{ozone admin container info}}) and disagrees with
> the other replicas.
> * It is marked UNHEALTHY by the next scan of that container. A client request
> that needs the DB fails and triggers an on demand scan ({{HddsDispatcher}}
> does this for every failed request), but not within
> {{hdds.container.scrub.min.gap}} (15 minutes by default) of the truncated
> scan, since that scan moved the scan timestamp forward. A container that
> receives no requests waits for the next background data scan (the interval is
> 7 days by default). Argued from the code, not run end to end.
> * No data is lost.
> The code is in the released versions 2.2.0 and 2.2.1, which include
> HDDS-14800.
> h3. Reproduction
> PASS, deterministic, unmodified source, needs a non root user.
> [^TestBugMC3ScanTruncatedByDbClose.java] ({{hadoop-hdds/container-service}})
> uses a schema V3 volume, a real {{KeyValueHandler}} and
> {{ContainerScanHelper}}. A closed container with 8 blocks is scanned once as
> a baseline. A second scan is paused by a throttler at the start of the third
> block; meanwhile a block is written for another open container on the same
> DB, the DB directory is made read only and the flush fails with an IOError,
> which closes the DB through {{closeOnError}}. After the scan resumes the
> container is still CLOSED, its checksum changed, the tree file holds 3 of 8
> blocks, the last ICR carries the new checksum with state CLOSED and the scan
> timestamp moved forward. A passing test means the defect is present.
> h3. Patch
> [^MC-3-report-data-scan-cut-short-by-closed-db.patch], against
> cce1ca28944886218132f01c0e271c1fd0700078. It also applies to master at
> 2536fe9cbc3.
> When the underlying table iterator ends, {{KeyValueBlockIterator.hasNext}}
> now throws an {{IOException}} if the DB is closed, instead of reporting the
> end of the blocks. It reads the unsynchronized {{RDBStore.isClosed}}, so it
> cannot block against {{AbstractRDBStore.stop}}, which waits for open
> iterators. The shared {{RDBStoreAbstractIterator}} used by OM, SCM and Recon
> is not changed. Every caller of the block iterator already handles an
> {{IOException}} from it, since creating the iterator on a closed DB already
> throws one. Besides the scanner this covers
> {{KeyValueContainerUtil.noBlocksInContainer}} (the empty check before a
> container delete and after block deletion), which could otherwise answer "no
> blocks" if the DB closed during its iteration, and the block counting and
> metadata checksum paths.
> {{KeyValueContainerCheck.scanData}} records the exception as
> {{INACCESSIBLE_DB}} and, when the block list could not be read in full, now
> returns an empty data tree, as it already does when the DB cannot be opened
> at the start of a scan ({{DataScanResult.unhealthyMetadata}} uses the same
> convention). So the replica is marked UNHEALTHY with checksum 0, the same as
> the other containers on that disk. A scan that finishes on an open DB and a
> scan of a container deleted during the scan behave as before.
> Covered by {{testKeyValueContainerCheckDBClosedDuringScan}} in the existing
> {{TestKeyValueContainerCheck}}, which stops the store from another thread
> during the scan and expects one {{INACCESSIBLE_DB}} error and an empty tree
> with checksum 0. Without the change it fails for all container test versions
> with no scan error reported. With it the block iterator, scanner and
> reconciliation suites and the suites of the other block iterator callers (887
> tests) and the three container scanner integration suites on a mini cluster
> (21 tests) pass, and checkstyle is clean. With the patch the reproduction
> fails because the replica is marked UNHEALTHY and reports checksum 0.
> Not changed here: a per-disk DB closed by RocksDB does not fail its volume,
> so after a transient full disk every container on it ends up UNHEALTHY, even
> though a restart would reopen the DB. That is a separate follow up (related:
> HDDS-13994, opening the DB read only on a full disk).
> Found by TLA+ model checking and code review of the datanode volume and
> per-disk RocksDB lifecycle paths under HDDS-15926, on commit
> cce1ca28944886218132f01c0e271c1fd0700078. 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]