summaryrefslogtreecommitdiff
path: root/internal/db
diff options
context:
space:
mode:
authorLibravatar tobi <31960611+tsmethurst@users.noreply.github.com>2022-10-03 10:46:11 +0200
committerLibravatar GitHub <noreply@github.com>2022-10-03 10:46:11 +0200
commit56f53a2a6f85876485e2ae67d48b78b448caed6e (patch)
tree9bd8d3fcaffd515d3dc90ff22c6cee17e8d0b073 /internal/db
parent[feature] Enlarge active/hovered custom emojis in statuses (#877) (diff)
downloadgotosocial-56f53a2a6f85876485e2ae67d48b78b448caed6e.tar.xz
[performance] add user cache and database (#879)
* go fmt * add + use user cache and database * fix import * update tests * remove unused relation
Diffstat (limited to 'internal/db')
-rw-r--r--internal/db/bundb/admin.go5
-rw-r--r--internal/db/bundb/bundb.go13
-rw-r--r--internal/db/bundb/user.go151
-rw-r--r--internal/db/bundb/user_test.go73
-rw-r--r--internal/db/db.go1
-rw-r--r--internal/db/user.go42
6 files changed, 281 insertions, 4 deletions
diff --git a/internal/db/bundb/admin.go b/internal/db/bundb/admin.go
index f66ed0294..9fa78eca0 100644
--- a/internal/db/bundb/admin.go
+++ b/internal/db/bundb/admin.go
@@ -30,6 +30,7 @@ import (
"time"
"github.com/superseriousbusiness/gotosocial/internal/ap"
+ "github.com/superseriousbusiness/gotosocial/internal/cache"
"github.com/superseriousbusiness/gotosocial/internal/config"
"github.com/superseriousbusiness/gotosocial/internal/db"
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
@@ -40,7 +41,8 @@ import (
)
type adminDB struct {
- conn *DBConn
+ conn *DBConn
+ userCache *cache.UserCache
}
func (a *adminDB) IsUsernameAvailable(ctx context.Context, username string) (bool, db.Error) {
@@ -175,6 +177,7 @@ func (a *adminDB) NewSignup(ctx context.Context, username string, reason string,
Exec(ctx); err != nil {
return nil, a.conn.ProcessError(err)
}
+ a.userCache.Put(u)
return u, nil
}
diff --git a/internal/db/bundb/bundb.go b/internal/db/bundb/bundb.go
index 1579fae76..70a44d4c1 100644
--- a/internal/db/bundb/bundb.go
+++ b/internal/db/bundb/bundb.go
@@ -87,6 +87,7 @@ type DBService struct {
db.Session
db.Status
db.Timeline
+ db.User
conn *DBConn
}
@@ -181,13 +182,15 @@ func NewBunDBService(ctx context.Context) (db.DB, error) {
notifCache.SetTTL(time.Minute*5, false)
notifCache.Start(time.Second * 10)
- // Prepare domain block cache
+ // Prepare other caches
blockCache := cache.NewDomainBlockCache()
+ userCache := cache.NewUserCache()
ps := &DBService{
Account: accounts,
Admin: &adminDB{
- conn: conn,
+ conn: conn,
+ userCache: userCache,
},
Basic: &basicDB{
conn: conn,
@@ -219,7 +222,11 @@ func NewBunDBService(ctx context.Context) (db.DB, error) {
},
Status: status,
Timeline: timeline,
- conn: conn,
+ User: &userDB{
+ conn: conn,
+ cache: userCache,
+ },
+ conn: conn,
}
// we can confidently return this useable service now
diff --git a/internal/db/bundb/user.go b/internal/db/bundb/user.go
new file mode 100644
index 000000000..46f24c4b2
--- /dev/null
+++ b/internal/db/bundb/user.go
@@ -0,0 +1,151 @@
+/*
+ GoToSocial
+ Copyright (C) 2021-2022 GoToSocial Authors admin@gotosocial.org
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+package bundb
+
+import (
+ "context"
+ "time"
+
+ "github.com/superseriousbusiness/gotosocial/internal/cache"
+ "github.com/superseriousbusiness/gotosocial/internal/db"
+ "github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
+ "github.com/uptrace/bun"
+)
+
+type userDB struct {
+ conn *DBConn
+ cache *cache.UserCache
+}
+
+func (u *userDB) newUserQ(user *gtsmodel.User) *bun.SelectQuery {
+ return u.conn.
+ NewSelect().
+ Model(user).
+ Relation("Account")
+}
+
+func (u *userDB) getUser(ctx context.Context, cacheGet func() (*gtsmodel.User, bool), dbQuery func(*gtsmodel.User) error) (*gtsmodel.User, db.Error) {
+ // Attempt to fetch cached user
+ user, cached := cacheGet()
+
+ if !cached {
+ user = &gtsmodel.User{}
+
+ // Not cached! Perform database query
+ err := dbQuery(user)
+ if err != nil {
+ return nil, u.conn.ProcessError(err)
+ }
+
+ // Place in the cache
+ u.cache.Put(user)
+ }
+
+ return user, nil
+}
+
+func (u *userDB) GetUserByID(ctx context.Context, id string) (*gtsmodel.User, db.Error) {
+ return u.getUser(
+ ctx,
+ func() (*gtsmodel.User, bool) {
+ return u.cache.GetByID(id)
+ },
+ func(user *gtsmodel.User) error {
+ return u.newUserQ(user).Where("user.id = ?", id).Scan(ctx)
+ },
+ )
+}
+
+func (u *userDB) GetUserByAccountID(ctx context.Context, accountID string) (*gtsmodel.User, db.Error) {
+ return u.getUser(
+ ctx,
+ func() (*gtsmodel.User, bool) {
+ return u.cache.GetByAccountID(accountID)
+ },
+ func(user *gtsmodel.User) error {
+ return u.newUserQ(user).Where("user.account_id = ?", accountID).Scan(ctx)
+ },
+ )
+}
+
+func (u *userDB) GetUserByEmailAddress(ctx context.Context, emailAddress string) (*gtsmodel.User, db.Error) {
+ return u.getUser(
+ ctx,
+ func() (*gtsmodel.User, bool) {
+ return u.cache.GetByEmail(emailAddress)
+ },
+ func(user *gtsmodel.User) error {
+ return u.newUserQ(user).Where("user.email = ?", emailAddress).Scan(ctx)
+ },
+ )
+}
+
+func (u *userDB) GetUserByConfirmationToken(ctx context.Context, confirmationToken string) (*gtsmodel.User, db.Error) {
+ return u.getUser(
+ ctx,
+ func() (*gtsmodel.User, bool) {
+ return u.cache.GetByConfirmationToken(confirmationToken)
+ },
+ func(user *gtsmodel.User) error {
+ return u.newUserQ(user).Where("user.confirmation_token = ?", confirmationToken).Scan(ctx)
+ },
+ )
+}
+
+func (u *userDB) PutUser(ctx context.Context, user *gtsmodel.User) (*gtsmodel.User, db.Error) {
+ if _, err := u.conn.
+ NewInsert().
+ Model(user).
+ Exec(ctx); err != nil {
+ return nil, u.conn.ProcessError(err)
+ }
+
+ u.cache.Put(user)
+ return user, nil
+}
+
+func (u *userDB) UpdateUser(ctx context.Context, user *gtsmodel.User, columns ...string) (*gtsmodel.User, db.Error) {
+ // Update the user's last-updated
+ user.UpdatedAt = time.Now()
+
+ if _, err := u.conn.
+ NewUpdate().
+ Model(user).
+ WherePK().
+ Column(columns...).
+ Exec(ctx); err != nil {
+ return nil, u.conn.ProcessError(err)
+ }
+
+ u.cache.Invalidate(user.ID)
+ return user, nil
+}
+
+func (u *userDB) DeleteUserByID(ctx context.Context, userID string) db.Error {
+ if _, err := u.conn.
+ NewDelete().
+ Model(&gtsmodel.User{ID: userID}).
+ WherePK().
+ Exec(ctx); err != nil {
+ return u.conn.ProcessError(err)
+ }
+
+ u.cache.Invalidate(userID)
+ return nil
+}
diff --git a/internal/db/bundb/user_test.go b/internal/db/bundb/user_test.go
new file mode 100644
index 000000000..6ad59fc8e
--- /dev/null
+++ b/internal/db/bundb/user_test.go
@@ -0,0 +1,73 @@
+/*
+ GoToSocial
+ Copyright (C) 2021-2022 GoToSocial Authors admin@gotosocial.org
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+package bundb_test
+
+import (
+ "context"
+ "testing"
+
+ "github.com/stretchr/testify/suite"
+ "github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
+)
+
+type UserTestSuite struct {
+ BunDBStandardTestSuite
+}
+
+func (suite *UserTestSuite) TestGetUser() {
+ user, err := suite.db.GetUserByID(context.Background(), suite.testUsers["local_account_1"].ID)
+ suite.NoError(err)
+ suite.NotNil(user)
+}
+
+func (suite *UserTestSuite) TestGetUserByEmailAddress() {
+ user, err := suite.db.GetUserByEmailAddress(context.Background(), suite.testUsers["local_account_1"].Email)
+ suite.NoError(err)
+ suite.NotNil(user)
+}
+
+func (suite *UserTestSuite) TestGetUserByAccountID() {
+ user, err := suite.db.GetUserByAccountID(context.Background(), suite.testAccounts["local_account_1"].ID)
+ suite.NoError(err)
+ suite.NotNil(user)
+}
+
+func (suite *UserTestSuite) TestUpdateUserSelectedColumns() {
+ testUser := suite.testUsers["local_account_1"]
+ user := &gtsmodel.User{
+ ID: testUser.ID,
+ Email: "whatever",
+ Locale: "es",
+ }
+
+ user, err := suite.db.UpdateUser(context.Background(), user, "email", "locale")
+ suite.NoError(err)
+ suite.NotNil(user)
+
+ dbUser, err := suite.db.GetUserByID(context.Background(), testUser.ID)
+ suite.NoError(err)
+ suite.NotNil(dbUser)
+ suite.Equal("whatever", dbUser.Email)
+ suite.Equal("es", dbUser.Locale)
+ suite.Equal(testUser.AccountID, dbUser.AccountID)
+}
+
+func TestUserTestSuite(t *testing.T) {
+ suite.Run(t, new(UserTestSuite))
+}
diff --git a/internal/db/db.go b/internal/db/db.go
index 0c1f2602a..52a76ecdb 100644
--- a/internal/db/db.go
+++ b/internal/db/db.go
@@ -44,6 +44,7 @@ type DB interface {
Session
Status
Timeline
+ User
/*
USEFUL CONVERSION FUNCTIONS
diff --git a/internal/db/user.go b/internal/db/user.go
new file mode 100644
index 000000000..a4d48db56
--- /dev/null
+++ b/internal/db/user.go
@@ -0,0 +1,42 @@
+/*
+ GoToSocial
+ Copyright (C) 2021-2022 GoToSocial Authors admin@gotosocial.org
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+package db
+
+import (
+ "context"
+
+ "github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
+)
+
+// User contains functions related to user getting/setting/creation.
+type User interface {
+ // GetUserByID returns one user with the given ID, or an error if something goes wrong.
+ GetUserByID(ctx context.Context, id string) (*gtsmodel.User, Error)
+ // GetUserByAccountID returns one user by its account ID, or an error if something goes wrong.
+ GetUserByAccountID(ctx context.Context, accountID string) (*gtsmodel.User, Error)
+ // GetUserByID returns one user with the given email address, or an error if something goes wrong.
+ GetUserByEmailAddress(ctx context.Context, emailAddress string) (*gtsmodel.User, Error)
+ // GetUserByConfirmationToken returns one user by its confirmation token, or an error if something goes wrong.
+ GetUserByConfirmationToken(ctx context.Context, confirmationToken string) (*gtsmodel.User, Error)
+ // UpdateUser updates one user by its primary key. If columns is set, only given columns
+ // will be updated. If not set, all columns will be updated.
+ UpdateUser(ctx context.Context, user *gtsmodel.User, columns ...string) (*gtsmodel.User, Error)
+ // DeleteUserByID deletes one user by its ID.
+ DeleteUserByID(ctx context.Context, userID string) Error
+}