diff --git a/cloud/apps/relay/src/credential-cleanup-sweep-postgres.test.ts b/cloud/apps/relay/src/credential-cleanup-sweep-postgres.test.ts index aa720a67b2d..45f38326d3a 100644 --- a/cloud/apps/relay/src/credential-cleanup-sweep-postgres.test.ts +++ b/cloud/apps/relay/src/credential-cleanup-sweep-postgres.test.ts @@ -120,10 +120,11 @@ describePostgres('credential cleanup against PostgreSQL', () => { expect(expiry).toContain('relay_invites_sweep_expiry') }) - it('plans the live-basis sweep off an index rather than the 1.5 GB heap', async () => { + it('plans the basis sweep off the composite index rather than the 1.5 GB heap', async () => { // The shape that made this the most expensive statement in the sweep: 20,000 settled bases to - // 50 live ones, so the composite (active, deadline) index spans 400x the rows the sweep wants. - // Which index serves it is the planner's call, the same as for the two invite sweeps below. + // 50 live ones. A partial index on active = 1 looks like the answer to that ratio and is not: + // a basis is inserted active and flipped to 0, so it accumulates the same dead entries, and + // the planner picks the composite index anyway. See the schema comment beside it. await database.query( `INSERT INTO relay_connection_bases (basis_conn_id, user_id, relay_host_id, relay_device_id, owning_control_generation, @@ -148,7 +149,7 @@ describePostgres('credential cleanup against PostgreSQL', () => { ) expect(sweep).not.toContain('Seq Scan on relay_connection_bases') - expect(sweep).toMatch(/using relay_connection_bases_(active|live)_deadline/) + expect(sweep).toContain('using relay_connection_bases_active_deadline') }) it('plans the drained basis reaper off the composite index, not the heap', async () => { diff --git a/cloud/apps/relay/src/database.ts b/cloud/apps/relay/src/database.ts index a62bb35798f..972f3ce2aa1 100644 --- a/cloud/apps/relay/src/database.ts +++ b/cloud/apps/relay/src/database.ts @@ -172,16 +172,14 @@ CREATE TABLE IF NOT EXISTS relay_connection_bases ( -- accumulate unboundedly. Unindexed it seq-scans millions of rows every cycle -- and holds the maintenance transaction open long enough to time out -- assignment lock waits. +-- Why not a partial index on active = 1: a basis is inserted active and flipped to 0, so each +-- deactivation leaves a dead entry in that index too. Measured on production-shaped history it +-- carries the same dead entries as this one, the planner picks this one in every state, and it +-- costs ~65 bytes of WAL per insert. Bloat here is cured by reaping and vacuum, not by a narrower +-- index. CREATE INDEX IF NOT EXISTS relay_connection_bases_active_deadline ON relay_connection_bases(active, deadline); --- schema-deferrable: created out of band, so a boot that cannot take the lock must retry --- Why: the index above spans every row, and inactive bases outnumber live ones by ~6.6M to a few --- hundred, so the sweep still walked ~283 MB of index to find them. This one holds only the rows --- the sweep can act on. Keeping both: the composite is also what makes the reaper an index range. -CREATE INDEX IF NOT EXISTS relay_connection_bases_live_deadline - ON relay_connection_bases(deadline) WHERE active = 1; - CREATE TABLE IF NOT EXISTS relay_direct_authorizations ( direct_auth_id TEXT PRIMARY KEY, user_id TEXT NOT NULL, diff --git a/cloud/apps/relay/src/postgres-maintenance-sweep-plans.test.ts b/cloud/apps/relay/src/postgres-maintenance-sweep-plans.test.ts index de8573a0d6c..a8d0e1e3bf9 100644 --- a/cloud/apps/relay/src/postgres-maintenance-sweep-plans.test.ts +++ b/cloud/apps/relay/src/postgres-maintenance-sweep-plans.test.ts @@ -56,9 +56,6 @@ describePostgres('PostgreSQL maintenance sweep plans', () => { const plan = result.rows.map((row) => String(row['QUERY PLAN'])).join('\n') expect(plan).not.toMatch(/Seq Scan on relay_connection_bases/) - // Either index keeps the sweep off the table. It used to be the composite one; the partial - // relay_connection_bases_live_deadline now wins on cost, because it spans only the live rows - // rather than all ~6.6M, and that is the improvement, not a regression in this invariant. - expect(plan).toMatch(/relay_connection_bases_(active|live)_deadline/) + expect(plan).toMatch(/relay_connection_bases_active_deadline/) }) }) diff --git a/cloud/apps/relay/src/relay-schema-lock-targets.test.ts b/cloud/apps/relay/src/relay-schema-lock-targets.test.ts index 26348bb7d9c..68a7c6f0216 100644 --- a/cloud/apps/relay/src/relay-schema-lock-targets.test.ts +++ b/cloud/apps/relay/src/relay-schema-lock-targets.test.ts @@ -27,12 +27,6 @@ const GOLDEN_LOCK_TAKING: SchemaLockTarget[] = [ { kind: 'index', table: 'relay_devices', name: 'relay_devices_current_hash', skipWhen: 'present' }, { kind: 'index', table: 'relay_devices', name: 'relay_devices_grace_hash', skipWhen: 'present' }, { kind: 'index', table: 'relay_connection_bases', name: 'relay_connection_bases_active_deadline', skipWhen: 'present' }, - { - kind: 'index', - table: 'relay_connection_bases', - name: 'relay_connection_bases_live_deadline', - skipWhen: 'present' - }, { kind: 'index', table: 'relay_direct_authorizations', @@ -251,7 +245,6 @@ describe('relay boot-time lock targets', () => { expect(deferrable.map((statement) => sqlWithoutComments(statement).replace(/\s+/g, ' '))).toEqual([ "CREATE INDEX IF NOT EXISTS relay_invites_sweep_expiry ON relay_invites(expires_at) WHERE state IN ('available', 'reserved', 'cooldown')", "CREATE INDEX IF NOT EXISTS relay_invites_sweep_reservation ON relay_invites(reservation_expires_at) WHERE state = 'reserved'", - 'CREATE INDEX IF NOT EXISTS relay_connection_bases_live_deadline ON relay_connection_bases(deadline) WHERE active = 1', 'CREATE INDEX IF NOT EXISTS relay_direct_authorizations_pending_deadline ON relay_direct_authorizations(deadline) WHERE consumed_at IS NULL', 'CREATE INDEX IF NOT EXISTS relay_rate_windows_started ON relay_rate_windows(window_started_at)', 'DROP INDEX IF EXISTS relay_assignment_activity_expiry', @@ -274,12 +267,6 @@ describe('relay boot-time lock targets', () => { name: 'relay_invites_sweep_reservation', skipWhen: 'present' }, - { - kind: 'index', - table: 'relay_connection_bases', - name: 'relay_connection_bases_live_deadline', - skipWhen: 'present' - }, { kind: 'index', table: 'relay_direct_authorizations',