Skip to content

HDDS-15939. [OEP] Design for accounting per-snapshot trapped deleted data - #10838

Draft
sadanand48 wants to merge 4 commits into
apache:masterfrom
sadanand48:HDDS-15939
Draft

HDDS-15939. [OEP] Design for accounting per-snapshot trapped deleted data#10838
sadanand48 wants to merge 4 commits into
apache:masterfrom
sadanand48:HDDS-15939

Conversation

@sadanand48

@sadanand48 sadanand48 commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

What changes were proposed in this pull request?

Design doc for accounting per-snapshot trapped deleted data.

snapshot-trapped-accounting

What is the link to the Apache JIRA

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

@sadanand48 sadanand48 added AI-gen snapshot https://issues.apache.org/jira/browse/HDDS-6517 labels Jul 22, 2026
* Increment snapshot counters **only** when `putIfAbsent` on ledger succeeds.
* Decrement **only** when CAS `ACCOUNTED_* → PURGED` (or `DIR_EXPAND_ACCOUNTED → PURGED` for dir
root after purge) succeeds.
* One ledger row per objectID; prevents double counting across create, expand, DDS promote, and purge.

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.

How do you propose to deal with multiple versions of an Object? deletedTable can can hold multiple OmKeyInfo versions for one DB key.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Good point instead of using the objectID in the ledger, we may use deleteDbKey which is currently used as a key in deletedTable to distinguish b/w multiple KeyInfo of same dbKey

Per file promoted:

```
pinningSnapshot = resolve pinning snapshot (same logic as ReclaimableKeyFilter)

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.

ReclaimableKeyFilter only checks the immediate previous snapshot but in Section 4 pinning snapshot is said to be "The oldest snapshot in the chain that still references the object's data".
I assume we want pinning snapshot to be the latter definition. If that's the case we can't use ReclaimableKeyFilter as it is. We may need to extend it to find the oldest snapshot with references to the file.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Right, instead of pinning-snapshot being oldest snapshot that holds the key it will be just the snapshot that has the deletedKey. I will update this

Comment on lines +346 to +349
┌─────────────────────────────────────────────────────────────────┐
│ ExpandAndAccountDirService (new) │
│ read-only subtree account → trappedKey* on pinning snapshot │
└─────────────────────────────────────────────────────────────────┘

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.

Wouldn't it be simpler to just credit the trappedKeys to the store snapshot after expansion as well?
Since there is no overlap of deleted entries among snapshots, whatever deletes are captured in the snapshot(store snapshot) will account towards the trapped space for that snapshot even after expansion.

Any thoughts? I might be missing something here?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Correct, this is what was intended that trappedKeys is credited to store snapshot.

Comment on lines +359 to +363
│ SnapshotDeletingService │
│ move tables S1→S2/AOS; update ledger snapshot_id on rows │
│ transfer trappedDirNamespace between store snapshots │
│ file entry snapshot_id unchanged until purge (pinning) │
└─────────────────────────────────────────────────────────────────┘

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.

For each key moved, updating snapshot_id in ledger_table for store snapshots and for pinning snapshots(It may be the after current snapshot is purged the next snapshot can be the oldest snapshot that holds reference to subfiles under a deleted root) and consistently transferring trapped* counters to the next snapshot for the entries that have been moved should all be an atomic operation.

|-------|------|-------------------|-------------|
| `trappedKeyBytes` | `long` | Snapshot create (`deletedTable` sum); ExpandAndAccountDirService; DDS promote to `deletedTable` (reclaimable dir) | Successful key purge |
| `trappedKeyNamespace` | `long` | Same | Successful key purge |
| `trappedDirNamespace` | `long` | Snapshot create (count of **root** rows in `deletedDirTable`) | DDS removes root from `deletedDirTable` |

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.

Since trapped* fields can be updated after snapshot create and after ExpandAndAccountDirService and/or DDS, should we add another field to indicate "pending deleted directory expansion" and "expanded" just so that it is clear for client reading this info off of the snapshot info.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This is tricky as the 'expanded' state would be momentary because say there is a snapshot delete after the snapshot has been marked expanded and that snapshot's dirs are moved to the 'expanded' snapshot its state would again be pending expansion

|-------|------|-------------------|-------------|
| `trappedKeyBytes` | `long` | Snapshot create (`deletedTable` sum); ExpandAndAccountDirService; DDS promote to `deletedTable` (reclaimable dir) | Successful key purge |
| `trappedKeyNamespace` | `long` | Same | Successful key purge |
| `trappedDirNamespace` | `long` | Snapshot create (count of **root** rows in `deletedDirTable`) | DDS removes root from `deletedDirTable` |

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.

To the trappedDirNamespace we could add the sub-directories that have been expanded once the subtree walk finishes. This can give some info about how much namespace is trapped.

@priyeshkaratha priyeshkaratha left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks @sadanand48 for working on this design. I have few comments please check it.

inline `SnapshotInfo` update per task — **not** one transaction per file.

### 6.5 Integration with existing services

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

generated one in the PR description

Comment thread hadoop-hdds/docs/content/design/snapshot-trapped-deleted-bytes.md

```
┌─────────────────────────────────────────────────────────────────┐
│ Snapshot create (createOmSnapshotCheckpoint) │

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

