Siyao Meng created HDDS-16806:
---------------------------------

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