Skip to content

CBG-4778: LWW conflict resolution for ISGR#7696

Merged
gregns1 merged 8 commits into
mainfrom
CBG-4778
Aug 29, 2025
Merged

CBG-4778: LWW conflict resolution for ISGR#7696
gregns1 merged 8 commits into
mainfrom
CBG-4778

Conversation

@gregns1
Copy link
Copy Markdown
Contributor

@gregns1 gregns1 commented Aug 21, 2025

CBG-4778

  • Adds HLV conflict resolution to active replicator. We need two defined given when we build active replicator we don't know what sub-protocol we're running in but also we need reference to pre 4.0 conflict resolution for pre upgraded docs.
  • Adds lower level test around local wins and remote wins
  • Adds higher level tests for conflict resolution around local wins and remote wins cases
  • Needed to have the ability to mock the HLV on a doc to look a certain way for test cases around MV so added test helper AlterHLVForTest
  • PR will not reconcile rev tree's at this time, later ticket for this
  • PR takes into account work for CBG-4789

Pre-review checklist

  • Removed debug logging (fmt.Print, log.Print, ...)
  • Logging sensitive data? Make sure it's tagged (e.g. base.UD(docID), base.MD(dbName))
  • Updated relevant information in the API specifications (such as endpoint descriptions, schemas, ...) in docs/api

Dependencies (if applicable)

  • Link upstream PRs
  • Update Go module dependencies when merged

Integration Tests

Copilot AI review requested due to automatic review settings August 21, 2025 17:19
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR adds HLV (Hybrid Logical Vector) conflict resolution support for inter-sync gateway replication (ISGR). It implements both local wins and remote wins conflict resolution strategies for version vector-based conflicts, along with comprehensive test coverage for various conflict scenarios including merge version handling and attachment preservation.

  • Adds HLV conflict resolution functions and infrastructure to support vector-based conflict resolution
  • Implements comprehensive test suite covering local wins, remote wins, merge version scenarios, and attachment handling
  • Updates existing tests to support both legacy and HLV conflict resolution modes

Reviewed Changes

Copilot reviewed 14 out of 14 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
rest/replicatortest/replicator_test.go Removes skips from tombstone tests and adds protocol version configuration
rest/replicatortest/replicator_conflict_test.go Adds comprehensive HLV conflict resolution test suite with 1100+ lines of tests
rest/blip_api_crud_test.go Removes skip from stat test
db/utilities_hlv_testing.go Adds test helper function to alter document HLV for testing scenarios
db/util_testing.go Updates function call to include conflict resolver parameter
db/sg_replicate_conflict_resolver.go Adds HLV-aware conflict resolution functions and ResolveForHLV method
db/sg_replicate_cfg.go Updates replicator config to support both legacy and HLV conflict resolvers
db/import_test.go Updates function call to include conflict resolver parameter
db/hybrid_logical_vector_test.go Adds unit tests for HLV conflict resolution algorithms
db/hybrid_logical_vector.go Implements core HLV conflict resolution logic
db/crud_test.go Updates function calls to include conflict resolver parameter
db/crud.go Integrates HLV conflict resolution into document update flow
db/blip_handler.go Updates BLIP handler to pass conflict resolver to HLV operations
db/active_replicator_config.go Adds HLV conflict resolver function configuration

Comment thread rest/replicatortest/replicator_conflict_test.go Outdated
Comment thread db/sg_replicate_conflict_resolver.go
Comment thread db/crud.go Outdated

// Local doc (localDoc) is persisted in the bucket unlike the incoming remote doc (remoteDoc).
// Internal properties of the localDoc can be accessed from syc metadata.
// Internal properties of the localDoc can be accessed from sync metadata.
Copy link

Copilot AI Aug 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo in comment: "syc" should be "sync"

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is fixed?

@torcolvin
Copy link
Copy Markdown
Collaborator

I plan to review this more, but I've created some helper functions #7704 to deal with updating HLV. These duplicate/obviate some of the code here. I needed to create these standalone functions to write unit tests that couchbase lite can use to ensure matching behavior.

Those functions will be used for blip client testing and for ISGR.

Comment thread db/crud.go Outdated
Comment thread db/crud.go
}

