Skip to content

Commit 2d00b50

Browse files
authored
chore: remove excess join in GetQuotaConsumedForUser query (#15338)
Filter is applied in original workspace query. We do not need to join `workspaces` twice. Use build_number instead of `created_at` for determining the last build.
1 parent 886dcbe commit 2d00b50

File tree

4 files changed

+115
-24
lines changed

4 files changed

+115
-24
lines changed

coderd/database/dbgen/dbgen.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -288,6 +288,15 @@ func WorkspaceBuild(t testing.TB, db database.Store, orig database.WorkspaceBuil
288288
if err != nil {
289289
return err
290290
}
291+
292+
if orig.DailyCost > 0 {
293+
err = db.UpdateWorkspaceBuildCostByID(genCtx, database.UpdateWorkspaceBuildCostByIDParams{
294+
ID: buildID,
295+
DailyCost: orig.DailyCost,
296+
})
297+
require.NoError(t, err)
298+
}
299+
291300
build, err = db.GetWorkspaceBuildByID(genCtx, buildID)
292301
if err != nil {
293302
return err

coderd/database/queries.sql.go

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

coderd/database/queries/quotas.sql

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -28,23 +28,17 @@ FROM
2828
INNER JOIN
2929
workspaces on wb.workspace_id = workspaces.id
3030
WHERE
31+
-- Only return workspaces that match the user + organization.
32+
-- Quotas are calculated per user per organization.
33+
NOT workspaces.deleted AND
3134
workspaces.owner_id = @owner_id AND
3235
workspaces.organization_id = @organization_id
3336
ORDER BY
3437
wb.workspace_id,
35-
wb.created_at DESC
38+
wb.build_number DESC
3639
)
3740
SELECT
3841
coalesce(SUM(daily_cost), 0)::BIGINT
3942
FROM
40-
workspaces
41-
INNER JOIN latest_builds ON
42-
latest_builds.workspace_id = workspaces.id
43-
WHERE
44-
NOT deleted AND
45-
-- We can likely remove these conditions since we check above.
46-
-- But it does not hurt to be defensive and make sure future query changes
47-
-- do not break anything.
48-
workspaces.owner_id = @owner_id AND
49-
workspaces.organization_id = @organization_id
43+
latest_builds
5044
;

enterprise/coderd/workspacequota_test.go

Lines changed: 96 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import (
2121
"github.com/coder/coder/v2/coderd/database/dbgen"
2222
"github.com/coder/coder/v2/coderd/database/dbtestutil"
2323
"github.com/coder/coder/v2/coderd/database/dbtime"
24+
"github.com/coder/coder/v2/coderd/rbac"
2425
"github.com/coder/coder/v2/coderd/util/ptr"
2526
"github.com/coder/coder/v2/codersdk"
2627
"github.com/coder/coder/v2/enterprise/coderd/coderdenttest"
@@ -31,9 +32,13 @@ import (
3132
)
3233

3334
func verifyQuota(ctx context.Context, t *testing.T, client *codersdk.Client, organizationID string, consumed, total int) {
35+
verifyQuotaUser(ctx, t, client, organizationID, codersdk.Me, consumed, total)
36+
}
37+
38+
func verifyQuotaUser(ctx context.Context, t *testing.T, client *codersdk.Client, organizationID string, user string, consumed, total int) {
3439
t.Helper()
3540

36-
got, err := client.WorkspaceQuota(ctx, organizationID, codersdk.Me)
41+
got, err := client.WorkspaceQuota(ctx, organizationID, user)
3742
require.NoError(t, err)
3843
require.EqualValues(t, codersdk.WorkspaceQuota{
3944
Budget: total,
@@ -43,7 +48,7 @@ func verifyQuota(ctx context.Context, t *testing.T, client *codersdk.Client, org
4348
// Remove this check when the deprecated endpoint is removed.
4449
// This just makes sure the deprecated endpoint is still working
4550
// as intended. It will only work for the default organization.
46-
deprecatedGot, err := deprecatedQuotaEndpoint(ctx, client, codersdk.Me)
51+
deprecatedGot, err := deprecatedQuotaEndpoint(ctx, client, user)
4752
require.NoError(t, err, "deprecated endpoint")
4853
// Only continue to check if the values differ
4954
if deprecatedGot.Budget != got.Budget || deprecatedGot.CreditsConsumed != got.CreditsConsumed {
@@ -300,6 +305,95 @@ func TestWorkspaceQuota(t *testing.T) {
300305
verifyQuota(ctx, t, owner, first.OrganizationID.String(), 0, 30)
301306
verifyQuota(ctx, t, owner, second.ID.String(), 0, 15)
302307
})
308+
309+
// ManyWorkspaces uses dbfake and dbgen to insert a scenario into the db.
310+
t.Run("ManyWorkspaces", func(t *testing.T) {
311+
t.Parallel()
312+
313+
owner, db, first := coderdenttest.NewWithDatabase(t, &coderdenttest.Options{
314+
LicenseOptions: &coderdenttest.LicenseOptions{
315+
Features: license.Features{
316+
codersdk.FeatureTemplateRBAC: 1,
317+
codersdk.FeatureMultipleOrganizations: 1,
318+
},
319+
},
320+
})
321+
client, _ := coderdtest.CreateAnotherUser(t, owner, first.OrganizationID, rbac.RoleOwner())
322+
323+
// Prepopulate database. Use dbfake as it is quicker and
324+
// easier than the api.
325+
ctx := testutil.Context(t, testutil.WaitLong)
326+
327+
user := dbgen.User(t, db, database.User{})
328+
noise := dbgen.User(t, db, database.User{})
329+
330+
second := dbfake.Organization(t, db).
331+
Members(user, noise).
332+
EveryoneAllowance(10).
333+
Group(database.Group{
334+
QuotaAllowance: 25,
335+
}, user, noise).
336+
Group(database.Group{
337+
QuotaAllowance: 30,
338+
}, noise).
339+
Do()
340+
341+
third := dbfake.Organization(t, db).
342+
Members(noise).
343+
EveryoneAllowance(7).
344+
Do()
345+
346+
verifyQuotaUser(ctx, t, client, second.Org.ID.String(), user.ID.String(), 0, 35)
347+
verifyQuotaUser(ctx, t, client, second.Org.ID.String(), noise.ID.String(), 0, 65)
348+
349+
// Workspaces owned by the user
350+
consumed := 0
351+
for i := 0; i < 2; i++ {
352+
const cost = 5
353+
dbfake.WorkspaceBuild(t, db,
354+
database.WorkspaceTable{
355+
OwnerID: user.ID,
356+
OrganizationID: second.Org.ID,
357+
}).
358+
Seed(database.WorkspaceBuild{
359+
DailyCost: cost,
360+
}).Do()
361+
consumed += cost
362+
}
363+
364+
// Add some noise
365+
// Workspace by the user in the third org
366+
dbfake.WorkspaceBuild(t, db,
367+
database.WorkspaceTable{
368+
OwnerID: user.ID,
369+
OrganizationID: third.Org.ID,
370+
}).
371+
Seed(database.WorkspaceBuild{
372+
DailyCost: 10,
373+
}).Do()
374+
375+
// Workspace by another user in third org
376+
dbfake.WorkspaceBuild(t, db,
377+
database.WorkspaceTable{
378+
OwnerID: noise.ID,
379+
OrganizationID: third.Org.ID,
380+
}).
381+
Seed(database.WorkspaceBuild{
382+
DailyCost: 10,
383+
}).Do()
384+
385+
// Workspace by another user in second org
386+
dbfake.WorkspaceBuild(t, db,
387+
database.WorkspaceTable{
388+
OwnerID: noise.ID,
389+
OrganizationID: second.Org.ID,
390+
}).
391+
Seed(database.WorkspaceBuild{
392+
DailyCost: 10,
393+
}).Do()
394+
395+
verifyQuotaUser(ctx, t, client, second.Org.ID.String(), user.ID.String(), consumed, 35)
396+
})
303397
}
304398

305399
// nolint:paralleltest,tparallel // Tests must run serially

0 commit comments

Comments
 (0)