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

Rollback MongoDB sharding rules for users collection #770

Merged
merged 1 commit into from
Jan 22, 2024
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
6 changes: 4 additions & 2 deletions api/types/project.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@

package types

import "time"
import (
"time"
)

// Project is a project that consists of multiple documents and clients.
type Project struct {
Expand All @@ -28,7 +30,7 @@ type Project struct {
Name string `json:"name"`

// Owner is the owner of this project.
Owner string `json:"owner"`
Owner ID `json:"owner"`

// AuthWebhookURL is the url of the authorization webhook.
AuthWebhookURL string `json:"auth_webhook_url"`
Expand Down
2 changes: 0 additions & 2 deletions build/docker/sharding/scripts/init-mongos1.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ function shardOfChunk(minKeyOfChunk) {
// Shard the database for the mongo client test
const mongoClientDB = "test-yorkie-meta-mongo-client"
sh.enableSharding(mongoClientDB)
sh.shardCollection(mongoClientDB + ".users", { username: 1 }, true)
sh.shardCollection(mongoClientDB + ".documents", { key: 1 })
sh.shardCollection(mongoClientDB + ".changes", { doc_key: 1 })
sh.shardCollection(mongoClientDB + ".snapshots", { doc_key: 1 })
Expand All @@ -31,7 +30,6 @@ db.adminCommand({ moveChunk: mongoClientDB + ".documents", find: { key: docSplit
// Shard the database for the server test
const serverDB = "test-yorkie-meta-server"
sh.enableSharding(serverDB)
sh.shardCollection(serverDB + ".users", { username: 1 }, true)
sh.shardCollection(serverDB + ".documents", { key: 1 })
sh.shardCollection(serverDB + ".changes", { doc_key: 1 })
sh.shardCollection(serverDB + ".snapshots", { doc_key: 1 })
Expand Down
15 changes: 9 additions & 6 deletions server/backend/database/database.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ type Database interface {
// FindProjectInfoByName returns a project by the given name.
FindProjectInfoByName(
ctx context.Context,
owner string,
owner types.ID,
name string,
) (*ProjectInfo, error)

Expand All @@ -99,17 +99,17 @@ type Database interface {
CreateProjectInfo(
ctx context.Context,
name string,
owner string,
owner types.ID,
clientDeactivateThreshold string,
) (*ProjectInfo, error)

// ListProjectInfos returns all project infos owned by owner.
ListProjectInfos(ctx context.Context, owner string) ([]*ProjectInfo, error)
ListProjectInfos(ctx context.Context, owner types.ID) ([]*ProjectInfo, error)

// UpdateProjectInfo updates the project.
UpdateProjectInfo(
ctx context.Context,
owner string,
owner types.ID,
id types.ID,
fields *types.UpdatableProjectFields,
) (*ProjectInfo, error)
Expand All @@ -121,8 +121,11 @@ type Database interface {
hashedPassword string,
) (*UserInfo, error)

// FindUserInfo returns a user by the given username.
FindUserInfo(ctx context.Context, username string) (*UserInfo, error)
// FindUserInfoByName returns a user by the given ID.
FindUserInfoByID(ctx context.Context, id types.ID) (*UserInfo, error)

// FindUserInfoByName returns a user by the given username.
FindUserInfoByName(ctx context.Context, username string) (*UserInfo, error)

// ListUserInfos returns all users.
ListUserInfos(ctx context.Context) ([]*UserInfo, error)
Expand Down
42 changes: 29 additions & 13 deletions server/backend/database/memory/database.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,13 +97,13 @@ func (d *DB) FindProjectInfoBySecretKey(
// FindProjectInfoByName returns a project by the given name.
func (d *DB) FindProjectInfoByName(
_ context.Context,
owner string,
owner types.ID,
name string,
) (*database.ProjectInfo, error) {
txn := d.db.Txn(false)
defer txn.Abort()

raw, err := txn.First(tblProjects, "owner_name", owner, name)
raw, err := txn.First(tblProjects, "owner_name", owner.String(), name)
if err != nil {
return nil, fmt.Errorf("find project by owner and name: %w", err)
}
Expand Down Expand Up @@ -143,7 +143,7 @@ func (d *DB) EnsureDefaultUserAndProject(
return nil, nil, err
}

project, err := d.ensureDefaultProjectInfo(ctx, username, clientDeactivateThreshold)
project, err := d.ensureDefaultProjectInfo(ctx, user.ID, clientDeactivateThreshold)
if err != nil {
return nil, nil, err
}
Expand Down Expand Up @@ -187,7 +187,7 @@ func (d *DB) ensureDefaultUserInfo(
// ensureDefaultProjectInfo creates the default project if it does not exist.
func (d *DB) ensureDefaultProjectInfo(
_ context.Context,
defaultUsername string,
defaultUserID types.ID,
defaultClientDeactivateThreshold string,
) (*database.ProjectInfo, error) {
txn := d.db.Txn(true)
Expand All @@ -200,7 +200,7 @@ func (d *DB) ensureDefaultProjectInfo(

var info *database.ProjectInfo
if raw == nil {
info = database.NewProjectInfo(database.DefaultProjectName, defaultUsername, defaultClientDeactivateThreshold)
info = database.NewProjectInfo(database.DefaultProjectName, defaultUserID, defaultClientDeactivateThreshold)
info.ID = database.DefaultProjectID
if err := txn.Insert(tblProjects, info); err != nil {
return nil, fmt.Errorf("insert project: %w", err)
Expand All @@ -217,15 +217,15 @@ func (d *DB) ensureDefaultProjectInfo(
func (d *DB) CreateProjectInfo(
_ context.Context,
name string,
owner string,
owner types.ID,
clientDeactivateThreshold string,
) (*database.ProjectInfo, error) {
txn := d.db.Txn(true)
defer txn.Abort()

// NOTE(hackerwins): Check if the project already exists.
// https://github.com/hashicorp/go-memdb/issues/7#issuecomment-270427642
existing, err := txn.First(tblProjects, "owner_name", owner, name)
existing, err := txn.First(tblProjects, "owner_name", owner.String(), name)
if err != nil {
return nil, fmt.Errorf("find project by owner and name: %w", err)
}
Expand Down Expand Up @@ -304,15 +304,15 @@ func (d *DB) FindNextNCyclingProjectInfos(
// ListProjectInfos returns all project infos owned by owner.
func (d *DB) ListProjectInfos(
_ context.Context,
owner string,
owner types.ID,
) ([]*database.ProjectInfo, error) {
txn := d.db.Txn(false)
defer txn.Abort()

iter, err := txn.LowerBound(
tblProjects,
"owner_name",
owner,
owner.String(),
"",
)
if err != nil {
Expand All @@ -335,7 +335,7 @@ func (d *DB) ListProjectInfos(
// UpdateProjectInfo updates the given project.
func (d *DB) UpdateProjectInfo(
_ context.Context,
owner string,
owner types.ID,
id types.ID,
fields *types.UpdatableProjectFields,
) (*database.ProjectInfo, error) {
Expand All @@ -356,7 +356,7 @@ func (d *DB) UpdateProjectInfo(
}

if fields.Name != nil {
existing, err := txn.First(tblProjects, "owner_name", owner, *fields.Name)
existing, err := txn.First(tblProjects, "owner_name", owner.String(), *fields.Name)
if err != nil {
return nil, fmt.Errorf("find project by owner and name: %w", err)
}
Expand Down Expand Up @@ -402,8 +402,24 @@ func (d *DB) CreateUserInfo(
return info, nil
}

// FindUserInfo finds a user by the given username.
func (d *DB) FindUserInfo(_ context.Context, username string) (*database.UserInfo, error) {
// FindUserInfoByID finds a user by the given ID.
func (d *DB) FindUserInfoByID(_ context.Context, clientID types.ID) (*database.UserInfo, error) {
txn := d.db.Txn(false)
defer txn.Abort()

raw, err := txn.First(tblUsers, "id", clientID.String())
if err != nil {
return nil, fmt.Errorf("find user by id: %w", err)
}
if raw == nil {
return nil, fmt.Errorf("%s: %w", clientID, database.ErrUserNotFound)
}

return raw.(*database.UserInfo).DeepCopy(), nil
}

// FindUserInfoByName finds a user by the given username.
func (d *DB) FindUserInfoByName(_ context.Context, username string) (*database.UserInfo, error) {
txn := d.db.Txn(false)
defer txn.Abort()

Expand Down
8 changes: 6 additions & 2 deletions server/backend/database/memory/database_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,12 @@ func TestDB(t *testing.T) {
testcases.RunListUserInfosTest(t, db)
})

t.Run("FindUserInfo test", func(t *testing.T) {
testcases.RunFindUserInfoTest(t, db)
t.Run("FindUserInfoByID test", func(t *testing.T) {
testcases.RunFindUserInfoByIDTest(t, db)
})

t.Run("FindUserInfoByName test", func(t *testing.T) {
testcases.RunFindUserInfoByNameTest(t, db)
})

t.Run("FindProjectInfoBySecretKey test", func(t *testing.T) {
Expand Down
35 changes: 26 additions & 9 deletions server/backend/database/mongo/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ func (c *Client) EnsureDefaultUserAndProject(
return nil, nil, err
}

projectInfo, err := c.ensureDefaultProjectInfo(ctx, userInfo.Username, clientDeactivateThreshold)
projectInfo, err := c.ensureDefaultProjectInfo(ctx, userInfo.ID, clientDeactivateThreshold)
if err != nil {
return nil, nil, err
}
Expand Down Expand Up @@ -162,10 +162,10 @@ func (c *Client) ensureDefaultUserInfo(
// ensureDefaultProjectInfo creates the default project info if it does not exist.
func (c *Client) ensureDefaultProjectInfo(
ctx context.Context,
defaultUsername string,
defaultUserID types.ID,
defaultClientDeactivateThreshold string,
) (*database.ProjectInfo, error) {
candidate := database.NewProjectInfo(database.DefaultProjectName, defaultUsername, defaultClientDeactivateThreshold)
candidate := database.NewProjectInfo(database.DefaultProjectName, defaultUserID, defaultClientDeactivateThreshold)
candidate.ID = database.DefaultProjectID

_, err := c.collection(ColProjects).UpdateOne(ctx, bson.M{
Expand Down Expand Up @@ -203,7 +203,7 @@ func (c *Client) ensureDefaultProjectInfo(
func (c *Client) CreateProjectInfo(
ctx context.Context,
name string,
owner string,
owner types.ID,
clientDeactivateThreshold string,
) (*database.ProjectInfo, error) {
info := database.NewProjectInfo(name, owner, clientDeactivateThreshold)
Expand Down Expand Up @@ -275,7 +275,7 @@ func (c *Client) FindNextNCyclingProjectInfos(
// ListProjectInfos returns all project infos owned by owner.
func (c *Client) ListProjectInfos(
ctx context.Context,
owner string,
owner types.ID,
) ([]*database.ProjectInfo, error) {
cursor, err := c.collection(ColProjects).Find(ctx, bson.M{
"owner": owner,
Expand Down Expand Up @@ -329,7 +329,7 @@ func (c *Client) FindProjectInfoBySecretKey(ctx context.Context, secretKey strin
// FindProjectInfoByName returns a project by name.
func (c *Client) FindProjectInfoByName(
ctx context.Context,
owner string,
owner types.ID,
name string,
) (*database.ProjectInfo, error) {
result := c.collection(ColProjects).FindOne(ctx, bson.M{
Expand Down Expand Up @@ -368,7 +368,7 @@ func (c *Client) FindProjectInfoByID(ctx context.Context, id types.ID) (*databas
// UpdateProjectInfo updates the project info.
func (c *Client) UpdateProjectInfo(
ctx context.Context,
owner string,
owner types.ID,
id types.ID,
fields *types.UpdatableProjectFields,
) (*database.ProjectInfo, error) {
Expand Down Expand Up @@ -428,8 +428,25 @@ func (c *Client) CreateUserInfo(
return info, nil
}

// FindUserInfo returns a user by username.
func (c *Client) FindUserInfo(ctx context.Context, username string) (*database.UserInfo, error) {
// FindUserInfoByID returns a user by ID.
func (c *Client) FindUserInfoByID(ctx context.Context, clientID types.ID) (*database.UserInfo, error) {
result := c.collection(ColUsers).FindOne(ctx, bson.M{
"_id": clientID,
})

userInfo := database.UserInfo{}
if err := result.Decode(&userInfo); err != nil {
if err == mongo.ErrNoDocuments {
return nil, fmt.Errorf("%s: %w", clientID, database.ErrUserNotFound)
}
return nil, fmt.Errorf("decode user info: %w", err)
}

return &userInfo, nil
}

// FindUserInfoByName returns a user by username.
func (c *Client) FindUserInfoByName(ctx context.Context, username string) (*database.UserInfo, error) {
result := c.collection(ColUsers).FindOne(ctx, bson.M{
"username": username,
})
Expand Down
8 changes: 6 additions & 2 deletions server/backend/database/mongo/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,8 +81,12 @@ func TestClient(t *testing.T) {
testcases.RunListUserInfosTest(t, cli)
})

t.Run("FindUserInfo test", func(t *testing.T) {
testcases.RunFindUserInfoTest(t, cli)
t.Run("FindUserInfoByID test", func(t *testing.T) {
testcases.RunFindUserInfoByIDTest(t, cli)
})

t.Run("FindUserInfoByName test", func(t *testing.T) {
testcases.RunFindUserInfoByNameTest(t, cli)
})

t.Run("FindProjectInfoBySecretKey test", func(t *testing.T) {
Expand Down
4 changes: 2 additions & 2 deletions server/backend/database/project_info.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ type ProjectInfo struct {
Name string `bson:"name"`

// Owner is the owner of this project.
Owner string `bson:"owner"`
Owner types.ID `bson:"owner"`

// PublicKey is the API key of this project.
PublicKey string `bson:"public_key"`
Expand All @@ -69,7 +69,7 @@ type ProjectInfo struct {
}

// NewProjectInfo creates a new ProjectInfo of the given name.
func NewProjectInfo(name string, owner string, clientDeactivateThreshold string) *ProjectInfo {
func NewProjectInfo(name string, owner types.ID, clientDeactivateThreshold string) *ProjectInfo {
return &ProjectInfo{
Name: name,
Owner: owner,
Expand Down
6 changes: 3 additions & 3 deletions server/backend/database/project_info_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,9 @@ import (

func TestProjectInfo(t *testing.T) {
t.Run("update fields test", func(t *testing.T) {
dummyOwnerName := "dummy"
dummyOwnerID := types.ID("000000000000000000000000")
clientDeactivateThreshold := "1h"
project := database.NewProjectInfo(t.Name(), dummyOwnerName, clientDeactivateThreshold)
project := database.NewProjectInfo(t.Name(), dummyOwnerID, clientDeactivateThreshold)

testName := "testName"
testURL := "testUrl"
Expand All @@ -44,7 +44,7 @@ func TestProjectInfo(t *testing.T) {

project.UpdateFields(&types.UpdatableProjectFields{AuthWebhookMethods: &testMethods})
assert.Equal(t, testMethods, project.AuthWebhookMethods)
assert.Equal(t, dummyOwnerName, project.Owner)
assert.Equal(t, dummyOwnerID, project.Owner)

project.UpdateFields(&types.UpdatableProjectFields{
ClientDeactivateThreshold: &testClientDeactivateThreshold,
Expand Down
Loading