From 1afd6403b2ab9ced940effb7e8ef8983f802fa6b Mon Sep 17 00:00:00 2001 From: Harlan Wilton Date: Mon, 5 Oct 2026 16:08:46 +1100 Subject: [PATCH 1/2] perf(d1): avoid sorting every AI Ready route --- .../0136_ai_ready_route_order_index.sql | 4 ++ scripts/lib/d1-schema-verifier.ts | 1 + test/unit/ai-ready-route-cost.test.ts | 37 +++++++++++++++++++ 3 files changed, 42 insertions(+) create mode 100644 migrations/0136_ai_ready_route_order_index.sql create mode 100644 test/unit/ai-ready-route-cost.test.ts diff --git a/migrations/0136_ai_ready_route_order_index.sql b/migrations/0136_ai_ready_route_order_index.sql new file mode 100644 index 00000000..e893c956 --- /dev/null +++ b/migrations/0136_ai_ready_route_order_index.sql @@ -0,0 +1,4 @@ +-- Match the AI Ready listing filter and route order so LIMIT stops early. +-- Existing status indexes still require sorting every eligible page. +CREATE INDEX IF NOT EXISTS idx_ai_ready_pages_status_route +ON ai_ready_pages(indexed, is_error, route); diff --git a/scripts/lib/d1-schema-verifier.ts b/scripts/lib/d1-schema-verifier.ts index f893f8cd..554c1c13 100644 --- a/scripts/lib/d1-schema-verifier.ts +++ b/scripts/lib/d1-schema-verifier.ts @@ -251,6 +251,7 @@ export const APPLIED_DUPLICATE_MIGRATION_NUMBERS: Readonly> /** diff --git a/test/unit/ai-ready-route-cost.test.ts b/test/unit/ai-ready-route-cost.test.ts new file mode 100644 index 00000000..f38a419c --- /dev/null +++ b/test/unit/ai-ready-route-cost.test.ts @@ -0,0 +1,37 @@ +import { readFileSync } from 'node:fs' +import Database from 'better-sqlite3' +import { describe, expect, it } from 'vitest' + +describe('AI Ready route listing cost', () => { + it('returns ordered eligible pages without scanning and sorting every eligible route', () => { + const db = new Database(':memory:') + db.exec(` + CREATE TABLE ai_ready_pages ( + route TEXT PRIMARY KEY, title TEXT, description TEXT, headings TEXT, + keywords TEXT, updated_at INTEGER, is_error INTEGER, locale TEXT, + indexed INTEGER, indexnow_synced_at INTEGER, indexed_at INTEGER + ); + CREATE INDEX idx_ai_ready_pages_indexed_is_error ON ai_ready_pages(indexed, is_error); + CREATE INDEX idx_ai_ready_pages_indexnow_scan ON ai_ready_pages(indexed, is_error, indexnow_synced_at, indexed_at); + `) + const insert = db.prepare('INSERT INTO ai_ready_pages(route,title,indexed,is_error) VALUES(?,?,?,?)') + db.transaction(() => { + for (let i = 0; i < 20_000; i++) { + const route = `/page/${String(i).padStart(5, '0')}` + insert.run(route, `Page ${i}`, i % 2, i % 5 === 0 ? 1 : 0) + } + })() + const migration = readFileSync('migrations/0136_ai_ready_route_order_index.sql', 'utf8') + db.exec(migration) + db.exec(migration) + const sql = 'SELECT route, title, description, headings, keywords, updated_at, is_error, locale FROM ai_ready_pages WHERE indexed = ? AND is_error = 0 ORDER BY route LIMIT ?' + const plan = db.prepare(`EXPLAIN QUERY PLAN ${sql}`).all(1, 3) as Array<{ detail: string }> + expect(plan.map(row => row.detail).join('\n')).not.toContain('TEMP B-TREE') + expect(db.prepare(sql).all(1, 3)).toMatchObject([ + { route: '/page/00001', title: 'Page 1' }, + { route: '/page/00003', title: 'Page 3' }, + { route: '/page/00007', title: 'Page 7' }, + ]) + db.close() + }) +}) From a6367edb71997f2577e6150e15e7e33929f8f6ac Mon Sep 17 00:00:00 2001 From: Harlan Wilton Date: Mon, 5 Oct 2026 16:24:02 +1100 Subject: [PATCH 2/2] test(d1): use a lowercase suite title --- test/unit/ai-ready-route-cost.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/unit/ai-ready-route-cost.test.ts b/test/unit/ai-ready-route-cost.test.ts index f38a419c..7cf7100a 100644 --- a/test/unit/ai-ready-route-cost.test.ts +++ b/test/unit/ai-ready-route-cost.test.ts @@ -2,7 +2,7 @@ import { readFileSync } from 'node:fs' import Database from 'better-sqlite3' import { describe, expect, it } from 'vitest' -describe('AI Ready route listing cost', () => { +describe('ai-ready route listing cost', () => { it('returns ordered eligible pages without scanning and sorting every eligible route', () => { const db = new Database(':memory:') db.exec(`