mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
fix: drop N+1 db query on template ACL available (#25465)
Fixes [PLAT-149](https://linear.app/codercom/issue/PLAT-149/template-permissions-search-is-extremely-slow-with-many-groups). `/acl/available` ran a db query per group. A deployment with >5,000 groups made this route extremely slow.
This commit is contained in:
@@ -12689,6 +12689,56 @@ func (q *sqlQuerier) GetGroupMembersCountByGroupID(ctx context.Context, arg GetG
|
||||
return count, err
|
||||
}
|
||||
|
||||
const getGroupMembersCountByGroupIDs = `-- name: GetGroupMembersCountByGroupIDs :many
|
||||
SELECT
|
||||
group_id,
|
||||
COUNT(*) AS member_count
|
||||
FROM group_members_expanded
|
||||
WHERE group_id = ANY($1 :: uuid[])
|
||||
AND CASE
|
||||
WHEN $2::bool THEN TRUE
|
||||
ELSE user_is_system = false
|
||||
END
|
||||
GROUP BY group_id
|
||||
`
|
||||
|
||||
type GetGroupMembersCountByGroupIDsParams struct {
|
||||
GroupIds []uuid.UUID `db:"group_ids" json:"group_ids"`
|
||||
IncludeSystem bool `db:"include_system" json:"include_system"`
|
||||
}
|
||||
|
||||
type GetGroupMembersCountByGroupIDsRow struct {
|
||||
GroupID uuid.UUID `db:"group_id" json:"group_id"`
|
||||
MemberCount int64 `db:"member_count" json:"member_count"`
|
||||
}
|
||||
|
||||
// Returns the total member count for each of the given group IDs in a
|
||||
// single query. Used to avoid N+1 lookups when listing many groups. Like
|
||||
// GetGroupMembersCountByGroupID, the count is returned even when the
|
||||
// caller does not have read access to individual group members.
|
||||
func (q *sqlQuerier) GetGroupMembersCountByGroupIDs(ctx context.Context, arg GetGroupMembersCountByGroupIDsParams) ([]GetGroupMembersCountByGroupIDsRow, error) {
|
||||
rows, err := q.db.QueryContext(ctx, getGroupMembersCountByGroupIDs, pq.Array(arg.GroupIds), arg.IncludeSystem)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var items []GetGroupMembersCountByGroupIDsRow
|
||||
for rows.Next() {
|
||||
var i GetGroupMembersCountByGroupIDsRow
|
||||
if err := rows.Scan(&i.GroupID, &i.MemberCount); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, i)
|
||||
}
|
||||
if err := rows.Close(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const insertGroupMember = `-- name: InsertGroupMember :exec
|
||||
INSERT INTO
|
||||
group_members (user_id, group_id)
|
||||
@@ -12906,6 +12956,14 @@ WHERE
|
||||
groups.id = ANY($4)
|
||||
ELSE true
|
||||
END
|
||||
-- Filter by group name or display name (substring, case-insensitive).
|
||||
AND CASE WHEN $5 :: text != '' THEN (
|
||||
groups.name ILIKE concat('%', $5, '%')
|
||||
OR groups.display_name ILIKE concat('%', $5, '%')
|
||||
)
|
||||
ELSE true
|
||||
END
|
||||
LIMIT NULLIF($6 :: int, 0)
|
||||
`
|
||||
|
||||
type GetGroupsParams struct {
|
||||
@@ -12913,6 +12971,8 @@ type GetGroupsParams struct {
|
||||
HasMemberID uuid.UUID `db:"has_member_id" json:"has_member_id"`
|
||||
GroupNames []string `db:"group_names" json:"group_names"`
|
||||
GroupIds []uuid.UUID `db:"group_ids" json:"group_ids"`
|
||||
Search string `db:"search" json:"search"`
|
||||
LimitOpt int32 `db:"limit_opt" json:"limit_opt"`
|
||||
}
|
||||
|
||||
type GetGroupsRow struct {
|
||||
@@ -12921,12 +12981,15 @@ type GetGroupsRow struct {
|
||||
OrganizationDisplayName string `db:"organization_display_name" json:"organization_display_name"`
|
||||
}
|
||||
|
||||
// A limit of 0 means "no limit".
|
||||
func (q *sqlQuerier) GetGroups(ctx context.Context, arg GetGroupsParams) ([]GetGroupsRow, error) {
|
||||
rows, err := q.db.QueryContext(ctx, getGroups,
|
||||
arg.OrganizationID,
|
||||
arg.HasMemberID,
|
||||
pq.Array(arg.GroupNames),
|
||||
pq.Array(arg.GroupIds),
|
||||
arg.Search,
|
||||
arg.LimitOpt,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
Reference in New Issue
Block a user