From 7b1fc44172241e68be9cc6a0887e948891e0d464 Mon Sep 17 00:00:00 2001 From: Mathias Fredriksson Date: Wed, 6 Aug 2025 12:56:21 +0300 Subject: [PATCH 1/2] fix(coderd/prometheusmetrics): no deleted wsbuilds to reduce db load This change removes the `GetLatestWorkspaceBuilds` query which includes all workspaces for all time (including deleted). This allows us to also stop using `GetProvisionerJobsByIDs` for said builds as the job status is included in `GetWorkspaces` called separately. Fixes coder/internal#717 --- coderd/database/dbauthz/dbauthz.go | 11 --- coderd/database/dbauthz/dbauthz_test.go | 6 -- coderd/database/dbmetrics/querymetrics.go | 7 -- coderd/database/dbmock/dbmock.go | 15 ---- coderd/database/querier.go | 1 - coderd/database/queries.sql.go | 59 --------------- coderd/database/queries/workspacebuilds.sql | 14 ---- coderd/prometheusmetrics/prometheusmetrics.go | 61 +++++---------- .../prometheusmetrics_test.go | 74 +++++++++++++++++++ 9 files changed, 93 insertions(+), 155 deletions(-) diff --git a/coderd/database/dbauthz/dbauthz.go b/coderd/database/dbauthz/dbauthz.go index 99dd9833fa5d6..ab1aadb9fda86 100644 --- a/coderd/database/dbauthz/dbauthz.go +++ b/coderd/database/dbauthz/dbauthz.go @@ -2157,17 +2157,6 @@ func (q *querier) GetLatestWorkspaceBuildByWorkspaceID(ctx context.Context, work return q.db.GetLatestWorkspaceBuildByWorkspaceID(ctx, workspaceID) } -func (q *querier) GetLatestWorkspaceBuilds(ctx context.Context) ([]database.WorkspaceBuild, error) { - // This function is a system function until we implement a join for workspace builds. - // This is because we need to query for all related workspaces to the returned builds. - // This is a very inefficient method of fetching the latest workspace builds. - // We should just join the rbac properties. - if err := q.authorizeContext(ctx, policy.ActionRead, rbac.ResourceSystem); err != nil { - return nil, err - } - return q.db.GetLatestWorkspaceBuilds(ctx) -} - func (q *querier) GetLatestWorkspaceBuildsByWorkspaceIDs(ctx context.Context, ids []uuid.UUID) ([]database.WorkspaceBuild, error) { // This function is a system function until we implement a join for workspace builds. if err := q.authorizeContext(ctx, policy.ActionRead, rbac.ResourceSystem); err != nil { diff --git a/coderd/database/dbauthz/dbauthz_test.go b/coderd/database/dbauthz/dbauthz_test.go index 66a477ebfbaba..3df5af16b18c8 100644 --- a/coderd/database/dbauthz/dbauthz_test.go +++ b/coderd/database/dbauthz/dbauthz_test.go @@ -4024,12 +4024,6 @@ func (s *MethodTestSuite) TestSystemFunctions() { LoginType: l.LoginType, }).Asserts(rbac.ResourceSystem, policy.ActionRead).Returns(l) })) - s.Run("GetLatestWorkspaceBuilds", s.Subtest(func(db database.Store, check *expects) { - dbtestutil.DisableForeignKeysAndTriggers(s.T(), db) - dbgen.WorkspaceBuild(s.T(), db, database.WorkspaceBuild{}) - dbgen.WorkspaceBuild(s.T(), db, database.WorkspaceBuild{}) - check.Args().Asserts(rbac.ResourceSystem, policy.ActionRead) - })) s.Run("GetActiveUserCount", s.Subtest(func(db database.Store, check *expects) { check.Args(false).Asserts(rbac.ResourceSystem, policy.ActionRead).Returns(int64(0)) })) diff --git a/coderd/database/dbmetrics/querymetrics.go b/coderd/database/dbmetrics/querymetrics.go index d0cd0d1ab797d..f31dae92c1371 100644 --- a/coderd/database/dbmetrics/querymetrics.go +++ b/coderd/database/dbmetrics/querymetrics.go @@ -929,13 +929,6 @@ func (m queryMetricsStore) GetLatestWorkspaceBuildByWorkspaceID(ctx context.Cont return build, err } -func (m queryMetricsStore) GetLatestWorkspaceBuilds(ctx context.Context) ([]database.WorkspaceBuild, error) { - start := time.Now() - builds, err := m.s.GetLatestWorkspaceBuilds(ctx) - m.queryLatencies.WithLabelValues("GetLatestWorkspaceBuilds").Observe(time.Since(start).Seconds()) - return builds, err -} - func (m queryMetricsStore) GetLatestWorkspaceBuildsByWorkspaceIDs(ctx context.Context, ids []uuid.UUID) ([]database.WorkspaceBuild, error) { start := time.Now() builds, err := m.s.GetLatestWorkspaceBuildsByWorkspaceIDs(ctx, ids) diff --git a/coderd/database/dbmock/dbmock.go b/coderd/database/dbmock/dbmock.go index e88763ba1eb74..c0e6a27cfcf0e 100644 --- a/coderd/database/dbmock/dbmock.go +++ b/coderd/database/dbmock/dbmock.go @@ -1937,21 +1937,6 @@ func (mr *MockStoreMockRecorder) GetLatestWorkspaceBuildByWorkspaceID(ctx, works return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetLatestWorkspaceBuildByWorkspaceID", reflect.TypeOf((*MockStore)(nil).GetLatestWorkspaceBuildByWorkspaceID), ctx, workspaceID) } -// GetLatestWorkspaceBuilds mocks base method. -func (m *MockStore) GetLatestWorkspaceBuilds(ctx context.Context) ([]database.WorkspaceBuild, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetLatestWorkspaceBuilds", ctx) - ret0, _ := ret[0].([]database.WorkspaceBuild) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// GetLatestWorkspaceBuilds indicates an expected call of GetLatestWorkspaceBuilds. -func (mr *MockStoreMockRecorder) GetLatestWorkspaceBuilds(ctx any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetLatestWorkspaceBuilds", reflect.TypeOf((*MockStore)(nil).GetLatestWorkspaceBuilds), ctx) -} - // GetLatestWorkspaceBuildsByWorkspaceIDs mocks base method. func (m *MockStore) GetLatestWorkspaceBuildsByWorkspaceIDs(ctx context.Context, ids []uuid.UUID) ([]database.WorkspaceBuild, error) { m.ctrl.T.Helper() diff --git a/coderd/database/querier.go b/coderd/database/querier.go index a50bb0bb2192a..ab29ad44945f4 100644 --- a/coderd/database/querier.go +++ b/coderd/database/querier.go @@ -211,7 +211,6 @@ type sqlcQuerier interface { GetLatestCryptoKeyByFeature(ctx context.Context, feature CryptoKeyFeature) (CryptoKey, error) GetLatestWorkspaceAppStatusesByWorkspaceIDs(ctx context.Context, ids []uuid.UUID) ([]WorkspaceAppStatus, error) GetLatestWorkspaceBuildByWorkspaceID(ctx context.Context, workspaceID uuid.UUID) (WorkspaceBuild, error) - GetLatestWorkspaceBuilds(ctx context.Context) ([]WorkspaceBuild, error) GetLatestWorkspaceBuildsByWorkspaceIDs(ctx context.Context, ids []uuid.UUID) ([]WorkspaceBuild, error) GetLicenseByID(ctx context.Context, id int32) (License, error) GetLicenses(ctx context.Context) ([]License, error) diff --git a/coderd/database/queries.sql.go b/coderd/database/queries.sql.go index 2c1381a3b99f1..4eda1f2d84ad8 100644 --- a/coderd/database/queries.sql.go +++ b/coderd/database/queries.sql.go @@ -18393,65 +18393,6 @@ func (q *sqlQuerier) GetLatestWorkspaceBuildByWorkspaceID(ctx context.Context, w return i, err } -const getLatestWorkspaceBuilds = `-- name: GetLatestWorkspaceBuilds :many -SELECT wb.id, wb.created_at, wb.updated_at, wb.workspace_id, wb.template_version_id, wb.build_number, wb.transition, wb.initiator_id, wb.provisioner_state, wb.job_id, wb.deadline, wb.reason, wb.daily_cost, wb.max_deadline, wb.template_version_preset_id, wb.has_ai_task, wb.ai_task_sidebar_app_id, wb.initiator_by_avatar_url, wb.initiator_by_username, wb.initiator_by_name -FROM ( - SELECT - workspace_id, MAX(build_number) as max_build_number - FROM - workspace_build_with_user AS workspace_builds - GROUP BY - workspace_id -) m -JOIN - workspace_build_with_user AS wb -ON m.workspace_id = wb.workspace_id AND m.max_build_number = wb.build_number -` - -func (q *sqlQuerier) GetLatestWorkspaceBuilds(ctx context.Context) ([]WorkspaceBuild, error) { - rows, err := q.db.QueryContext(ctx, getLatestWorkspaceBuilds) - if err != nil { - return nil, err - } - defer rows.Close() - var items []WorkspaceBuild - for rows.Next() { - var i WorkspaceBuild - if err := rows.Scan( - &i.ID, - &i.CreatedAt, - &i.UpdatedAt, - &i.WorkspaceID, - &i.TemplateVersionID, - &i.BuildNumber, - &i.Transition, - &i.InitiatorID, - &i.ProvisionerState, - &i.JobID, - &i.Deadline, - &i.Reason, - &i.DailyCost, - &i.MaxDeadline, - &i.TemplateVersionPresetID, - &i.HasAITask, - &i.AITaskSidebarAppID, - &i.InitiatorByAvatarUrl, - &i.InitiatorByUsername, - &i.InitiatorByName, - ); 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 getLatestWorkspaceBuildsByWorkspaceIDs = `-- name: GetLatestWorkspaceBuildsByWorkspaceIDs :many SELECT wb.id, wb.created_at, wb.updated_at, wb.workspace_id, wb.template_version_id, wb.build_number, wb.transition, wb.initiator_id, wb.provisioner_state, wb.job_id, wb.deadline, wb.reason, wb.daily_cost, wb.max_deadline, wb.template_version_preset_id, wb.has_ai_task, wb.ai_task_sidebar_app_id, wb.initiator_by_avatar_url, wb.initiator_by_username, wb.initiator_by_name FROM ( diff --git a/coderd/database/queries/workspacebuilds.sql b/coderd/database/queries/workspacebuilds.sql index be76b6642df1f..be7bec5fa08f2 100644 --- a/coderd/database/queries/workspacebuilds.sql +++ b/coderd/database/queries/workspacebuilds.sql @@ -91,20 +91,6 @@ JOIN workspace_build_with_user AS wb ON m.workspace_id = wb.workspace_id AND m.max_build_number = wb.build_number; --- name: GetLatestWorkspaceBuilds :many -SELECT wb.* -FROM ( - SELECT - workspace_id, MAX(build_number) as max_build_number - FROM - workspace_build_with_user AS workspace_builds - GROUP BY - workspace_id -) m -JOIN - workspace_build_with_user AS wb -ON m.workspace_id = wb.workspace_id AND m.max_build_number = wb.build_number; - -- name: InsertWorkspaceBuild :exec INSERT INTO workspace_builds ( diff --git a/coderd/prometheusmetrics/prometheusmetrics.go b/coderd/prometheusmetrics/prometheusmetrics.go index 4fd2cfda607ed..321d8d5a54a99 100644 --- a/coderd/prometheusmetrics/prometheusmetrics.go +++ b/coderd/prometheusmetrics/prometheusmetrics.go @@ -168,59 +168,37 @@ func Workspaces(ctx context.Context, logger slog.Logger, registerer prometheus.R ctx, cancelFunc := context.WithCancel(ctx) done := make(chan struct{}) - updateWorkspaceTotals := func() { - builds, err := db.GetLatestWorkspaceBuilds(ctx) - if err != nil { - if errors.Is(err, sql.ErrNoRows) { - // clear all series if there are no database entries - workspaceLatestBuildTotals.Reset() - } else { - logger.Warn(ctx, "failed to load latest workspace builds", slog.Error(err)) - } - return - } - jobIDs := make([]uuid.UUID, 0, len(builds)) - for _, build := range builds { - jobIDs = append(jobIDs, build.JobID) - } - jobs, err := db.GetProvisionerJobsByIDs(ctx, jobIDs) - if err != nil { - ids := make([]string, 0, len(jobIDs)) - for _, id := range jobIDs { - ids = append(ids, id.String()) - } - - logger.Warn(ctx, "failed to load provisioner jobs", slog.F("ids", ids), slog.Error(err)) - return - } - - workspaceLatestBuildTotals.Reset() - for _, job := range jobs { - status := codersdk.ProvisionerJobStatus(job.JobStatus) - workspaceLatestBuildTotals.WithLabelValues(string(status)).Add(1) - // TODO: deprecated: remove in the future - workspaceLatestBuildTotalsDeprecated.WithLabelValues(string(status)).Add(1) - } - } - - updateWorkspaceStatuses := func() { + updateWorkspaceMetrics := func() { ws, err := db.GetWorkspaces(ctx, database.GetWorkspacesParams{ Deleted: false, WithSummary: false, }) if err != nil { if errors.Is(err, sql.ErrNoRows) { - // clear all series if there are no database entries + workspaceLatestBuildTotals.Reset() workspaceLatestBuildStatuses.Reset() + } else { + logger.Warn(ctx, "failed to load active workspaces for metrics", slog.Error(err)) } - - logger.Warn(ctx, "failed to load active workspaces", slog.Error(err)) return } + workspaceLatestBuildTotals.Reset() workspaceLatestBuildStatuses.Reset() + for _, w := range ws { - workspaceLatestBuildStatuses.WithLabelValues(string(w.LatestBuildStatus), w.TemplateName, w.TemplateVersionName.String, w.OwnerUsername, string(w.LatestBuildTransition)).Add(1) + status := string(w.LatestBuildStatus) + workspaceLatestBuildTotals.WithLabelValues(status).Add(1) + // TODO: deprecated: remove in the future + workspaceLatestBuildTotalsDeprecated.WithLabelValues(status).Add(1) + + workspaceLatestBuildStatuses.WithLabelValues( + status, + w.TemplateName, + w.TemplateVersionName.String, + w.OwnerUsername, + string(w.LatestBuildTransition), + ).Add(1) } } @@ -230,8 +208,7 @@ func Workspaces(ctx context.Context, logger slog.Logger, registerer prometheus.R doTick := func() { defer ticker.Reset(duration) - updateWorkspaceTotals() - updateWorkspaceStatuses() + updateWorkspaceMetrics() } go func() { diff --git a/coderd/prometheusmetrics/prometheusmetrics_test.go b/coderd/prometheusmetrics/prometheusmetrics_test.go index 473dbf46bd958..28046c1dff3fb 100644 --- a/coderd/prometheusmetrics/prometheusmetrics_test.go +++ b/coderd/prometheusmetrics/prometheusmetrics_test.go @@ -247,6 +247,32 @@ func TestWorkspaceLatestBuildTotals(t *testing.T) { codersdk.ProvisionerJobSucceeded: 3, codersdk.ProvisionerJobRunning: 1, }, + }, { + Name: "MultipleWithDeleted", + Database: func() database.Store { + db, _ := dbtestutil.NewDB(t) + u := dbgen.User(t, db, database.User{}) + org := dbgen.Organization(t, db, database.Organization{}) + insertCanceled(t, db, u, org) + insertFailed(t, db, u, org) + insertSuccess(t, db, u, org) + insertRunning(t, db, u, org) + + // Verify that deleted workspaces/builds are NOT counted in metrics. + n, err := cryptorand.Intn(5) + require.NoError(t, err) + for range 1 + n { + insertDeleted(t, db, u, org) + } + return db + }, + Total: 4, // Only non-deleted workspaces should be counted + Status: map[codersdk.ProvisionerJobStatus]int{ + codersdk.ProvisionerJobCanceled: 1, + codersdk.ProvisionerJobFailed: 1, + codersdk.ProvisionerJobSucceeded: 1, + codersdk.ProvisionerJobRunning: 1, + }, }} { t.Run(tc.Name, func(t *testing.T) { t.Parallel() @@ -323,6 +349,33 @@ func TestWorkspaceLatestBuildStatuses(t *testing.T) { codersdk.ProvisionerJobSucceeded: 3, codersdk.ProvisionerJobRunning: 1, }, + }, { + Name: "MultipleWithDeleted", + Database: func() database.Store { + db, _ := dbtestutil.NewDB(t) + u := dbgen.User(t, db, database.User{}) + org := dbgen.Organization(t, db, database.Organization{}) + insertTemplates(t, db, u, org) + insertCanceled(t, db, u, org) + insertFailed(t, db, u, org) + insertSuccess(t, db, u, org) + insertRunning(t, db, u, org) + + // Verify that deleted workspaces/builds are NOT counted in metrics. + n, err := cryptorand.Intn(5) + require.NoError(t, err) + for range 1 + n { + insertDeleted(t, db, u, org) + } + return db + }, + ExpectedWorkspaces: 4, // Only non-deleted workspaces should be counted + ExpectedStatuses: map[codersdk.ProvisionerJobStatus]int{ + codersdk.ProvisionerJobCanceled: 1, + codersdk.ProvisionerJobFailed: 1, + codersdk.ProvisionerJobSucceeded: 1, + codersdk.ProvisionerJobRunning: 1, + }, }} { t.Run(tc.Name, func(t *testing.T) { t.Parallel() @@ -907,3 +960,24 @@ func insertSuccess(t *testing.T, db database.Store, u database.User, org databas }) require.NoError(t, err) } + +func insertDeleted(t *testing.T, db database.Store, u database.User, org database.Organization) { + job := insertRunning(t, db, u, org) + err := db.UpdateProvisionerJobWithCompleteByID(context.Background(), database.UpdateProvisionerJobWithCompleteByIDParams{ + ID: job.ID, + CompletedAt: sql.NullTime{ + Time: dbtime.Now(), + Valid: true, + }, + }) + require.NoError(t, err) + + build, err := db.GetWorkspaceBuildByJobID(context.Background(), job.ID) + require.NoError(t, err) + + err = db.UpdateWorkspaceDeletedByID(context.Background(), database.UpdateWorkspaceDeletedByIDParams{ + ID: build.WorkspaceID, + Deleted: true, + }) + require.NoError(t, err) +} From c304f4ada01cfadf0a115e80e3de8d6a353976f9 Mon Sep 17 00:00:00 2001 From: Mathias Fredriksson Date: Fri, 8 Aug 2025 15:06:17 +0000 Subject: [PATCH 2/2] update help for metrics --- coderd/prometheusmetrics/prometheusmetrics.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/coderd/prometheusmetrics/prometheusmetrics.go b/coderd/prometheusmetrics/prometheusmetrics.go index 321d8d5a54a99..cda274145e159 100644 --- a/coderd/prometheusmetrics/prometheusmetrics.go +++ b/coderd/prometheusmetrics/prometheusmetrics.go @@ -150,7 +150,7 @@ func Workspaces(ctx context.Context, logger slog.Logger, registerer prometheus.R Namespace: "coderd", Subsystem: "api", Name: "workspace_latest_build", - Help: "The current number of workspace builds by status.", + Help: "The current number of workspace builds by status for all non-deleted workspaces.", }, []string{"status"}) if err := registerer.Register(workspaceLatestBuildTotals); err != nil { return nil, err @@ -159,7 +159,7 @@ func Workspaces(ctx context.Context, logger slog.Logger, registerer prometheus.R workspaceLatestBuildStatuses := prometheus.NewGaugeVec(prometheus.GaugeOpts{ Namespace: "coderd", Name: "workspace_latest_build_status", - Help: "The current workspace statuses by template, transition, and owner.", + Help: "The current workspace statuses by template, transition, and owner for all non-deleted workspaces.", }, []string{"status", "template_name", "template_version", "workspace_owner", "workspace_transition"}) if err := registerer.Register(workspaceLatestBuildStatuses); err != nil { return nil, err pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy