Skip to content

Commit ef0d74f

Browse files
authored
chore: improve performance of 'GetLatestWorkspaceBuildsByWorkspaceIDs' (coder#19452)
Closes coder/internal#716 This prevents a scan over the entire `workspace_build` table by removing a `join`. This is still imperfect as we are still scanning over the number of builds for the workspaces in the arguments. Ideally we would have some index or something precomputed. Then we could skip scanning over the builds for the correct workspaces that are not the latest.
1 parent 63c1325 commit ef0d74f

File tree

3 files changed

+92
-28
lines changed

3 files changed

+92
-28
lines changed

coderd/database/querier_test.go

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import (
3232
"github.com/coder/coder/v2/coderd/provisionerdserver"
3333
"github.com/coder/coder/v2/coderd/rbac"
3434
"github.com/coder/coder/v2/coderd/rbac/policy"
35+
"github.com/coder/coder/v2/coderd/util/slice"
3536
"github.com/coder/coder/v2/codersdk"
3637
"github.com/coder/coder/v2/provisionersdk"
3738
"github.com/coder/coder/v2/testutil"
@@ -6579,3 +6580,75 @@ func TestWorkspaceBuildDeadlineConstraint(t *testing.T) {
65796580
}
65806581
}
65816582
}
6583+
6584+
// TestGetLatestWorkspaceBuildsByWorkspaceIDs populates the database with
6585+
// workspaces and builds. It then tests that
6586+
// GetLatestWorkspaceBuildsByWorkspaceIDs returns the latest build for some
6587+
// subset of the workspaces.
6588+
func TestGetLatestWorkspaceBuildsByWorkspaceIDs(t *testing.T) {
6589+
t.Parallel()
6590+
6591+
db, _ := dbtestutil.NewDB(t)
6592+
6593+
org := dbgen.Organization(t, db, database.Organization{})
6594+
admin := dbgen.User(t, db, database.User{})
6595+
6596+
tv := dbfake.TemplateVersion(t, db).
6597+
Seed(database.TemplateVersion{
6598+
OrganizationID: org.ID,
6599+
CreatedBy: admin.ID,
6600+
}).
6601+
Do()
6602+
6603+
users := make([]database.User, 5)
6604+
wrks := make([][]database.WorkspaceTable, len(users))
6605+
exp := make(map[uuid.UUID]database.WorkspaceBuild)
6606+
for i := range users {
6607+
users[i] = dbgen.User(t, db, database.User{})
6608+
dbgen.OrganizationMember(t, db, database.OrganizationMember{
6609+
UserID: users[i].ID,
6610+
OrganizationID: org.ID,
6611+
})
6612+
6613+
// Each user gets 2 workspaces.
6614+
wrks[i] = make([]database.WorkspaceTable, 2)
6615+
for wi := range wrks[i] {
6616+
wrks[i][wi] = dbgen.Workspace(t, db, database.WorkspaceTable{
6617+
TemplateID: tv.Template.ID,
6618+
OwnerID: users[i].ID,
6619+
})
6620+
6621+
// Choose a deterministic number of builds per workspace
6622+
// No more than 5 builds though, that would be excessive.
6623+
for j := int32(1); int(j) <= (i+wi)%5; j++ {
6624+
wb := dbfake.WorkspaceBuild(t, db, wrks[i][wi]).
6625+
Seed(database.WorkspaceBuild{
6626+
WorkspaceID: wrks[i][wi].ID,
6627+
BuildNumber: j + 1,
6628+
}).
6629+
Do()
6630+
6631+
exp[wrks[i][wi].ID] = wb.Build // Save the final workspace build
6632+
}
6633+
}
6634+
}
6635+
6636+
// Only take half the users. And only take 1 workspace per user for the test.
6637+
// The others are just noice. This just queries a subset of workspaces and builds
6638+
// to make sure the noise doesn't interfere with the results.
6639+
assertWrks := wrks[:len(users)/2]
6640+
ctx := testutil.Context(t, testutil.WaitLong)
6641+
ids := slice.Convert[[]database.WorkspaceTable, uuid.UUID](assertWrks, func(pair []database.WorkspaceTable) uuid.UUID {
6642+
return pair[0].ID
6643+
})
6644+
6645+
require.Greater(t, len(ids), 0, "expected some workspace ids for test")
6646+
builds, err := db.GetLatestWorkspaceBuildsByWorkspaceIDs(ctx, ids)
6647+
require.NoError(t, err)
6648+
for _, b := range builds {
6649+
expB, ok := exp[b.WorkspaceID]
6650+
require.Truef(t, ok, "unexpected workspace build for workspace id %s", b.WorkspaceID)
6651+
require.Equalf(t, expB.ID, b.ID, "unexpected workspace build id for workspace id %s", b.WorkspaceID)
6652+
require.Equal(t, expB.BuildNumber, b.BuildNumber, "unexpected build number")
6653+
}
6654+
}

coderd/database/queries.sql.go

Lines changed: 9 additions & 14 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coderd/database/queries/workspacebuilds.sql

Lines changed: 10 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -76,20 +76,16 @@ LIMIT
7676
1;
7777

7878
-- name: GetLatestWorkspaceBuildsByWorkspaceIDs :many
79-
SELECT wb.*
80-
FROM (
81-
SELECT
82-
workspace_id, MAX(build_number) as max_build_number
83-
FROM
84-
workspace_build_with_user AS workspace_builds
85-
WHERE
86-
workspace_id = ANY(@ids :: uuid [ ])
87-
GROUP BY
88-
workspace_id
89-
) m
90-
JOIN
91-
workspace_build_with_user AS wb
92-
ON m.workspace_id = wb.workspace_id AND m.max_build_number = wb.build_number;
79+
SELECT
80+
DISTINCT ON (workspace_id)
81+
*
82+
FROM
83+
workspace_build_with_user AS workspace_builds
84+
WHERE
85+
workspace_id = ANY(@ids :: uuid [ ])
86+
ORDER BY
87+
workspace_id, build_number DESC -- latest first
88+
;
9389

9490
-- name: InsertWorkspaceBuild :exec
9591
INSERT INTO

0 commit comments

Comments
 (0)