perf(query): reduce user query duration (#10037)

# Which Problems Are Solved

The resource usage to query user(s) on the database was high and
therefore could have performance impact.

# How the Problems Are Solved

Database queries involving the users and loginnames table were improved
and an index was added for user by email query.

# Additional Changes

- spellchecks
- updated apis on load tests

# additional info

needs cherry pick to v3

(cherry picked from commit 4df138286b)
This commit is contained in:
Silvan
2025-06-06 10:48:29 +02:00
committed by Livio Spring
parent 8b04ddf0e2
commit 8ac4b61ee6
26 changed files with 225 additions and 689 deletions

View File

@@ -182,21 +182,15 @@ var (
userLoginNamesTable = loginNameTable.setAlias("login_names")
userLoginNamesUserIDCol = LoginNameUserIDCol.setTable(userLoginNamesTable)
userLoginNamesNameCol = LoginNameNameCol.setTable(userLoginNamesTable)
userLoginNamesInstanceIDCol = LoginNameInstanceIDCol.setTable(userLoginNamesTable)
userLoginNamesListCol = Column{
name: "loginnames",
name: "login_names",
table: userLoginNamesTable,
}
userLoginNamesLowerListCol = Column{
name: "loginnames_lower",
userPreferredLoginNameCol = Column{
name: "preferred_login_name",
table: userLoginNamesTable,
}
userPreferredLoginNameTable = loginNameTable.setAlias("preferred_login_name")
userPreferredLoginNameUserIDCol = LoginNameUserIDCol.setTable(userPreferredLoginNameTable)
userPreferredLoginNameCol = LoginNameNameCol.setTable(userPreferredLoginNameTable)
userPreferredLoginNameIsPrimaryCol = LoginNameIsPrimaryCol.setTable(userPreferredLoginNameTable)
userPreferredLoginNameInstanceIDCol = LoginNameInstanceIDCol.setTable(userPreferredLoginNameTable)
)
var (
@@ -441,7 +435,7 @@ func (q *Queries) GetHumanProfile(ctx context.Context, userID string, queries ..
}
stmt, args, err := query.Where(eq).ToSql()
if err != nil {
return nil, zerrors.ThrowInternal(err, "QUERY-Dgbg2", "Errors.Query.SQLStatment")
return nil, zerrors.ThrowInternal(err, "QUERY-Dgbg2", "Errors.Query.SQLStatement")
}
err = q.client.QueryRowContext(ctx, func(row *sql.Row) error {
@@ -465,7 +459,7 @@ func (q *Queries) GetHumanEmail(ctx context.Context, userID string, queries ...S
}
stmt, args, err := query.Where(eq).ToSql()
if err != nil {
return nil, zerrors.ThrowInternal(err, "QUERY-BHhj3", "Errors.Query.SQLStatment")
return nil, zerrors.ThrowInternal(err, "QUERY-BHhj3", "Errors.Query.SQLStatement")
}
err = q.client.QueryRowContext(ctx, func(row *sql.Row) error {
@@ -489,7 +483,7 @@ func (q *Queries) GetHumanPhone(ctx context.Context, userID string, queries ...S
}
stmt, args, err := query.Where(eq).ToSql()
if err != nil {
return nil, zerrors.ThrowInternal(err, "QUERY-Dg43g", "Errors.Query.SQLStatment")
return nil, zerrors.ThrowInternal(err, "QUERY-Dg43g", "Errors.Query.SQLStatement")
}
err = q.client.QueryRowContext(ctx, func(row *sql.Row) error {
@@ -575,7 +569,7 @@ func (q *Queries) GetNotifyUser(ctx context.Context, shouldTriggered bool, queri
}
stmt, args, err := query.Where(eq).ToSql()
if err != nil {
return nil, zerrors.ThrowInternal(err, "QUERY-Err3g", "Errors.Query.SQLStatment")
return nil, zerrors.ThrowInternal(err, "QUERY-Err3g", "Errors.Query.SQLStatement")
}
err = q.client.QueryRowContext(ctx, func(row *sql.Row) error {
@@ -593,7 +587,7 @@ func (q *Queries) CountUsers(ctx context.Context, queries *UserSearchQueries) (c
eq := sq.Eq{UserInstanceIDCol.identifier(): authz.GetInstance(ctx).InstanceID()}
stmt, args, err := queries.toQuery(query).Where(eq).ToSql()
if err != nil {
return 0, zerrors.ThrowInternal(err, "QUERY-w3Dx", "Errors.Query.SQLStatment")
return 0, zerrors.ThrowInternal(err, "QUERY-w3Dx", "Errors.Query.SQLStatement")
}
err = q.client.QueryContext(ctx, func(rows *sql.Rows) error {
@@ -634,7 +628,7 @@ func (q *Queries) searchUsers(ctx context.Context, queries *UserSearchQueries, f
stmt, args, err := query.ToSql()
if err != nil {
return nil, zerrors.ThrowInternal(err, "QUERY-Dgbg2", "Errors.Query.SQLStatment")
return nil, zerrors.ThrowInternal(err, "QUERY-Dgbg2", "Errors.Query.SQLStatement")
}
err = q.client.QueryContext(ctx, func(rows *sql.Rows) error {
@@ -681,7 +675,7 @@ func (q *Queries) IsUserUnique(ctx context.Context, username, email, resourceOwn
eq := sq.Eq{UserInstanceIDCol.identifier(): authz.GetInstance(ctx).InstanceID()}
stmt, args, err := query.Where(eq).ToSql()
if err != nil {
return false, zerrors.ThrowInternal(err, "QUERY-Dg43g", "Errors.Query.SQLStatment")
return false, zerrors.ThrowInternal(err, "QUERY-Dg43g", "Errors.Query.SQLStatement")
}
err = q.client.QueryRowContext(ctx, func(row *sql.Row) error {
@@ -780,12 +774,8 @@ func NewUserPreferredLoginNameSearchQuery(value string, comparison TextCompariso
return NewTextQuery(userPreferredLoginNameCol, value, comparison)
}
func NewUserLoginNamesSearchQuery(value string) (SearchQuery, error) {
return NewTextQuery(userLoginNamesLowerListCol, strings.ToLower(value), TextListContains)
}
func NewUserLoginNameExistsQuery(value string, comparison TextComparison) (SearchQuery, error) {
// linking queries for the subselect
// linking queries for the sub select
instanceQuery, err := NewColumnComparisonQuery(LoginNameInstanceIDCol, UserInstanceIDCol, ColumnEquals)
if err != nil {
return nil, err
@@ -816,30 +806,16 @@ func triggerUserProjections(ctx context.Context) {
triggerBatch(ctx, projection.UserProjection, projection.LoginNameProjection)
}
func prepareLoginNamesQuery() (string, []interface{}, error) {
return sq.Select(
userLoginNamesUserIDCol.identifier(),
"ARRAY_AGG("+userLoginNamesNameCol.identifier()+")::TEXT[] AS "+userLoginNamesListCol.name,
"ARRAY_AGG(LOWER("+userLoginNamesNameCol.identifier()+"))::TEXT[] AS "+userLoginNamesLowerListCol.name,
userLoginNamesInstanceIDCol.identifier(),
).From(userLoginNamesTable.identifier()).
GroupBy(
userLoginNamesUserIDCol.identifier(),
userLoginNamesInstanceIDCol.identifier(),
).ToSql()
}
func preparePreferredLoginNamesQuery() (string, []interface{}, error) {
return sq.Select(
userPreferredLoginNameUserIDCol.identifier(),
userPreferredLoginNameCol.identifier(),
userPreferredLoginNameInstanceIDCol.identifier(),
).From(userPreferredLoginNameTable.identifier()).
Where(sq.Eq{
userPreferredLoginNameIsPrimaryCol.identifier(): true,
},
).ToSql()
}
var joinLoginNames = `LEFT JOIN LATERAL (` +
`SELECT` +
` ARRAY_AGG(ln.login_name ORDER BY ln.login_name) AS login_names,` +
` MAX(CASE WHEN ln.is_primary THEN ln.login_name ELSE NULL END) AS preferred_login_name` +
` FROM` +
` projections.login_names3 AS ln` +
` WHERE` +
` ln.user_id = ` + UserIDCol.identifier() +
` AND ln.instance_id = ` + UserInstanceIDCol.identifier() +
`) AS login_names ON TRUE`
func scanUser(row *sql.Row) (*User, error) {
u := new(User)
@@ -939,64 +915,6 @@ func scanUser(row *sql.Row) (*User, error) {
return u, nil
}
func prepareUserQuery() (sq.SelectBuilder, func(*sql.Row) (*User, error)) {
loginNamesQuery, loginNamesArgs, err := prepareLoginNamesQuery()
if err != nil {
return sq.SelectBuilder{}, nil
}
preferredLoginNameQuery, preferredLoginNameArgs, err := preparePreferredLoginNamesQuery()
if err != nil {
return sq.SelectBuilder{}, nil
}
return sq.Select(
UserIDCol.identifier(),
UserCreationDateCol.identifier(),
UserChangeDateCol.identifier(),
UserResourceOwnerCol.identifier(),
UserSequenceCol.identifier(),
UserStateCol.identifier(),
UserTypeCol.identifier(),
UserUsernameCol.identifier(),
userLoginNamesListCol.identifier(),
userPreferredLoginNameCol.identifier(),
HumanUserIDCol.identifier(),
HumanFirstNameCol.identifier(),
HumanLastNameCol.identifier(),
HumanNickNameCol.identifier(),
HumanDisplayNameCol.identifier(),
HumanPreferredLanguageCol.identifier(),
HumanGenderCol.identifier(),
HumanAvatarURLCol.identifier(),
HumanEmailCol.identifier(),
HumanIsEmailVerifiedCol.identifier(),
HumanPhoneCol.identifier(),
HumanIsPhoneVerifiedCol.identifier(),
HumanPasswordChangeRequiredCol.identifier(),
HumanPasswordChangedCol.identifier(),
HumanMFAInitSkippedCol.identifier(),
MachineUserIDCol.identifier(),
MachineNameCol.identifier(),
MachineDescriptionCol.identifier(),
MachineSecretCol.identifier(),
MachineAccessTokenTypeCol.identifier(),
countColumn.identifier(),
).
From(userTable.identifier()).
LeftJoin(join(HumanUserIDCol, UserIDCol)).
LeftJoin(join(MachineUserIDCol, UserIDCol)).
LeftJoin("("+loginNamesQuery+") AS "+userLoginNamesTable.alias+" ON "+
userLoginNamesUserIDCol.identifier()+" = "+UserIDCol.identifier()+" AND "+
userLoginNamesInstanceIDCol.identifier()+" = "+UserInstanceIDCol.identifier(),
loginNamesArgs...).
LeftJoin("("+preferredLoginNameQuery+") AS "+userPreferredLoginNameTable.alias+" ON "+
userPreferredLoginNameUserIDCol.identifier()+" = "+UserIDCol.identifier()+" AND "+
userPreferredLoginNameInstanceIDCol.identifier()+" = "+UserInstanceIDCol.identifier(),
preferredLoginNameArgs...).
PlaceholderFormat(sq.Dollar),
scanUser
}
func prepareProfileQuery() (sq.SelectBuilder, func(*sql.Row) (*Profile, error)) {
return sq.Select(
UserIDCol.identifier(),
@@ -1158,14 +1076,6 @@ func preparePhoneQuery() (sq.SelectBuilder, func(*sql.Row) (*Phone, error)) {
}
func prepareNotifyUserQuery() (sq.SelectBuilder, func(*sql.Row) (*NotifyUser, error)) {
loginNamesQuery, loginNamesArgs, err := prepareLoginNamesQuery()
if err != nil {
return sq.SelectBuilder{}, nil
}
preferredLoginNameQuery, preferredLoginNameArgs, err := preparePreferredLoginNamesQuery()
if err != nil {
return sq.SelectBuilder{}, nil
}
return sq.Select(
UserIDCol.identifier(),
UserCreationDateCol.identifier(),
@@ -1196,14 +1106,7 @@ func prepareNotifyUserQuery() (sq.SelectBuilder, func(*sql.Row) (*NotifyUser, er
From(userTable.identifier()).
LeftJoin(join(HumanUserIDCol, UserIDCol)).
LeftJoin(join(NotifyUserIDCol, UserIDCol)).
LeftJoin("("+loginNamesQuery+") AS "+userLoginNamesTable.alias+" ON "+
userLoginNamesUserIDCol.identifier()+" = "+UserIDCol.identifier()+" AND "+
userLoginNamesInstanceIDCol.identifier()+" = "+UserInstanceIDCol.identifier(),
loginNamesArgs...).
LeftJoin("("+preferredLoginNameQuery+") AS "+userPreferredLoginNameTable.alias+" ON "+
userPreferredLoginNameUserIDCol.identifier()+" = "+UserIDCol.identifier()+" AND "+
userPreferredLoginNameInstanceIDCol.identifier()+" = "+UserInstanceIDCol.identifier(),
preferredLoginNameArgs...).
JoinClause(joinLoginNames).
PlaceholderFormat(sq.Dollar),
scanNotifyUser
}
@@ -1347,14 +1250,6 @@ func prepareUserUniqueQuery() (sq.SelectBuilder, func(*sql.Row) (bool, error)) {
}
func prepareUsersQuery() (sq.SelectBuilder, func(*sql.Rows) (*Users, error)) {
loginNamesQuery, loginNamesArgs, err := prepareLoginNamesQuery()
if err != nil {
return sq.SelectBuilder{}, nil
}
preferredLoginNameQuery, preferredLoginNameArgs, err := preparePreferredLoginNamesQuery()
if err != nil {
return sq.SelectBuilder{}, nil
}
return sq.Select(
UserIDCol.identifier(),
UserCreationDateCol.identifier(),
@@ -1389,14 +1284,7 @@ func prepareUsersQuery() (sq.SelectBuilder, func(*sql.Rows) (*Users, error)) {
From(userTable.identifier()).
LeftJoin(join(HumanUserIDCol, UserIDCol)).
LeftJoin(join(MachineUserIDCol, UserIDCol)).
LeftJoin("("+loginNamesQuery+") AS "+userLoginNamesTable.alias+" ON "+
userLoginNamesUserIDCol.identifier()+" = "+UserIDCol.identifier()+" AND "+
userLoginNamesInstanceIDCol.identifier()+" = "+UserInstanceIDCol.identifier(),
loginNamesArgs...).
LeftJoin("("+preferredLoginNameQuery+") AS "+userPreferredLoginNameTable.alias+" ON "+
userPreferredLoginNameUserIDCol.identifier()+" = "+UserIDCol.identifier()+" AND "+
userPreferredLoginNameInstanceIDCol.identifier()+" = "+UserInstanceIDCol.identifier(),
preferredLoginNameArgs...).
JoinClause(joinLoginNames).
PlaceholderFormat(sq.Dollar),
func(rows *sql.Rows) (*Users, error) {
users := make([]*User, 0)