// resolveLocalWinsHLV will update remote doc's body and attachments to match the local doc, and return a new HLV for local wins
func (db *DatabaseCollectionWithUser) resolveLocalWinsHLV(ctx context.Context, localDoc, remoteDoc *Document) (*HybridLogicalVector, error) {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There seem to be a few differences between resolveLocalWinsHLV and resolveDocLocalWins, some of which seem right, and some of which seem not right.

  1. There's not a new revtree id that is created in this function. You would need a local revtree id to support 3.x CBL replication even after 4.x ISGR replication.
  2. There's handling of branching tombstones. I'm not sure there will ever be branching since the local revtree is already overwritten by remote revtree by the time we are here? but I'm actually not sure.
  3. tombstoneActiveRevision means that the old revision is added to the doc.History for revtree ids.

Copy link
Copy Markdown
Contributor Author

@gregns1 gregns1 Aug 26, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. New RevID is generated after this function is done inside PutExistsingCurrentVersion
    2 & 3. There is work after this to reconcile revTree's on conflict resolution, the idea here is work for this will take place there

Comment thread db/crud.go Outdated
Comment thread db/hybrid_logical_vector.go
Comment thread db/hybrid_logical_vector.go Outdated
Comment thread db/sg_replicate_cfg.go Outdated
Comment on lines +59 to +60
LocalHLV *HybridLogicalVector
RemoteHLV *HybridLogicalVector
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are there going to be cases where only one HLV is present?

Copy link
Copy Markdown
Contributor Author

@gregns1 gregns1 Aug 26, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only both HLV's will be here, if one is missing it may be given an implicit HLV or we fall back to rev tree conflict handling.

Comment thread db/sg_replicate_conflict_resolver.go Outdated
Comment on lines +152 to +156
winningRev, ok := winner[BodyCV]
if !ok {
c.stats.ConflictResultMergeCount.Add(1)
return winner, ConflictResolutionMerge, nil
}
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What are the cases where this gets hit vs the end of this function

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The aim here was to keep in sync with current existing conflict resolution functions.

Comment thread db/sg_replicate_conflict_resolver.go Outdated
@gregns1 gregns1 assigned torcolvin and unassigned gregns1 Aug 28, 2025
Copy link
Copy Markdown
Collaborator

@torcolvin torcolvin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, I think we can sort out the other stuff in a future PR.

Comment thread db/crud.go
Comment on lines +1376 to +1377
var newHLV *HybridLogicalVector
newHLV, err = db.resolveHLVConflict(ctx, doc, newDoc, conflictResolver.hlvConflictResolver)
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit

Suggested change
var newHLV *HybridLogicalVector
newHLV, err = db.resolveHLVConflict(ctx, doc, newDoc, conflictResolver.hlvConflictResolver)
newHLV, err := db.resolveHLVConflict(ctx, doc, newDoc, conflictResolver.hlvConflictResolver)

Comment thread db/crud.go
Comment on lines +1981 to +1983
remoteDoc.RemoveBody()
remoteDoc.Deleted = localDoc.IsDeleted()
remoteDoc.SetAttachments(localDoc.Attachments().ShallowCopy())
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is fine for now, but is it possible that resolving an HLV conflict will also mean that affects a revtree as well?

I suspect it will need to call

sync_gateway/db/crud.go

Lines 1824 to 1862 in 02429a5

// Note: not setting expiry, as syncData.expiry is reference only and isn't guaranteed to match the bucket doc expiry
remoteDoc.RemoveBody()
remoteDoc.Deleted = localDoc.IsDeleted()
remoteDoc.SetAttachments(localDoc.Attachments().ShallowCopy())
// If the local doc had attachments, any with revpos more recent than the common ancestor will need
// to have their revpos updated when we rewrite the rev as a child of the remote branch.
if remoteDoc.Attachments() != nil {
// Identify generation of common ancestor and new rev
commonAncestorRevID := localDoc.SyncData.History.findAncestorFromSet(localDoc.GetRevTreeID(), docHistory)
commonAncestorGen := 0
if commonAncestorRevID != "" {
commonAncestorGen, _ = ParseRevID(ctx, commonAncestorRevID)
}
newRevIDGen, _ := ParseRevID(ctx, newRevID)
// If attachment revpos is older than common ancestor, or common ancestor doesn't exist, set attachment's
// revpos to the generation of newRevID (i.e. treat as previously unknown to this revtree branch)
for _, value := range remoteDoc.Attachments() {
attachmentMeta, ok := value.(map[string]interface{})
if !ok {
base.WarnfCtx(ctx, "Unable to parse attachment meta during conflict resolution for %s/%s: %v", base.UD(localDoc.ID), localDoc.SyncData.GetRevTreeID(), value)
continue
}
revpos, _ := base.ToInt64(attachmentMeta["revpos"])
if revpos > int64(commonAncestorGen) || commonAncestorGen == 0 {
attachmentMeta["revpos"] = newRevIDGen
}
}
}
remoteDoc._rawBody = docBodyBytes
// Tombstone the local revision
localRevID := localDoc.GetRevTreeID()
tombstoneRevID, tombstoneErr := db.tombstoneActiveRevision(ctx, localDoc, localRevID)
if tombstoneErr != nil {
return "", nil, tombstoneErr
}
as well.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will be taken care of in CBG-4791

@gregns1 gregns1 merged commit 4812825 into main Aug 29, 2025
58 of 59 checks passed
@gregns1 gregns1 deleted the CBG-4778 branch August 29, 2025 12:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants