Skip to content

Commit 64d518b

Browse files
committed
fix(orb): index orb_relay_pending by created_at so the relay-drain prune stops full-scanning
pruneRelayPending runs on every pull-mode relay-drain (fleet-wide, every 30s) and filters orb_relay_pending by a bare created_at predicate: SELECT ... WHERE created_at < ? ORDER BY created_at, delivery_id LIMIT ? DELETE WHERE created_at < ? Both existing indexes (idx_orb_relay_pending_install, idx_orb_relay_pending_coalesce) lead with installation_id, so neither serves a created_at-only predicate — the SELECT and DELETE full-scan the table and can blow the pull request's AbortSignal.timeout(30_000) budget, surfacing as the recurring TimeoutError in Sentry. Add a (created_at, delivery_id) index: it turns both queries into range scans and lets the SELECT satisfy its ORDER BY (created_at, delivery_id) straight from the index with no temp-B-tree sort. Closes #7430
1 parent 25decd9 commit 64d518b

3 files changed

Lines changed: 49 additions & 0 deletions

File tree

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
-- #7430: pruneRelayPending (run on every pull-mode relay-drain, fleet-wide, every 30s) filters
2+
-- orb_relay_pending by a bare created_at predicate:
3+
-- SELECT ... WHERE created_at < ? ORDER BY created_at, delivery_id LIMIT ?
4+
-- DELETE WHERE created_at < ?
5+
-- Both existing indexes lead with installation_id (idx_orb_relay_pending_install /
6+
-- idx_orb_relay_pending_coalesce), so neither serves a created_at-only predicate — the SELECT and
7+
-- DELETE full-scan the table and can exceed the pull request's AbortSignal.timeout(30_000) budget.
8+
-- A (created_at, delivery_id) index turns both into range scans and lets the SELECT satisfy its
9+
-- ORDER BY (created_at, delivery_id) from the index without a sort.
10+
CREATE INDEX IF NOT EXISTS idx_orb_relay_pending_created_at ON orb_relay_pending (created_at, delivery_id);

src/db/schema.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -879,6 +879,10 @@ export const orbRelayPending = sqliteTable(
879879
coalesce: index("idx_orb_relay_pending_coalesce")
880880
.on(table.installationId, table.coalesceKey)
881881
.where(sql`coalesce_key IS NOT NULL`),
882+
// #7430: pruneRelayPending filters by a bare created_at predicate (no installation_id), which the
883+
// two installation_id-leading indexes above can't serve — this one keeps that fleet-wide 30s drain
884+
// prune a range scan instead of a full table scan.
885+
createdAt: index("idx_orb_relay_pending_created_at").on(table.createdAt, table.deliveryId),
882886
}),
883887
);
884888

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import { describe, expect, it } from "vitest";
2+
import { createTestEnv } from "../helpers/d1";
3+
4+
describe("orb_relay_pending created_at prune index", () => {
5+
it("creates the (created_at, delivery_id) index and pruneRelayPending's queries use it (SEARCH, not SCAN)", async () => {
6+
const env = createTestEnv();
7+
8+
const idx = await env.DB.prepare("SELECT name FROM sqlite_master WHERE type='index' AND name = ?")
9+
.bind("idx_orb_relay_pending_created_at")
10+
.first<{ name: string }>();
11+
expect(idx?.name).toBe("idx_orb_relay_pending_created_at");
12+
13+
// pruneRelayPending's SELECT: WHERE created_at < ? ORDER BY created_at, delivery_id — must SEARCH via
14+
// the new index, whose column order matches the ORDER BY so no separate sort (temp B-tree) is needed.
15+
const selectPlan = await env.DB.prepare(
16+
"EXPLAIN QUERY PLAN SELECT delivery_id, event_name, installation_id FROM orb_relay_pending WHERE created_at < datetime('now', '-' || ? || ' hours') ORDER BY created_at, delivery_id LIMIT ?",
17+
)
18+
.bind(24, 20)
19+
.all<{ detail: string }>();
20+
const selectDetail = (selectPlan.results ?? []).map((row) => row.detail).join(" ");
21+
expect(selectDetail).toContain("idx_orb_relay_pending_created_at");
22+
expect(selectDetail).not.toContain("SCAN orb_relay_pending ");
23+
expect(selectDetail).not.toContain("USE TEMP B-TREE FOR ORDER BY");
24+
25+
// pruneRelayPending's DELETE: WHERE created_at < ? — must SEARCH via the same index.
26+
const deletePlan = await env.DB.prepare(
27+
"EXPLAIN QUERY PLAN DELETE FROM orb_relay_pending WHERE created_at < datetime('now', '-' || ? || ' hours')",
28+
)
29+
.bind(24)
30+
.all<{ detail: string }>();
31+
const deleteDetail = (deletePlan.results ?? []).map((row) => row.detail).join(" ");
32+
expect(deleteDetail).toContain("idx_orb_relay_pending_created_at");
33+
expect(deleteDetail).not.toContain("SCAN orb_relay_pending ");
34+
});
35+
});

0 commit comments

Comments
 (0)