Skip to content

HDDS-15390. Utility to generate dependency ordered snapdiff report.#10778

Draft
SaketaChalamchala wants to merge 4 commits into
apache:masterfrom
SaketaChalamchala:HDDS-15390
Draft

HDDS-15390. Utility to generate dependency ordered snapdiff report.#10778
SaketaChalamchala wants to merge 4 commits into
apache:masterfrom
SaketaChalamchala:HDDS-15390

Conversation

@SaketaChalamchala

Copy link
Copy Markdown
Contributor

What changes were proposed in this pull request?

Developed with the help of Cursor AI.

This PR is intended as the foundation for a more efficient snapshot diff (HDDS-9154).

Baseline snapshot diff reports order entries by diff type (DELETEs first, then RENAME/CREATE/MODIFY). That ordering is not always safe to replay. For example, if A/B is renamed to C/B and directory A is deleted, replaying the DELETE before the RENAME fails on because the rename source no longer exists.
This PR adds isolated utilities for dependency-ordered snapshot diff report generation.

SnapDiffDependencyEntry

  • Wraps a classified diff entry with objectId, parentObjectId, and the underlying DiffReportEntry
  • Provides the metadata needed to build hierarchy and path-conflict edges

SnapDiffDependencyGraph

  • Accepts a list of SnapDiffDependencyEntry values and builds a directed dependency graph in the constructor
  • Applies these dependency rules defined via directed edges (u -> v means u must appear before v):
    • Parent CREATE/RENAME/MODIFY before child CREATE/RENAME/MODIFY
    • Child DELETE before parent DELETE
    • Non-delete entry before DELETE of its parent object
    • DELETE before CREATE/RENAME(target) that targets the same path
    • RENAME(source) before CREATE that targets the same path
  • Exposes getOrderedEntries() using Kahn's algorithm
  • Exposes static toOrderedReportEntries() to convert ordered dependency entries into DiffReportEntry payloads

What is the link to the Apache JIRA

https://issues.apache.org/jira/browse/HDDS-15390

How was this patch tested?

Unit Test.

@SaketaChalamchala SaketaChalamchala added the snapshot https://issues.apache.org/jira/browse/HDDS-6517 label Jul 16, 2026
…encyGraph

The dependency graph indexed multiple non-delete nodes per objectId (e.g.
RENAME + MODIFY) but never added an edge between them, so topological sort
could emit RENAME before a MODIFY reported at the from-snapshot source path.
Add an intra-object edge oriented by which path the entry carries, plus a
reproduction test.

Co-authored-by: Cursor <cursoragent@cursor.com>
Change-Id: Ib7ebd867f58b864f17ec28f61843390a34cf0705
@jojochuang

Copy link
Copy Markdown
Contributor

Potential issue: same-object RENAME/MODIFY are not ordered

SnapDiffDependencyGraph indexes multiple non-delete nodes per objectId (e.g. a RENAME + a MODIFY for the same object), but it never adds an edge between those entries. Topological sort can therefore emit the RENAME before a MODIFY that is reported at the from-snapshot (pre-rename) source path — and once the path has been renamed away, that MODIFY can no longer be applied at its reported path.

Concrete example

Object 2 under dir 1, from parent/a.txt (v1) to parent/b.txt (v2) — renamed and modified. Snapdiff emits, with MODIFY at the from-snapshot path:

RENAME  source="parent/a.txt"  target="parent/b.txt"   (objectId=2, parent=1)
MODIFY  source="parent/a.txt"                          (objectId=2, parent=1)

No edge links the two nodes, so both have inDegree == 0 and Kahn's sort emits them in input order: [RENAME, MODIFY]. A consumer then renames parent/a.txt -> parent/b.txt and tries to modify the now-nonexistent parent/a.txt. Correct order is [MODIFY, RENAME].

Note the existing testModifyAndRenameForSameObjectKeepDependencyOrder does not catch this: it only asserts the parent CREATE is first and that the two tail entries share objectId == 2 — it never asserts the RENAME-vs-MODIFY relative order, and its data uses MODIFY at the rename target path.

Reproduction test (fails on current code)

@Test
void testModifyAtSourcePathOrderedBeforeRename() {
  List<SnapDiffDependencyEntry> entries = Arrays.asList(
      entry(2L, 1L, RENAME, "parent/a.txt", "parent/b.txt"),
      entry(2L, 1L, MODIFY, "parent/a.txt"));

  List<DiffType> orderedTypes = toDiffTypes(sort(entries));
  assertEquals(Arrays.asList(MODIFY, RENAME), orderedTypes);
}
expected: <[MODIFY, RENAME]> but was: <[RENAME, MODIFY]>

Suggested fix

Add an intra-object edge oriented by which path the entry carries: an entry at the RENAME source path must precede the RENAME; the RENAME must precede an entry at its target path. With that edge, the reproduction test and all existing TestSnapDiffDependencyGraph cases pass (11/11).

I pushed a candidate fix + the reproduction test to a branch for reference:
https://github.com/jojochuang/ozone/tree/HDDS-15390-rename-modify-order-fix

Found via automated review (Cursor Bugbot).

@smengcl smengcl left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @SaketaChalamchala for the patch.


addPathConflictEdges(deleteNodesByPath, createNodesByPath,
renameNodesByTargetPath);
addRenameBeforeCreateEdges(renameNodesBySourcePath, createNodesByPath);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Renames also need path dependencies on other renames. For A -> B and B -> C, B -> C must run first to free B. Currently only RENAME -> CREATE is covered, so input order [A -> B, B -> C] is returned unchanged and the first rename cannot be replayed. Please add the corresponding rename-to-rename edge, with chain and cycle tests.

Comment on lines +35 to +36
public SnapDiffDependencyEntry(long objectId, long parentObjectId,
DiffReportEntry reportEntry) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A cross-parent rename needs both parent IDs. For A/B -> C/B with DELETE A and CREATE C, C must be created before the rename, while A must remain until after it. A single parentObjectId can enforce only one of these dependencies. Please carry the source and target parent IDs, or equivalent explicit dependencies.

Comment on lines +169 to +172
if (entry.isDelete()) {
if (parentDeleteNodeId != null) {
addEdge(nodeId, parentDeleteNodeId);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The DELETE branch ignores a parent RENAME. For [RENAME A -> B, DELETE A/child], the input order remains unchanged and the delete then addresses a path that has moved. Please add a child DELETE -> parent RENAME edge and a test for this case.

orderedEntries.add(nodes.get(nodeId));
for (int dependentNodeId : adjacencyList.get(nodeId)) {
int updatedInDegree = inDegree.get(dependentNodeId) - 1;
inDegree.put(dependentNodeId, updatedInDegree);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getOrderedEntries() mutates the graph’s stored inDegree. After one call, a second call can enqueue every node and return input order instead of dependency order. Please sort using a local copy of the indegrees or cache the result, and add a repeated-call test.

Comment on lines +115 to +116
adjacencyList.put(nodeId, new HashSet<>());
inDegree.put(nodeId, 0);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Each node allocates an empty HashSet plus boxed entries in two HashMaps, before the temporary path and object indexes. The default changed-key limit is one billion, so this representation can exhaust OM heap far below the configured limit. Please consider primitive, lazy, or batched graph state and add scale coverage.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

AI-gen snapshot https://issues.apache.org/jira/browse/HDDS-6517

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants