Skip to content
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

Optimize Minimum VersionVector Computation for Performance #1153

Merged
merged 4 commits into from
Feb 17, 2025
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 92 additions & 0 deletions pkg/document/time/version_vector_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
package time_test

import (
"testing"

"github.com/stretchr/testify/assert"

"github.com/yorkie-team/yorkie/pkg/document/time"
"github.com/yorkie-team/yorkie/test/helper"
)

func TestVersionVector(t *testing.T) {
actor1, _ := time.ActorIDFromHex("000000000000000000000001")
actor2, _ := time.ActorIDFromHex("000000000000000000000002")
actor3, _ := time.ActorIDFromHex("000000000000000000000003")

tests := []struct {
name string
v1 time.VersionVector
v2 time.VersionVector
expect time.VersionVector
}{
{
name: "empty vectors",
v1: time.NewVersionVector(),
v2: time.NewVersionVector(),
expect: time.NewVersionVector(),
},
{
name: "v1 has values, v2 is empty",
v1: helper.NewVersionVectorFromActors(map[*time.ActorID]int64{
actor1: 5,
actor2: 3,
}),
v2: time.NewVersionVector(),
expect: helper.NewVersionVectorFromActors(map[*time.ActorID]int64{
actor1: 0,
actor2: 0,
}),
},
{
name: "v2 has values, v1 is empty",
v1: time.NewVersionVector(),
v2: helper.NewVersionVectorFromActors(map[*time.ActorID]int64{
actor1: 5,
actor2: 3,
}),
expect: helper.NewVersionVectorFromActors(map[*time.ActorID]int64{
actor1: 0,
actor2: 0,
}),
},
{
name: "both vectors have same keys with different values",
v1: helper.NewVersionVectorFromActors(map[*time.ActorID]int64{
actor1: 5,
actor2: 3,
}),
v2: helper.NewVersionVectorFromActors(map[*time.ActorID]int64{
actor1: 3,
actor2: 4,
}),
expect: helper.NewVersionVectorFromActors(map[*time.ActorID]int64{
actor1: 3,
actor2: 3,
}),
},
{
name: "vectors have different keys",
v1: helper.NewVersionVectorFromActors(map[*time.ActorID]int64{
actor1: 5,
actor2: 3,
}),
v2: helper.NewVersionVectorFromActors(map[*time.ActorID]int64{
actor2: 4,
actor3: 6,
}),
expect: helper.NewVersionVectorFromActors(map[*time.ActorID]int64{
actor1: 0,
actor2: 3,
actor3: 0,
}),
},
}

for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
result := tc.v1.Min(tc.v2)
assert.Equal(t, tc.expect, result)
})
}
}
140 changes: 140 additions & 0 deletions server/backend/database/database_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ import (
"github.com/stretchr/testify/assert"

"github.com/yorkie-team/yorkie/api/types"
"github.com/yorkie-team/yorkie/pkg/document/time"
"github.com/yorkie-team/yorkie/server/backend/database"
"github.com/yorkie-team/yorkie/test/helper"
)

func TestID(t *testing.T) {
Expand All @@ -39,3 +42,140 @@ func TestID(t *testing.T) {
assert.Equal(t, bytes, bytesID)
})
}

func TestFindMinVersionVector(t *testing.T) {
actor1, _ := time.ActorIDFromHex("000000000000000000000001")
actor2, _ := time.ActorIDFromHex("000000000000000000000002")
actor3, _ := time.ActorIDFromHex("000000000000000000000003")

tests := []struct {
name string
vvInfos []database.VersionVectorInfo
excludeClientID types.ID
expect time.VersionVector
}{
{
name: "empty version vector infos",
vvInfos: []database.VersionVectorInfo{},
excludeClientID: "",
expect: nil,
},
{
name: "single version vector info",
vvInfos: []database.VersionVectorInfo{
{
ClientID: "client1",
VersionVector: helper.NewVersionVectorFromActors(map[*time.ActorID]int64{
actor1: 5,
actor2: 3,
}),
},
},
excludeClientID: "",
expect: helper.NewVersionVectorFromActors(map[*time.ActorID]int64{
actor1: 5,
actor2: 3,
}),
},
{
name: "exclude client",
vvInfos: []database.VersionVectorInfo{
{
ClientID: "client1",
VersionVector: helper.NewVersionVectorFromActors(map[*time.ActorID]int64{
actor1: 5,
actor2: 3,
}),
},
{
ClientID: "client2",
VersionVector: helper.NewVersionVectorFromActors(map[*time.ActorID]int64{
actor1: 3,
actor2: 4,
}),
},
},
excludeClientID: "client1",
expect: helper.NewVersionVectorFromActors(map[*time.ActorID]int64{
actor1: 3,
actor2: 4,
}),
},
{
name: "exclude all clients",
vvInfos: []database.VersionVectorInfo{
{
ClientID: "client1",
VersionVector: helper.NewVersionVectorFromActors(map[*time.ActorID]int64{
actor1: 5,
}),
},
},
excludeClientID: "client1",
expect: nil,
},
{
name: "multiple clients with different actors",
vvInfos: []database.VersionVectorInfo{
{
ClientID: "client1",
VersionVector: helper.NewVersionVectorFromActors(map[*time.ActorID]int64{
actor1: 5,
actor2: 3,
}),
},
{
ClientID: "client2",
VersionVector: helper.NewVersionVectorFromActors(map[*time.ActorID]int64{
actor2: 2,
actor3: 4,
}),
},
},
excludeClientID: "",
expect: helper.NewVersionVectorFromActors(map[*time.ActorID]int64{
actor1: 0,
actor2: 2,
actor3: 0,
}),
},
{
name: "all clients have same actors",
vvInfos: []database.VersionVectorInfo{
{
ClientID: "client1",
VersionVector: helper.NewVersionVectorFromActors(map[*time.ActorID]int64{
actor1: 5,
actor2: 3,
}),
},
{
ClientID: "client2",
VersionVector: helper.NewVersionVectorFromActors(map[*time.ActorID]int64{
actor1: 3,
actor2: 4,
}),
},
{
ClientID: "client3",
VersionVector: helper.NewVersionVectorFromActors(map[*time.ActorID]int64{
actor1: 4,
actor2: 2,
}),
},
},
excludeClientID: "",
expect: helper.NewVersionVectorFromActors(map[*time.ActorID]int64{
actor1: 3,
actor2: 2,
}),
},
}

for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
result := database.FindMinVersionVector(tc.vvInfos, tc.excludeClientID)
assert.Equal(t, tc.expect, result)
})
}
}
25 changes: 8 additions & 17 deletions server/backend/database/memory/database.go
Original file line number Diff line number Diff line change
Expand Up @@ -1360,30 +1360,21 @@ func (d *DB) UpdateAndFindMinSyncedVersionVector(
return nil, fmt.Errorf("find all version vectors: %w", err)
}

