Skip to content

HDDS-16046. [Ozone versioning] [T4] Read / permanent delete by versionId + promotion - #10937

Open
symious wants to merge 19 commits into
apache:HDDS-15728from
symious:HDDS-16046
Open

HDDS-16046. [Ozone versioning] [T4] Read / permanent delete by versionId + promotion#10937
symious wants to merge 19 commits into
apache:HDDS-15728from
symious:HDDS-16046

Conversation

@symious

@symious symious commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

What changes were proposed in this pull request?

Please only review commits starts with "T4".

This ticket includes the following tasks.

Sub-task Scope Acceptance
T4.1 read by versionId GET/HEAD/LOOKUP with versionId (incl. null addressing) current / noncurrent / null addressing all read correctly; a request without a versionId still reads the current version unchanged; naming the current version's id performs no versionedKeyTable read; an unknown versionId returns KEY_NOT_FOUND; a current delete marker read without a versionId returns KEY_NOT_FOUND; a versionId addressing a delete marker, current or not, returns KEY_IS_DELETE_MARKER; versions behind a marker stay readable by versionId; a versionId against an FSO or LEGACY bucket returns NOT_SUPPORTED_OPERATION.
T4.2 permanent delete (non-current) DELETE ?versionId= on noncurrent versions; blocks to deletedTable the targeted version disappears, others unaffected; quota deducted
T4.3 version promotion deleting current triggers the §4.5 switch (incl. marker deletion / object restore) next-newest promoted with content unchanged field by field; key disappears after its only version is deleted; restore flow tested end to end

What is the link to the Apache JIRA

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

How was this patch tested?

unit test.

symious and others added 19 commits July 16, 2026 11:06
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…equest

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Key names in OBJECT_STORE buckets contain '/' verbatim, so a '/' separator
interleaves a key's versions with those of keys nested under it, breaking
the single-seek promotion and the merged ListObjectVersions order.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Rename NULL_VERSION_ID to UNSET_VERSION_ID: 0 is the unset value of the
optional proto field, not the id of the null version. A null version carries
a normally generated id and is identified by isNullVersion, so that a null
created between two versioned writes orders as the middle version rather
than the oldest.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The reclaim branches skipped S3-versioned buckets only because the legacy
isVersionEnabled flag is kept in sync with an ENABLED status. Depend on the
status directly, so that dropping that sync cannot strand a version record
by reclaiming the blocks it still refers to.

OMKeyCommitRequestWithFSO is left alone: isS3VersioningEnabled() requires the
OBJECT_STORE layout, so the check is structurally false on the FSO path.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
KeyArgs gains versionId and nullVersion: a null version carries a normally
generated id like any other version, so the null slot needs a selector of its
own rather than a reserved id. The current version is checked before the
versionedKeyTable, so naming it costs no extra read, and the null slot is found
by a bounded scan of the key's version prefix.

Addressing a delete marker by version is reported as KEY_IS_DELETE_MARKER
rather than KEY_NOT_FOUND: S3 answers 405 for it and 404 only for a read that
lands on a current marker without naming a version. The status mapping itself
belongs to the gateway.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
DELETE ?versionId= is the only delete that destroys data on a versioned bucket:
the version leaves the versionedKeyTable and its blocks go to the deletedTable,
which stays the single path through which version blocks are reclaimed. The null
slot is addressed by attribute, so it is found by the same bounded prefix scan
the read path uses.

Addressing the current version is rejected for now: removing it has to promote
the next-newest version to keep the keyTable authoritative, which T4.3 adds.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
keyTable holds the current version of every key that still has one, so removing
the current version has to hand the place over: one seek on the key's version
prefix yields the newest remaining version, which moves back into the keyTable
in the same WriteBatch as the delete. The record travels unchanged - promotion
is positional, and a version keeps the identity it was created with. When no
version survives, the key disappears entirely.

Deleting a current delete marker this way is exactly S3's restore-an-object
flow: the version the marker superseded becomes current again.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant