Siyao Meng created HDDS-17742:
---------------------------------

             Summary: Optimized full snapshot diff omits changes once deep 
cleaning or a snapshot purge advances the from snapshot's lastTransactionInfo
                 Key: HDDS-17742
                 URL: https://issues.apache.org/jira/browse/HDDS-17742
             Project: Apache Ozone
          Issue Type: Bug
            Reporter: Siyao Meng
         Attachments: CR-4-gate-full-diff-on-snapshot-create-transaction.patch, 
TestBugCR4FullDiffGateAfterPurge.java

h3. Mechanism
In full diff mode {{SnapshotDiffManager.resolveFullDiffUpdateIdGate}} takes the 
transaction index of the from snapshot's {{lastTransactionInfo}}, and 
{{FullDiffSequentialReader}} treats every to side row whose updateID is at or 
below it as unchanged; {{MergeJoinSnapDiffWriter}} then emits nothing for it. 
{{lastTransactionInfo}} does not stay at the creation index: 
{{OMKeyPurgeRequest}} and {{OMDirectoriesPurgeRequestWithFSO}} (deep cleaning 
of the snapshot), {{OMSnapshotMoveUtils}} and {{OMSnapshotPurgeRequest}} (for 
the snapshots after a purged one) rewrite it later. Changes made after the from 
snapshot was created but before such a rewrite then fall under the gate. 
{{createTransactionInfo}} records the creation index and is never rewritten.

The gate also assumes that the from snapshot is the older one. Nothing enforces 
that, and in a diff from a newer to an older snapshot every to side row is at 
or below the from snapshot's index, so renames, creates and modifications are 
dropped there too, while the legacy diff reports them.

h3. Trigger
{{ozone.om.snapshot.diff.optimized.enabled=true}} and a full diff: forced by 
the client or by {{ozone.om.snapshot.force.full.diff}}, or, with the native 
library loaded, the automatic fallback when the compaction DAG cannot serve the 
diff. Then either:
* the from snapshot's {{lastTransactionInfo}} has moved: deep cleaning 
({{ozone.snapshot.deep.cleaning.enabled}}, on by default) purges anything from 
the snapshot, or an older snapshot is deleted and purged. Example run: create 
{{/d/k1}}, take snapshots snap0 and snap1, rename {{/d/k1}} to {{/d/k1r}}, 
create {{/d/k2}}, take snapshot snap2, delete snap0 and let 
{{SnapshotDeletingService}} purge it, then request a full diff snap1 to snap2;
* or the diff goes from a newer to an older snapshot, for example snap2 to 
snap1.

h3. Impact
Deep cleaning moves the gate without any snapshot being deleted, so a from 
snapshot that held reclaimable deleted keys when it was created is enough 
(argued from the code; the purge route was run). In the purge example the DONE 
report is empty instead of {{[RENAME d/k1 -> d/k1r, CREATE d/k2]}}, for ordered 
and unordered reports, while the same diff before the purge is correct. In a 
reverse diff the report keeps only the deletes ({{[DELETE k2]}} instead of 
{{[RENAME k1r -> k1, DELETE k2]}}). The report is persisted and returned for 
every later read of the pair.

h3. Reproduction
[^TestBugCR4FullDiffGateAfterPurge.java] ({{hadoop-ozone/integration-test}}, no 
native library needed) runs the purge example on a mini cluster through the 
FileSystem, snapshot and diff APIs, with the real {{SnapshotDeletingService}} 
run once instead of on its interval. A control diff before the purge reports 
the rename and the create. After the purge the unfixed code fails with 
{{Expecting actual: [] to contain exactly in any order: [R ./d/k1 -> ./d/k1r, + 
./d/k2]}}; with the patch it passes. The reverse case is covered by the 
regression test below.

h3. Patch
[^CR-4-gate-full-diff-on-snapshot-create-transaction.patch], against 
d3b75c75bf9ad475551202477c3a21a3f4b968b7. It also applies to master at 
bbc8505e00d.

The gate now uses the from snapshot's {{createTransactionInfo}}, and only when 
both snapshots have it and the from index is lower than the to index. Otherwise 
the gate is disabled and every row is compared, which is correct but slower: 
snapshots created before 2.1.0 have no {{createTransactionInfo}}, and a reverse 
diff has no rows after the from snapshot's creation. It does not fall back to 
{{lastTransactionInfo}}. The {{FullDiffSequentialReader}} Javadoc of the gate 
is updated to match.

Covered by two tests in the existing {{TestOptimizedSnapshotDiff}} (native 
suite), using a new overload of the {{OmSnapshotTests}} helper that takes the 
force full diff flag: {{testForcedFullDiffAfterSnapshotPurge}} (purge example, 
also asserts that the purge moved {{lastTransactionInfo}} past 
{{createTransactionInfo}}) and {{testForcedFullDiffFromNewerToOlderSnapshot}} 
(reverse diff). On the unfixed code the first gets an empty report and the 
second {{[- ./reverse/k2]}}. With the patch {{TestOptimizedSnapshotDiff}}, the 
snapshot diff unit suites and {{TestSnapshotDiffManager}} (191 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