PhoenixKit.Migrations.Postgres.V193 (phoenix_kit v2.29.1)

Copy Markdown View Source

V193: indexes for the AI spend caps.

phoenix_kit_ai's spend caps (PhoenixKitAI.Budget.spent/3) sum cost_cents over the trailing 24 hours of SUCCESSFUL requests, for one endpoint or one user:

WHERE endpoint_uuid = $1 AND inserted_at >= $2 AND status = 'success'

phoenix_kit_ai_requests only had single-column indexes, so the planner either walks one endpoint's (or user's) entire history to answer a one-day question, or combines two bitmaps. These two composite partial indexes answer it directly — equality column first, the time range second, only successful rows, and cost_cents carried in the index so the sum can be read from it:

(endpoint_uuid, inserted_at) INCLUDE (cost_cents) WHERE status = 'success'
(user_uuid,     inserted_at) INCLUDE (cost_cents) WHERE status = 'success'

The predicate must stay a literal in the query

A partial index is only used when the planner can prove the query's condition implies the index's. status = 'success' written as a literal does; status = $1 does not once Postgres switches a prepared statement to a generic plan, and the index silently drops out of the plan — fast in a test, slow in production. Budget.spent/3 writes the status as a literal for exactly this reason.

No global (inserted_at) WHERE status = 'success' index: nothing queries a site-wide window today, and an unused index is write cost on the table every AI call inserts into.

Why not CONCURRENTLY

Same reasoning as V167/V168: a failed concurrent build leaves an INVALID index that IF NOT EXISTS then matches by name, turning the next run into a silent no-op. A plain build takes a SHARE lock (reads continue; inserts into this table wait for the build), bounded by lock_timeout so a long-running transaction makes the migration fail loudly instead of hanging. On a very large request log, run this migration in a quiet window.

Additive only. Index names stay bare on CREATE (prefix-safe migration rules).

Summary

Functions

Rolls V193 back: drops both indexes. Nothing is lost but speed.

Functions

down(opts)

Rolls V193 back: drops both indexes. Nothing is lost but speed.

up(opts)