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 all 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
108 changes: 108 additions & 0 deletions pkg/document/time/version_vector_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
/*
* Copyright 2025 The Yorkie Authors. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

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.VersionVectorOf(map[*time.ActorID]int64{
actor1: 5,
actor2: 3,
}),
v2: time.NewVersionVector(),
expect: helper.VersionVectorOf(map[*time.ActorID]int64{
actor1: 0,
actor2: 0,
}),
},
{
name: "v2 has values, v1 is empty",
v1: time.NewVersionVector(),
v2: helper.VersionVectorOf(map[*time.ActorID]int64{
actor1: 5,
actor2: 3,
}),
expect: helper.VersionVectorOf(map[*time.ActorID]int64{
actor1: 0,
actor2: 0,
}),
},
{
name: "both vectors have same keys with different values",
v1: helper.VersionVectorOf(map[*time.ActorID]int64{
actor1: 5,
actor2: 3,
}),
v2: helper.VersionVectorOf(map[*time.ActorID]int64{
actor1: 3,
actor2: 4,
}),
expect: helper.VersionVectorOf(map[*time.ActorID]int64{
actor1: 3,
actor2: 3,
}),
},
{
name: "vectors have different keys",
v1: helper.VersionVectorOf(map[*time.ActorID]int64{
actor1: 5,
actor2: 3,
}),
v2: helper.VersionVectorOf(map[*time.ActorID]int64{
actor2: 4,
actor3: 6,
}),
expect: helper.VersionVectorOf(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.VersionVectorOf(map[*time.ActorID]int64{
actor1: 5,
actor2: 3,
}),
},
},
excludeClientID: "",
expect: helper.VersionVectorOf(map[*time.ActorID]int64{
actor1: 5,
actor2: 3,
}),
},
{
name: "exclude client",
vvInfos: []database.VersionVectorInfo{
{
ClientID: "client1",
VersionVector: helper.VersionVectorOf(map[*time.ActorID]int64{
actor1: 5,
actor2: 3,
}),
},
{
ClientID: "client2",
VersionVector: helper.VersionVectorOf(map[*time.ActorID]int64{
actor1: 3,
actor2: 4,
}),
},
},
excludeClientID: "client1",
expect: helper.VersionVectorOf(map[*time.ActorID]int64{
actor1: 3,
actor2: 4,
}),
},
{
name: "exclude all clients",
vvInfos: []database.VersionVectorInfo{
{
ClientID: "client1",
VersionVector: helper.VersionVectorOf(map[*time.ActorID]int64{
actor1: 5,
}),
},
},
excludeClientID: "client1",
expect: nil,
},
{
name: "multiple clients with different actors",
vvInfos: []database.VersionVectorInfo{
{
ClientID: "client1",
VersionVector: helper.VersionVectorOf(map[*time.ActorID]int64{
actor1: 5,
actor2: 3,
}),
},
{
ClientID: "client2",
VersionVector: helper.VersionVectorOf(map[*time.ActorID]int64{
actor2: 2,
actor3: 4,
}),
},
},
excludeClientID: "",
expect: helper.VersionVectorOf(map[*time.ActorID]int64{
actor1: 0,
actor2: 2,
actor3: 0,
}),
},
{
name: "all clients have same actors",
vvInfos: []database.VersionVectorInfo{
{
ClientID: "client1",
VersionVector: helper.VersionVectorOf(map[*time.ActorID]int64{
actor1: 5,
actor2: 3,
}),
},
{
ClientID: "client2",
VersionVector: helper.VersionVectorOf(map[*time.ActorID]int64{
actor1: 3,
actor2: 4,
}),
},
{
ClientID: "client3",
VersionVector: helper.VersionVectorOf(map[*time.ActorID]int64{
actor1: 4,
actor2: 2,
}),
},
},
excludeClientID: "",
expect: helper.VersionVectorOf(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
}

// VersionVectorOf creates a new version vector from the given actors.
func VersionVectorOf(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