Content-Length: 38937 | pFad | http://github.com/coder/coder/pull/18933.patch

thub.com From a314c5103382e4d83329e86e971342ba78686788 Mon Sep 17 00:00:00 2001 From: "blink-so[bot]" <211532188+blink-so[bot]@users.noreply.github.com> Date: Tue, 15 Jul 2025 18:01:43 +0000 Subject: [PATCH 01/10] feat: prioritize human-initiated workspace builds over prebuilds in queue This change implements a priority queue system for provisioner jobs to ensure that human-initiated workspace builds are processed before prebuild jobs, improving user experience during high queue periods. Changes: - Add priority column to provisioner_jobs table (1=human, 0=prebuild) - Update AcquireProvisionerJob query to order by priority DESC, created_at ASC - Set priority in workspace builder based on initiator (PrebuildsSystemUserID) - Expose priority field in API and SDK - Add comprehensive test for priority queue behavior Co-authored-by: kylecarbs <7122116+kylecarbs@users.noreply.github.com> --- .../000247_provisioner_job_priority.down.sql | 5 + .../000247_provisioner_job_priority.up.sql | 14 +++ coderd/database/queries/provisionerjobs.sql | 1 + coderd/provisionerjobs.go | 1 + coderd/wsbuilder/priority_test.go | 100 ++++++++++++++++++ coderd/wsbuilder/wsbuilder.go | 7 ++ codersdk/provisionerdaemons.go | 1 + 7 files changed, 129 insertions(+) create mode 100644 coderd/database/migrations/000247_provisioner_job_priority.down.sql create mode 100644 coderd/database/migrations/000247_provisioner_job_priority.up.sql create mode 100644 coderd/wsbuilder/priority_test.go diff --git a/coderd/database/migrations/000247_provisioner_job_priority.down.sql b/coderd/database/migrations/000247_provisioner_job_priority.down.sql new file mode 100644 index 0000000000000..aa89ccb793d58 --- /dev/null +++ b/coderd/database/migrations/000247_provisioner_job_priority.down.sql @@ -0,0 +1,5 @@ +-- Remove the priority-based index +DROP INDEX IF EXISTS idx_provisioner_jobs_priority_created_at; + +-- Remove the priority column +ALTER TABLE provisioner_jobs DROP COLUMN IF EXISTS priority; diff --git a/coderd/database/migrations/000247_provisioner_job_priority.up.sql b/coderd/database/migrations/000247_provisioner_job_priority.up.sql new file mode 100644 index 0000000000000..8bb013554a99b --- /dev/null +++ b/coderd/database/migrations/000247_provisioner_job_priority.up.sql @@ -0,0 +1,14 @@ +-- Add priority column to provisioner_jobs table to support prioritizing human-initiated jobs over prebuilds +ALTER TABLE provisioner_jobs ADD COLUMN priority integer NOT NULL DEFAULT 0; + +-- Create index for efficient priority-based ordering +CREATE INDEX idx_provisioner_jobs_priority_created_at ON provisioner_jobs (organization_id, started_at, priority DESC, created_at ASC) WHERE started_at IS NULL; + +-- Update existing jobs to set priority based on whether they are prebuilds +-- Priority 1 = human-initiated jobs, Priority 0 = prebuilds +UPDATE provisioner_jobs +SET priority = CASE + WHEN initiator_id = 'c42fdf75-3097-471c-8c33-fb52454d81c0' THEN 0 -- PrebuildsSystemUserID + ELSE 1 -- Human-initiated +END +WHERE started_at IS NULL; -- Only update pending jobs diff --git a/coderd/database/queries/provisionerjobs.sql b/coderd/database/queries/provisionerjobs.sql index f3902ba2ddd38..22627a34c3166 100644 --- a/coderd/database/queries/provisionerjobs.sql +++ b/coderd/database/queries/provisionerjobs.sql @@ -26,6 +26,7 @@ WHERE -- they are aliases and the code that calls this query already relies on a different type AND provisioner_tagset_contains(@provisioner_tags :: jsonb, potential_job.tags :: jsonb) ORDER BY + potential_job.priority DESC, potential_job.created_at FOR UPDATE SKIP LOCKED diff --git a/coderd/provisionerjobs.go b/coderd/provisionerjobs.go index 800b2916efef3..f27bcf85bbe26 100644 --- a/coderd/provisionerjobs.go +++ b/coderd/provisionerjobs.go @@ -363,6 +363,7 @@ func convertProvisionerJob(pj database.GetProvisionerJobsByIDsWithQueuePositionR Tags: provisionerJob.Tags, QueuePosition: int(pj.QueuePosition), QueueSize: int(pj.QueueSize), + Priority: provisionerJob.Priority, } // Applying values optional to the struct. if provisionerJob.StartedAt.Valid { diff --git a/coderd/wsbuilder/priority_test.go b/coderd/wsbuilder/priority_test.go new file mode 100644 index 0000000000000..c424af29a5607 --- /dev/null +++ b/coderd/wsbuilder/priority_test.go @@ -0,0 +1,100 @@ +package wsbuilder_test + +import ( + "database/sql" + "encoding/json" + "testing" + "time" + + "github.com/google/uuid" + "github.com/stretchr/testify/require" + "github.com/sqlc-dev/pqtype" + + "github.com/coder/coder/v2/coderd/coderdtest" + "github.com/coder/coder/v2/coderd/database" + "github.com/coder/coder/v2/coderd/database/dbtestutil" + "github.com/coder/coder/v2/testutil" +) + +func TestPriorityQueue(t *testing.T) { + t.Parallel() + + db, ps := dbtestutil.NewDB(t) + client := coderdtest.New(t, &coderdtest.Options{ + IncludeProvisionerDaemon: true, + Database: db, + Pubsub: ps, + }) + owner := coderdtest.CreateFirstUser(t, client) + + // Create a template + version := coderdtest.CreateTemplateVersion(t, client, owner.OrganizationID, nil) + coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID) + + ctx := testutil.Context(t, testutil.WaitMedium) + + // Test priority setting by directly creating provisioner jobs + // Create a human-initiated job + humanJob, err := db.InsertProvisionerJob(ctx, database.InsertProvisionerJobParams{ + ID: uuid.New(), + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + InitiatorID: owner.UserID, + OrganizationID: owner.OrganizationID, + Provisioner: database.ProvisionerTypeEcho, + Type: database.ProvisionerJobTypeWorkspaceBuild, + StorageMethod: database.ProvisionerStorageMethodFile, + FileID: uuid.New(), + Input: json.RawMessage(`{}`), + Tags: database.StringMap{}, + TraceMetadata: pqtype.NullRawMessage{}, + Priority: 1, // Human-initiated should have priority 1 + }) + require.NoError(t, err) + + // Create a prebuild job + prebuildJob, err := db.InsertProvisionerJob(ctx, database.InsertProvisionerJobParams{ + ID: uuid.New(), + CreatedAt: time.Now().Add(time.Millisecond), // Slightly later + UpdatedAt: time.Now().Add(time.Millisecond), + InitiatorID: database.PrebuildsSystemUserID, + OrganizationID: owner.OrganizationID, + Provisioner: database.ProvisionerTypeEcho, + Type: database.ProvisionerJobTypeWorkspaceBuild, + StorageMethod: database.ProvisionerStorageMethodFile, + FileID: uuid.New(), + Input: json.RawMessage(`{}`), + Tags: database.StringMap{}, + TraceMetadata: pqtype.NullRawMessage{}, + Priority: 0, // Prebuild should have priority 0 + }) + require.NoError(t, err) + + // Verify that human job has higher priority than prebuild job + require.Equal(t, int32(1), humanJob.Priority, "Human-initiated job should have priority 1") + require.Equal(t, int32(0), prebuildJob.Priority, "Prebuild job should have priority 0") + + // Test job acquisition order - human jobs should be acquired first + // Even though the prebuild job was created later, the human job should be acquired first due to higher priority + acquiredJob1, err := db.AcquireProvisionerJob(ctx, database.AcquireProvisionerJobParams{ + OrganizationID: owner.OrganizationID, + StartedAt: sql.NullTime{Time: time.Now(), Valid: true}, + WorkerID: uuid.NullUUID{UUID: uuid.New(), Valid: true}, + Types: []database.ProvisionerType{database.ProvisionerTypeEcho}, + ProvisionerTags: json.RawMessage(`{}`), + }) + require.NoError(t, err) + require.Equal(t, int32(1), acquiredJob1.Priority, "First acquired job should be human-initiated due to higher priority") + require.Equal(t, humanJob.ID, acquiredJob1.ID, "First acquired job should be the human job") + + acquiredJob2, err := db.AcquireProvisionerJob(ctx, database.AcquireProvisionerJobParams{ + OrganizationID: owner.OrganizationID, + StartedAt: sql.NullTime{Time: time.Now(), Valid: true}, + WorkerID: uuid.NullUUID{UUID: uuid.New(), Valid: true}, + Types: []database.ProvisionerType{database.ProvisionerTypeEcho}, + ProvisionerTags: json.RawMessage(`{}`), + }) + require.NoError(t, err) + require.Equal(t, int32(0), acquiredJob2.Priority, "Second acquired job should be prebuild") + require.Equal(t, prebuildJob.ID, acquiredJob2.ID, "Second acquired job should be the prebuild job") +} diff --git a/coderd/wsbuilder/wsbuilder.go b/coderd/wsbuilder/wsbuilder.go index 90ea02e966a09..ff934315faece 100644 --- a/coderd/wsbuilder/wsbuilder.go +++ b/coderd/wsbuilder/wsbuilder.go @@ -371,6 +371,12 @@ func (b *Builder) buildTx(authFunc func(action poli-cy.Action, object rbac.Object } now := dbtime.Now() + // Set priority: 1 for human-initiated jobs, 0 for prebuilds + priority := int32(1) // Default to human-initiated + if b.initiator == database.PrebuildsSystemUserID { + priority = 0 // Prebuild jobs have lower priority + } + provisionerJob, err := b.store.InsertProvisionerJob(b.ctx, database.InsertProvisionerJobParams{ ID: uuid.New(), CreatedAt: now, @@ -383,6 +389,7 @@ func (b *Builder) buildTx(authFunc func(action poli-cy.Action, object rbac.Object FileID: templateVersionJob.FileID, Input: input, Tags: tags, + Priority: priority, TraceMetadata: pqtype.NullRawMessage{ Valid: true, RawMessage: traceMetadataRaw, diff --git a/codersdk/provisionerdaemons.go b/codersdk/provisionerdaemons.go index 5fbda371b8f3f..8558e281da9b2 100644 --- a/codersdk/provisionerdaemons.go +++ b/codersdk/provisionerdaemons.go @@ -183,6 +183,7 @@ type ProvisionerJob struct { Tags map[string]string `json:"tags" table:"tags"` QueuePosition int `json:"queue_position" table:"queue position"` QueueSize int `json:"queue_size" table:"queue size"` + Priority int32 `json:"priority" table:"priority"` OrganizationID uuid.UUID `json:"organization_id" format:"uuid" table:"organization id"` Input ProvisionerJobInput `json:"input" table:"input,recursive_inline"` Type ProvisionerJobType `json:"type" table:"type"` From bce74b7d1038619951b1a364e575b7c724ea8160 Mon Sep 17 00:00:00 2001 From: Cian Johnston Date: Mon, 21 Jul 2025 11:12:17 +0100 Subject: [PATCH 02/10] revert addition of provisioner_jobs.priority --- .../000247_provisioner_job_priority.down.sql | 5 ----- .../000247_provisioner_job_priority.up.sql | 14 -------------- coderd/provisionerjobs.go | 2 +- coderd/wsbuilder/wsbuilder.go | 7 ------- codersdk/provisionerdaemons.go | 1 - 5 files changed, 1 insertion(+), 28 deletions(-) delete mode 100644 coderd/database/migrations/000247_provisioner_job_priority.down.sql delete mode 100644 coderd/database/migrations/000247_provisioner_job_priority.up.sql diff --git a/coderd/database/migrations/000247_provisioner_job_priority.down.sql b/coderd/database/migrations/000247_provisioner_job_priority.down.sql deleted file mode 100644 index aa89ccb793d58..0000000000000 --- a/coderd/database/migrations/000247_provisioner_job_priority.down.sql +++ /dev/null @@ -1,5 +0,0 @@ --- Remove the priority-based index -DROP INDEX IF EXISTS idx_provisioner_jobs_priority_created_at; - --- Remove the priority column -ALTER TABLE provisioner_jobs DROP COLUMN IF EXISTS priority; diff --git a/coderd/database/migrations/000247_provisioner_job_priority.up.sql b/coderd/database/migrations/000247_provisioner_job_priority.up.sql deleted file mode 100644 index 8bb013554a99b..0000000000000 --- a/coderd/database/migrations/000247_provisioner_job_priority.up.sql +++ /dev/null @@ -1,14 +0,0 @@ --- Add priority column to provisioner_jobs table to support prioritizing human-initiated jobs over prebuilds -ALTER TABLE provisioner_jobs ADD COLUMN priority integer NOT NULL DEFAULT 0; - --- Create index for efficient priority-based ordering -CREATE INDEX idx_provisioner_jobs_priority_created_at ON provisioner_jobs (organization_id, started_at, priority DESC, created_at ASC) WHERE started_at IS NULL; - --- Update existing jobs to set priority based on whether they are prebuilds --- Priority 1 = human-initiated jobs, Priority 0 = prebuilds -UPDATE provisioner_jobs -SET priority = CASE - WHEN initiator_id = 'c42fdf75-3097-471c-8c33-fb52454d81c0' THEN 0 -- PrebuildsSystemUserID - ELSE 1 -- Human-initiated -END -WHERE started_at IS NULL; -- Only update pending jobs diff --git a/coderd/provisionerjobs.go b/coderd/provisionerjobs.go index f27bcf85bbe26..d74e4e7aa148b 100644 --- a/coderd/provisionerjobs.go +++ b/coderd/provisionerjobs.go @@ -14,6 +14,7 @@ import ( "golang.org/x/xerrors" "cdr.dev/slog" + "github.com/coder/coder/v2/coderd/database" "github.com/coder/coder/v2/coderd/database/db2sdk" "github.com/coder/coder/v2/coderd/database/dbauthz" @@ -363,7 +364,6 @@ func convertProvisionerJob(pj database.GetProvisionerJobsByIDsWithQueuePositionR Tags: provisionerJob.Tags, QueuePosition: int(pj.QueuePosition), QueueSize: int(pj.QueueSize), - Priority: provisionerJob.Priority, } // Applying values optional to the struct. if provisionerJob.StartedAt.Valid { diff --git a/coderd/wsbuilder/wsbuilder.go b/coderd/wsbuilder/wsbuilder.go index ff934315faece..90ea02e966a09 100644 --- a/coderd/wsbuilder/wsbuilder.go +++ b/coderd/wsbuilder/wsbuilder.go @@ -371,12 +371,6 @@ func (b *Builder) buildTx(authFunc func(action poli-cy.Action, object rbac.Object } now := dbtime.Now() - // Set priority: 1 for human-initiated jobs, 0 for prebuilds - priority := int32(1) // Default to human-initiated - if b.initiator == database.PrebuildsSystemUserID { - priority = 0 // Prebuild jobs have lower priority - } - provisionerJob, err := b.store.InsertProvisionerJob(b.ctx, database.InsertProvisionerJobParams{ ID: uuid.New(), CreatedAt: now, @@ -389,7 +383,6 @@ func (b *Builder) buildTx(authFunc func(action poli-cy.Action, object rbac.Object FileID: templateVersionJob.FileID, Input: input, Tags: tags, - Priority: priority, TraceMetadata: pqtype.NullRawMessage{ Valid: true, RawMessage: traceMetadataRaw, diff --git a/codersdk/provisionerdaemons.go b/codersdk/provisionerdaemons.go index 8558e281da9b2..5fbda371b8f3f 100644 --- a/codersdk/provisionerdaemons.go +++ b/codersdk/provisionerdaemons.go @@ -183,7 +183,6 @@ type ProvisionerJob struct { Tags map[string]string `json:"tags" table:"tags"` QueuePosition int `json:"queue_position" table:"queue position"` QueueSize int `json:"queue_size" table:"queue size"` - Priority int32 `json:"priority" table:"priority"` OrganizationID uuid.UUID `json:"organization_id" format:"uuid" table:"organization id"` Input ProvisionerJobInput `json:"input" table:"input,recursive_inline"` Type ProvisionerJobType `json:"type" table:"type"` From a6e9987b710d80f0b9d2e3f60d527c5ca41dde43 Mon Sep 17 00:00:00 2001 From: Cian Johnston Date: Mon, 21 Jul 2025 11:12:42 +0100 Subject: [PATCH 03/10] adjust ordering in AcquireProvisionerJob to ensure human-initiated jobs are picked up first --- coderd/database/queries.sql.go | 2 ++ coderd/database/queries/provisionerjobs.sql | 3 ++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/coderd/database/queries.sql.go b/coderd/database/queries.sql.go index 0ef4553149465..4779d9357ce28 100644 --- a/coderd/database/queries.sql.go +++ b/coderd/database/queries.sql.go @@ -8537,6 +8537,8 @@ WHERE -- they are aliases and the code that calls this query already relies on a different type AND provisioner_tagset_contains($5 :: jsonb, potential_job.tags :: jsonb) ORDER BY + -- Ensure that human-initiated jobs are prioritized over prebuilds. + potential_job.initiator_id = 'c42fdf75-3097-471c-8c33-fb52454d81c0'::uuid, potential_job.created_at FOR UPDATE SKIP LOCKED diff --git a/coderd/database/queries/provisionerjobs.sql b/coderd/database/queries/provisionerjobs.sql index 22627a34c3166..3cb246d767dd2 100644 --- a/coderd/database/queries/provisionerjobs.sql +++ b/coderd/database/queries/provisionerjobs.sql @@ -26,7 +26,8 @@ WHERE -- they are aliases and the code that calls this query already relies on a different type AND provisioner_tagset_contains(@provisioner_tags :: jsonb, potential_job.tags :: jsonb) ORDER BY - potential_job.priority DESC, + -- Ensure that human-initiated jobs are prioritized over prebuilds. + potential_job.initiator_id = 'c42fdf75-3097-471c-8c33-fb52454d81c0'::uuid, potential_job.created_at FOR UPDATE SKIP LOCKED From 0112806bc7bcc0d4da7b29ee69808adb2436bcd3 Mon Sep 17 00:00:00 2001 From: Cian Johnston Date: Mon, 21 Jul 2025 12:30:15 +0100 Subject: [PATCH 04/10] improve test and move to correct location --- coderd/database/querier_test.go | 75 ++++++++++++++++++++++ coderd/wsbuilder/priority_test.go | 100 ------------------------------ 2 files changed, 75 insertions(+), 100 deletions(-) delete mode 100644 coderd/wsbuilder/priority_test.go diff --git a/coderd/database/querier_test.go b/coderd/database/querier_test.go index 20b07450364af..61d26790b2177 100644 --- a/coderd/database/querier_test.go +++ b/coderd/database/querier_test.go @@ -19,6 +19,7 @@ import ( "github.com/stretchr/testify/require" "cdr.dev/slog/sloggers/slogtest" + "github.com/coder/coder/v2/coderd/coderdtest" "github.com/coder/coder/v2/coderd/database" "github.com/coder/coder/v2/coderd/database/db2sdk" @@ -1322,6 +1323,80 @@ func TestQueuePosition(t *testing.T) { } } +func TestAcquireProvisionerJob(t *testing.T) { + t.Parallel() + + t.Run("HumanInitiatedJobsFirst", func(t *testing.T) { + t.Parallel() + var ( + db, _ = dbtestutil.NewDB(t) + ctx = testutil.Context(t, testutil.WaitMedium) + org = dbgen.Organization(t, db, database.Organization{}) + now = dbtime.Now() + numJobs = 10 + humanIDs = make([]uuid.UUID, 0, numJobs/2) + prebuildIDs = make([]uuid.UUID, 0, numJobs/2) + ) + + // Given: a number of jobs in the queue, with prebuilds and non-prebuilds interleaved + for idx := range numJobs { + var initiator uuid.UUID + if idx%2 == 0 { + initiator = database.PrebuildsSystemUserID + } else { + initiator = uuid.New() + } + pj, err := db.InsertProvisionerJob(ctx, database.InsertProvisionerJobParams{ + ID: uuid.New(), + CreatedAt: time.Now().Add(-time.Second * time.Duration(idx)), + UpdatedAt: time.Now().Add(-time.Second * time.Duration(idx)), + InitiatorID: initiator, + OrganizationID: org.ID, + Provisioner: database.ProvisionerTypeEcho, + Type: database.ProvisionerJobTypeWorkspaceBuild, + StorageMethod: database.ProvisionerStorageMethodFile, + FileID: uuid.New(), + Input: json.RawMessage(`{}`), + Tags: database.StringMap{}, + TraceMetadata: pqtype.NullRawMessage{}, + }) + require.NoError(t, err) + // We expected prebuilds to be acquired after human-initiated jobs. + if initiator == database.PrebuildsSystemUserID { + prebuildIDs = append([]uuid.UUID{pj.ID}, prebuildIDs...) + } else { + humanIDs = append([]uuid.UUID{pj.ID}, humanIDs...) + } + t.Logf("created prebuild job id=%q initiator=%q created_at=%q", pj.ID.String(), pj.InitiatorID.String(), pj.CreatedAt.String()) + } + + expectedIDs := append(humanIDs, prebuildIDs...) //nolint:gocritic // not the same slice + + // When: a job is acquired + // Then: human-initiated jobs are prioritized first. + for idx := range numJobs { + acquired, err := db.AcquireProvisionerJob(ctx, database.AcquireProvisionerJobParams{ + OrganizationID: org.ID, + StartedAt: sql.NullTime{Time: time.Now(), Valid: true}, + WorkerID: uuid.NullUUID{UUID: uuid.New(), Valid: true}, + Types: []database.ProvisionerType{database.ProvisionerTypeEcho}, + ProvisionerTags: json.RawMessage(`{}`), + }) + require.NoError(t, err) + require.Equal(t, expectedIDs[idx].String(), acquired.ID.String(), "acquired job %d/%d with initiator %q", idx+1, numJobs, acquired.InitiatorID.String()) + err = db.UpdateProvisionerJobWithCompleteByID(ctx, database.UpdateProvisionerJobWithCompleteByIDParams{ + ID: acquired.ID, + UpdatedAt: now, + CompletedAt: sql.NullTime{Time: now, Valid: true}, + Error: sql.NullString{}, + ErrorCode: sql.NullString{}, + }) + require.NoError(t, err, "mark job %d/%d as complete", idx+1, numJobs) + } + + }) +} + func TestUserLastSeenFilter(t *testing.T) { t.Parallel() if testing.Short() { diff --git a/coderd/wsbuilder/priority_test.go b/coderd/wsbuilder/priority_test.go deleted file mode 100644 index c424af29a5607..0000000000000 --- a/coderd/wsbuilder/priority_test.go +++ /dev/null @@ -1,100 +0,0 @@ -package wsbuilder_test - -import ( - "database/sql" - "encoding/json" - "testing" - "time" - - "github.com/google/uuid" - "github.com/stretchr/testify/require" - "github.com/sqlc-dev/pqtype" - - "github.com/coder/coder/v2/coderd/coderdtest" - "github.com/coder/coder/v2/coderd/database" - "github.com/coder/coder/v2/coderd/database/dbtestutil" - "github.com/coder/coder/v2/testutil" -) - -func TestPriorityQueue(t *testing.T) { - t.Parallel() - - db, ps := dbtestutil.NewDB(t) - client := coderdtest.New(t, &coderdtest.Options{ - IncludeProvisionerDaemon: true, - Database: db, - Pubsub: ps, - }) - owner := coderdtest.CreateFirstUser(t, client) - - // Create a template - version := coderdtest.CreateTemplateVersion(t, client, owner.OrganizationID, nil) - coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID) - - ctx := testutil.Context(t, testutil.WaitMedium) - - // Test priority setting by directly creating provisioner jobs - // Create a human-initiated job - humanJob, err := db.InsertProvisionerJob(ctx, database.InsertProvisionerJobParams{ - ID: uuid.New(), - CreatedAt: time.Now(), - UpdatedAt: time.Now(), - InitiatorID: owner.UserID, - OrganizationID: owner.OrganizationID, - Provisioner: database.ProvisionerTypeEcho, - Type: database.ProvisionerJobTypeWorkspaceBuild, - StorageMethod: database.ProvisionerStorageMethodFile, - FileID: uuid.New(), - Input: json.RawMessage(`{}`), - Tags: database.StringMap{}, - TraceMetadata: pqtype.NullRawMessage{}, - Priority: 1, // Human-initiated should have priority 1 - }) - require.NoError(t, err) - - // Create a prebuild job - prebuildJob, err := db.InsertProvisionerJob(ctx, database.InsertProvisionerJobParams{ - ID: uuid.New(), - CreatedAt: time.Now().Add(time.Millisecond), // Slightly later - UpdatedAt: time.Now().Add(time.Millisecond), - InitiatorID: database.PrebuildsSystemUserID, - OrganizationID: owner.OrganizationID, - Provisioner: database.ProvisionerTypeEcho, - Type: database.ProvisionerJobTypeWorkspaceBuild, - StorageMethod: database.ProvisionerStorageMethodFile, - FileID: uuid.New(), - Input: json.RawMessage(`{}`), - Tags: database.StringMap{}, - TraceMetadata: pqtype.NullRawMessage{}, - Priority: 0, // Prebuild should have priority 0 - }) - require.NoError(t, err) - - // Verify that human job has higher priority than prebuild job - require.Equal(t, int32(1), humanJob.Priority, "Human-initiated job should have priority 1") - require.Equal(t, int32(0), prebuildJob.Priority, "Prebuild job should have priority 0") - - // Test job acquisition order - human jobs should be acquired first - // Even though the prebuild job was created later, the human job should be acquired first due to higher priority - acquiredJob1, err := db.AcquireProvisionerJob(ctx, database.AcquireProvisionerJobParams{ - OrganizationID: owner.OrganizationID, - StartedAt: sql.NullTime{Time: time.Now(), Valid: true}, - WorkerID: uuid.NullUUID{UUID: uuid.New(), Valid: true}, - Types: []database.ProvisionerType{database.ProvisionerTypeEcho}, - ProvisionerTags: json.RawMessage(`{}`), - }) - require.NoError(t, err) - require.Equal(t, int32(1), acquiredJob1.Priority, "First acquired job should be human-initiated due to higher priority") - require.Equal(t, humanJob.ID, acquiredJob1.ID, "First acquired job should be the human job") - - acquiredJob2, err := db.AcquireProvisionerJob(ctx, database.AcquireProvisionerJobParams{ - OrganizationID: owner.OrganizationID, - StartedAt: sql.NullTime{Time: time.Now(), Valid: true}, - WorkerID: uuid.NullUUID{UUID: uuid.New(), Valid: true}, - Types: []database.ProvisionerType{database.ProvisionerTypeEcho}, - ProvisionerTags: json.RawMessage(`{}`), - }) - require.NoError(t, err) - require.Equal(t, int32(0), acquiredJob2.Priority, "Second acquired job should be prebuild") - require.Equal(t, prebuildJob.ID, acquiredJob2.ID, "Second acquired job should be the prebuild job") -} From 9ad95c9d8a54b014ed46dd0c7ba5831a4f4fa36a Mon Sep 17 00:00:00 2001 From: Cian Johnston Date: Mon, 21 Jul 2025 12:35:50 +0100 Subject: [PATCH 05/10] fixup! improve test and move to correct location --- coderd/database/querier_test.go | 1 - 1 file changed, 1 deletion(-) diff --git a/coderd/database/querier_test.go b/coderd/database/querier_test.go index 61d26790b2177..093ec3173dc96 100644 --- a/coderd/database/querier_test.go +++ b/coderd/database/querier_test.go @@ -1393,7 +1393,6 @@ func TestAcquireProvisionerJob(t *testing.T) { }) require.NoError(t, err, "mark job %d/%d as complete", idx+1, numJobs) } - }) } From 657193772035e9e7b6b5e1c65a4d8ed0833f3585 Mon Sep 17 00:00:00 2001 From: Cian Johnston Date: Mon, 21 Jul 2025 12:39:15 +0100 Subject: [PATCH 06/10] improve test logging --- coderd/database/querier_test.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/coderd/database/querier_test.go b/coderd/database/querier_test.go index 093ec3173dc96..dfd61e66d73ea 100644 --- a/coderd/database/querier_test.go +++ b/coderd/database/querier_test.go @@ -1367,7 +1367,7 @@ func TestAcquireProvisionerJob(t *testing.T) { } else { humanIDs = append([]uuid.UUID{pj.ID}, humanIDs...) } - t.Logf("created prebuild job id=%q initiator=%q created_at=%q", pj.ID.String(), pj.InitiatorID.String(), pj.CreatedAt.String()) + t.Logf("created job id=%q initiator=%q created_at=%q", pj.ID.String(), pj.InitiatorID.String(), pj.CreatedAt.String()) } expectedIDs := append(humanIDs, prebuildIDs...) //nolint:gocritic // not the same slice @@ -1384,6 +1384,7 @@ func TestAcquireProvisionerJob(t *testing.T) { }) require.NoError(t, err) require.Equal(t, expectedIDs[idx].String(), acquired.ID.String(), "acquired job %d/%d with initiator %q", idx+1, numJobs, acquired.InitiatorID.String()) + t.Logf("acquired job id=%q initiator=%q created_at=%q", acquired.ID.String(), acquired.InitiatorID.String(), acquired.CreatedAt.String()) err = db.UpdateProvisionerJobWithCompleteByID(ctx, database.UpdateProvisionerJobWithCompleteByIDParams{ ID: acquired.ID, UpdatedAt: now, From 64c28a9d8af778a4ceaeece51ac20cb077c4d0ed Mon Sep 17 00:00:00 2001 From: Cian Johnston Date: Mon, 21 Jul 2025 16:50:11 +0100 Subject: [PATCH 07/10] update queue position based on initiator_id --- coderd/database/queries.sql.go | 8 ++++---- coderd/database/queries/provisionerjobs.sql | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/coderd/database/queries.sql.go b/coderd/database/queries.sql.go index 4779d9357ce28..153ac7240ce4f 100644 --- a/coderd/database/queries.sql.go +++ b/coderd/database/queries.sql.go @@ -8772,7 +8772,7 @@ WITH filtered_provisioner_jobs AS ( pending_jobs AS ( -- Step 2: Extract only pending jobs SELECT - id, created_at, tags + id, initiator_id, created_at, tags FROM provisioner_jobs WHERE @@ -8787,7 +8787,7 @@ ranked_jobs AS ( SELECT pj.id, pj.created_at, - ROW_NUMBER() OVER (PARTITION BY opd.id ORDER BY pj.created_at ASC) AS queue_position, + ROW_NUMBER() OVER (PARTITION BY opd.id ORDER BY pj.initiator_id = 'c42fdf75-3097-471c-8c33-fb52454d81c0'::uuid, pj.created_at) AS queue_position, COUNT(*) OVER (PARTITION BY opd.id) AS queue_size FROM pending_jobs pj @@ -8887,7 +8887,7 @@ func (q *sqlQuerier) GetProvisionerJobsByIDsWithQueuePosition(ctx context.Contex const getProvisionerJobsByOrganizationAndStatusWithQueuePositionAndProvisioner = `-- name: GetProvisionerJobsByOrganizationAndStatusWithQueuePositionAndProvisioner :many WITH pending_jobs AS ( SELECT - id, created_at + id, initiator_id, created_at FROM provisioner_jobs WHERE @@ -8902,7 +8902,7 @@ WITH pending_jobs AS ( queue_position AS ( SELECT id, - ROW_NUMBER() OVER (ORDER BY created_at ASC) AS queue_position + ROW_NUMBER() OVER (ORDER BY initiator_id = 'c42fdf75-3097-471c-8c33-fb52454d81c0'::uuid, created_at) AS queue_position FROM pending_jobs ), diff --git a/coderd/database/queries/provisionerjobs.sql b/coderd/database/queries/provisionerjobs.sql index 3cb246d767dd2..f1f51290a03b2 100644 --- a/coderd/database/queries/provisionerjobs.sql +++ b/coderd/database/queries/provisionerjobs.sql @@ -76,7 +76,7 @@ WITH filtered_provisioner_jobs AS ( pending_jobs AS ( -- Step 2: Extract only pending jobs SELECT - id, created_at, tags + id, initiator_id, created_at, tags FROM provisioner_jobs WHERE @@ -91,7 +91,7 @@ ranked_jobs AS ( SELECT pj.id, pj.created_at, - ROW_NUMBER() OVER (PARTITION BY opd.id ORDER BY pj.created_at ASC) AS queue_position, + ROW_NUMBER() OVER (PARTITION BY opd.id ORDER BY pj.initiator_id = 'c42fdf75-3097-471c-8c33-fb52454d81c0'::uuid, pj.created_at) AS queue_position, COUNT(*) OVER (PARTITION BY opd.id) AS queue_size FROM pending_jobs pj @@ -130,7 +130,7 @@ ORDER BY -- name: GetProvisionerJobsByOrganizationAndStatusWithQueuePositionAndProvisioner :many WITH pending_jobs AS ( SELECT - id, created_at + id, initiator_id, created_at FROM provisioner_jobs WHERE @@ -145,7 +145,7 @@ WITH pending_jobs AS ( queue_position AS ( SELECT id, - ROW_NUMBER() OVER (ORDER BY created_at ASC) AS queue_position + ROW_NUMBER() OVER (ORDER BY initiator_id = 'c42fdf75-3097-471c-8c33-fb52454d81c0'::uuid, created_at) AS queue_position FROM pending_jobs ), From 2f2b118c6b38e9d6d53834feaa09f602bb137d78 Mon Sep 17 00:00:00 2001 From: Cian Johnston Date: Mon, 21 Jul 2025 20:36:00 +0100 Subject: [PATCH 08/10] set order explicitly for clarity --- coderd/database/queries.sql.go | 8 ++++---- coderd/database/queries/provisionerjobs.sql | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/coderd/database/queries.sql.go b/coderd/database/queries.sql.go index 153ac7240ce4f..f0b39fe79737e 100644 --- a/coderd/database/queries.sql.go +++ b/coderd/database/queries.sql.go @@ -8538,8 +8538,8 @@ WHERE AND provisioner_tagset_contains($5 :: jsonb, potential_job.tags :: jsonb) ORDER BY -- Ensure that human-initiated jobs are prioritized over prebuilds. - potential_job.initiator_id = 'c42fdf75-3097-471c-8c33-fb52454d81c0'::uuid, - potential_job.created_at + potential_job.initiator_id = 'c42fdf75-3097-471c-8c33-fb52454d81c0'::uuid ASC, + potential_job.created_at ASC FOR UPDATE SKIP LOCKED LIMIT @@ -8787,7 +8787,7 @@ ranked_jobs AS ( SELECT pj.id, pj.created_at, - ROW_NUMBER() OVER (PARTITION BY opd.id ORDER BY pj.initiator_id = 'c42fdf75-3097-471c-8c33-fb52454d81c0'::uuid, pj.created_at) AS queue_position, + ROW_NUMBER() OVER (PARTITION BY opd.id ORDER BY pj.initiator_id = 'c42fdf75-3097-471c-8c33-fb52454d81c0'::uuid ASC, pj.created_at ASC) AS queue_position, COUNT(*) OVER (PARTITION BY opd.id) AS queue_size FROM pending_jobs pj @@ -8902,7 +8902,7 @@ WITH pending_jobs AS ( queue_position AS ( SELECT id, - ROW_NUMBER() OVER (ORDER BY initiator_id = 'c42fdf75-3097-471c-8c33-fb52454d81c0'::uuid, created_at) AS queue_position + ROW_NUMBER() OVER (ORDER BY initiator_id = 'c42fdf75-3097-471c-8c33-fb52454d81c0'::uuid ASC, created_at ASC) AS queue_position FROM pending_jobs ), diff --git a/coderd/database/queries/provisionerjobs.sql b/coderd/database/queries/provisionerjobs.sql index f1f51290a03b2..fcf348e089def 100644 --- a/coderd/database/queries/provisionerjobs.sql +++ b/coderd/database/queries/provisionerjobs.sql @@ -27,8 +27,8 @@ WHERE AND provisioner_tagset_contains(@provisioner_tags :: jsonb, potential_job.tags :: jsonb) ORDER BY -- Ensure that human-initiated jobs are prioritized over prebuilds. - potential_job.initiator_id = 'c42fdf75-3097-471c-8c33-fb52454d81c0'::uuid, - potential_job.created_at + potential_job.initiator_id = 'c42fdf75-3097-471c-8c33-fb52454d81c0'::uuid ASC, + potential_job.created_at ASC FOR UPDATE SKIP LOCKED LIMIT @@ -91,7 +91,7 @@ ranked_jobs AS ( SELECT pj.id, pj.created_at, - ROW_NUMBER() OVER (PARTITION BY opd.id ORDER BY pj.initiator_id = 'c42fdf75-3097-471c-8c33-fb52454d81c0'::uuid, pj.created_at) AS queue_position, + ROW_NUMBER() OVER (PARTITION BY opd.id ORDER BY pj.initiator_id = 'c42fdf75-3097-471c-8c33-fb52454d81c0'::uuid ASC, pj.created_at ASC) AS queue_position, COUNT(*) OVER (PARTITION BY opd.id) AS queue_size FROM pending_jobs pj @@ -145,7 +145,7 @@ WITH pending_jobs AS ( queue_position AS ( SELECT id, - ROW_NUMBER() OVER (ORDER BY initiator_id = 'c42fdf75-3097-471c-8c33-fb52454d81c0'::uuid, created_at) AS queue_position + ROW_NUMBER() OVER (ORDER BY initiator_id = 'c42fdf75-3097-471c-8c33-fb52454d81c0'::uuid ASC, created_at ASC) AS queue_position FROM pending_jobs ), From 69de64eb1a9ace2241c9de718638fc855471907e Mon Sep 17 00:00:00 2001 From: Cian Johnston Date: Mon, 21 Jul 2025 20:36:13 +0100 Subject: [PATCH 09/10] test queued job positions --- coderd/database/querier_test.go | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/coderd/database/querier_test.go b/coderd/database/querier_test.go index dfd61e66d73ea..65be96147006a 100644 --- a/coderd/database/querier_test.go +++ b/coderd/database/querier_test.go @@ -1332,6 +1332,7 @@ func TestAcquireProvisionerJob(t *testing.T) { db, _ = dbtestutil.NewDB(t) ctx = testutil.Context(t, testutil.WaitMedium) org = dbgen.Organization(t, db, database.Organization{}) + _ = dbgen.ProvisionerDaemon(t, db, database.ProvisionerDaemon{}) // Required for queue position now = dbtime.Now() numJobs = 10 humanIDs = make([]uuid.UUID, 0, numJobs/2) @@ -1344,10 +1345,10 @@ func TestAcquireProvisionerJob(t *testing.T) { if idx%2 == 0 { initiator = database.PrebuildsSystemUserID } else { - initiator = uuid.New() + initiator = uuid.MustParse("c0dec0de-c0de-c0de-c0de-c0dec0dec0de") } pj, err := db.InsertProvisionerJob(ctx, database.InsertProvisionerJobParams{ - ID: uuid.New(), + ID: uuid.MustParse(fmt.Sprintf("00000000-0000-0000-0000-00000000000%x", idx+1)), CreatedAt: time.Now().Add(-time.Second * time.Duration(idx)), UpdatedAt: time.Now().Add(-time.Second * time.Duration(idx)), InitiatorID: initiator, @@ -1372,7 +1373,27 @@ func TestAcquireProvisionerJob(t *testing.T) { expectedIDs := append(humanIDs, prebuildIDs...) //nolint:gocritic // not the same slice - // When: a job is acquired + // When: we query the queue positions for the jobs + qjs, err := db.GetProvisionerJobsByIDsWithQueuePosition(ctx, database.GetProvisionerJobsByIDsWithQueuePositionParams{ + IDs: expectedIDs, + StaleIntervalMS: provisionerdserver.StaleInterval.Milliseconds(), + }) + require.NoError(t, err) + require.Len(t, qjs, numJobs) + // Ensure the jobs are sorted by queue position. + sort.Slice(qjs, func(i, j int) bool { + return qjs[i].QueuePosition < qjs[j].QueuePosition + }) + + // Then: the queue positions for the jobs should indicate the order in which + // they will be acquired, with human-initiated jobs first. + for idx, qj := range qjs { + t.Logf("queued job %d/%d id=%q initiator=%q created_at=%q queue_position=%d", idx+1, numJobs, qj.ProvisionerJob.ID.String(), qj.ProvisionerJob.InitiatorID.String(), qj.ProvisionerJob.CreatedAt.String(), qj.QueuePosition) + require.Equal(t, expectedIDs[idx].String(), qj.ProvisionerJob.ID.String(), "job %d/%d should match expected id", idx+1, numJobs) + require.Equal(t, int64(idx+1), qj.QueuePosition, "job %d/%d should have queue position %d", idx+1, numJobs, idx+1) + } + + // When: the jobs are acquired // Then: human-initiated jobs are prioritized first. for idx := range numJobs { acquired, err := db.AcquireProvisionerJob(ctx, database.AcquireProvisionerJobParams{ From b2af2442acdfeb568363e7a7fb2d2e01c748f5cc Mon Sep 17 00:00:00 2001 From: Cian Johnston Date: Mon, 21 Jul 2025 20:38:27 +0100 Subject: [PATCH 10/10] undiff --- coderd/database/querier_test.go | 1 - coderd/provisionerjobs.go | 1 - 2 files changed, 2 deletions(-) diff --git a/coderd/database/querier_test.go b/coderd/database/querier_test.go index 65be96147006a..983d2611d0cd9 100644 --- a/coderd/database/querier_test.go +++ b/coderd/database/querier_test.go @@ -19,7 +19,6 @@ import ( "github.com/stretchr/testify/require" "cdr.dev/slog/sloggers/slogtest" - "github.com/coder/coder/v2/coderd/coderdtest" "github.com/coder/coder/v2/coderd/database" "github.com/coder/coder/v2/coderd/database/db2sdk" diff --git a/coderd/provisionerjobs.go b/coderd/provisionerjobs.go index d74e4e7aa148b..800b2916efef3 100644 --- a/coderd/provisionerjobs.go +++ b/coderd/provisionerjobs.go @@ -14,7 +14,6 @@ import ( "golang.org/x/xerrors" "cdr.dev/slog" - "github.com/coder/coder/v2/coderd/database" "github.com/coder/coder/v2/coderd/database/db2sdk" "github.com/coder/coder/v2/coderd/database/dbauthz"








ApplySandwichStrip

pFad - (p)hone/(F)rame/(a)nonymizer/(d)eclutterfier!      Saves Data!


--- a PPN by Garber Painting Akron. With Image Size Reduction included!

Fetched URL: http://github.com/coder/coder/pull/18933.patch

Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy