Siyao Meng created HDDS-17026:
---------------------------------
Summary: Datanode container create that fails with an unchecked
exception leaks the reserved container size on the volume until restart
Key: HDDS-17026
URL: https://issues.apache.org/jira/browse/HDDS-17026
Project: Apache Ozone
Issue Type: Bug
Reporter: Siyao Meng
Attachments:
MC-10-release-reservation-on-unchecked-create-failure.patch,
TestBugMC10DbLoadReservationLeak.java
h3. Mechanism
{{KeyValueContainer.create}} reserves the container size ({{maxSize}}, 5 GB by
default) on the volume that {{chooseVolume}} picks.
{{KeyValueHandler.handleCreateContainer}} gives that reservation back only in
its {{catch (StorageContainerException)}} branch. When {{create}} fails with an
unchecked exception after the volume was chosen, the exception passes that
branch, {{KeyValueHandler.handle}} turns it into a {{CONTAINER_INTERNAL_ERROR}}
response, and the volume's committed bytes stay raised by the full container
size. Nothing recomputes committed bytes while the datanode runs, so each such
attempt adds another {{maxSize}} until restart. Present since HDDS-12810
(2.1.0), which moved the reservation into {{chooseVolume}}; 2.0.0 is not
affected (checked in the ozone-2.0.0, 2.1.0 and 2.2.1 tags).
h3. Trigger
Today this is reached through the state described in HDDS-11485: a data volume
whose per-volume DB did not load at restart (for example its {{DS-<id>}}
directory or {{container.db}} is gone, or a dedicated DB volume was lost) stays
in service with {{dbParentDir}} unset. Every container create placed on it,
including the implicit create {{HddsDispatcher}} issues for the first write to
a new container, throws {{NullPointerException("dbParentDir == null")}} from
{{KeyValueContainerLocationUtil.getContainerDBFile}} after the reservation. Any
other unchecked failure in {{create}} after the volume is chosen leaks the same
way.
h3. Impact
* Each failed create leaves {{maxSize}} committed on the volume. In the
reproduction three creates left 15 GB committed on an empty volume, and the
storage report sent to SCM carries it.
* The volume choosing policy counts committed bytes against the volume's free
space, so the phantom bytes grow until they fill it; after that the policy
stops picking the volume (by code reading, not run). On the known trigger every
create on that volume fails anyway, so the harm is the wrong committed and free
space figures, on the datanode and in what SCM sees.
* The creates themselves fail either way; this report is only about the space
they leave reserved.
h3. Reproduction
PASS on unmodified source. [^TestBugMC10DbLoadReservationLeak.java]
({{hadoop-hdds/container-service}}, target path and Maven command in the class
comment) formats a data volume whose DB lives on a dedicated DB volume, loses
the DB disk and drops it from the configuration, then rebuilds the volume set
with a real {{StorageVolumeChecker}}, loads the DBs and runs the startup volume
check. The volume is {{NORMAL}} with {{isDbLoaded=false}} and
{{dbParentDir=null}}. Three CreateContainer requests go through
{{KeyValueHandler.handle}}; each returns {{CONTAINER_INTERNAL_ERROR}} with
"dbParentDir == null", no container is added, and committed bytes grow to
5368709120, 10737418240 and 16106127360. A later volume check still finds the
volume healthy and its report carries 3 x 5 GB committed. A passing test means
the defect is present.
h3. Patch
[^MC-10-release-reservation-on-unchecked-create-failure.patch], against
cce1ca28944886218132f01c0e271c1fd0700078. It also applies to master at
2536fe9cbc3.
{{handleCreateContainer}} now releases the reservation in its {{finally}} block
whenever the container was not created, instead of only in the
{{StorageContainerException}} branch. {{ContainerData.releaseCommitSpace}},
unchanged, already gives back the space only while the reservation is still
held and then clears the flag, so a path that already released it is not
released twice, and the branch for an already existing container, which
reserves nothing, is unaffected. Volume state handling is not changed: with the
patch such a volume still stays in service and the creates still fail, but
nothing stays reserved. The root cause of that volume state is tracked in
HDDS-11485; this fix is independent of it.
Covered by {{testCreateContainerReleasesSpaceOnUncheckedFailure}} in the
existing {{TestKeyValueHandler}}: a schema V3 volume with its working directory
but no per-volume DB, one CreateContainer through {{KeyValueHandler.handle}}.
Without the change it fails with "expected: 0L but was: 5368709120L"; with it
the volume has no committed bytes. The new
{{testCreateContainerKeepsSpaceOfCreatedContainer}} covers the guard: a
successful create keeps {{maxSize}} committed with the reservation flag set,
and a repeated create of the same container leaves both unchanged; it fails if
the release is made unconditional. The existing
{{testCreateContainerWithFailure}}, which makes {{ContainerSet.addContainer}}
throw {{StorageContainerException}}, covers the checked path and passes
unchanged. With the change the handler, container, dispatcher, persistence and
{{OzoneContainer}} suites (551 tests) pass and checkstyle is clean. With the
patch the reproduction fails at its committed bytes check after the first
create, while the volume is still {{NORMAL}}.
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. Related but not
duplicates: HDDS-11485 (the volume state that reaches this today) and
HDDS-17009 (releases the reservation on a volume that the retry loop in
{{KeyValueContainer.create}} gives up on; the two patches are independent and
apply together). 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]