# Choose a vector database

For a small project, store your vectors in the database you already run: if that
is Postgres, add the `pgvector` extension and you are done. Reach for a dedicated
vector store only when scale, latency, or rich metadata filtering force the issue.
This guide explains what a vector database does, then gives you a
checkable way to choose one.

## What does a vector database do?

A vector
is the numeric fingerprint of a chunk of text. You produce one per chunk with an
[embedding model](/guides/embeddings-for-beginners), then store it. At query time you embed the question into the same
number space and ask: which stored vectors point in the same direction as this one?
That single operation, nearest-neighbour search,
is the whole job.

A vector database is the storage and search engine for that operation. It does
three things a plain table does not:

- **Indexes vectors for speed.** Comparing a query against every row is exact but
  slow once you pass a few hundred thousand rows. A vector index trades a small
  amount of recall for a large drop in latency.
- **Filters on metadata.** Production queries are rarely "find similar text"
  alone. They are "find similar text where `tenant_id = 42` and `published = true`".
  The store applies those filters alongside the vector search.
- **Stays current.** You insert, update, and delete vectors as your source data
  changes, without rebuilding everything.

If you are new to the surrounding pipeline (chunking, embedding, retrieving), read
[RAG Fundamentals](/guides/rag-fundamentals) first. This guide is about the one
box in that pipeline labelled "vector store".

## The one decision that drives the choice

Before comparing products, answer one question: do you already run a relational
database for this project?

- **Yes, and you have under a few million vectors.** Use that database's vector
  extension. For Postgres that is `pgvector`. You get vector search, metadata
  filtering, transactions, and backups in the system you already operate. No new
  service, no new bill, no second source of truth to keep in sync.
- **No, or vectors are the centre of the product, or you are past a few million
  rows with tight latency targets.** A dedicated vector store earns its keep.

Most small projects sit in the first bucket and never need to leave it. The common
mistake is to start with a dedicated store "to be safe" and pay for operating a
second database before there is a single user.

## When should I use pgvector?

`pgvector` is an open-source extension that adds a `vector` column type and
nearest-neighbour operators to Postgres. You enable it once, store embeddings in a
normal column, and query with SQL.

You do not write any model-calling code to use it. You ask Claude to wire it up.

```text
In this Postgres project, add a documents table with an embedding column using the pgvector extension. The embedding dimension is 1536. Show me the SQL migration and explain the index choice before running it.
```

The query is ordinary SQL. pgvector gives you distance operators rather than a
new query language:

| Operator | Meaning | Use when |
| --- | --- | --- |
| `<=>` | cosine distance | comparing meaning; the standard default for text |
| `<->` | L2 (Euclidean) distance | embeddings are normalised and you want straight-line distance |
| `<#>` | negative inner product | your embedding model is tuned for dot-product scoring |

A nearest-neighbour query reads like any other `ORDER BY ... LIMIT`:

```sql
SELECT id, body
FROM documents
WHERE tenant_id = 42
ORDER BY embedding <=> $1
LIMIT 5;
```

The `WHERE` clause is the point: vector search and your existing relational
filters run in one query, against one consistent copy of the data.A selective `WHERE` plus an HNSW index can quietly return fewer rows than your `LIMIT`, because the graph walk visits a fixed candidate pool first and your filter then discards most of it. Do not assume an empty result means no matches; widen the search pool (raise `hnsw.ef_search`) or add a plain B-tree index on the filtered column so Postgres can pre-filter when the predicate is very selective.

### Which pgvector index should I use, HNSW or IVFFlat?

Without an index, that query scans every row. That is exact and fine for tens of
thousands of rows. Past that, add an index. pgvector offers two, and the choice is
checkable, not a matter of taste.

| Index | Build | Memory | Recall | Best when |
| --- | --- | --- | --- | --- |
| HNSW | slower | higher | high with little tuning | data arrives over time; you want good recall by default |
| IVFFlat | faster | lower | needs tuning | you bulk-load data first, then build the index once |

HNSW
builds a graph incrementally, so it works correctly even on a table that is still
filling up, and it reaches high recall without much tuning. The cost is more memory
and slower index builds.
IVFFlat
clusters vectors first, so it builds faster and uses less memory, but it needs
representative data already in the table when you create the index. Build IVFFlat
on an empty table and you get meaningless clusters and poor recall.

The matching index definitions, one per distance operator family:The index operator class has to match the operator your query uses. Build with `vector_cosine_ops` but sort by `<->` (L2) and Postgres silently ignores the index and falls back to a full scan, so it looks fast in testing and crawls in production. Pick one distance, then use its operator everywhere.

```sql
CREATE INDEX ON documents USING hnsw (embedding vector_cosine_ops);
CREATE INDEX ON documents USING ivfflat (embedding vector_cosine_ops) WITH (lists = 100);
```

