History

remove double reads updating object metadata (#13542)

Removes RLock/RUnlock for updating metadata, since we already take a write lock to update metadata, this change removes reading of xl.meta as well as an additional lock, the performance gain should increase 3x theoretically for - PutObjectRetention - PutObjectLegalHold This optimization is mainly for Veeam like workloads that require a certain level of iops from these API calls, we were losing iops.

Harshavardhana committed 5 years ago

Commit 4ed0eb71 parent 2af54457 files · +117 −117Browse files

Unified · Split

7 files
  1. cmd/api-errors.go +2 −0
  2. cmd/bucket-object-lock.go +31 −23
  3. cmd/bucket-replication.go +17 −17
  4. cmd/erasure-object.go +13 −7
  5. cmd/object-api-errors.go +7 −0
  6. cmd/object-api-interface.go +4 −0
  7. cmd/object-handlers.go +43 −70
cmd/api-errors.go+2−0
@@ -1956,6 +1956,8 @@ func toAPIErrorCode(ctx context.Context, err error) (apiErr APIErrorCode) { · 1955 unmodified lines
19561956 apiErr = ErrNoSuchKey
19571957 case MethodNotAllowed:
19581958 apiErr = ErrMethodNotAllowed
1959+ case ObjectLocked:
1960+ apiErr = ErrObjectLocked
19591961 case InvalidVersionID:
19601962 apiErr = ErrInvalidVersionID
19611963 case VersionNotFound:
cmd/bucket-object-lock.go+31−23
@@ -175,68 +175,76 @@ func enforceRetentionBypassForDelete(ctx context.Context, r *http.Request, bucke · 174 unmodified lines
175175 // For objects in "Governance" mode, overwrite is allowed if a) object retention date is past OR
176176 // governance bypass headers are set and user has governance bypass permissions.
177177 // Objects in compliance mode can be overwritten only if retention date is being extended. No mode change is permitted.
178−func enforceRetentionBypassForPut(ctx context.Context, r *http.Request, bucket, object string, getObjectInfoFn GetObjectInfoFn, objRetention *objectlock.ObjectRetention, cred auth.Credentials, owner bool) (ObjectInfo, APIErrorCode) {
178+func enforceRetentionBypassForPut(ctx context.Context, r *http.Request, oi ObjectInfo, objRetention *objectlock.ObjectRetention, cred auth.Credentials, owner bool) error {
179179 byPassSet := objectlock.IsObjectLockGovernanceBypassSet(r.Header)
180− opts, err := getOpts(ctx, r, bucket, object)
181− if err != nil {
182− return ObjectInfo{}, toAPIErrorCode(ctx, err)
183− }
184−
185− oi, err := getObjectInfoFn(ctx, bucket, object, opts)
186− if err != nil {
187− return oi, toAPIErrorCode(ctx, err)
188− }
189180
190181 t, err := objectlock.UTCNowNTP()
191182 if err != nil {
192183 logger.LogIf(ctx, err)
193− return oi, ErrObjectLocked
184+ return ObjectLocked{Bucket: oi.Bucket, Object: oi.Name, VersionID: oi.VersionID}
194185 }
195186
196− // Pass in relative days from current time, to additionally to verify "object-lock-remaining-retention-days" policy if any.
187+ // Pass in relative days from current time, to additionally
188+ // to verify "object-lock-remaining-retention-days" policy if any.
197189 days := int(math.Ceil(math.Abs(objRetention.RetainUntilDate.Sub(t).Hours()) / 24))
198190
199191 ret := objectlock.GetObjectRetentionMeta(oi.UserDefined)
200192 if ret.Mode.Valid() {
201193 // Retention has expired you may change whatever you like.
202194 if ret.RetainUntilDate.Before(t) {
203− perm := isPutRetentionAllowed(bucket, object,
195+ apiErr := isPutRetentionAllowed(oi.Bucket, oi.Name,
204196 days, objRetention.RetainUntilDate.Time,
205197 objRetention.Mode, byPassSet, r, cred,
206198 owner)
207− return oi, perm
199+ switch apiErr {
200+ case ErrAccessDenied:
201+ return errAuthentication
202+ }
203+ return nil
208204 }
209205
210206 switch ret.Mode {
211207 case objectlock.RetGovernance:
212− govPerm := isPutRetentionAllowed(bucket, object, days,
208+ govPerm := isPutRetentionAllowed(oi.Bucket, oi.Name, days,
213209 objRetention.RetainUntilDate.Time, objRetention.Mode,
214210 byPassSet, r, cred, owner)
215211 // Governance mode retention period cannot be shortened, if x-amz-bypass-governance is not set.
216212 if !byPassSet {
217213 if objRetention.Mode != objectlock.RetGovernance || objRetention.RetainUntilDate.Before((ret.RetainUntilDate.Time)) {
218− return oi, ErrObjectLocked
214+ return ObjectLocked{Bucket: oi.Bucket, Object: oi.Name, VersionID: oi.VersionID}
219215 }
220216 }
221− return oi, govPerm
217+ switch govPerm {
218+ case ErrAccessDenied:
219+ return errAuthentication
220+ }
221+ return nil
222222 case objectlock.RetCompliance:
223223 // Compliance retention mode cannot be changed or shortened.
224224 // https://docs.aws.amazon.com/AmazonS3/latest/dev/object-lock-overview.html#object-lock-retention-modes
225225 if objRetention.Mode != objectlock.RetCompliance || objRetention.RetainUntilDate.Before((ret.RetainUntilDate.Time)) {
226− return oi, ErrObjectLocked
226+ return ObjectLocked{Bucket: oi.Bucket, Object: oi.Name, VersionID: oi.VersionID}
227227 }
228− compliancePerm := isPutRetentionAllowed(bucket, object,
228+ apiErr := isPutRetentionAllowed(oi.Bucket, oi.Name,
229229 days, objRetention.RetainUntilDate.Time, objRetention.Mode,
230230 false, r, cred, owner)
231− return oi, compliancePerm
231+ switch apiErr {
232+ case ErrAccessDenied:
233+ return errAuthentication
234+ }
235+ return nil
232236 }
233− return oi, ErrNone
237+ return nil
234238 } // No pre-existing retention metadata present.
235239
236− perm := isPutRetentionAllowed(bucket, object,
240+ apiErr := isPutRetentionAllowed(oi.Bucket, oi.Name,
237241 days, objRetention.RetainUntilDate.Time,
238242 objRetention.Mode, byPassSet, r, cred, owner)
239− return oi, perm
243+ switch apiErr {
244+ case ErrAccessDenied:
245+ return errAuthentication
246+ }
247+ return nil
240248 }
241249
242250 // checkPutObjectLockAllowed enforces object retention policy and legal hold policy
cmd/bucket-replication.go+17−17
@@ -910,24 +910,24 @@ func replicateObject(ctx context.Context, ri ReplicateObjectInfo, objectAPI Obje · 909 unmodified lines
910910 // metadata should be updated with last resync timestamp.
911911 if objInfo.ReplicationStatusInternal != newReplStatusInternal || rinfos.ReplicationResynced() {
912912 popts := ObjectOptions{
913− MTime: objInfo.ModTime,
914− VersionID: objInfo.VersionID,
915− UserDefined: make(map[string]string, len(objInfo.UserDefined)),
916− }
917− for k, v := range objInfo.UserDefined {
918− popts.UserDefined[k] = v
919− }
920− popts.UserDefined[ReservedMetadataPrefixLower+ReplicationStatus] = newReplStatusInternal
921− popts.UserDefined[ReservedMetadataPrefixLower+ReplicationTimestamp] = UTCNow().Format(time.RFC3339Nano)
922− popts.UserDefined[xhttp.AmzBucketReplicationStatus] = string(rinfos.ReplicationStatus())
923− for _, rinfo := range rinfos.Targets {
924− if rinfo.ResyncTimestamp != "" {
925− popts.UserDefined[targetResetHeader(rinfo.Arn)] = rinfo.ResyncTimestamp
926− }
927− }
928− if objInfo.UserTags != "" {
929− popts.UserDefined[xhttp.AmzObjectTagging] = objInfo.UserTags
913+ MTime: objInfo.ModTime,
914+ VersionID: objInfo.VersionID,
915+ EvalMetadataFn: func(oi ObjectInfo) error {
916+ oi.UserDefined[ReservedMetadataPrefixLower+ReplicationStatus] = newReplStatusInternal
917+ oi.UserDefined[ReservedMetadataPrefixLower+ReplicationTimestamp] = UTCNow().Format(time.RFC3339Nano)
918+ oi.UserDefined[xhttp.AmzBucketReplicationStatus] = string(rinfos.ReplicationStatus())
919+ for _, rinfo := range rinfos.Targets {
920+ if rinfo.ResyncTimestamp != "" {
921+ oi.UserDefined[targetResetHeader(rinfo.Arn)] = rinfo.ResyncTimestamp
922+ }
923+ }
924+ if objInfo.UserTags != "" {
925+ oi.UserDefined[xhttp.AmzObjectTagging] = objInfo.UserTags
926+ }
927+ return nil
928+ },
930929 }
930+
931931 if _, err = objectAPI.PutObjectMetadata(ctx, bucket, object, popts); err != nil {
932932 logger.LogIf(ctx, fmt.Errorf("Unable to update replication metadata for %s/%s(%s): %w",
933933 bucket, objInfo.Name, objInfo.VersionID, err))
cmd/erasure-object.go+13−7
@@ -1440,13 +1440,21 @@ func (er erasureObjects) PutObjectMetadata(ctx context.Context, bucket, object s · 1439 unmodified lines
14401440 return ObjectInfo{}, toObjectErr(err, bucket, object)
14411441 }
14421442 if fi.Deleted {
1443− if opts.VersionID == "" {
1444− return ObjectInfo{}, toObjectErr(errFileNotFound, bucket, object)
1445− }
14461443 return ObjectInfo{}, toObjectErr(errMethodNotAllowed, bucket, object)
14471444 }
14481445
1449− for k, v := range opts.UserDefined {
1446+ // if version-id is not specified retention is supposed to be set on the latest object.
1447+ if opts.VersionID == "" {
1448+ opts.VersionID = fi.VersionID
1449+ }
1450+
1451+ objInfo := fi.ToObjectInfo(bucket, object)
1452+ if opts.EvalMetadataFn != nil {
1453+ if err := opts.EvalMetadataFn(objInfo); err != nil {
1454+ return ObjectInfo{}, err
1455+ }
1456+ }
1457+ for k, v := range objInfo.UserDefined {
14501458 fi.Metadata[k] = v
14511459 }
14521460 fi.ModTime = opts.MTime
@@ -1456,9 +1464,7 @@ func (er erasureObjects) PutObjectMetadata(ctx context.Context, bucket, object s · 3 unmodified lines
14561464 return ObjectInfo{}, toObjectErr(err, bucket, object)
14571465 }
14581466
1459− objInfo := fi.ToObjectInfo(bucket, object)
1460− return objInfo, nil
1461−
1467+ return fi.ToObjectInfo(bucket, object), nil
14621468 }
14631469
14641470 // PutObjectTags - replace or add tags to an existing object
cmd/object-api-errors.go+7−0
@@ -291,6 +291,13 @@ func (e MethodNotAllowed) Error() string { · 290 unmodified lines
291291 return "Method not allowed: " + e.Bucket + "/" + e.Object
292292 }
293293
294+// ObjectLocked object is currently WORM protected.
295+type ObjectLocked GenericError
296+
297+func (e ObjectLocked) Error() string {
298+ return "Object is WORM protected and cannot be overwritten: " + e.Bucket + "/" + e.Object + "(" + e.VersionID + ")"
299+}
300+
294301 // ObjectAlreadyExists object already exists.
295302 type ObjectAlreadyExists GenericError
296303
cmd/object-api-interface.go+4−0
@@ -33,6 +33,9 @@ import ( · 32 unmodified lines
3333 // CheckPreconditionFn returns true if precondition check failed.
3434 type CheckPreconditionFn func(o ObjectInfo) bool
3535
36+// EvalMetadataFn validates input objInfo and returns an updated metadata
37+type EvalMetadataFn func(o ObjectInfo) error
38+
3639 // GetObjectInfoFn is the signature of GetObjectInfo function.
3740 type GetObjectInfoFn func(ctx context.Context, bucket, object string, opts ObjectOptions) (ObjectInfo, error)
3841
@@ -50,6 +53,7 @@ type ObjectOptions struct { · 11 unmodified lines
5053 UserDefined map[string]string // only set in case of POST/PUT operations
5154 PartNumber int // only useful in case of GetObject/HeadObject
5255 CheckPrecondFn CheckPreconditionFn // only set during GetObject/HeadObject/CopyObjectPart preconditional valuation
56+ EvalMetadataFn EvalMetadataFn // only set for retention settings, meant to be used only when updating metadata in-place.
5357 DeleteReplication ReplicationState // Represents internal replication state needed for Delete replication
5458 Transition TransitionOptions
5559 Expiration ExpirationOptions
cmd/object-handlers.go+43−70
@@ -3521,53 +3521,39 @@ func (api objectAPIHandlers) PutObjectLegalHoldHandler(w http.ResponseWriter, r · 3520 unmodified lines
35213521 return
35223522 }
35233523
3524− getObjectInfo := objectAPI.GetObjectInfo
3525− if api.CacheAPI() != nil {
3526− getObjectInfo = api.CacheAPI().GetObjectInfo
3527− }
3528−
35293524 opts, err := getOpts(ctx, r, bucket, object)
35303525 if err != nil {
35313526 writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
35323527 return
35333528 }
35343529
3535− objInfo, err := getObjectInfo(ctx, bucket, object, opts)
3530+ popts := ObjectOptions{
3531+ MTime: opts.MTime,
3532+ VersionID: opts.VersionID,
3533+ EvalMetadataFn: func(oi ObjectInfo) error {
3534+ oi.UserDefined[strings.ToLower(xhttp.AmzObjectLockLegalHold)] = strings.ToUpper(string(legalHold.Status))
3535+ oi.UserDefined[ReservedMetadataPrefixLower+ObjectLockLegalHoldTimestamp] = UTCNow().Format(time.RFC3339Nano)
3536+
3537+ dsc := mustReplicate(ctx, bucket, object, getMustReplicateOptions(oi, replication.MetadataReplicationType, opts))
3538+ if dsc.ReplicateAny() {
3539+ oi.UserDefined[ReservedMetadataPrefixLower+ReplicationTimestamp] = UTCNow().Format(time.RFC3339Nano)
3540+ oi.UserDefined[ReservedMetadataPrefixLower+ReplicationStatus] = dsc.PendingStatus()
3541+ }
3542+ return nil
3543+ },
3544+ }
3545+
3546+ objInfo, err := objectAPI.PutObjectMetadata(ctx, bucket, object, popts)
35363547 if err != nil {
35373548 writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
35383549 return
35393550 }
3540− if objInfo.DeleteMarker {
3541− writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrMethodNotAllowed), r.URL)
3542− return
3543− }
3544− objInfo.UserDefined[strings.ToLower(xhttp.AmzObjectLockLegalHold)] = strings.ToUpper(string(legalHold.Status))
3545− objInfo.UserDefined[ReservedMetadataPrefixLower+ObjectLockLegalHoldTimestamp] = UTCNow().Format(time.RFC3339Nano)
35463551
35473552 dsc := mustReplicate(ctx, bucket, object, getMustReplicateOptions(objInfo, replication.MetadataReplicationType, opts))
3548− if dsc.ReplicateAny() {
3549− objInfo.UserDefined[ReservedMetadataPrefixLower+ReplicationTimestamp] = UTCNow().Format(time.RFC3339Nano)
3550− objInfo.UserDefined[ReservedMetadataPrefixLower+ReplicationStatus] = dsc.PendingStatus()
3551− }
3552− // if version-id is not specified retention is supposed to be set on the latest object.
3553− if opts.VersionID == "" {
3554− opts.VersionID = objInfo.VersionID
3555− }
3556− popts := ObjectOptions{
3557− MTime: opts.MTime,
3558− VersionID: opts.VersionID,
3559− UserDefined: make(map[string]string, len(objInfo.UserDefined)),
3560− }
3561− for k, v := range objInfo.UserDefined {
3562− popts.UserDefined[k] = v
3563− }
3564− if _, err = objectAPI.PutObjectMetadata(ctx, bucket, object, popts); err != nil {
3565− writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
3566− return
3567− }
35683553 if dsc.ReplicateAny() {
35693554 scheduleReplication(ctx, objInfo.Clone(), objectAPI, dsc, replication.MetadataReplicationType)
35703555 }
3556+
35713557 writeSuccessResponseHeadersOnly(w)
35723558
35733559 // Notify object event.
@@ -3703,50 +3689,37 @@ func (api objectAPIHandlers) PutObjectRetentionHandler(w http.ResponseWriter, r · 129 unmodified lines
37033689 return
37043690 }
37053691
3706− getObjectInfo := objectAPI.GetObjectInfo
3707− if api.CacheAPI() != nil {
3708− getObjectInfo = api.CacheAPI().GetObjectInfo
3692+ popts := ObjectOptions{
3693+ MTime: opts.MTime,
3694+ VersionID: opts.VersionID,
3695+ EvalMetadataFn: func(oi ObjectInfo) error {
3696+ if err := enforceRetentionBypassForPut(ctx, r, oi, objRetention, cred, owner); err != nil {
3697+ return err
3698+ }
3699+ if objRetention.Mode.Valid() {
3700+ oi.UserDefined[strings.ToLower(xhttp.AmzObjectLockMode)] = string(objRetention.Mode)
3701+ oi.UserDefined[strings.ToLower(xhttp.AmzObjectLockRetainUntilDate)] = objRetention.RetainUntilDate.UTC().Format(time.RFC3339)
3702+ } else {
3703+ oi.UserDefined[strings.ToLower(xhttp.AmzObjectLockMode)] = ""
3704+ oi.UserDefined[strings.ToLower(xhttp.AmzObjectLockRetainUntilDate)] = ""
3705+ }
3706+ oi.UserDefined[ReservedMetadataPrefixLower+ObjectLockRetentionTimestamp] = UTCNow().Format(time.RFC3339Nano)
3707+ dsc := mustReplicate(ctx, bucket, object, getMustReplicateOptions(oi, replication.MetadataReplicationType, opts))
3708+ if dsc.ReplicateAny() {
3709+ oi.UserDefined[ReservedMetadataPrefixLower+ReplicationTimestamp] = UTCNow().Format(time.RFC3339Nano)
3710+ oi.UserDefined[ReservedMetadataPrefixLower+ReplicationStatus] = dsc.PendingStatus()
3711+ }
3712+ return nil
3713+ },
37093714 }
37103715
3711− objInfo, s3Err := enforceRetentionBypassForPut(ctx, r, bucket, object, getObjectInfo, objRetention, cred, owner)
3712− if s3Err != ErrNone {
3713− writeErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Err), r.URL)
3714− return
3715− }
3716− if objInfo.DeleteMarker {
3717− writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrMethodNotAllowed), r.URL)
3716+ objInfo, err := objectAPI.PutObjectMetadata(ctx, bucket, object, popts)
3717+ if err != nil {
3718+ writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
37183719 return
37193720 }
3720− if objRetention.Mode.Valid() {
3721− objInfo.UserDefined[strings.ToLower(xhttp.AmzObjectLockMode)] = string(objRetention.Mode)
3722− objInfo.UserDefined[strings.ToLower(xhttp.AmzObjectLockRetainUntilDate)] = objRetention.RetainUntilDate.UTC().Format(time.RFC3339)
3723− } else {
3724− objInfo.UserDefined[strings.ToLower(xhttp.AmzObjectLockMode)] = ""
3725− objInfo.UserDefined[strings.ToLower(xhttp.AmzObjectLockRetainUntilDate)] = ""
3726− }
3727− objInfo.UserDefined[ReservedMetadataPrefixLower+ObjectLockRetentionTimestamp] = UTCNow().Format(time.RFC3339Nano)
37283721
37293722 dsc := mustReplicate(ctx, bucket, object, getMustReplicateOptions(objInfo, replication.MetadataReplicationType, opts))
3730− if dsc.ReplicateAny() {
3731− objInfo.UserDefined[ReservedMetadataPrefixLower+ReplicationTimestamp] = UTCNow().Format(time.RFC3339Nano)
3732− objInfo.UserDefined[ReservedMetadataPrefixLower+ReplicationStatus] = dsc.PendingStatus()
3733− }
3734− // if version-id is not specified retention is supposed to be set on the latest object.
3735− if opts.VersionID == "" {
3736− opts.VersionID = objInfo.VersionID
3737− }
3738− popts := ObjectOptions{
3739− MTime: opts.MTime,
3740− VersionID: opts.VersionID,
3741− UserDefined: make(map[string]string, len(objInfo.UserDefined)),
3742− }
3743− for k, v := range objInfo.UserDefined {
3744− popts.UserDefined[k] = v
3745− }
3746− if _, err = objectAPI.PutObjectMetadata(ctx, bucket, object, popts); err != nil {
3747− writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
3748− return
3749− }
37503723 if dsc.ReplicateAny() {
37513724 scheduleReplication(ctx, objInfo.Clone(), objectAPI, dsc, replication.MetadataReplicationType)
37523725 }

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t