Siyao Meng created HDDS-17732:
---------------------------------

             Summary: SnapDiffJobStore.close stops at the first RocksDB error 
and leaves the job's temporary column families in the snapshot diff DB until OM 
restart
                 Key: HDDS-17732
                 URL: https://issues.apache.org/jira/browse/HDDS-17732
             Project: Apache Ozone
          Issue Type: Bug
            Reporter: Siyao Meng
         Attachments: CR-13-best-effort-snap-diff-job-store-close.patch, 
TestBugCR13CloseAfterRocksDbFailure.java

h3. Mechanism
{{SnapDiffJobStore.close}} runs its steps in sequence without try/finally: 
{{flushWrites}}, close the write batch and write options, drop every tracked 
temporary column family through {{dropAndClose}}, close the column family 
options. Any exception ends it:
* A failed {{flushWrites}} leaves the batch as it was, so after a stage flush 
failed, the flush in {{close}} repeats the same write, fails again, and no 
column family is dropped and no native object is closed.
* {{dropAndClose}} removes the handle from the tracking list before 
{{SnapshotUtils.dropColumnFamilyHandle}}, which turns a {{RocksDBException}} 
into a {{RuntimeException}}. That handle is then never closed and the remaining 
column families are never dropped.

Nothing in a running OM drops them later; only 
{{OmSnapshotManager.dropUnknownColumnFamilies}} at OM startup does. A dropped 
column family keeps its files on disk while a handle to it is open, so the 
unclosed handle also keeps that space until the process exits.

h3. Trigger
A RocksDB write or column family drop fails on the snapshot diff DB while an 
optimized snapshot diff job runs, which in practice means a full or failing 
disk under {{ozone.om.snapshot.diff.db.dir}}. Only the optimized snapshot diff 
path ({{ozone.om.snapshot.diff.optimized.enabled}}, default false) uses 
{{SnapDiffJobStore}}, and the class is not in any release yet. The job store is 
closed by the try with resources in 
{{SnapshotDiffManager.generateSnapshotDiffReport}}. Each job creates nine 
temporary column families (seven for OBS buckets), plus up to four more when 
its in memory sets spill, all sized by the diff, so the job that fills the 
volume is the one whose data stays.

If the error happens before the report is complete, the job is marked FAILED as 
usual. The store is closed after {{updateJobStatusToDone}}, and some families 
are still open then (on an OBS bucket the four classified families and the 
dependency node family). If one of those drops fails, the job stays DONE, which 
is correct because its report is complete; the existing caller then calls 
{{updateJobStatusToFailed}}, which throws an {{IllegalStateException}} for the 
DONE job before the close error is logged. That caller behaviour is not changed 
here.

h3. Impact
* The temporary column families of the failed job, with their data, stay in the 
snapshot diff DB until the OM restarts. When the snapshot diff DB is on its own 
volume, the OM keeps running and the space is not released, so later diff jobs 
can keep failing for lack of space.
* One {{ColumnFamilyHandle}} (drop path), or the write batch, write options, 
column family options and all handles (flush path) are never closed (native 
memory).
* Job state is not corrupted: a FAILED job can be resubmitted under a new job 
ID, and a DONE job keeps its complete report.

The legacy path, {{SnapshotDiffManager.generateLegacySnapshotDiffReport}}, 
drops its three temporary column families with the same sequential pattern, 
which is in every release from 1.4.0 to 2.2.2; it is handled separately.

h3. Reproduction
[^TestBugCR13CloseAfterRocksDbFailure.java] ({{hadoop-ozone/ozone-manager}}): a 
real RocksDB and the store's public calls; the RocksDB error is injected with a 
Mockito spy on the {{RocksDB}} object (write fails; or the first drop is 
applied and then fails, as a full disk does when RocksDB cannot persist its 
OPTIONS file). On d3b75c75bf9ad475551202477c3a21a3f4b968b7 both tests fail: 
after the failed flush all nine temporary column families of the job remain, 
after the failed drop the other eight remain and the handle of the failed drop 
is still open. With the patch both pass. A real out of space error was 
reproduced only by the original investigation (on a 16 MB disk image), not with 
the attached test.

h3. Patch
[^CR-13-best-effort-snap-diff-job-store-close.patch], against 
d3b75c75bf9ad475551202477c3a21a3f4b968b7. It also applies to master at 
bbc8505e00d.

{{close}} now attempts every step even if an earlier one fails: the flush 
failure no longer skips the rest, every tracked column family is attempted (the 
first failure is thrown with later ones added as suppressed), and the write 
batch, write options and column family options are closed in finally blocks. 
{{dropAndClose}} closes the handle in a finally block. A store that closes 
without error behaves as before.

Covered by two tests in the existing {{TestMergeJoinSnapDiffWriter}}, which 
already drives {{SnapDiffJobStore}} on a real RocksDB: a failed flush and a 
failed drop, each asserting that {{close}} reports the error and no column 
family of the job is left. Without the change both fail on the column family 
check: the flush test finds all nine families of the job, the drop test the 
other eight. With it {{TestMergeJoinSnapDiffWriter}}, 
{{TestDagDiffSequentialReader}}, {{TestFullDiffSequentialReader}}, 
{{TestSnapDiffPathResolver}}, {{TestSnapDiffDependencyGraph}} and 
{{TestSnapshotDiffManager}} (139 tests) pass and checkstyle is clean.

Found by code review of the OM optimized snapshot diff paths, as part of the 
TLA+ verification effort under HDDS-15926, on commit 
d3b75c75bf9ad475551202477c3a21a3f4b968b7. 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