For a small project that grows gradually, start with HNSW and cosine distance.
That combination gives good recall with the least tuning. Let Claude generate the
migration and explain the tradeoff back to you so the decision is recorded:

```text
Add an HNSW index on the documents.embedding column using cosine distance. In a comment above the migration, note why HNSW over IVFFlat for a table that fills incrementally.
```

## When should I move to a dedicated vector store?

A dedicated vector database is a separate service whose entire design centres on
nearest-neighbour search. You move to one when a concrete limit bites, not on a
hunch. The signals:

- **Scale.** You are past roughly the low tens of millions of vectors and the
  Postgres index no longer fits comfortably in memory.
- **Latency under load.** You have a strict per-query budget at high concurrency
  and your Postgres instance cannot hold it while also serving its other traffic.
- **Vectors are the product.** Search relevance is the core feature, so you want
  built-in [hybrid search](/guides/hybrid-search-for-rag), reranking hooks, and tuning knobs as first-class parts
  of the engine.
- **Operational separation.** You want vector load to scale independently from
  your transactional database.

If none of these is true, staying on `pgvector` is the cheaper and simpler
correct answer.

### The shape of the field

The dedicated stores split along one axis first: managed (someone else runs it)
versus self-hosted (you run it). Pick that axis before picking a name.

| Store | Hosting | Picked for |
| --- | --- | --- |
| Pinecone | managed only | zero operational overhead; you never run the database |
| Qdrant | open source · managed cloud | strong performance-to-cost on self-hosted |
| Weaviate | open source · managed cloud | hybrid search as a built-in feature |
| Chroma | open source · embedded or server | simplicity for local development and prototypes |
| pgvector | runs inside Postgres | reusing the database you already operate |

Treat the "picked for" column as a starting hypothesis, then verify against your
own data and query mix. Vendor benchmarks use vendor-friendly workloads. Yours is
the only one that counts.

## Connect a store to Claude through MCP

Once a store holds your data, you want Claude to query it during a coding or
research session without you copying results around by hand. That is what the
Model Context Protocol
(MCP) is for. You run an MCP server that exposes the store's search as a tool, and
Claude calls it. See [MCP fundamentals](/guides/mcp-fundamentals) for the full
picture and [Useful MCP servers](/guides/useful-mcp-servers) for what already
exists.

An MCP server is registered in a JSON config. A server that talks to a managed
store over the network usually takes a connection URL and an API key as
environment variables:

```json
{
  "mcpServers": {
    "vector-store": {
      "command": "npx",
      "args": ["-y", "some-vector-store-mcp-server"],
      "env": {
        "VECTOR_STORE_URL": "https://your-instance.example.com",
        "VECTOR_STORE_API_KEY": "your-key-here"
      }
    }
  }
}
```

The exact server name, arguments, and environment variables come from that
server's own documentation, not from memory. Keep the API key in an environment
variable rather than pasting it into the config file, and review what the server
can do before you trust it: read [MCP security basics](/guides/mcp-security-basics)
first. A vector-store tool that can delete collections is one prompt away from
deleting them.

With the server connected, you drive retrieval in plain language:

```text
Using the vector-store tool, search the support-docs collection for chunks about password reset, filtered to the published articles, and show the top five with their source URLs.
```

## A safe path for a real project

You do not have to choose the final store on day one. Pick a path that lets you
defer the expensive decision until you have data to make it with.

1. **Start on `pgvector`.** Add the extension to your existing Postgres, store
   embeddings, and add an HNSW cosine index. You now have working retrieval with
   no new service.
2. **Keep the embedding step separate from storage.** Whatever produces your
   vectors should not assume a specific store. Then moving stores is a change in
   one place, not a rewrite.
3. **Measure before you migrate.** Track query latency at your real concurrency
   and watch index memory. Migrate only when a number, not a worry, crosses a
   line you set in advance.
4. **When you migrate, re-evaluate retrieval.** A new store changes the index and
   sometimes the distance metric. Re-run your retrieval evaluation so a regression
   shows up as a number. [RAG Fundamentals](/guides/rag-fundamentals) covers
   recall and precision; check both before and after.

Ask Claude to hold you to this rather than reaching for a heavier tool early:

```text
Review how this project stores and queries embeddings. Confirm the embedding step is decoupled from the storage layer, and list anything that would make swapping the vector store hard later. Do not change code yet, just report.
```

## What to remember

- A vector database is storage plus nearest-neighbour search plus metadata
  filtering. That is the whole job.
- If you already run Postgres and have under a few million vectors, use
  `pgvector`. It is the cheapest correct answer for a small project.
- Default to an HNSW index with cosine distance for data that grows over time;
  use IVFFlat only when you bulk-load first and want faster, lighter builds.
- Move to a dedicated store when a concrete limit (scale, latency, or store-centric
  features) bites, and decide managed versus self-hosted before picking a name.
- Connect whichever store you choose to Claude through MCP so retrieval happens in
  your session, and lock down the server's permissions first.
