Setup guide

Add semantic search with pgvector

Store embeddings beside source records so search can use normal filters and permissions.

The Goal

Build semantic search without splitting your data model on day one.

The first version should answer one question: can users find better results with embeddings than with plain keyword search?

Store Chunks, Not Mystery Vectors

create extension if not exists vector;

create table document_chunks (
  id bigserial primary key,
  document_id bigint not null,
  account_id bigint not null,
  body text not null,
  embedding_model text not null,
  embedding vector(1536) not null,
  created_at timestamptz not null default now()
);

Keep the source text. Keep the source ID. Keep the account or permission boundary.

If the vector search result cannot explain what record it came from, the feature will be hard to debug.

For a small dataset, exact search is easier to reason about.

select document_id, body
from document_chunks
where account_id = 42
order by embedding <=> $1
limit 8;

The account_id filter matters. Do not search every customer’s data and filter later in application code.

Add An Index When You Need It

When exact search gets slow, add an approximate index.

create index document_chunks_embedding_hnsw
  on document_chunks
  using hnsw (embedding vector_cosine_ops);

After adding the index, test recall. Do the right results still show up? If not, the index is fast but not useful.

Improve Quality Before Infrastructure

Most early search quality problems come from the content, not the database.

Check these first:

Changing chunking often helps more than changing infrastructure.

Keep Model Versions

Store the model name or version with each embedding. If you change models, rebuild or separate the old vectors.

Mixed embeddings are a quiet source of bad search results.

When To Move On

Stay with pgvector while search is part of the product, not the whole product.

Move to dedicated search infrastructure when search needs its own team, heavy ranking work, large index operations, or scale controls that do not fit your Postgres setup.

References