-
Notifications
You must be signed in to change notification settings - Fork 20
ARSN-620 Fix lifecycle listings stuck on PHD master keys #2685
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: development/8.5
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -1,5 +1,5 @@ | ||||||
| const { DelimiterVersions } = require('./delimiterVersions'); | ||||||
| const { FILTER_END, FILTER_SKIP } = require('./tools'); | ||||||
| const { FILTER_END, FILTER_SKIP, FILTER_ACCEPT } = require('./tools'); | ||||||
|
|
||||||
| const TRIM_METADATA_MIN_BLOB_SIZE = 10000; | ||||||
|
|
||||||
|
|
@@ -30,6 +30,9 @@ class DelimiterNonCurrent extends DelimiterVersions { | |||||
| // internal state | ||||||
| this.prevKey = null; | ||||||
| this.staleDate = null; | ||||||
| // Last PHD master key scanned. handlePHDMaster keeps the resume marker one | ||||||
| // PHD key behind. See there for why. | ||||||
| this.prevPHDKey = undefined; | ||||||
|
|
||||||
| this.scannedKeys = 0; | ||||||
| } | ||||||
|
|
@@ -144,6 +147,70 @@ class DelimiterNonCurrent extends DelimiterVersions { | |||||
| return; | ||||||
| } | ||||||
|
|
||||||
| /** | ||||||
| * Advance the resume marker over a scanned PHD master key. | ||||||
| * | ||||||
| * THE BUG IT FIXES: a run of dangling PHD masters longer than | ||||||
| * maxScannedLifecycleListingEntries truncated the listing with no | ||||||
| * NextKeyMarker. The next page then repeated the first page, and the | ||||||
| * listing never moved forward. | ||||||
| * | ||||||
| * THE RULE: the marker moves to the PREVIOUS PHD key. It never points at | ||||||
| * the key being scanned. A marker on the scanned key gives the next listing | ||||||
| * a key-marker with no version-id-marker. S3 reads that as "start after | ||||||
| * every version of this key". The listing would then skip the versions of a | ||||||
| * PHD master that still has some, and NCVE would never see them. One key | ||||||
| * behind costs one re-scanned entry per truncation, and skips nothing. | ||||||
| * | ||||||
| * Example, scan limit 3, dangling PHD masters phd-1 ... phd-6: | ||||||
| * 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 -> done | ||||||
| * | ||||||
| * THE FALLBACK: on the first PHD of a listing there is no previous PHD key, | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. cf my comment, I believe the fallback should be removed. |
||||||
| * and no version key has set a marker yet. The rule above would leave the | ||||||
| * marker empty, and the listing would loop again. The marker points at | ||||||
| * `key` instead. This one key loses its versions for this pass, and the | ||||||
| * listing moves forward. | ||||||
| * | ||||||
| * WHAT IT DOES NOT TOUCH: the method updates the marker only. It leaves | ||||||
| * prevKey and staleDate alone. The next version key scanned is the newest | ||||||
| * surviving version under the PHD, and the repair promotes it back to | ||||||
| * master. Untouched state keeps that version classified as current, so the | ||||||
| * listing never returns it as an expirable noncurrent version. | ||||||
| * | ||||||
| * Example, a PHD master with two surviving versions: | ||||||
| * apple (PHD) -> marker stays behind apple, prevKey untouched | ||||||
| * apple\0v1 -> first version seen for apple -> current, protected | ||||||
| * apple\0v2 -> noncurrent -> expirable, staleDate = v1's date | ||||||
| * | ||||||
| * apple\0v1 is not deduplicated as the master copy: a PHD gets its | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
| * versionId at delete time, and that id matches no version key. Setting | ||||||
| * prevKey='apple' here would classify apple\0v1 as noncurrent. NCVE would | ||||||
| * then expire the very version the repair needs to promote: data loss. | ||||||
| * | ||||||
| * @param {String} key - The PHD master key | ||||||
| * @param {String} versionId - always undefined for a master key | ||||||
| * @param {String} value - The PHD placeholder value | ||||||
| * @return {number} - filter return value | ||||||
| */ | ||||||
| // eslint-disable-next-line @typescript-eslint/no-unused-vars | ||||||
| handlePHDMaster(key, versionId, value) { | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't understand the reason to pass Also, for a similar reason I think we don't need to pass |
||||||
| if (this.prevPHDKey !== undefined && this.prevPHDKey > (this.nextKeyMarker || '')) { | ||||||
| // Move the marker forward only. prevPHDKey can hold a key from an | ||||||
| // earlier run of PHDs that the listing already passed. | ||||||
| this.nextKeyMarker = this.prevPHDKey; | ||||||
| this.nextVersionIdMarker = undefined; | ||||||
| } else if (!this.nextKeyMarker) { | ||||||
| // No previous PHD, and no marker yet. Skip this key's versions | ||||||
| // rather than leave the listing unable to move forward. | ||||||
| this.nextKeyMarker = key; | ||||||
| this.nextVersionIdMarker = undefined; | ||||||
| } | ||||||
|
Comment on lines
+204
to
+209
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure we should keep this second condition block, which seems to add the possibility to skip legit noncurrent versions (i.e. keeps a buggy behavior). My reasoning is, if we're there it means:
So removing this block may remove the remaining buggy situation as Not 100% sure, please double check my reasoning. |
||||||
| this.prevPHDKey = key; | ||||||
| return FILTER_ACCEPT; | ||||||
| } | ||||||
|
|
||||||
| /** | ||||||
| * Parses the stringified entry's value and remove the location property if too large. | ||||||
| * @param {string} s - sringified value | ||||||
|
|
||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,5 +1,5 @@ | ||||||||||||||||||||||||
| const DelimiterVersions = require('./delimiterVersions').DelimiterVersions; | ||||||||||||||||||||||||
| const { FILTER_END } = require('./tools'); | ||||||||||||||||||||||||
| const { FILTER_END, FILTER_ACCEPT } = require('./tools'); | ||||||||||||||||||||||||
| const TRIM_METADATA_MIN_BLOB_SIZE = 10000; | ||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||
| * Handle object listing with parameters. This extends the base class DelimiterVersions | ||||||||||||||||||||||||
|
|
@@ -171,6 +171,57 @@ class DelimiterOrphanDeleteMarker extends DelimiterVersions { | |||||||||||||||||||||||
| return; | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||
| * Process a scanned PHD master key as a key transition, exactly like | ||||||||||||||||||||||||
| * the new-key branch of addVersion(). This serves two purposes: | ||||||||||||||||||||||||
| * | ||||||||||||||||||||||||
| * 1. Advance the resume position. On truncation the marker is | ||||||||||||||||||||||||
| * this.prevKeyName (one key behind, so an undecided key is | ||||||||||||||||||||||||
| * re-scanned by the next listing). Without this method, a run of | ||||||||||||||||||||||||
| * dangling PHD masters longer than | ||||||||||||||||||||||||
| * maxScannedLifecycleListingEntries would truncate the listing | ||||||||||||||||||||||||
| * with no marker, and every retry would restart from scratch, | ||||||||||||||||||||||||
| * forever. | ||||||||||||||||||||||||
| * | ||||||||||||||||||||||||
| * Example: scan limit 3, dangling PHD masters phd-1 ... phd-6: | ||||||||||||||||||||||||
| * page 1: phd-1, phd-2, phd-3 -> truncated, NextMarker=phd-2 | ||||||||||||||||||||||||
| * page 2: phd-3, phd-4, phd-5 -> truncated, NextMarker=phd-4 | ||||||||||||||||||||||||
| * page 3: phd-5, phd-6 -> done | ||||||||||||||||||||||||
| * (before: page 1 had no NextMarker -> page 2 = page 1 -> loop) | ||||||||||||||||||||||||
| * | ||||||||||||||||||||||||
| * 2. Resolve the held candidate. A delete marker is kept in memory | ||||||||||||||||||||||||
| * (keyName/value) until an entry of ANOTHER key proves it has no | ||||||||||||||||||||||||
| * other version, i.e. that it is an orphan. A PHD master is such | ||||||||||||||||||||||||
| * an entry, so the candidate must be emitted here, BEFORE the | ||||||||||||||||||||||||
| * marker moves past it: once behind the marker it would never be | ||||||||||||||||||||||||
| * scanned again, and the orphan delete marker never expired. | ||||||||||||||||||||||||
| * | ||||||||||||||||||||||||
| * Example: scan limit 3, keyspace: banana\0v1 (DM), phd-1 ...: | ||||||||||||||||||||||||
| * banana\0v1 -> held as candidate (orphan? unknown yet) | ||||||||||||||||||||||||
| * phd-1 -> new key: banana proven orphan -> emitted | ||||||||||||||||||||||||
| * (advancing the marker without emitting would silently drop | ||||||||||||||||||||||||
| * banana's delete marker forever) | ||||||||||||||||||||||||
| * | ||||||||||||||||||||||||
| * The PHD key itself is never held as a candidate (value = null): | ||||||||||||||||||||||||
| * it is not a delete marker, and a dangling PHD has no versions. | ||||||||||||||||||||||||
| * @param {String} key - The PHD master key | ||||||||||||||||||||||||
| * @param {String} versionId - always undefined for a master key | ||||||||||||||||||||||||
| * @param {String} value - The PHD placeholder value | ||||||||||||||||||||||||
| * @return {number} - filter return value | ||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||
| // eslint-disable-next-line @typescript-eslint/no-unused-vars | ||||||||||||||||||||||||
| handlePHDMaster(key, versionId, value) { | ||||||||||||||||||||||||
| if (key !== this.keyName) { | ||||||||||||||||||||||||
| if (this.value) { | ||||||||||||||||||||||||
| this._addOrphan(); | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
| this.prevKeyName = this.keyName; | ||||||||||||||||||||||||
| this.keyName = key; | ||||||||||||||||||||||||
| this.value = null; | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
| return FILTER_ACCEPT; | ||||||||||||||||||||||||
|
Comment on lines
+214
to
+222
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. maybe simpler:
Suggested change
Should be equivalent since PHDs always have a different key than the previous entry. |
||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| result() { | ||||||||||||||||||||||||
| // Only check for remaining last orphan delete marker if the listing is not interrupted. | ||||||||||||||||||||||||
| // This will help avoid false positives. | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This comment looks suspicious to me: "S3 reads that as ...":
DelimiterVersionswill scan all versions starting atNextKeyMarkerifNextVersionIdMarkeris not present (but it will skip the master).I believe a more correct comment should say that the listing would skip the "master version of" and leave it unable to recognize that the upcoming version is the new current version. But it should still see it.