diff --git a/internal/postgres/schema.go b/internal/postgres/schema.go index 414851fe..12797245 100644 --- a/internal/postgres/schema.go +++ b/internal/postgres/schema.go @@ -690,13 +690,29 @@ func createContentSearchIndexesPG(ctx context.Context, db *sql.DB) { log.Printf("pg schema: invalid pg_trgm schema %q: %v", extSchema, err) return } + // fastupdate=off keeps the index bounded: the default fastupdate=on + // buffers inserts into a pending list that only VACUUM merges, which grows + // unbounded when continuous ingest starves autovacuum. if _, err := db.ExecContext(ctx, fmt.Sprintf( `CREATE INDEX IF NOT EXISTS idx_messages_content_trgm - ON messages USING gin (content %s.gin_trgm_ops)`, quotedExt, + ON messages USING gin (content %s.gin_trgm_ops) + WITH (fastupdate = off)`, quotedExt, )); err != nil { log.Printf( "pg schema: creating messages.content trigram index failed: %v", err, ) + return + } + // CREATE INDEX IF NOT EXISTS only applies WITH (fastupdate = off) on + // first creation. Re-apply on every boot so stores upgraded from a + // prior schema (which left fastupdate=on) also get the bounded index. + if _, err := db.ExecContext(ctx, + `ALTER INDEX idx_messages_content_trgm SET (fastupdate = off)`, + ); err != nil { + log.Printf( + "pg schema: disabling fastupdate on messages.content trigram index failed: %v", + err, + ) } } diff --git a/internal/postgres/search_content_pgtest_test.go b/internal/postgres/search_content_pgtest_test.go index bb0397c7..df278f48 100644 --- a/internal/postgres/search_content_pgtest_test.go +++ b/internal/postgres/search_content_pgtest_test.go @@ -481,6 +481,24 @@ func TestPGContentSearchTrigramIndex(t *testing.T) { ).Scan(&hasIdx), "query pg_indexes") assert.True(t, hasIdx, "idx_messages_content_trgm missing after EnsureSchema") + + // fastupdate=off must be set so the pending list cannot grow + // unbounded under continuous ingest. The schema bootstrap applies + // this on every boot (including stores upgraded from an earlier + // schema that created the index with the default fastupdate=on). + var fastupdateOff bool + require.NoError(t, store.DB().QueryRowContext(ctx, + `SELECT EXISTS ( + SELECT 1 + FROM pg_class c + JOIN pg_namespace n ON n.oid = c.relnamespace + WHERE n.nspname = $1 + AND c.relname = 'idx_messages_content_trgm' + AND 'fastupdate=off' = ANY(c.reloptions) + )`, contentSearchSchema, + ).Scan(&fastupdateOff), "query pg_class.reloptions") + assert.True(t, fastupdateOff, + "idx_messages_content_trgm must have fastupdate=off") } // TestPGSearchContentRegex verifies regex mode.