From b0cc806422b11a4c8e7dace3a249e8d603133e72 Mon Sep 17 00:00:00 2001 From: JSONbored <49853598+JSONbored@users.noreply.github.com> Date: Mon, 20 Jul 2026 04:01:49 -0700 Subject: [PATCH] fix(orb): index orb_relay_pending.created_at to stop full table scans (#7430) pruneRelayPending's SELECT and DELETE filter only by created_at, but both existing indexes on orb_relay_pending lead with installation_id -- neither could serve that predicate, so both fell back to a full table scan, run fleet-wide on every 30s orb-relay-drain pull, occasionally exceeding the pull request's own 30s timeout budget (Sentry: orb_relay_drain TimeoutError, recurring since 2026-07-15). Adds the missing created_at index (migration 0167, mirrored in the Drizzle schema) so both queries become index range scans. --- ...167_orb_relay_pending_created_at_index.sql | 5 +++ src/db/schema.ts | 3 ++ ...orb-relay-pending-created-at-index.test.ts | 37 +++++++++++++++++++ 3 files changed, 45 insertions(+) create mode 100644 migrations/0167_orb_relay_pending_created_at_index.sql create mode 100644 test/unit/orb-relay-pending-created-at-index.test.ts diff --git a/migrations/0167_orb_relay_pending_created_at_index.sql b/migrations/0167_orb_relay_pending_created_at_index.sql new file mode 100644 index 0000000000..f12072beb9 --- /dev/null +++ b/migrations/0167_orb_relay_pending_created_at_index.sql @@ -0,0 +1,5 @@ +-- pruneRelayPending (src/orb/relay.ts) runs a fleet-wide TTL sweep filtered ONLY by created_at (not scoped to +-- one installation_id), on every orb-relay-drain pull (#7430). Both existing indexes on this table lead with +-- installation_id, so that predicate couldn't use either one -- the SELECT and DELETE both fell back to a +-- full table scan, occasionally exceeding the pull request's 30s timeout budget. +CREATE INDEX IF NOT EXISTS idx_orb_relay_pending_created_at ON orb_relay_pending (created_at); diff --git a/src/db/schema.ts b/src/db/schema.ts index aa3cd1d232..bb8be66b9a 100644 --- a/src/db/schema.ts +++ b/src/db/schema.ts @@ -879,6 +879,9 @@ export const orbRelayPending = sqliteTable( coalesce: index("idx_orb_relay_pending_coalesce") .on(table.installationId, table.coalesceKey) .where(sql`coalesce_key IS NOT NULL`), + // pruneRelayPending (src/orb/relay.ts) filters/deletes by created_at alone (fleet-wide TTL sweep, not + // scoped to one installation) -- neither index above leads with created_at, so that scan was unindexed. + createdAt: index("idx_orb_relay_pending_created_at").on(table.createdAt), }), ); diff --git a/test/unit/orb-relay-pending-created-at-index.test.ts b/test/unit/orb-relay-pending-created-at-index.test.ts new file mode 100644 index 0000000000..328958b33c --- /dev/null +++ b/test/unit/orb-relay-pending-created-at-index.test.ts @@ -0,0 +1,37 @@ +import { describe, expect, it } from "vitest"; +import { createTestEnv } from "../helpers/d1"; + +// Regression test for #7430: pruneRelayPending (src/orb/relay.ts) filters/deletes by created_at alone +// (a fleet-wide TTL sweep, not scoped to one installation_id). Neither pre-existing index on +// orb_relay_pending leads with created_at, so both queries fell back to a full table scan -- occasionally +// exceeding the orb-relay-drain pull's 30s timeout budget under fleet load. +describe("orb_relay_pending created_at index", () => { + it("creates the created_at index and pruneRelayPending's queries use 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("idx_orb_relay_pending_created_at") + .first<{ name: string }>(); + expect(idx?.name).toBe("idx_orb_relay_pending_created_at"); + + // The drop-log sample SELECT: WHERE created_at < ? ORDER BY created_at, delivery_id LIMIT ?. + const selectPlan = await env.DB.prepare( + "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 ?", + ) + .bind(24, 20) + .all<{ detail: string }>(); + const selectDetail = (selectPlan.results ?? []).map((row) => row.detail).join(" "); + expect(selectDetail).toContain("idx_orb_relay_pending_created_at"); + expect(selectDetail).not.toContain("SCAN orb_relay_pending"); + + // The prune DELETE: WHERE created_at < ?. + const deletePlan = await env.DB.prepare( + "EXPLAIN QUERY PLAN DELETE FROM orb_relay_pending WHERE created_at < datetime('now', '-' || ? || ' hours')", + ) + .bind(24) + .all<{ detail: string }>(); + const deleteDetail = (deletePlan.results ?? []).map((row) => row.detail).join(" "); + expect(deleteDetail).toContain("idx_orb_relay_pending_created_at"); + expect(deleteDetail).not.toContain("SCAN orb_relay_pending"); + }); +});