From db45ca7af5cd7d3797b421d88108e0629674feba Mon Sep 17 00:00:00 2001 From: Marc Anders Date: Sun, 7 Jun 2026 19:13:49 +0200 Subject: [PATCH] fix(pgvector): skip HNSW index creation when index already exists CREATE INDEX IF NOT EXISTS still requires table ownership and fails with 'must be owner of table' on managed Postgres (e.g. Cloud SQL) where the schema was bootstrapped by a different role. When the index exists from a prior run, we now skip the CREATE entirely by checking pg_class first. This prevents ANN search from being unnecessarily disabled, which was causing activation to fall back to Path B (fetch-all). --- src/db/postgres/index.ts | 28 +++++++++++++++++++++------ src/db/postgres/migrations.ts | 36 ++++++++++++++++++++++++----------- 2 files changed, 47 insertions(+), 17 deletions(-) diff --git a/src/db/postgres/index.ts b/src/db/postgres/index.ts index 5df699c..1b639f6 100644 --- a/src/db/postgres/index.ts +++ b/src/db/postgres/index.ts @@ -465,14 +465,30 @@ export class PostgresKnowledgeDB implements IKnowledgeStore { this.pgvectorReady = false; return; } + // Check whether the HNSW index already exists. CREATE INDEX IF NOT EXISTS + // still requires table ownership, so it can fail with "must be owner of + // table" on managed Postgres where the schema was bootstrapped by a + // different role (e.g. Cloud SQL with a separate admin user). When the + // index is already there from a prior run, treat that as success and + // keep ANN enabled instead of degrading to Path B. + const hnswExists = await this.sql` + SELECT 1 FROM pg_class c + JOIN pg_namespace n ON n.oid = c.relnamespace + WHERE c.relkind = 'i' + AND n.nspname = current_schema() + AND c.relname = 'idx_entry_embedding_halfvec_hnsw' + `; + try { await this.sql`DROP INDEX IF EXISTS idx_entry_embedding_vec_hnsw`; - await this.sql.unsafe(` - CREATE INDEX IF NOT EXISTS idx_entry_embedding_halfvec_hnsw - ON knowledge_entry - USING hnsw ((embedding_vec::halfvec(${dims})) halfvec_cosine_ops) - WITH (m = 16, ef_construction = 64) - `); + if (hnswExists.length === 0) { + await this.sql.unsafe(` + CREATE INDEX IF NOT EXISTS idx_entry_embedding_halfvec_hnsw + ON knowledge_entry + USING hnsw ((embedding_vec::halfvec(${dims})) halfvec_cosine_ops) + WITH (m = 16, ef_construction = 64) + `); + } } catch (err) { logger.warn( `[pg-db] ANN index creation failed — ANN search disabled. Will retry on next startup. Error: ${err instanceof Error ? err.message : String(err)}`, diff --git a/src/db/postgres/migrations.ts b/src/db/postgres/migrations.ts index 7a834a9..696655b 100644 --- a/src/db/postgres/migrations.ts +++ b/src/db/postgres/migrations.ts @@ -514,17 +514,31 @@ export const PG_MIGRATIONS: Array<{ // Replace legacy vector HNSW index with halfvec ANN index when supported. await sql`DROP INDEX IF EXISTS idx_entry_embedding_vec_hnsw`; if (dims <= 4000) { - try { - await sql.unsafe(` - CREATE INDEX IF NOT EXISTS idx_entry_embedding_halfvec_hnsw - ON knowledge_entry - USING hnsw ((embedding_vec::halfvec(${dims})) halfvec_cosine_ops) - WITH (m = 16, ef_construction = 64) - `); - } catch (err) { - logger.warn( - `[pg-db] Migration v17: ANN index creation skipped. ANN search disabled for this store. Error: ${err instanceof Error ? err.message : String(err)}`, - ); + // Skip CREATE INDEX if the halfvec HNSW index is already present. + // CREATE INDEX IF NOT EXISTS still requires table ownership and + // fails with "must be owner of table" on managed Postgres where + // the schema was bootstrapped by a separate admin user. When the + // index exists from a prior run, treat that as success. + const hnswExists = await sql` + SELECT 1 FROM pg_class c + JOIN pg_namespace n ON n.oid = c.relnamespace + WHERE c.relkind = 'i' + AND n.nspname = current_schema() + AND c.relname = 'idx_entry_embedding_halfvec_hnsw' + `; + if (hnswExists.length === 0) { + try { + await sql.unsafe(` + CREATE INDEX IF NOT EXISTS idx_entry_embedding_halfvec_hnsw + ON knowledge_entry + USING hnsw ((embedding_vec::halfvec(${dims})) halfvec_cosine_ops) + WITH (m = 16, ef_construction = 64) + `); + } catch (err) { + logger.warn( + `[pg-db] Migration v17: ANN index creation skipped. ANN search disabled for this store. Error: ${err instanceof Error ? err.message : String(err)}`, + ); + } } } else { logger.warn(