CLDSRV-961 Tests for lifecycle listings over PHD master keys - #6250
nicolas2bert wants to merge 1 commit into
Conversation
Hello nicolas2bert,My role is to assist you with the merge of this Available options
Available commands
Status report is not available. |
Incorrect fix versionThe
Considering where you are trying to merge, I ignored possible hotfix versions and I expected to find:
Please check the |
42ec3b9 to
ca7fa09
Compare
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files
@@ Coverage Diff @@
## development/9.4 #6250 +/- ##
===================================================
- Coverage 86.50% 86.46% -0.05%
===================================================
Files 212 212
Lines 14581 14581
===================================================
- Hits 12614 12607 -7
- Misses 1967 1974 +7
Flags with carried forward coverage won't be shown. Click here to find out more. 🚀 New features to boost your workflow:
|
Pin arsenal to 8.5.17, which fixes lifecycle listings (DelimiterNonCurrent, DelimiterOrphanDeleteMarker) getting stuck forever on a run of dangling PHD master keys longer than the scan cap. bucketd runs the actual lifecycle listing, not cloudserver, so also repin the MetaData image to the released ghcr.io/scality/metadata:9.17.0-standalone tag (MD-1359): the s3c legs need the fix there too, not only in the pinned arsenal. Add a functional suite proving the real stack -- an actual metadata backend that writes PHD masters and races a repair timer, through the real backbeat route -- makes forward progress across such a desert. The listing algorithm itself is exhaustively unit-tested in Arsenal against both v0 and v1 (scality/Arsenal#2685); this suite intentionally does not re-derive those cases and stays scoped to end-to-end wiring.
684da69 to
329f1f0
Compare
Incorrect fix versionThe
Considering where you are trying to merge, I ignored possible hotfix versions and I expected to find:
Please check the |
Problem
Lifecycle listings can get stuck forever on PHD master keys.
A PHD (placeholder) master is written when the current version of a key is deleted. A repair job later promotes the newest surviving version back to master. Until the repair runs, the master key holds a PHD value. If the repair never runs, or if no version survives, the PHD stays.
DelimiterVersions accepted a PHD master and skipped it without touching the resume marker. That is fine for a plain version listing, which has no scan budget. It is not fine for the two lifecycle listings, DelimiterNonCurrent and DelimiterOrphanDeleteMarker. Both count every scanned entry against maxScannedLifecycleListingEntries and truncate when the budget runs out.
So a run of PHD masters longer than the scan limit burns the whole budget without moving the marker. The listing returns IsTruncated: true with no NextKeyMarker. The next listing starts at the same place and returns the same thing. Lifecycle never gets past that point, and nothing beyond it is ever expired.
Example
Keyspace (v0), scan limit 3:
phd-1 .. phd-6 dangling PHD masters
zebra\0v1 current
zebra\0v2 noncurrent -> must be expired
Before:
page 1: scans phd-1, phd-2, phd-3 -> truncated, no NextKeyMarker
page 2: identical request -> identical result
...forever. zebra\0v2 is never expired.
After:
page 1: phd-1, phd-2, phd-3 -> truncated, NextKeyMarker=phd-2
page 2: phd-3, phd-4, phd-5 -> truncated, NextKeyMarker=phd-4
page 3: phd-5, phd-6, zebra\0v1, zebra\0v2 -> zebra\0v2 listed
What changed
DelimiterVersions — new handlePHDMaster hook
Both PHD branches (keyHandler_NotSkippingV0 and keyHandler_NotSkippingV1) now call handlePHDMaster(key, versionId, value) instead of returning FILTER_ACCEPT directly.
The default implementation returns FILTER_ACCEPT and changes no state. Plain version listings behave exactly as before.
DelimiterOrphanDeleteMarker — treat a PHD as a key transition
The override runs the same new-key branch as addVersion():
The second point matters. A delete marker is held in memory until an entry of another key proves it is an orphan. A PHD master is such an entry. If the marker moved past the candidate without emitting it, the candidate would never be scanned again, and the orphan delete marker would never expire.
scan limit 3, keyspace: banana\0v1 (delete marker), phd-1, phd-2, ...
banana\0v1 -> held as candidate
phd-1 -> new key: banana is proven orphan -> emitted, then marker moves
DelimiterNonCurrent — keep the marker one PHD key behind
The override sets nextKeyMarker to the previous PHD key, never to the key being scanned, and clears nextVersionIdMarker. The marker only moves forward.
A marker on the key being scanned would give the next listing a key-marker with no version-id-marker. S3 reads that as "start after every version of this key". The versions of a PHD master that still has some would be skipped, and they would never be expired. One key behind costs one re-scanned entry per truncation, and skips nothing.
The override leaves prevKey and staleDate untouched. This is deliberate. The next version key scanned is the newest surviving version under the PHD — the one the repair promotes back to master. It must stay classified as current:
apple (PHD) -> marker left behind apple, prevKey untouched
apple\0v1 -> first version seen for apple -> current, protected
apple\0v2 -> noncurrent -> expirable, staleDate = v1's date
Setting prevKey = 'apple' would make apple\0v1 look noncurrent. Noncurrent version expiration would then delete the very version the repair needs to promote. That is data loss.
One fallback remains: if the scan limit lands on the first PHD of the listing, there is no previous PHD and no marker yet. The marker then points at that key, and that one key loses its versions for this pass. Progress matters more than one missed pass, and the alternative is the infinite loop described above.
Tests
All cases run against both v0 and v1 bucket formats.