BB-786: trigger cold transition from oplog - #2830
Conversation
Hello francoisferrand,My role is to assist you with the merge of this Available options
Available commands
Status report is not available. |
Branches have divergedThis pull request's source branch To avoid any integration risks, please re-synchronize them using one of the
Note: If you choose to rebase, you may have to ask me to rebuild |
Codecov Report❌ Patch coverage is
Additional details and impacted files
... and 4 files with indirect coverage changes
@@ Coverage Diff @@
## development/9.6 #2830 +/- ##
===================================================
- Coverage 76.13% 75.87% -0.27%
===================================================
Files 203 203
Lines 14029 14070 +41
===================================================
- Hits 10681 10675 -6
- Misses 3338 3385 +47
Partials 10 10
Flags with carried forward coverage won't be shown. Click here to find out more. 🚀 New features to boost your workflow:
|
c10b011 to
02191ba
Compare
Waiting for approvalThe following approvals are needed before I can proceed with the merge:
|
Support "direct-to-cold" transitions: when an object is uploaded with a cold storage class, cloudserver stores the data in the hot location and records the requested cold class in the object metadata, together with the transition-in-progress flag. The lifecycle queue populator now detects these objects from the oplog and publishes the cold archive request, reusing the whole existing transition pipeline. The transition-in-progress flag must not be cleared while requeuing such an object, since the flag (and the cold storage class) is what identifies it as pending a direct transition. Conversely, a bucket lifecycle rule must not transition an object which is already declared as cold. The metadata update completing the transition is stamped with a distinct 's3:LifecycleTransition:Direct' origin op, so that consumers can tell a direct transition from a lifecycle-driven one. Issue: BB-786
The queue populator parsed each oplog entry several times: once in filter() to look at the bucket, then again in every handler it may call. Each handler also re-checked the originOp it cares about, so the list of interesting operations was spread over the whole file. Decode the entry once in filter() and switch on the originOp there, passing the parsed value down. Handlers now only decide whether the object itself qualifies, which is what the new transition handler needs anyway. Issue: BB-786
02191ba to
85b3f9f
Compare
|
|
||
| // The object already exposes the location it is transitioned to, so this is a | ||
| // direct-to-cold transition. | ||
| const isDirect = objMD.getAmzStorageClass() === newLocation; |
There was a problem hiding this comment.
Should we move that to a dedicated function and avoid comment + code duplication ? Like something hasTargetedLocation ?
There was a problem hiding this comment.
(or better, return directly the target OriginOp ?)
There was a problem hiding this comment.
I was tempted to reuse some generic isColdTransition function, but this ends up adding lot of code for very limited benefit - c.f. https://github.com/scality/backbeat/pull/2830/changes#r3901327310
Trygin to be less ambitious, and just extracting a function here (→ in utils, since it needs to be shared), it should indeed return the target originOp. But I wonder if this is really a benefit: that function would take an object and a location, and return either transition or transition:direct, which does not provide a clean abstraction/semantics: we could deduplicate the code into a transitionOriginOp() function, but would still not be so clear, so readers would most likely jump to the utils package - effectively reducing readability.
On top of it, it would introduce a significant risk: having such a function would invite people to update the code like the following, which will not work (as objMD is updated by the first call to setAmzStorageClass)
objMD.setLocation()
.setDataStoreName(newLocation)
.setAmzStorageClass(newLocation)
.setOriginOp(transitionOriginOp(objMD, newLocation)) It would work by using a variable:
const originOp = transitionOriginOp(objMD, newLocation);
objMD.setLocation()
.setDataStoreName(newLocation)
.setAmzStorageClass(newLocation)
.setOriginOp(originOp)
...but make the ordering constraint very difficult to see - and risk people changing (or trying to change - hopefully we will catch it...) the code later.
All in all in this instance I think it is better to keep the duplication (3 lines x 2) instead of paying the indirection on every read; what do you think?
| * @param {ObjectMD} md - object metadata | ||
| * @return {boolean} true if this is a pending direct transition | ||
| */ | ||
| _isDirectTransition(md) { |
There was a problem hiding this comment.
| _isDirectTransition(md) { | |
| _isDirectToColdTransition(md) { |
|
|
||
| // The object already exposes the location it is transitioned to, so this is a | ||
| // direct-to-cold transition. | ||
| const isDirect = objMD.getAmzStorageClass() === newLocation; |
There was a problem hiding this comment.
| const isDirect = objMD.getAmzStorageClass() === newLocation; | |
| const isDirectToCold = objMD.getAmzStorageClass() === newLocation; |
| /** | ||
| * Check whether the object transition was requested directly in the PUT request (as opposed | ||
| * to being triggered by a lifecycle rule): in that case the requested cold storage class is | ||
| * declared in the object metadata, while the data still lies in a hot location. |
There was a problem hiding this comment.
| * declared in the object metadata, while the data still lies in a hot location. | |
| * Check that the object is a direct to cold transition (without lifecycle trigger) |
The next of the description describe the inside of the function
| } | ||
| // Skip direct-to-cold objects, whose transition is triggered by the queue | ||
| // populator and require the transition in progress flag to be set | ||
| if (locationsConfig[objectMD.getAmzStorageClass()]?.isCold) { |
There was a problem hiding this comment.
| objectVersion: version, | ||
| requestId: uuid(), | ||
| size: value['content-length'], | ||
| eTag: value['content-md5'], |
There was a problem hiding this comment.
Should we add " ? Not sure about this one here
| // if entry is a versioned object and is the master entry, skip task as | ||
| // the non-master entry will be processed |
There was a problem hiding this comment.
| // if entry is a versioned object and is the master entry, skip task as | |
| // the non-master entry will be processed | |
| // Skip it as the key will be processed by the non-master entry |
|
|
||
| // if entry is a versioned object and is the master entry, skip task as | ||
| // the non-master entry will be processed | ||
| if (this._isVersionedObject(value) && isMasterKey(entry.key)) { |
There was a problem hiding this comment.
This is a copy/paste, should we refactor that to have the logic at one place ?
Waiting for approvalThe following approvals are needed before I can proceed with the merge:
The following reviewers are expecting changes from the author, or must review again: |
Support "direct-to-cold" transitions: an object uploaded with a cold storage class must end up archived in that cold location, without waiting for a lifecycle rule.
Cloudserver (out of scope here) stores the data in the hot location on such a PUT, and records the requested cold class in
x-amz-storage-classtogether with the transition-in-progress flag, while keeping the ordinary create origin op so that bucket notifications still fire.The lifecycle queue populator now detects these objects from the MongoDB oplog and publishes the cold archive request, reusing the exact message shape of
ReplicationAPI.sendDataMoverAction: the whole downstream pipeline (Sorbet -> cold status topic ->LifecycleColdStatusArchiveTask-> GC) is unchanged. The populator is strictly publish-only, and never writes object metadata. The discriminator only accepts origin ops that backbeat itself never writes, so a transition is never re-triggered by its own metadata updates; objects already holdingarchive.archiveInfoare excluded, while a pending deferred restore is not.The transition-in-progress flag must be kept when requeuing such an object, since that flag (together with the cold storage class) is precisely what identifies it as pending: clearing it would hide the object from the populator and break the retry loop after a single attempt. Conversely, a bucket lifecycle rule must not transition an object which is already declared as cold.
Finally, the metadata update completing the transition is stamped with a distinct
s3:LifecycleTransition:Directorigin op when the object already declared the target cold class, so consumers can tell a direct transition from a lifecycle-driven one.Issue: BB-786