Siyao Meng created HDDS-16814:
---------------------------------
Summary: DiskBalancer move drops the in memory data checksum of
the moved container replica, so the datanode reports data checksum 0 for it
Key: HDDS-16814
URL: https://issues.apache.org/jira/browse/HDDS-16814
Project: Apache Ozone
Issue Type: Bug
Reporter: Siyao Meng
Attachments:
CR-6-load-data-checksum-of-container-moved-by-diskbalancer.patch,
TestBugCR6DiskBalancerMoveDataChecksum.java
h3. Mechanism
{{DiskBalancerService.DiskBalancerTask}} copies a container to the destination
volume and imports the copy with
{{KeyValueHandler.importContainer(ContainerData)}}. That method creates the new
{{KeyValueContainerData}} with the copy constructor, which leaves the state
{{OPEN}}, and calls
{{KeyValueContainer.importContainerData(KeyValueContainerData)}}. The import
runs {{KeyValueContainerUtil.parseKVContainerData}} first and restores the
original state ({{CLOSED}} or {{QUASI_CLOSED}}) only afterwards. During the
parse the container looks open, and {{loadAndSetContainerDataChecksum}} returns
at once for an open container. The new replica is put into the {{ContainerSet}}
without a data checksum in memory, while its merkle tree file and its RocksDB
metadata on the destination volume carry the value of the source.
The container report is built from the in memory value
({{KeyValueContainerData.buildContainerReplicaProto}}), so the replica is
reported with data checksum 0. The move sends no incremental container report,
so SCM and Recon receive the 0 with the next full container report
({{hdds.container.report.interval}}, default 60m). The datanode side was run,
the SCM and Recon side is read from the code.
Only the DiskBalancer move is affected. The import of a replicated container
goes through the same {{importContainerData}}, but
{{TarContainerPacker.unpackContainerData}} sets the new container to
{{RECOVERING}} before the parse
({{ContainerPacker.persistCustomContainerState}}), so the checksum is loaded.
The existing {{TestKeyValueContainer.testContainerImportExport}} asserts that
and passes on the unmodified source (run). EC reconstruction and the container
load at datanode start do not use this import.
The open state also makes the parse skip the empty check
({{noBlocksInContainer}} returns false for an open container), so the import
never marks a moved replica as empty. DiskBalancer does not pick containers
with zero used bytes, so this is not expected to be reachable (read from the
code, not run).
The parse was moved in front of the state change by HDDS-12233, the early
return for open containers is from HDDS-12745, and DiskBalancer imports this
way since HDDS-12439. 2.2.0 and 2.2.1 contain all three with the same code
(read from the tags, not run there). 2.1.x has no DiskBalancer. The nearest
existing issue is HDDS-13023, which fixed the container file checksum
verification that the same reordering broke for this import, and did not cover
the data checksum.
h3. Trigger
# A {{CLOSED}} or {{QUASI_CLOSED}} container replica (the states DiskBalancer
moves by default, {{hdds.datanode.disk.balancer.container.states}}) has a data
checksum (from a data scan, from reconciliation, or built from metadata at
close).
# An administrator starts DiskBalancer on the datanode, and it moves the
container to another volume.
No concurrency and no failure is needed. Every move of a container that has a
data checksum ends this way.
h3. Impact
* From the next full container report on, the moved replica is reported with
data checksum 0 although its data did not change. {{ozone admin container
reconcile --status}} then shows the replicas of the container as not matching
({{ReconcileSubcommand}}), and Recon lists the container as REPLICA_MISMATCH
({{ReconReplicationManager.hasDataChecksumMismatch}}). Both compare the raw
values and do not treat 0 as unknown (read from the code, not run).
* After DiskBalancer has balanced a datanode this holds for every container it
moved.
* The value comes back at a datanode restart (run at the level of the load
call, see Reproduction), at the next data scan of the replica (background or on
demand; the background scanner runs with
{{hdds.container.scrub.data.scan.interval}} default 7d and is throttled to 5
MB/s per volume by default), when the replica is reconciled with a peer, or
when a moved {{QUASI_CLOSED}} replica is closed (the last three read from the
code, not run).
* No data is affected, and peers that reconcile with the replica read its
merkle tree file, which is correct. SCM takes no automatic action on the data
checksum today. The effect is a wrong mismatch signal for administrators and
for Recon.
h3. Reproduction
PASS, deterministic, unmodified source.
[^TestBugCR6DiskBalancerMoveDataChecksum.java]
({{hadoop-hdds/container-service}}) uses the setup of {{TestDiskBalancerTask}}:
a real {{KeyValueHandler}}, a real container on disk, two volumes and the real
{{DiskBalancerTask}}, for every schema version of {{ContainerTestVersionInfo}}.
There is no SCM and no Recon. A {{CLOSED}} container with a block row and a
merkle tree is loaded from disk as at datanode start and then moved. The test
asserts that the tree file and RocksDB of the moved replica carry the checksum,
that the replica in the {{ContainerSet}} has none and its container report has
data checksum 0, that every other field of the report is equal to the report
before the move, that no incremental container report was sent, and that
parsing the moved replica from disk again (the call {{ContainerReader}} makes
at datanode start) returns the checksum. A passing test means the defect is
present.
h3. Patch
[^CR-6-load-data-checksum-of-container-moved-by-diskbalancer.patch], against
1e528b97aed3eef1be6846f1f0767c1ef98a883a. It also applies to master at
8ca44914771.
{{KeyValueHandler.importContainer(ContainerData)}}, which only DiskBalancer
calls, sets the new container object to {{RECOVERING}} before it calls
{{importContainerData}}. That is the state {{DiskBalancerTask}} has already
written to the container file of the copy, and the state the new object has
during the parse in a replication import. {{importContainerData}} and the
replication import are not changed. The state is set in memory only, on an
object that is not yet in the {{ContainerSet}}. The container file is written
as before, once, at the end of {{importContainerData}} and with the original
state, so the copy is not left {{RECOVERING}} on disk after a successful move
(the regression test reads the file back), and an import that fails half way
leaves on disk what it left before the change. For a move the new replica gets
the data checksum of the copy, and a copy without blocks is marked empty as it
would be at datanode start.
The move still sends no incremental container report. With the checksum loaded,
the report of the moved replica is equal to the report before the move except
for the in memory read and write counters, which start again from 0 as after a
datanode restart, so there is nothing new for SCM.
This change should go in after or together with HDDS-16807: in two of the
orders described there, the incremental container report that the scan at the
end of the reconciliation sends for the live replica is sent only because the
moved replica has no data checksum in memory, so with this change alone SCM
keeps the report built from the retired replica until the next full container
report (run with the reproduction attached to HDDS-16807).
Covered by {{moveKeepsContainerReport}} and
{{moveOfContainerWithoutDataChecksum}} in the existing
{{TestDiskBalancerTask}}. The first asserts that the whole container report of
a replica that has seen no I/O is the same before and after the move, and that
the container file of the copy is {{CLOSED}}; without the change it fails with
{{dataChecksum: 0}} instead of {{dataChecksum: 1395005929}} as the only
difference. The second moves a container without blocks and without a merkle
tree and asserts that it still has no data checksum after the move, so a later
scan can set it, and that it is marked empty; without the change it fails on
the empty flag. With the change the unit suites of the {{keyvalue}},
{{diskbalancer}}, {{replication}} and {{ozoneimpl}} packages in
{{hadoop-hdds/container-service}} pass (1485 tests, 18 skipped by the suites),
checkstyle is clean, and the reproduction fails on its first assertion about
the missing checksum. 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]