mirror of
https://github.com/coder/coder.git
synced 2026-09-21 20:51:01 +08:00
fix: optimize GetProvisionerJobsByIDsWithQueuePosition query (#22724)
This commit is contained in:
@@ -3867,6 +3867,37 @@ func TestGetProvisionerJobsByIDsWithQueuePosition(t *testing.T) {
|
||||
queueSizes: nil, // TODO(yevhenii): should it be empty array instead?
|
||||
queuePositions: nil,
|
||||
},
|
||||
// Many daemons with identical tags should produce same results as one.
|
||||
{
|
||||
name: "duplicate-daemons-same-tags",
|
||||
jobTags: []database.StringMap{
|
||||
{"a": "1"},
|
||||
{"a": "1", "b": "2"},
|
||||
},
|
||||
daemonTags: []database.StringMap{
|
||||
{"a": "1", "b": "2"},
|
||||
{"a": "1", "b": "2"},
|
||||
{"a": "1", "b": "2"},
|
||||
},
|
||||
queueSizes: []int64{2, 2},
|
||||
queuePositions: []int64{1, 2},
|
||||
},
|
||||
// Jobs that don't match any queried job's daemon should still
|
||||
// have correct queue positions.
|
||||
{
|
||||
name: "irrelevant-daemons-filtered",
|
||||
jobTags: []database.StringMap{
|
||||
{"a": "1"},
|
||||
{"x": "9"},
|
||||
},
|
||||
daemonTags: []database.StringMap{
|
||||
{"a": "1"},
|
||||
{"x": "9"},
|
||||
},
|
||||
queueSizes: []int64{1},
|
||||
queuePositions: []int64{1},
|
||||
skipJobIDs: map[int]struct{}{1: {}},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
@@ -4192,6 +4223,51 @@ func TestGetProvisionerJobsByIDsWithQueuePosition_OrderValidation(t *testing.T)
|
||||
assert.EqualValues(t, []int64{1, 2, 3, 4, 5, 6}, queuePositions, "expected queue positions to be set correctly")
|
||||
}
|
||||
|
||||
func TestGetProvisionerJobsByIDsWithQueuePosition_DuplicateDaemons(t *testing.T) {
|
||||
t.Parallel()
|
||||
db, _ := dbtestutil.NewDB(t)
|
||||
now := dbtime.Now()
|
||||
ctx := testutil.Context(t, testutil.WaitShort)
|
||||
|
||||
// Create 3 pending jobs with the same tags.
|
||||
jobs := make([]database.ProvisionerJob, 3)
|
||||
for i := range jobs {
|
||||
jobs[i] = dbgen.ProvisionerJob(t, db, nil, database.ProvisionerJob{
|
||||
CreatedAt: now.Add(-time.Duration(3-i) * time.Minute),
|
||||
Tags: database.StringMap{"scope": "organization", "owner": ""},
|
||||
})
|
||||
}
|
||||
|
||||
// Create 50 daemons with identical tags (simulates scale).
|
||||
for i := range 50 {
|
||||
dbgen.ProvisionerDaemon(t, db, database.ProvisionerDaemon{
|
||||
Name: fmt.Sprintf("daemon_%d", i),
|
||||
Provisioners: []database.ProvisionerType{database.ProvisionerTypeEcho},
|
||||
Tags: database.StringMap{"scope": "organization", "owner": ""},
|
||||
})
|
||||
}
|
||||
|
||||
jobIDs := make([]uuid.UUID, len(jobs))
|
||||
for i, j := range jobs {
|
||||
jobIDs[i] = j.ID
|
||||
}
|
||||
|
||||
results, err := db.GetProvisionerJobsByIDsWithQueuePosition(ctx,
|
||||
database.GetProvisionerJobsByIDsWithQueuePositionParams{
|
||||
IDs: jobIDs,
|
||||
StaleIntervalMS: provisionerdserver.StaleInterval.Milliseconds(),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.Len(t, results, 3)
|
||||
|
||||
// All daemons have identical tags, so queue should be same as
|
||||
// if there were just one daemon.
|
||||
for i, r := range results {
|
||||
assert.Equal(t, int64(3), r.QueueSize, "job %d queue size", i)
|
||||
assert.Equal(t, int64(i+1), r.QueuePosition, "job %d queue position", i)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGroupRemovalTrigger(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
||||
@@ -12654,7 +12654,7 @@ const getProvisionerJobsByIDsWithQueuePosition = `-- name: GetProvisionerJobsByI
|
||||
WITH filtered_provisioner_jobs AS (
|
||||
-- Step 1: Filter provisioner_jobs
|
||||
SELECT
|
||||
id, created_at
|
||||
id, created_at, tags
|
||||
FROM
|
||||
provisioner_jobs
|
||||
WHERE
|
||||
@@ -12669,21 +12669,32 @@ pending_jobs AS (
|
||||
WHERE
|
||||
job_status = 'pending'
|
||||
),
|
||||
online_provisioner_daemons AS (
|
||||
SELECT id, tags FROM provisioner_daemons pd
|
||||
WHERE pd.last_seen_at IS NOT NULL AND pd.last_seen_at >= (NOW() - ($2::bigint || ' ms')::interval)
|
||||
unique_daemon_tags AS (
|
||||
SELECT DISTINCT tags FROM provisioner_daemons pd
|
||||
WHERE pd.last_seen_at IS NOT NULL
|
||||
AND pd.last_seen_at >= (NOW() - ($2::bigint || ' ms')::interval)
|
||||
),
|
||||
relevant_daemon_tags AS (
|
||||
SELECT udt.tags
|
||||
FROM unique_daemon_tags udt
|
||||
WHERE EXISTS (
|
||||
SELECT 1 FROM filtered_provisioner_jobs fpj
|
||||
WHERE provisioner_tagset_contains(udt.tags, fpj.tags)
|
||||
)
|
||||
),
|
||||
ranked_jobs AS (
|
||||
-- Step 3: Rank only pending jobs based on provisioner availability
|
||||
SELECT
|
||||
pj.id,
|
||||
pj.created_at,
|
||||
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
|
||||
ROW_NUMBER() OVER (PARTITION BY rdt.tags ORDER BY pj.initiator_id = 'c42fdf75-3097-471c-8c33-fb52454d81c0'::uuid ASC, pj.created_at ASC) AS queue_position,
|
||||
COUNT(*) OVER (PARTITION BY rdt.tags) AS queue_size
|
||||
FROM
|
||||
pending_jobs pj
|
||||
INNER JOIN online_provisioner_daemons opd
|
||||
ON provisioner_tagset_contains(opd.tags, pj.tags) -- Join only on the small pending set
|
||||
INNER JOIN
|
||||
relevant_daemon_tags rdt
|
||||
ON
|
||||
provisioner_tagset_contains(rdt.tags, pj.tags)
|
||||
),
|
||||
final_jobs AS (
|
||||
-- Step 4: Compute best queue position and max queue size per job
|
||||
|
||||
@@ -79,7 +79,7 @@ WHERE
|
||||
WITH filtered_provisioner_jobs AS (
|
||||
-- Step 1: Filter provisioner_jobs
|
||||
SELECT
|
||||
id, created_at
|
||||
id, created_at, tags
|
||||
FROM
|
||||
provisioner_jobs
|
||||
WHERE
|
||||
@@ -94,21 +94,32 @@ pending_jobs AS (
|
||||
WHERE
|
||||
job_status = 'pending'
|
||||
),
|
||||
online_provisioner_daemons AS (
|
||||
SELECT id, tags FROM provisioner_daemons pd
|
||||
WHERE pd.last_seen_at IS NOT NULL AND pd.last_seen_at >= (NOW() - (@stale_interval_ms::bigint || ' ms')::interval)
|
||||
unique_daemon_tags AS (
|
||||
SELECT DISTINCT tags FROM provisioner_daemons pd
|
||||
WHERE pd.last_seen_at IS NOT NULL
|
||||
AND pd.last_seen_at >= (NOW() - (@stale_interval_ms::bigint || ' ms')::interval)
|
||||
),
|
||||
relevant_daemon_tags AS (
|
||||
SELECT udt.tags
|
||||
FROM unique_daemon_tags udt
|
||||
WHERE EXISTS (
|
||||
SELECT 1 FROM filtered_provisioner_jobs fpj
|
||||
WHERE provisioner_tagset_contains(udt.tags, fpj.tags)
|
||||
)
|
||||
),
|
||||
ranked_jobs AS (
|
||||
-- Step 3: Rank only pending jobs based on provisioner availability
|
||||
SELECT
|
||||
pj.id,
|
||||
pj.created_at,
|
||||
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
|
||||
ROW_NUMBER() OVER (PARTITION BY rdt.tags ORDER BY pj.initiator_id = 'c42fdf75-3097-471c-8c33-fb52454d81c0'::uuid ASC, pj.created_at ASC) AS queue_position,
|
||||
COUNT(*) OVER (PARTITION BY rdt.tags) AS queue_size
|
||||
FROM
|
||||
pending_jobs pj
|
||||
INNER JOIN online_provisioner_daemons opd
|
||||
ON provisioner_tagset_contains(opd.tags, pj.tags) -- Join only on the small pending set
|
||||
INNER JOIN
|
||||
relevant_daemon_tags rdt
|
||||
ON
|
||||
provisioner_tagset_contains(rdt.tags, pj.tags)
|
||||
),
|
||||
final_jobs AS (
|
||||
-- Step 4: Compute best queue position and max queue size per job
|
||||
|
||||
Reference in New Issue
Block a user