// 02. Compute min version vector.
var minVersionVector time.VersionVector

// 02-1. Compute min version vector of other clients and collect attachedActorIDs.
var versionVectorInfos []database.VersionVectorInfo
for raw := iterator.Next(); raw != nil; raw = iterator.Next() {
vvi := raw.(*database.VersionVectorInfo)
if clientInfo.ID == vvi.ClientID {
continue
}

if minVersionVector == nil {
minVersionVector = vvi.VersionVector
continue
}

minVersionVector = minVersionVector.Min(vvi.VersionVector)
versionVectorInfos = append(versionVectorInfos, *vvi)
}

// 02-1. Compute min version vector of other clients.
minVersionVector := database.FindMinVersionVector(versionVectorInfos, clientInfo.ID)
// 02-2. Compute min version vector with current client's version vector.
if minVersionVector == nil {
minVersionVector = versionVector
} else {
minVersionVector = minVersionVector.Min(versionVector)
}

// 02-2. Compute min version vector with current client's version vector.
minVersionVector = minVersionVector.Min(versionVector)

// 03. Update current client's version vector. If the client is detached, remove it.
// This is only for the current client and does not affect the version vector of other clients.
if err = d.UpdateVersionVector(ctx, clientInfo, docRefKey, versionVector); err != nil {
Expand Down
24 changes: 5 additions & 19 deletions server/backend/database/mongo/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -1258,29 +1258,15 @@ func (c *Client) UpdateAndFindMinSyncedVersionVector(
return nil, fmt.Errorf("decode version vectors: %w", err)
}

// 02. Compute min version vector.
var minVersionVector time.VersionVector

// 02-1. Compute min version vector of other clients and collect attachedActorIDs.
for _, vvi := range versionVectorInfos {
if clientInfo.ID == vvi.ClientID {
continue
}

if minVersionVector == nil {
minVersionVector = vvi.VersionVector
continue
}

minVersionVector = minVersionVector.Min(vvi.VersionVector)
}
// 02-1. Compute min version vector of other clients.
minVersionVector := database.FindMinVersionVector(versionVectorInfos, clientInfo.ID)
// 02-2. Compute min version vector with current client's version vector.
if minVersionVector == nil {
minVersionVector = versionVector
} else {
minVersionVector = minVersionVector.Min(versionVector)
}

// 02-2. Compute min version vector with current client's version vector.
minVersionVector = minVersionVector.Min(versionVector)

// 03. Update current client's version vector. If the client is detached, remove it.
// This is only for the current client and does not affect the version vector of other clients.
if err = c.UpdateVersionVector(ctx, clientInfo, docRefKey, versionVector); err != nil {
Expand Down
33 changes: 33 additions & 0 deletions server/backend/database/version_vector.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,36 @@ type VersionVectorInfo struct {
ClientID types.ID `bson:"client_id"`
VersionVector time.VersionVector `bson:"version_vector"`
}

// FindMinVersionVector finds the minimum version vector from the given version vector infos.
// It excludes the version vector of the given client ID if specified.
func FindMinVersionVector(vvInfos []VersionVectorInfo, excludeClientID types.ID) time.VersionVector {
var minVV time.VersionVector

for _, vvi := range vvInfos {
if vvi.ClientID == excludeClientID {
continue
}

if minVV == nil {
minVV = vvi.VersionVector.DeepCopy()
continue
}

for actorID, lamport := range vvi.VersionVector {
if currentLamport, exists := minVV[actorID]; !exists {
minVV[actorID] = 0
} else if lamport < currentLamport {
minVV[actorID] = lamport
}
}

for actorID := range minVV {
if _, exists := vvi.VersionVector[actorID]; !exists {
minVV[actorID] = 0
}
}
}

return minVV
}
9 changes: 9 additions & 0 deletions test/helper/helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,15 @@ func MaxVersionVector(actors ...*time.ActorID) time.VersionVector {
return vector
}

// NewVersionVectorFromActors creates a new VersionVector from a map of ActorIDs and their lamport timestamps.
func NewVersionVectorFromActors(actors map[*time.ActorID]int64) time.VersionVector {
vector := time.NewVersionVector()
for actor, lamport := range actors {
vector.Set(actor, lamport)
}
return vector
}

// TokensEqualBetween is a helper function that checks the tokens between the given
// indexes.
func TokensEqualBetween(t assert.TestingT, tree *index.Tree[*crdt.TreeNode], from, to int, expected []string) bool {
Expand Down
Loading