mirror of
https://github.com/coder/coder.git
synced 2026-10-16 14:48:56 +08:00
perf(coderd/database): aggregate AI spend before joining user profiles (#29737)
The organization spend query joined user profile fields onto every usage row before aggregating. On dogfood's September window, this sorted 1.23 million rows and wrote about 1.5 GiB of temporary data per request. Aggregate by user first, then join profiles and apply ordering and pagination. A counterbalanced production-data benchmark (six runs each, AB/BA order, September 1 through September 22 15:00 UTC cutoff, 1,233,784 usage rows) measured median database execution time of 6.08 s before and 3.12 s after this change. Ranges were 5.74 to 8.32 s and 2.52 to 4.06 s, respectively; median per-pair speedup was 2.10x. Median temporary writes fell from 1,610 MiB to 329 MiB. Both queries had zero shared-buffer reads in every run. These are EXPLAIN ANALYZE timings on a busy database, not API/page-load latency. A same-snapshot, unlimited-page comparison found zero differing rows in either direction, including row order, across all 88 users. Refs #28964. This is a one-layer performance stack; further optimization experiments are not included here. No schema, API, or frontend changes. <details> <summary>Delivery record</summary> Head: `41a179a17464bb54bbc9b3886e9c3ea6b7bda7ef`. SQLC generation, targeted database/HTTP/export/authorization tests, and the full pre-commit hook passed before publication and again before requesting Codex review. The existing query test now covers usage across two groups in one organization. Remote browser UAT: not run. Production SQL evidence covers a prototype verified identical to the committed query; this head is not deployed. Frontend checks were not run because no frontend or response type changed. Local pre-push checks were skipped as requested. Codex: [OK](https://github.com/coder/coder/pull/29737#issuecomment-5780120712) at 2026-09-22 16:27:06 UTC for `41a179a174`, after the [request](https://github.com/coder/coder/pull/29737#issuecomment-5780086039) at 16:24:34 UTC. No findings or unresolved threads. Completed independent review passes: 1/6. CI: all current-head check runs passed or were skipped, including the required gate. The non-required `Pixel / Review` status remains pending. Codex reports its current-head review completed; one trigger, no retriggers, no code changes requested. </details> > Xum opened and manages this PR on behalf of @ibetitsmike.
This commit is contained in:
@@ -19286,6 +19286,7 @@ func TestListOrganizationAISpendUsers(t *testing.T) {
|
||||
|
||||
org := dbgen.Organization(t, db, database.Organization{})
|
||||
group := dbgen.Group(t, db, database.Group{OrganizationID: org.ID})
|
||||
secondGroup := dbgen.Group(t, db, database.Group{OrganizationID: org.ID})
|
||||
otherOrg := dbgen.Organization(t, db, database.Organization{})
|
||||
otherGroup := dbgen.Group(t, db, database.Group{OrganizationID: otherOrg.ID})
|
||||
|
||||
@@ -19314,7 +19315,7 @@ func TestListOrganizationAISpendUsers(t *testing.T) {
|
||||
for _, u := range []usage{
|
||||
// alice: 1500 priced plus one unpriced usage without a recorded client.
|
||||
{user: alice, group: inGroup, at: start, providerName: "anthropic-prod", model: "claude", client: vscode, cost: priced(1000)},
|
||||
{user: alice, group: inGroup, at: start.Add(time.Hour), providerName: "openai-prod", model: "gpt-4", cost: priced(500)},
|
||||
{user: alice, group: uuid.NullUUID{UUID: secondGroup.ID, Valid: true}, at: start.Add(time.Hour), providerName: "openai-prod", model: "gpt-4", cost: priced(500)},
|
||||
{user: alice, group: inGroup, at: start.Add(2 * time.Hour), providerName: "openai-prod", model: "gpt-4o"},
|
||||
// bob: the most expensive user.
|
||||
{user: bob, group: inGroup, at: start.Add(time.Hour), providerName: "anthropic-prod", model: "claude", client: cursor, cost: priced(3000)},
|
||||
|
||||
Generated
+47
-42
@@ -3686,59 +3686,64 @@ func (q *sqlQuerier) IncrementUserAIDailySpend(ctx context.Context, arg Incremen
|
||||
}
|
||||
|
||||
const listOrganizationAISpendUsers = `-- name: ListOrganizationAISpendUsers :many
|
||||
WITH spend AS (
|
||||
SELECT
|
||||
ai.initiator_id AS user_id,
|
||||
COALESCE(SUM(tu.cost_micros), 0)::BIGINT AS cost_micros,
|
||||
COUNT(*) FILTER (WHERE tu.cost_micros IS NULL)::BIGINT AS unpriced_usage_count,
|
||||
ARRAY_AGG(DISTINCT ai.provider ORDER BY ai.provider)::text[] AS providers,
|
||||
ARRAY_AGG(DISTINCT COALESCE(ai.client, 'Unknown') ORDER BY COALESCE(ai.client, 'Unknown'))::text[] AS clients,
|
||||
ARRAY_AGG(DISTINCT ai.model ORDER BY ai.model)::text[] AS models
|
||||
FROM aibridge_token_usages tu
|
||||
JOIN aibridge_interceptions ai ON ai.id = tu.interception_id
|
||||
JOIN groups ON groups.id = tu.effective_group_id
|
||||
WHERE groups.organization_id = $1
|
||||
AND tu.created_at >= $4::timestamptz
|
||||
AND tu.created_at < $5::timestamptz
|
||||
AND CASE
|
||||
WHEN $6::text != '' THEN ai.provider_name = $6::text
|
||||
ELSE true
|
||||
END
|
||||
AND CASE
|
||||
WHEN $7::text != '' THEN ai.model = $7::text
|
||||
ELSE true
|
||||
END
|
||||
AND CASE
|
||||
WHEN $8::text != '' THEN COALESCE(ai.client, 'Unknown') = $8::text
|
||||
ELSE true
|
||||
END
|
||||
GROUP BY ai.initiator_id
|
||||
)
|
||||
SELECT
|
||||
ai.initiator_id AS user_id,
|
||||
users.username AS username,
|
||||
users.name AS name,
|
||||
users.avatar_url AS avatar_url,
|
||||
groups.organization_id AS organization_id,
|
||||
COALESCE(SUM(tu.cost_micros), 0)::BIGINT AS cost_micros,
|
||||
COUNT(*) FILTER (WHERE tu.cost_micros IS NULL)::BIGINT AS unpriced_usage_count,
|
||||
ARRAY_AGG(DISTINCT ai.provider ORDER BY ai.provider)::text[] AS providers,
|
||||
ARRAY_AGG(DISTINCT COALESCE(ai.client, 'Unknown') ORDER BY COALESCE(ai.client, 'Unknown'))::text[] AS clients,
|
||||
ARRAY_AGG(DISTINCT ai.model ORDER BY ai.model)::text[] AS models,
|
||||
COUNT(*) OVER ()::BIGINT AS count,
|
||||
COALESCE(SUM(SUM(tu.cost_micros)) OVER (), 0)::BIGINT AS total_cost_micros,
|
||||
COALESCE(SUM(COUNT(*) FILTER (WHERE tu.cost_micros IS NULL)) OVER (), 0)::BIGINT AS total_unpriced_usage_count
|
||||
FROM aibridge_token_usages tu
|
||||
JOIN aibridge_interceptions ai ON ai.id = tu.interception_id
|
||||
JOIN users ON users.id = ai.initiator_id
|
||||
JOIN groups ON groups.id = tu.effective_group_id
|
||||
WHERE groups.organization_id = $1
|
||||
AND tu.created_at >= $2::timestamptz
|
||||
AND tu.created_at < $3::timestamptz
|
||||
AND CASE
|
||||
WHEN $4::text != '' THEN ai.provider_name = $4::text
|
||||
ELSE true
|
||||
END
|
||||
AND CASE
|
||||
WHEN $5::text != '' THEN ai.model = $5::text
|
||||
ELSE true
|
||||
END
|
||||
AND CASE
|
||||
WHEN $6::text != '' THEN COALESCE(ai.client, 'Unknown') = $6::text
|
||||
ELSE true
|
||||
END
|
||||
GROUP BY
|
||||
ai.initiator_id,
|
||||
spend.user_id,
|
||||
users.username,
|
||||
users.name,
|
||||
users.avatar_url,
|
||||
groups.organization_id
|
||||
ORDER BY cost_micros DESC, LOWER(users.username), ai.initiator_id
|
||||
LIMIT NULLIF($8::int, 0)
|
||||
OFFSET $7::int
|
||||
$1::uuid AS organization_id,
|
||||
spend.cost_micros,
|
||||
spend.unpriced_usage_count,
|
||||
spend.providers,
|
||||
spend.clients,
|
||||
spend.models,
|
||||
COUNT(*) OVER ()::BIGINT AS count,
|
||||
COALESCE(SUM(spend.cost_micros) OVER (), 0)::BIGINT AS total_cost_micros,
|
||||
COALESCE(SUM(spend.unpriced_usage_count) OVER (), 0)::BIGINT AS total_unpriced_usage_count
|
||||
FROM spend
|
||||
JOIN users ON users.id = spend.user_id
|
||||
ORDER BY cost_micros DESC, LOWER(users.username), spend.user_id
|
||||
LIMIT NULLIF($3::int, 0)
|
||||
OFFSET $2::int
|
||||
`
|
||||
|
||||
type ListOrganizationAISpendUsersParams struct {
|
||||
OrganizationID uuid.UUID `db:"organization_id" json:"organization_id"`
|
||||
OffsetOpt int32 `db:"offset_opt" json:"offset_opt"`
|
||||
LimitOpt int32 `db:"limit_opt" json:"limit_opt"`
|
||||
PeriodStart time.Time `db:"period_start" json:"period_start"`
|
||||
PeriodEnd time.Time `db:"period_end" json:"period_end"`
|
||||
ProviderName string `db:"provider_name" json:"provider_name"`
|
||||
Model string `db:"model" json:"model"`
|
||||
Client string `db:"client" json:"client"`
|
||||
OffsetOpt int32 `db:"offset_opt" json:"offset_opt"`
|
||||
LimitOpt int32 `db:"limit_opt" json:"limit_opt"`
|
||||
}
|
||||
|
||||
type ListOrganizationAISpendUsersRow struct {
|
||||
@@ -3765,13 +3770,13 @@ type ListOrganizationAISpendUsersRow struct {
|
||||
func (q *sqlQuerier) ListOrganizationAISpendUsers(ctx context.Context, arg ListOrganizationAISpendUsersParams) ([]ListOrganizationAISpendUsersRow, error) {
|
||||
rows, err := q.db.QueryContext(ctx, listOrganizationAISpendUsers,
|
||||
arg.OrganizationID,
|
||||
arg.OffsetOpt,
|
||||
arg.LimitOpt,
|
||||
arg.PeriodStart,
|
||||
arg.PeriodEnd,
|
||||
arg.ProviderName,
|
||||
arg.Model,
|
||||
arg.Client,
|
||||
arg.OffsetOpt,
|
||||
arg.LimitOpt,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -501,46 +501,51 @@ LIMIT NULLIF(@limit_opt::int, 0);
|
||||
-- providers, clients, and models each user spent through and the count and
|
||||
-- totals over every matching user. It must keep the same joins and predicates as
|
||||
-- ExportOrganizationAISpend so both report the same token usage.
|
||||
WITH spend AS (
|
||||
SELECT
|
||||
ai.initiator_id AS user_id,
|
||||
COALESCE(SUM(tu.cost_micros), 0)::BIGINT AS cost_micros,
|
||||
COUNT(*) FILTER (WHERE tu.cost_micros IS NULL)::BIGINT AS unpriced_usage_count,
|
||||
ARRAY_AGG(DISTINCT ai.provider ORDER BY ai.provider)::text[] AS providers,
|
||||
ARRAY_AGG(DISTINCT COALESCE(ai.client, 'Unknown') ORDER BY COALESCE(ai.client, 'Unknown'))::text[] AS clients,
|
||||
ARRAY_AGG(DISTINCT ai.model ORDER BY ai.model)::text[] AS models
|
||||
FROM aibridge_token_usages tu
|
||||
JOIN aibridge_interceptions ai ON ai.id = tu.interception_id
|
||||
JOIN groups ON groups.id = tu.effective_group_id
|
||||
WHERE groups.organization_id = @organization_id
|
||||
AND tu.created_at >= @period_start::timestamptz
|
||||
AND tu.created_at < @period_end::timestamptz
|
||||
AND CASE
|
||||
WHEN @provider_name::text != '' THEN ai.provider_name = @provider_name::text
|
||||
ELSE true
|
||||
END
|
||||
AND CASE
|
||||
WHEN @model::text != '' THEN ai.model = @model::text
|
||||
ELSE true
|
||||
END
|
||||
AND CASE
|
||||
WHEN @client::text != '' THEN COALESCE(ai.client, 'Unknown') = @client::text
|
||||
ELSE true
|
||||
END
|
||||
GROUP BY ai.initiator_id
|
||||
)
|
||||
SELECT
|
||||
ai.initiator_id AS user_id,
|
||||
users.username AS username,
|
||||
users.name AS name,
|
||||
users.avatar_url AS avatar_url,
|
||||
groups.organization_id AS organization_id,
|
||||
COALESCE(SUM(tu.cost_micros), 0)::BIGINT AS cost_micros,
|
||||
COUNT(*) FILTER (WHERE tu.cost_micros IS NULL)::BIGINT AS unpriced_usage_count,
|
||||
ARRAY_AGG(DISTINCT ai.provider ORDER BY ai.provider)::text[] AS providers,
|
||||
ARRAY_AGG(DISTINCT COALESCE(ai.client, 'Unknown') ORDER BY COALESCE(ai.client, 'Unknown'))::text[] AS clients,
|
||||
ARRAY_AGG(DISTINCT ai.model ORDER BY ai.model)::text[] AS models,
|
||||
COUNT(*) OVER ()::BIGINT AS count,
|
||||
COALESCE(SUM(SUM(tu.cost_micros)) OVER (), 0)::BIGINT AS total_cost_micros,
|
||||
COALESCE(SUM(COUNT(*) FILTER (WHERE tu.cost_micros IS NULL)) OVER (), 0)::BIGINT AS total_unpriced_usage_count
|
||||
FROM aibridge_token_usages tu
|
||||
JOIN aibridge_interceptions ai ON ai.id = tu.interception_id
|
||||
JOIN users ON users.id = ai.initiator_id
|
||||
JOIN groups ON groups.id = tu.effective_group_id
|
||||
WHERE groups.organization_id = @organization_id
|
||||
AND tu.created_at >= @period_start::timestamptz
|
||||
AND tu.created_at < @period_end::timestamptz
|
||||
AND CASE
|
||||
WHEN @provider_name::text != '' THEN ai.provider_name = @provider_name::text
|
||||
ELSE true
|
||||
END
|
||||
AND CASE
|
||||
WHEN @model::text != '' THEN ai.model = @model::text
|
||||
ELSE true
|
||||
END
|
||||
AND CASE
|
||||
WHEN @client::text != '' THEN COALESCE(ai.client, 'Unknown') = @client::text
|
||||
ELSE true
|
||||
END
|
||||
GROUP BY
|
||||
ai.initiator_id,
|
||||
spend.user_id,
|
||||
users.username,
|
||||
users.name,
|
||||
users.avatar_url,
|
||||
groups.organization_id
|
||||
ORDER BY cost_micros DESC, LOWER(users.username), ai.initiator_id
|
||||
@organization_id::uuid AS organization_id,
|
||||
spend.cost_micros,
|
||||
spend.unpriced_usage_count,
|
||||
spend.providers,
|
||||
spend.clients,
|
||||
spend.models,
|
||||
COUNT(*) OVER ()::BIGINT AS count,
|
||||
COALESCE(SUM(spend.cost_micros) OVER (), 0)::BIGINT AS total_cost_micros,
|
||||
COALESCE(SUM(spend.unpriced_usage_count) OVER (), 0)::BIGINT AS total_unpriced_usage_count
|
||||
FROM spend
|
||||
JOIN users ON users.id = spend.user_id
|
||||
ORDER BY cost_micros DESC, LOWER(users.username), spend.user_id
|
||||
LIMIT NULLIF(@limit_opt::int, 0)
|
||||
OFFSET @offset_opt::int;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user