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

[performance] retry db queries on busy errors #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
4 changes: 0 additions & 4 deletions example/config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -194,10 +194,6 @@ db-tls-ca-cert: ""
#
# If you set the multiplier to less than 1, only one open connection will be used regardless of cpu count.
#
# PLEASE NOTE!!: This setting currently only applies for Postgres. SQLite will always use 1 connection regardless
# of what is set here. This behavior will change in future when we implement better SQLITE_BUSY handling.
# See https://github.com/superseriousbusiness/gotosocial/issues/1407 for more details.
#
# Examples: [16, 8, 10, 2]
# Default: 8
db-max-open-conns-multiplier: 8
Expand Down
46 changes: 23 additions & 23 deletions internal/db/account.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,96 +27,96 @@ import (
// Account contains functions related to account getting/setting/creation.
type Account interface {
// GetAccountByID returns one account with the given ID, or an error if something goes wrong.
GetAccountByID(ctx context.Context, id string) (*gtsmodel.Account, Error)
GetAccountByID(ctx context.Context, id string) (*gtsmodel.Account, error)

// GetAccountByURI returns one account with the given URI, or an error if something goes wrong.
GetAccountByURI(ctx context.Context, uri string) (*gtsmodel.Account, Error)
GetAccountByURI(ctx context.Context, uri string) (*gtsmodel.Account, error)

// GetAccountByURL returns one account with the given URL, or an error if something goes wrong.
GetAccountByURL(ctx context.Context, uri string) (*gtsmodel.Account, Error)
GetAccountByURL(ctx context.Context, uri string) (*gtsmodel.Account, error)

// GetAccountByUsernameDomain returns one account with the given username and domain, or an error if something goes wrong.
GetAccountByUsernameDomain(ctx context.Context, username string, domain string) (*gtsmodel.Account, Error)
GetAccountByUsernameDomain(ctx context.Context, username string, domain string) (*gtsmodel.Account, error)

// GetAccountByPubkeyID returns one account with the given public key URI (ID), or an error if something goes wrong.
GetAccountByPubkeyID(ctx context.Context, id string) (*gtsmodel.Account, Error)
GetAccountByPubkeyID(ctx context.Context, id string) (*gtsmodel.Account, error)

// GetAccountByInboxURI returns one account with the given inbox_uri, or an error if something goes wrong.
GetAccountByInboxURI(ctx context.Context, uri string) (*gtsmodel.Account, Error)
GetAccountByInboxURI(ctx context.Context, uri string) (*gtsmodel.Account, error)

// GetAccountByOutboxURI returns one account with the given outbox_uri, or an error if something goes wrong.
GetAccountByOutboxURI(ctx context.Context, uri string) (*gtsmodel.Account, Error)
GetAccountByOutboxURI(ctx context.Context, uri string) (*gtsmodel.Account, error)

// GetAccountByFollowingURI returns one account with the given following_uri, or an error if something goes wrong.
GetAccountByFollowingURI(ctx context.Context, uri string) (*gtsmodel.Account, Error)
GetAccountByFollowingURI(ctx context.Context, uri string) (*gtsmodel.Account, error)

// GetAccountByFollowersURI returns one account with the given followers_uri, or an error if something goes wrong.
GetAccountByFollowersURI(ctx context.Context, uri string) (*gtsmodel.Account, Error)
GetAccountByFollowersURI(ctx context.Context, uri string) (*gtsmodel.Account, error)

// PopulateAccount ensures that all sub-models of an account are populated (e.g. avatar, header etc).
PopulateAccount(ctx context.Context, account *gtsmodel.Account) error

// PutAccount puts one account in the database.
PutAccount(ctx context.Context, account *gtsmodel.Account) Error
PutAccount(ctx context.Context, account *gtsmodel.Account) error

// UpdateAccount updates one account by ID.
UpdateAccount(ctx context.Context, account *gtsmodel.Account, columns ...string) Error
UpdateAccount(ctx context.Context, account *gtsmodel.Account, columns ...string) error

// DeleteAccount deletes one account from the database by its ID.
// DO NOT USE THIS WHEN SUSPENDING ACCOUNTS! In that case you should mark the
// account as suspended instead, rather than deleting from the db entirely.
DeleteAccount(ctx context.Context, id string) Error
DeleteAccount(ctx context.Context, id string) error

// GetAccountCustomCSSByUsername returns the custom css of an account on this instance with the given username.
GetAccountCustomCSSByUsername(ctx context.Context, username string) (string, Error)
GetAccountCustomCSSByUsername(ctx context.Context, username string) (string, error)

// GetAccountFaves fetches faves/likes created by the target accountID.
GetAccountFaves(ctx context.Context, accountID string) ([]*gtsmodel.StatusFave, Error)
GetAccountFaves(ctx context.Context, accountID string) ([]*gtsmodel.StatusFave, error)

// GetAccountsUsingEmoji fetches all account models using emoji with given ID stored in their 'emojis' column.
GetAccountsUsingEmoji(ctx context.Context, emojiID string) ([]*gtsmodel.Account, error)

// GetAccountStatusesCount is a shortcut for the common action of counting statuses produced by accountID.
CountAccountStatuses(ctx context.Context, accountID string) (int, Error)
CountAccountStatuses(ctx context.Context, accountID string) (int, error)

// CountAccountPinned returns the total number of pinned statuses owned by account with the given id.
CountAccountPinned(ctx context.Context, accountID string) (int, Error)
CountAccountPinned(ctx context.Context, accountID string) (int, error)

// GetAccountStatuses is a shortcut for getting the most recent statuses. accountID is optional, if not provided
// then all statuses will be returned. If limit is set to 0, the size of the returned slice will not be limited. This can
// be very memory intensive so you probably shouldn't do this!
//
// In the case of no statuses, this function will return db.ErrNoEntries.
GetAccountStatuses(ctx context.Context, accountID string, limit int, excludeReplies bool, excludeReblogs bool, maxID string, minID string, mediaOnly bool, publicOnly bool) ([]*gtsmodel.Status, Error)
GetAccountStatuses(ctx context.Context, accountID string, limit int, excludeReplies bool, excludeReblogs bool, maxID string, minID string, mediaOnly bool, publicOnly bool) ([]*gtsmodel.Status, error)

// GetAccountPinnedStatuses returns ONLY statuses owned by the give accountID for which a corresponding StatusPin
// exists in the database. Statuses which are not pinned will not be returned by this function.
//
// Statuses will be returned in the order in which they were pinned, from latest pinned to oldest pinned (descending).
//
// In the case of no statuses, this function will return db.ErrNoEntries.
GetAccountPinnedStatuses(ctx context.Context, accountID string) ([]*gtsmodel.Status, Error)
GetAccountPinnedStatuses(ctx context.Context, accountID string) ([]*gtsmodel.Status, error)

// GetAccountWebStatuses is similar to GetAccountStatuses, but it's specifically for returning statuses that
// should be visible via the web view of an account. So, only public, federated statuses that aren't boosts
// or replies.
//
// In the case of no statuses, this function will return db.ErrNoEntries.
GetAccountWebStatuses(ctx context.Context, accountID string, limit int, maxID string) ([]*gtsmodel.Status, Error)
GetAccountWebStatuses(ctx context.Context, accountID string, limit int, maxID string) ([]*gtsmodel.Status, error)

GetAccountBlocks(ctx context.Context, accountID string, maxID string, sinceID string, limit int) ([]*gtsmodel.Account, string, string, Error)
GetAccountBlocks(ctx context.Context, accountID string, maxID string, sinceID string, limit int) ([]*gtsmodel.Account, string, string, error)

// GetAccountLastPosted simply gets the timestamp of the most recent post by the account.
//
// If webOnly is true, then the time of the last non-reply, non-boost, public status of the account will be returned.
//
// The returned time will be zero if account has never posted anything.
GetAccountLastPosted(ctx context.Context, accountID string, webOnly bool) (time.Time, Error)
GetAccountLastPosted(ctx context.Context, accountID string, webOnly bool) (time.Time, error)

// SetAccountHeaderOrAvatar sets the header or avatar for the given accountID to the given media attachment.
SetAccountHeaderOrAvatar(ctx context.Context, mediaAttachment *gtsmodel.MediaAttachment, accountID string) Error
SetAccountHeaderOrAvatar(ctx context.Context, mediaAttachment *gtsmodel.MediaAttachment, accountID string) error

// GetInstanceAccount returns the instance account for the given domain.
// If domain is empty, this instance account will be returned.
GetInstanceAccount(ctx context.Context, domain string) (*gtsmodel.Account, Error)
GetInstanceAccount(ctx context.Context, domain string) (*gtsmodel.Account, error)
}
10 changes: 5 additions & 5 deletions internal/db/admin.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,26 +27,26 @@ import (
type Admin interface {
// IsUsernameAvailable checks whether a given username is available on our domain.
// Returns an error if the username is already taken, or something went wrong in the db.
IsUsernameAvailable(ctx context.Context, username string) (bool, Error)
IsUsernameAvailable(ctx context.Context, username string) (bool, error)

// IsEmailAvailable checks whether a given email address for a new account is available to be used on our domain.
// Return an error if:
// A) the email is already associated with an account
// B) we block signups from this email domain
// C) something went wrong in the db
IsEmailAvailable(ctx context.Context, email string) (bool, Error)
IsEmailAvailable(ctx context.Context, email string) (bool, error)

// NewSignup creates a new user in the database with the given parameters.
// By the time this function is called, it should be assumed that all the parameters have passed validation!
NewSignup(ctx context.Context, newSignup gtsmodel.NewSignup) (*gtsmodel.User, Error)
NewSignup(ctx context.Context, newSignup gtsmodel.NewSignup) (*gtsmodel.User, error)

// CreateInstanceAccount creates an account in the database with the same username as the instance host value.
// Ie., if the instance is hosted at 'example.org' the instance user will have a username of 'example.org'.
// This is needed for things like serving files that belong to the instance and not an individual user/account.
CreateInstanceAccount(ctx context.Context) Error
CreateInstanceAccount(ctx context.Context) error

// CreateInstanceInstance creates an instance in the database with the same domain as the instance host value.
// Ie., if the instance is hosted at 'example.org' the instance will have a domain of 'example.org'.
// This is needed for things like serving instance information through /api/v1/instance
CreateInstanceInstance(ctx context.Context) Error
CreateInstanceInstance(ctx context.Context) error
}
26 changes: 13 additions & 13 deletions internal/db/basic.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,58 +23,58 @@ import "context"
type Basic interface {
// CreateTable creates a table for the given interface.
// For implementations that don't use tables, this can just return nil.
CreateTable(ctx context.Context, i interface{}) Error
CreateTable(ctx context.Context, i interface{}) error

// CreateAllTables creates *all* tables necessary for the running of GoToSocial.
// Because it uses the 'if not exists' parameter it is safe to run against a GtS that's already been initialized.
CreateAllTables(ctx context.Context) Error
CreateAllTables(ctx context.Context) error

// DropTable drops the table for the given interface.
// For implementations that don't use tables, this can just return nil.
DropTable(ctx context.Context, i interface{}) Error
DropTable(ctx context.Context, i interface{}) error

// Stop should stop and close the database connection cleanly, returning an error if this is not possible.
// If the database implementation doesn't need to be stopped, this can just return nil.
Stop(ctx context.Context) Error
Stop(ctx context.Context) error

// IsHealthy should return nil if the database connection is healthy, or an error if not.
IsHealthy(ctx context.Context) Error
IsHealthy(ctx context.Context) error

// GetByID gets one entry by its id. In a database like postgres, this might be the 'id' field of the entry,
// for other implementations (for example, in-memory) it might just be the key of a map.
// The given interface i will be set to the result of the query, whatever it is. Use a pointer or a slice.
// In case of no entries, a 'no entries' error will be returned
GetByID(ctx context.Context, id string, i interface{}) Error
GetByID(ctx context.Context, id string, i interface{}) error

// GetWhere gets one entry where key = value. This is similar to GetByID but allows the caller to specify the
// name of the key to select from.
// The given interface i will be set to the result of the query, whatever it is. Use a pointer or a slice.
// In case of no entries, a 'no entries' error will be returned
GetWhere(ctx context.Context, where []Where, i interface{}) Error
GetWhere(ctx context.Context, where []Where, i interface{}) error

// GetAll will try to get all entries of type i.
// The given interface i will be set to the result of the query, whatever it is. Use a pointer or a slice.
// In case of no entries, a 'no entries' error will be returned
GetAll(ctx context.Context, i interface{}) Error
GetAll(ctx context.Context, i interface{}) error

// Put simply stores i. It is up to the implementation to figure out how to store it, and using what key.
// The given interface i will be set to the result of the query, whatever it is. Use a pointer or a slice.
Put(ctx context.Context, i interface{}) Error
Put(ctx context.Context, i interface{}) error

// UpdateByID updates values of i based on its id.
// If any columns are specified, these will be updated exclusively.
// Otherwise, the whole model will be updated.
// The given interface i will be set to the result of the query, whatever it is. Use a pointer or a slice.
UpdateByID(ctx context.Context, i interface{}, id string, columns ...string) Error
UpdateByID(ctx context.Context, i interface{}, id string, columns ...string) error

// UpdateWhere updates column key of interface i with the given value, where the given parameters apply.
UpdateWhere(ctx context.Context, where []Where, key string, value interface{}, i interface{}) Error
UpdateWhere(ctx context.Context, where []Where, key string, value interface{}, i interface{}) error

// DeleteByID removes i with id id.
// If i didn't exist anyway, then no error should be returned.
DeleteByID(ctx context.Context, id string, i interface{}) Error
DeleteByID(ctx context.Context, id string, i interface{}) error

// DeleteWhere deletes i where key = value
// If i didn't exist anyway, then no error should be returned.
DeleteWhere(ctx context.Context, where []Where, i interface{}) Error
DeleteWhere(ctx context.Context, where []Where, i interface{}) error
}
Loading