What happens during upgrade? If you have chains of snapshots?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

We need a tool to update the counters on upgrade like the quota repair tool which we could run, We could also add another task to the QuotaRepair tool itself.

@smengcl

smengcl commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Thanks @sadanand48 for the design doc


1. Re-validate snapshot chain (`expectedPreviousSnapshotId`).
2. Re-check `ReclaimableDirFilter`; if now reclaimable → exit (DDS owns expansion).
3. Read-only walk `fileTable` under dir root (and subdirs still in `directoryTable`).

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.

DDS already scans child directories when the root is not reclaimable. DDS also calls ReclaimableKeyFilter for each file. DDS can move a child directory before this service reads directoryTable. The new walk can then miss the child subtree and mark the parent complete. Define coordination with DDS, or collect the accounting during the existing DDS traversal.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

DDS already scans child directories when the root is not reclaimable

This is wrong, it doesn't scan it

boolean isDirReclaimable = reclaimableDirFilter.apply(pendingDeletedDirInfo);
Optional<PurgePathRequest> request = prepareDeleteDirRequest(
    pendingDeletedDirInfo.getValue(),
    pendingDeletedDirInfo.getKey(), isDirReclaimable, allSubDirList,
    getOzoneManager().getKeyManager(), reclaimableFileFilter, remainNum);

And inside prepareDeleteDirRequest (lines 485-501, which I can see from the surrounding context), it does:

if (isDirReclaimable) {
  // It expands the directory and adds files to subFiles and dirs to subDirs
  ...
}

Define coordination with DDS, or collect the accounting during the existing DDS traversal.

Yes this is addressed on line 241

* Call SCM or purge blocks.
* Update `trappedDirNamespace` before subtree walk completion.

**Ratis pressure mitigation:** accumulate counter deltas in memory; one batched `SetSnapshotProperty` or

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.

putIfAbsent and the counter delta must use the same OM Ratis request and active DB batch. A separate SetSnapshotProperty request creates a failure window. A persisted ledger entry can cause a retry to skip a missing counter increment. Define the request, cache update, DB batch, and replay behavior.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Added explicit text that putIfAbsent and the counter delta must use the same OM Ratis request and active DB batch to prevent failure windows


New OM table: `snapshotTrappedLedgerTable` (name TBD).

**Key:** `deleteDbKey` (row key in `deletedTable` / `deletedDirTable`)

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 raw deleteDbKey has two prefix formats. deletedTable uses a volume and bucket name prefix. deletedDirTable uses an FSO ID prefix. Snapshot SST filtering supports one prefix for each table. Define a normalized ledger key and a filtering rule. Also define when OM removes PURGED rows.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Updated the format, the the ledger key to be /{volumeId}/{bucketId}/{deleteDbKey}

|-------|--------------|
| `referencedSize` | Live data at create — orthogonal |
| `exclusiveSize` | Legacy deep-clean side effect — keep for reconciliation; do not sum with `trappedKeyBytes` |
| `snapshotUsedBytes` (bucket) | Superset across snapshots + AOS; authoritative for quota |

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.

snapshotUsedBytes is not a superset after the new service counts files that remain in fileTable. Those bytes remain in usedBytes until DDS moves the files. trappedKeyBytes can exceed snapshotUsedBytes. Correct this relationship and state which totals operators can compare.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Added a note for this


## 3. Goals and Non-Goals

### Goals

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.

I don't see a non-goal section anymore?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

updated

Comment on lines +133 to +134
| **Store snapshot** | The snapshot whose DB holds a `deletedTable` / `deletedDirTable` row |
| **Pinning snapshot** | In this design, counter ownership follows the **store snapshot** (snapshot DB currently holding the deleted row) |

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.

If counters always belong to the store snapshot, just remove Pinning snapshot and use store snapshot throughout?

@sadanand48

Copy link
Copy Markdown
Contributor Author

About why the ledger/cache for the accounted keys is needed:

Table presence only tells us that a deleted row exists; it does not tell us whether trapped accounting has already been applied for that row. We need exactly-once semantics across retries/replays and across multiple paths (snapshot create, DDS expand/promote, purge, snapshot-delete move). This is especially important for FSO where only dir roots are accounted at create, while subtree files can be accounted later during DDS traversal and may be revisited multiple times. A persisted ledger provides durable “already-accounted / purged” state and preserves ownership when rows move between snapshots, which table membership alone cannot represent.

@jojochuang jojochuang changed the title HDDS-15939. Design for accounting per-snapshot trapped deleted data HDDS-15939. [OEP] Design for accounting per-snapshot trapped deleted data Aug 20, 2026
@sadanand48

Copy link
Copy Markdown
Contributor Author

@smengcl @jojochuang I was considering the performance concerns that were discussed around having a separate service and additional implementation with new table etc and thought that we could have a tool instead similar to du that will give out on-demand space distribution for the bucket. That way it doesn't always stress the system and only will be run when the user wants an explanation. I've raised a PR for it in #11122 . Let me know what you think.

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

Labels

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants