-
Notifications
You must be signed in to change notification settings - Fork 141
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
CBG-3895 allow xattrs to be deleted at the same time as they are updated #6791
base: main
Are you sure you want to change the base?
Conversation
5c43c06
to
d77b347
Compare
- Update StoreSemantic which can only be Replace if xattrs are being deleted, it has to be Insert if the xattrs are being updated on a tombstone
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.
Some initial comments questions on the non-test code changes, will review the test code in more detail in a subsequent pass.
base/collection_xattr.go
Outdated
@@ -189,12 +190,12 @@ func (c *Collection) SubdocWrite(ctx context.Context, k string, subdocKey string | |||
} | |||
|
|||
// subdocGetBodyAndXattr retrieves the document body and xattrs in a single LookupIn subdoc operation. Does not require both to exist. | |||
func (c *Collection) subdocGetBodyAndXattrs(ctx context.Context, k string, xattrKeys []string, fetchBody bool) (rawBody []byte, xattrs map[string][]byte, cas uint64, err error) { | |||
func (c *Collection) subdocGetBodyAndXattrs(ctx context.Context, k string, xattrKeys []string, fetchBody bool) (isTombstone bool, rawBody []byte, xattrs map[string][]byte, cas uint64, err error) { |
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.
What's the scenario where isTombstone is required? (i.e. where the combination of fetchBody=true, rawBody=nil, and err=nil isn't sufficient for the caller to determine isTombstone).
One concern I have is that returning isTombstone=false may be misleading if it's actually not being determined (because fetchBody=false).
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.
I thought that this was required if the body was nil
or {}
because they unmarshal into nil but the only place this was used was where we did a raw comparison so I removed this.
None of the tests in the base package fail but I think https://jenkins.sgwdev.com/job/SyncGateway-Integration/2448/ TestOnDemandWriteImportReplacingNilDoc might be hitting this issue.
base/collection_xattr.go
Outdated
return uint64(result.Cas()), nil | ||
} | ||
|
||
// ressurectWithBodyAndXattrs inserts a document and associated xattrs in a single mutateIn operation. |
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.
// ressurectWithBodyAndXattrs inserts a document and associated xattrs in a single mutateIn operation. | |
// resurrectWithBodyAndXattrs inserts a document and associated xattrs in a single mutateIn operation. |
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.
The resurrectWithBodyAndXattrs code ended up identical to insertBodyAndXattrs, unless I'm missing something. Can we remove it?
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.
fixed, I kept the outer function as separate so it was clear when you were doing an insertion or a resurrection conceptually.
It is unfortunate that we can't cas check on ressurection.
base/collection_xattr.go
Outdated
options.Internal.DocFlags = gocb.SubdocDocFlagAccessDeleted | ||
fillMutateInOptions(ctx, options, opts) | ||
|
||
result, mutateErr := c.Collection.MutateIn(k, mutateOps, options) | ||
if mutateErr != nil { | ||
if isKVError(mutateErr, memd.StatusSubDocBadMulti) { | ||
mutateErr = fmt.Errorf("%w: %w", ErrXattrNotFound, mutateErr) |
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.
Is ErrXattrNotFound actually the only case we get StatusSubDocBadMulti?
Does the 'xattr not found' only apply to attempted deletes, or does it also include xattrs we're updating? (i.e. does this function support inserting new xattrs?)
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.
I don't actually know how we would hit this error, so I've removed this.
This comes from deleteBodyAndXattrs
so I'm not sure if it was a copy paste mistake.
} | ||
mutateOps = append(mutateOps, gocb.ReplaceSpec("", bytesToRawMessage(v), nil)) | ||
mutateOps = appendMacroExpansions(mutateOps, opts) | ||
|
||
options := &gocb.MutateInOptions{ | ||
Expiry: CbsExpiryToDuration(exp), | ||
StoreSemantic: gocb.StoreSemanticsUpsert, | ||
StoreSemantic: gocb.StoreSemanticsReplace, |
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.
Same question as above - do the xattrs have to already exist to use StoreSemanticsReplace?
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.
Replace implies that the body has to exist, it does not care about the presence of the xattr. This is covered by the test TestWriteWithXattrs
but you should make sure to confirm.
base/collection_xattr.go
Outdated
Cas: gocb.Cas(cas), | ||
} | ||
fillMutateInOptions(ctx, options, opts) | ||
result, mutateErr := c.Collection.MutateIn(k, mutateOps, options) | ||
if mutateErr != nil { | ||
if errors.Is(mutateErr, gocb.ErrDocumentNotFound) { | ||
casMismatchErr := sgbucket.CasMismatchErr{Actual: 0, Expected: cas} |
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.
Converting a 'not found' to a 'cas mismatch' seems like it's hiding some information from callers. What's the reason to do this conversion here?
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.
I think this had to do with how I was doing an assert. This raises a CasMismatchErr in rosmar because it is expecting to be able to do an insert where there was no body. But I added the assertion that is requireDocNotFoundOrCasMismatch
so I've removed this wrapped.
That said, I think in the future wrapping these types of errors would be a good idea, so we don't have to use IsCasMismatch
} | ||
if cas == 0 && len(xattrsToDelete) > 0 { | ||
return 0, sgbucket.ErrDeleteXattrOnDocumentInsert | ||
} |
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.
We don't do this check in other methods that attempt to delete xattrs (like updateXattrs). Is it needed there also?
} | ||
|
||
if writeErr == nil { | ||
return casOut, nil | ||
} | ||
|
||
if IsCasMismatch(writeErr) { | ||
if previousLoopCas != nil && *previousLoopCas == cas { |
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.
What scenario has the potential to cause this loop, or is this strictly defensive?
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 was added defensively and allowed me to check error conditions.
We talked about this offline, and it was a good idea to do in the tests.
base/collection_xattr_common.go
Outdated
} | ||
|
||
if writeErr == nil { | ||
return casOut, nil | ||
} | ||
|
||
if IsCasMismatch(writeErr) { | ||
if previousLoopCas != nil && *previousLoopCas == cas { | ||
err := RedactErrorf("Stopping an infinite loop trying to update doc with xattr for key=%s, xattrKeys=%s", UD(k), UD(xattrKeys)) |
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.
I think this error would be better written as something like "CAS retry triggered, but no change in document CAS detected"
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.
fixed
couchbase/sg-bucket#120
couchbaselabs/rosmar#34
Changed APIs to support xattrs that are being upserted to include xattrs to be deleted. See sg-bucket API.
Update StoreSemantic which can only be Replace if xattrs are being deleted, it has to be Insert if the xattrs are being updated on a tombstone.
Pre-review checklist
fmt.Print
,log.Print
, ...)base.UD(docID)
,base.MD(dbName)
)docs/api
Dependencies (if applicable)
Integration Tests
GSI=true,xattrs=true
https://jenkins.sgwdev.com/job/SyncGateway-Integration/2392/ (CBS 7.6.1), one independent 7.6.1 specific failureGSI=true,xattrs=true
https://jenkins.sgwdev.com/job/SyncGateway-Integration/2393/ (CBS 7.2) (fixed issue on jenkins in followup PR)