Siyao Meng created HDDS-17427:
---------------------------------

             Summary: Datanode restarted during SchemaV3 finalization can 
create a second RocksDB for a data volume on another DB volume
                 Key: HDDS-17427
                 URL: https://issues.apache.org/jira/browse/HDDS-17427
             Project: Apache Ozone
          Issue Type: Bug
            Reporter: Siyao Meng
         Attachments: MC-5-reuse-existing-schema-v3-db-on-finalize-rerun.patch, 
TestBugMC5FinalizeRerunSecondDb.java

h3. Mechanism
{{ComponentVersionManager.finalizeUpgrade}} runs the upgrade action of a 
version and only then writes the new apparent version to the VERSION file. If 
the datanode stops between the two, it restarts below DATANODE_SCHEMA_V3, 
{{OzoneContainer}} loads no per volume RocksDB, and every data volume has 
{{dbParentDir == null}}. When SCM resends {{FinalizeVersionCommand}}, 
{{DatanodeSchemaV3FinalizeAction}} calls {{HddsVolume.createDbStore}} again for 
every data volume. With {{hdds.datanode.container.db.dir}} set to two or more 
directories, {{createDbStore}} picks a DB volume at random and does not check 
whether one already holds {{<clusterID>/<storageID>/container.db}} for this 
data volume, so it can create a second RocksDB for the same storageID on 
another DB volume.

Nothing reports the second copy. At every start 
{{HddsVolumeUtil.mapDbVolumesToDataVolumesIfNeeded}} maps the storageID to the 
last DB volume holding it in iteration order, and the finalize action itself 
reopens the DB through the same mapping, so while the set of DB volumes does 
not change all writes go to one copy and the other stays empty and unused. The 
javadoc of {{TestDatanodeUpgradeToSchemaV3.testFinalizeTwice}} ("finalize twice 
won't recreate any RocksDB") states the intent; that test calls 
{{finalizeUpgrade}} again only after the first call succeeded, so the action 
does not run a second time.

h3. Trigger
# A datanode with two or more DB volumes and SchemaV3 enabled is finalized for 
DATANODE_SCHEMA_V3. This happens only once, on a datanode first installed with 
a release before 1.3.0.
# It stops after the action created the DBs and before the VERSION file is 
written (killed, or the VERSION write failed and the datanode was restarted 
before a later retry succeeded).
# After the restart the action runs again and picks another DB volume for a 
data volume (probability 1/2 per re-run with two DB volumes).
# Later the DB volume holding the copy in use is lost or removed from 
{{hdds.datanode.container.db.dir}}, and the datanode restarts.

A failed VERSION write without a restart is not enough: the retry in the same 
process skips the volume.

h3. Impact
After step 4 the data volume does not fail. It maps to the other, empty copy 
and loads it without an error. Its SchemaV3 containers load with no blocks 
(they keep the state in their {{.container}} file, so a CLOSED container stays 
CLOSED), GetBlock for their blocks returns {{NO_SUCH_BLOCK}}, and the metadata 
and data scanners find nothing wrong. With a single copy the same loss leaves 
the containers unloaded, so the replica is visibly missing. That SCM then 
counts the empty CLOSED replica as a healthy replica and does not re-replicate 
the container is argued from {{RatisContainerReplicaCount}}, not run. Before 
step 4 the second copy only takes space.

h3. Reproduction
PASS on unmodified source. [^TestBugMC5FinalizeRerunSecondDb.java] 
({{hadoop-hdds/container-service}}) runs a real {{DatanodeStateMachine}} with 
one data volume and two DB volumes, pre-finalized at 
ERASURE_CODED_STORAGE_SUPPORT. The stop between action and VERSION write is 
produced by making the layout version directory read only during 
{{finalizeUpgrade}} (the real action runs, the real VERSION write fails), 
followed by a normal restart, repeated until a second copy exists (at most 30 
tries, 4 in the recorded run). It then finalizes, writes and closes a container 
with one block, restarts once with the same configuration (block still there), 
and once after removing the DB volume in use from 
{{hdds.datanode.container.db.dir}}: the container is CLOSED with 
{{blockCount=0}}, GetBlock returns {{NO_SUCH_BLOCK}}, both scans report no 
errors. A passing test means the defect is present. A real disk failure of the 
DB volume takes the same path in the code but was not run.

h3. Patch
[^MC-5-reuse-existing-schema-v3-db-on-finalize-rerun.patch], against 
679c9ccbee740a0af3ca9f76fc2d4bc6586049eb. It also applies to master at 
69d77beff60.

{{HddsVolume.createDbStore}} reuses the DB volume that already holds a 
directory for this storageID ({{DbVolume.getHddsVolumeIDs}}, filled by the DB 
volume's directory scan at startup) and picks one at random only when there is 
none. RocksDB then opens the existing instance. The placement of new DBs (first 
finalization, new volumes) is unchanged. Not covered: the reuse looks only at 
healthy DB volumes, so a DB volume holding the copy that is failed when the 
action runs again still gets a second copy elsewhere, and so does a data volume 
whose first DB was created on the data volume itself when DB volumes are added 
between the crashed run and the re-run. Both need a second event in the same 
window. Reordering the finalization to write VERSION first was not chosen, 
because a stop in between would then leave a datanode at DATANODE_SCHEMA_V3 
without any DB.

Covered by {{testCreateDbStoreAfterRestartReusesExistingDb}} in the existing 
{{TestHddsVolumeUtil}} (three data volumes, three DB volumes, two simulated 
restarts, each followed by {{createDbStore}}), which fails without the change. 
With it the volume, volume utility and upgrade test packages of 
{{hadoop-hdds/container-service}} plus {{TestOzoneContainer}} and 
{{TestContainerReader}} pass (303 tests), checkstyle is clean. With the patch 
the reproduction fails, because no second copy appears in 30 tries. Datanodes 
that already have a duplicate are not changed by the patch.

Found by TLA+ model checking and code review of the SCM and datanode upgrade 
finalization paths under HDDS-15926, on commit 
679c9ccbee740a0af3ca9f76fc2d4bc6586049eb. 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