Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions migrations/0028_ai_usage_status_index.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
-- Covers the daily AI-neuron budget query (sumAiEstimatedNeuronsSince):
-- SELECT sum(estimated_neurons) FROM ai_usage_events WHERE status = 'ok' AND created_at >= ?
-- Without this index that aggregate full-scans ai_usage_events, and it runs on every AI review/summary.
-- Numbered 0028 to avoid colliding with migrations 0026/0027 reserved by the AI-review PR (#652); the
-- D1 migration runner applies un-applied files in order and tolerates gaps.
CREATE INDEX IF NOT EXISTS ai_usage_events_status_created_idx ON ai_usage_events (status, created_at);
3 changes: 3 additions & 0 deletions src/db/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -923,5 +923,8 @@ export const aiUsageEvents = sqliteTable(
(table) => ({
featureCreated: index("ai_usage_events_feature_created_idx").on(table.feature, table.createdAt),
actorCreated: index("ai_usage_events_actor_created_idx").on(table.actor, table.createdAt),
// Covers the daily-budget query (sumAiEstimatedNeuronsSince): WHERE status='ok' AND created_at >= ?.
// Without it that aggregate full-scans ai_usage_events, which runs on every AI review/summary.
statusCreated: index("ai_usage_events_status_created_idx").on(table.status, table.createdAt),
}),
);
23 changes: 23 additions & 0 deletions test/unit/ai-usage-index.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { describe, expect, it } from "vitest";
import { createTestEnv } from "../helpers/d1";

describe("ai_usage_events budget index", () => {
it("creates the (status, created_at) index and the budget query uses it (SEARCH, not SCAN)", async () => {
const env = createTestEnv();

const idx = await env.DB.prepare("SELECT name FROM sqlite_master WHERE type='index' AND name = ?")
.bind("ai_usage_events_status_created_idx")
.first<{ name: string }>();
expect(idx?.name).toBe("ai_usage_events_status_created_idx");

// sumAiEstimatedNeuronsSince: WHERE status='ok' AND created_at >= ? — must SEARCH via the new index.
const plan = await env.DB.prepare(
"EXPLAIN QUERY PLAN SELECT coalesce(sum(estimated_neurons),0) FROM ai_usage_events WHERE created_at >= ? AND status = 'ok'",
)
.bind("2026-01-01T00:00:00.000Z")
.all<{ detail: string }>();
const detail = (plan.results ?? []).map((row) => row.detail).join(" ");
expect(detail).toContain("ai_usage_events_status_created_idx");
expect(detail).not.toContain("SCAN ai_usage_events ");
});
});