Skip to content

Commit c4b69bb

Browse files
johnstcnblink-so[bot]kylecarbs
authored
fix: prioritise human-initiated builds over prebuilds (#18933)
Continues from #18882 - Reverts extraneous changes - Adds explicit `ORDER BY initiator_id = $PREBUILDS_USER_ID` to `AcquireProvisionerJob` - Improves test added for above PR --------- Co-authored-by: blink-so[bot] <211532188+blink-so[bot]@users.noreply.github.com> Co-authored-by: kylecarbs <7122116+kylecarbs@users.noreply.github.com>
1 parent e98dce7 commit c4b69bb

File tree

3 files changed

+109
-10
lines changed

3 files changed

+109
-10
lines changed

coderd/database/querier_test.go

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1322,6 +1322,101 @@ func TestQueuePosition(t *testing.T) {
13221322
}
13231323
}
13241324

1325+
func TestAcquireProvisionerJob(t *testing.T) {
1326+
t.Parallel()
1327+
1328+
t.Run("HumanInitiatedJobsFirst", func(t *testing.T) {
1329+
t.Parallel()
1330+
var (
1331+
db, _ = dbtestutil.NewDB(t)
1332+
ctx = testutil.Context(t, testutil.WaitMedium)
1333+
org = dbgen.Organization(t, db, database.Organization{})
1334+
_ = dbgen.ProvisionerDaemon(t, db, database.ProvisionerDaemon{}) // Required for queue position
1335+
now = dbtime.Now()
1336+
numJobs = 10
1337+
humanIDs = make([]uuid.UUID, 0, numJobs/2)
1338+
prebuildIDs = make([]uuid.UUID, 0, numJobs/2)
1339+
)
1340+
1341+
// Given: a number of jobs in the queue, with prebuilds and non-prebuilds interleaved
1342+
for idx := range numJobs {
1343+
var initiator uuid.UUID
1344+
if idx%2 == 0 {
1345+
initiator = database.PrebuildsSystemUserID
1346+
} else {
1347+
initiator = uuid.MustParse("c0dec0de-c0de-c0de-c0de-c0dec0dec0de")
1348+
}
1349+
pj, err := db.InsertProvisionerJob(ctx, database.InsertProvisionerJobParams{
1350+
ID: uuid.MustParse(fmt.Sprintf("00000000-0000-0000-0000-00000000000%x", idx+1)),
1351+
CreatedAt: time.Now().Add(-time.Second * time.Duration(idx)),
1352+
UpdatedAt: time.Now().Add(-time.Second * time.Duration(idx)),
1353+
InitiatorID: initiator,
1354+
OrganizationID: org.ID,
1355+
Provisioner: database.ProvisionerTypeEcho,
1356+
Type: database.ProvisionerJobTypeWorkspaceBuild,
1357+
StorageMethod: database.ProvisionerStorageMethodFile,
1358+
FileID: uuid.New(),
1359+
Input: json.RawMessage(`{}`),
1360+
Tags: database.StringMap{},
1361+
TraceMetadata: pqtype.NullRawMessage{},
1362+
})
1363+
require.NoError(t, err)
1364+
// We expected prebuilds to be acquired after human-initiated jobs.
1365+
if initiator == database.PrebuildsSystemUserID {
1366+
prebuildIDs = append([]uuid.UUID{pj.ID}, prebuildIDs...)
1367+
} else {
1368+
humanIDs = append([]uuid.UUID{pj.ID}, humanIDs...)
1369+
}
1370+
t.Logf("created job id=%q initiator=%q created_at=%q", pj.ID.String(), pj.InitiatorID.String(), pj.CreatedAt.String())
1371+
}
1372+
1373+
expectedIDs := append(humanIDs, prebuildIDs...) //nolint:gocritic // not the same slice
1374+
1375+
// When: we query the queue positions for the jobs
1376+
qjs, err := db.GetProvisionerJobsByIDsWithQueuePosition(ctx, database.GetProvisionerJobsByIDsWithQueuePositionParams{
1377+
IDs: expectedIDs,
1378+
StaleIntervalMS: provisionerdserver.StaleInterval.Milliseconds(),
1379+
})
1380+
require.NoError(t, err)
1381+
require.Len(t, qjs, numJobs)
1382+
// Ensure the jobs are sorted by queue position.
1383+
sort.Slice(qjs, func(i, j int) bool {
1384+
return qjs[i].QueuePosition < qjs[j].QueuePosition
1385+
})
1386+
1387+
// Then: the queue positions for the jobs should indicate the order in which
1388+
// they will be acquired, with human-initiated jobs first.
1389+
for idx, qj := range qjs {
1390+
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)
1391+
require.Equal(t, expectedIDs[idx].String(), qj.ProvisionerJob.ID.String(), "job %d/%d should match expected id", idx+1, numJobs)
1392+
require.Equal(t, int64(idx+1), qj.QueuePosition, "job %d/%d should have queue position %d", idx+1, numJobs, idx+1)
1393+
}
1394+
1395+
// When: the jobs are acquired
1396+
// Then: human-initiated jobs are prioritized first.
1397+
for idx := range numJobs {
1398+
acquired, err := db.AcquireProvisionerJob(ctx, database.AcquireProvisionerJobParams{
1399+
OrganizationID: org.ID,
1400+
StartedAt: sql.NullTime{Time: time.Now(), Valid: true},
1401+
WorkerID: uuid.NullUUID{UUID: uuid.New(), Valid: true},
1402+
Types: []database.ProvisionerType{database.ProvisionerTypeEcho},
1403+
ProvisionerTags: json.RawMessage(`{}`),
1404+
})
1405+
require.NoError(t, err)
1406+
require.Equal(t, expectedIDs[idx].String(), acquired.ID.String(), "acquired job %d/%d with initiator %q", idx+1, numJobs, acquired.InitiatorID.String())
1407+
t.Logf("acquired job id=%q initiator=%q created_at=%q", acquired.ID.String(), acquired.InitiatorID.String(), acquired.CreatedAt.String())
1408+
err = db.UpdateProvisionerJobWithCompleteByID(ctx, database.UpdateProvisionerJobWithCompleteByIDParams{
1409+
ID: acquired.ID,
1410+
UpdatedAt: now,
1411+
CompletedAt: sql.NullTime{Time: now, Valid: true},
1412+
Error: sql.NullString{},
1413+
ErrorCode: sql.NullString{},
1414+
})
1415+
require.NoError(t, err, "mark job %d/%d as complete", idx+1, numJobs)
1416+
}
1417+
})
1418+
}
1419+
13251420
func TestUserLastSeenFilter(t *testing.T) {
13261421
t.Parallel()
13271422
if testing.Short() {

coderd/database/queries.sql.go

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

coderd/database/queries/provisionerjobs.sql

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,9 @@ WHERE
2626
-- they are aliases and the code that calls this query already relies on a different type
2727
AND provisioner_tagset_contains(@provisioner_tags :: jsonb, potential_job.tags :: jsonb)
2828
ORDER BY
29-
potential_job.created_at
29+
-- Ensure that human-initiated jobs are prioritized over prebuilds.
30+
potential_job.initiator_id = 'c42fdf75-3097-471c-8c33-fb52454d81c0'::uuid ASC,
31+
potential_job.created_at ASC
3032
FOR UPDATE
3133
SKIP LOCKED
3234
LIMIT
@@ -74,7 +76,7 @@ WITH filtered_provisioner_jobs AS (
7476
pending_jobs AS (
7577
-- Step 2: Extract only pending jobs
7678
SELECT
77-
id, created_at, tags
79+
id, initiator_id, created_at, tags
7880
FROM
7981
provisioner_jobs
8082
WHERE
@@ -89,7 +91,7 @@ ranked_jobs AS (
8991
SELECT
9092
pj.id,
9193
pj.created_at,
92-
ROW_NUMBER() OVER (PARTITION BY opd.id ORDER BY pj.created_at ASC) AS queue_position,
94+
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,
9395
COUNT(*) OVER (PARTITION BY opd.id) AS queue_size
9496
FROM
9597
pending_jobs pj
@@ -128,7 +130,7 @@ ORDER BY
128130
-- name: GetProvisionerJobsByOrganizationAndStatusWithQueuePositionAndProvisioner :many
129131
WITH pending_jobs AS (
130132
SELECT
131-
id, created_at
133+
id, initiator_id, created_at
132134
FROM
133135
provisioner_jobs
134136
WHERE
@@ -143,7 +145,7 @@ WITH pending_jobs AS (
143145
queue_position AS (
144146
SELECT
145147
id,
146-
ROW_NUMBER() OVER (ORDER BY created_at ASC) AS queue_position
148+
ROW_NUMBER() OVER (ORDER BY initiator_id = 'c42fdf75-3097-471c-8c33-fb52454d81c0'::uuid ASC, created_at ASC) AS queue_position
147149
FROM
148150
pending_jobs
149151
),

0 commit comments

Comments
 (0)
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