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

Siyao Meng updated HDDS-16806:
------------------------------
    Attachment: CR-4-add-chunks-without-checksums-to-scanner-merkle-tree.patch

> Container scanner builds an empty merkle tree for containers written with 
> checksum type NONE, which breaks reconciliation of those containers
> ---------------------------------------------------------------------------------------------------------------------------------------------
>
>                 Key: HDDS-16806
>                 URL: https://issues.apache.org/jira/browse/HDDS-16806
>             Project: Apache Ozone
>          Issue Type: Bug
>            Reporter: Siyao Meng
>            Priority: Minor
>         Attachments: 
> CR-4-add-chunks-without-checksums-to-scanner-merkle-tree.patch, 
> TestBugCR4ScannerTreeWithoutChecksums.java
>
>
> h3. Mechanism
> The merkle tree of a container replica is built in two places. 
> {{KeyValueHandler.updateAndGetContainerChecksumFromMetadata}} adds every 
> block and chunk found in the block metadata. 
> {{KeyValueContainerCheck.scanBlock}}, used by the data scan, adds a block and 
> its chunks only inside the branch for chunks whose checksum type is not 
> {{NONE}}. For a container written with {{ozone.client.checksum.type=NONE}} a 
> data scan therefore replaces the tree with one that has no live blocks (data 
> checksum 0 if no block deletion has been recorded). The replica itself stays 
> healthy.
> Reconciliation compares trees, so the empty tree has two effects:
> * A scanned replica sees every block of a peer that still has the tree from 
> metadata as missing, pulls all of them and overwrites its own block files 
> with the peer's bytes.
> * Two scanned replicas both have trees without live blocks, so reconciliation 
> finds no difference between them whatever live blocks they hold.
> For CRC32 the two builders agree, and 
> {{TestContainerReconciliationWithMockDatanodes.setup}} asserts that. No 
> existing test covers {{NONE}}.
> The scanner has built its tree this way since HDDS-10374. Blocks that are 
> present locally are pulled again since HDDS-14763, which removed the check 
> that skipped them (both read from history, not run on a release).
> h3. Trigger
> # Blocks are written with {{ozone.client.checksum.type=NONE}} and the 
> container is closed. All replicas get a tree from metadata that lists every 
> block.
> # The data scanner (background or on demand) scans one replica. Its tree is 
> now empty. An on demand scan also runs automatically when a replica is 
> imported by container replication, and the tree file travels with the 
> container, so a freshly replicated replica has the empty tree while its 
> source still has the tree from metadata, without any scan timing involved 
> (read from code, not run).
> # An administrator runs {{ozone admin container reconcile}}. The scanned 
> replica pulls every block from a peer that has not been scanned yet.
> For the second effect: all replicas have been scanned, one of them has lost a 
> block, and the reconcile command does not restore it. The scan at the end of 
> every reconcile task empties the tree of the replica that ran it, so a 
> reconcile of such a container moves its replicas into this state (read from 
> code).
> h3. Impact
> * Present, healthy block files are replaced by the peer's copy, which cannot 
> be verified for type {{NONE}}. If the peer's copy is damaged, the good copy 
> is lost. In the reproduction the peer has one flipped byte and the scanned 
> replica ends up with it (run).
> * A block that a replica has lost is not repaired by reconciliation once the 
> replicas are scanned, and all replicas report the same data checksum (0 if 
> nothing was deleted), so the divergence is not visible in the reported 
> checksums either (run at the datanode, SCM not run).
> * Only containers written with checksum type {{NONE}} are affected.
> h3. Reproduction
> PASS, deterministic, unmodified source. 
> [^TestBugCR4ScannerTreeWithoutChecksums.java] 
> ({{hadoop-hdds/container-service}}) uses the setup of 
> {{TestContainerReconciliationWithMockDatanodes}}: two datanodes, each a real 
> {{KeyValueHandler}} with a real container on disk and a real on demand 
> scanner, the real {{BlockInputStream}}, and only the static 
> {{ContainerProtocolCalls}} transport routed to the peer's handler. There is 
> no SCM. Three tests: the scanned tree of a {{NONE}} container is empty with 
> data checksum 0 while the replica stays CLOSED (CRC32 as the control); a 
> scanned replica is overwritten with the peer's flipped byte copy, and is left 
> alone when it was not scanned (control); a lost block is still absent after 
> reconcile when both replicas were scanned. A passing test means the defect is 
> present.
> h3. Patch
> [^CR-4-add-chunks-without-checksums-to-scanner-merkle-tree.patch], against 
> 1e528b97aed3eef1be6846f1f0767c1ef98a883a. It also applies to master at 
> 8ca44914771.
> {{scanBlock}} adds a chunk without checksums to its tree as healthy, with the 
> same {{addChunks}} call that the metadata builder uses, so both builders 
> produce the same tree for a healthy {{NONE}} container. Blocks without chunk 
> data on disk are still listed only by the metadata builder, for every 
> checksum type; that difference is older and is not changed here. Nothing 
> changes for containers with a real checksum type. For a {{NONE}} container 
> that was already scanned, the reported data checksum changes from 0 to the 
> metadata value at its next data scan, and until all its replicas have been 
> rescanned a rescanned replica and one that was not differ as a scanned and an 
> unscanned one do today. Containers whose replicas had all been scanned 
> already, where reconcile did nothing, get that window again for up to one 
> data scan cycle after the upgrade, and Recon shows them as REPLICA_MISMATCH 
> during it. Nothing reconciles automatically. The patch does not make 
> corruption inside a {{NONE}} chunk detectable: the scanner still does not 
> read such a chunk and does not check its length.
> Covered by {{testContainerWithoutChunkChecksumsAfterScan}} in the existing 
> {{TestContainerReconciliationWithMockDatanodes}}, which checks that the 
> scanned data checksum equals the one from metadata, that a replica with a 
> lost block is visible after all replicas are scanned, and that reconciliation 
> repairs it. Without the change it fails on the first check (data checksum 0). 
> With it the unit suites of the {{keyvalue}}, {{ozoneimpl}} and {{checksum}} 
> packages in {{hadoop-hdds/container-service}} pass (1124 tests, 18 skipped by 
> the suites, including the scanner suites), checkstyle is clean, and the three 
> bug cases of the reproduction fail because the scanned tree is no longer 
> empty. Not run on a mini cluster.
> Found by code review of the container reconciliation paths in the datanode 
> and SCM, as part of the TLA+ verification effort under HDDS-15926, on commit 
> 1e528b97aed3eef1be6846f1f0767c1ef98a883a. 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]

Reply via email to