Back to blog

// OSSeva Blog

Operations

pgvector in Production: HNSW vs IVFFlat, the Limits, and When a Dedicated Vector Database Fits Better

Randall McClure10 min read

The short answer

For most teams that already run PostgreSQL, pgvector is enough. It adds a vector column type and nearest-neighbour search to the database you already back up, replicate and monitor, so embeddings sit next to the rows they describe and every query can join, filter and run inside a transaction. It offers two approximate index types: HNSW, which gives better speed for a given recall, and IVFFlat, which builds faster and uses less memory.

A dedicated vector database earns its place when the vector workload outgrows one well-sized PostgreSQL server and its replicas, when you need features pgvector does not have built in, such as GPU indexes or ranking fusion across dense and sparse vectors, or when there is no PostgreSQL in the stack to begin with. Qdrant, Milvus, Pinecone and OpenSearch k-NN each solve a different version of that problem.

What pgvector gives you

pgvector is an open source extension, released under the PostgreSQL licence, that supports PostgreSQL 13 and later. The current release is 0.8.7, from 1 October 2026. It provides:

  • Four types: vector (single precision), halfvec (half precision), bit (binary) and sparsevec.
  • Six distance functions: L2, inner product, cosine, L1, Hamming and Jaccard.
  • Exact nearest-neighbour search by default, which the README describes as perfect recall, and approximate search once you add an index.
  • Everything PostgreSQL already does: ACID transactions, joins, and point-in-time recovery. pgvector writes to the write-ahead log, so streaming replicas and WAL archives include vectors and their indexes.
CREATE EXTENSION vector;

CREATE TABLE documents (
  id        bigserial PRIMARY KEY,
  tenant_id int NOT NULL,
  body      text,
  embedding vector(1536)
);

-- Ten nearest documents by cosine distance
SELECT id, body
FROM documents
ORDER BY embedding <=> '[0.012, -0.034, ...]'
LIMIT 10;

For hybrid search, combine it with PostgreSQL's own full-text search in the same query, which the pgvector README shows as the recommended approach.

HNSW vs IVFFlat

HNSWIVFFlat
StructureMultilayer graphVectors divided into lists; a query searches the closest lists
Speed for a given recallBetterLower
Build time and memorySlower build, more memoryFaster build, less memory
When to create itAny time, even on an empty table, because there is no training stepAfter the table holds representative data
Build optionsm and ef_constructionlists: rows / 1000 up to 1M rows, sqrt(rows) above
Query optionhnsw.ef_search, 40 by defaultivfflat.probes; start at sqrt(lists)
-- HNSW with cosine distance, built without blocking writes
CREATE INDEX CONCURRENTLY documents_embedding_hnsw
  ON documents USING hnsw (embedding vector_cosine_ops)
  WITH (m = 16, ef_construction = 64);

SET hnsw.ef_search = 100;   -- higher recall, slower queries

-- IVFFlat, after the data is loaded
CREATE INDEX CONCURRENTLY documents_embedding_ivf
  ON documents USING ivfflat (embedding vector_cosine_ops)
  WITH (lists = 1000);

SET ivfflat.probes = 32;

Pick HNSW unless build time or memory is the constraint. Either way, an approximate index changes results: queries return slightly different rows after you add one. The README suggests measuring recall by running the same queries with index scans disabled and comparing.

The limits that matter in production

Dimensions

A vector column can store up to 16,000 dimensions, but HNSW and IVFFlat index only up to 2,000. halfvec indexes up to 4,000 dimensions, bit up to 64,000, and sparsevec up to 1,000 non-zero elements. For larger embeddings, the README lists half-precision indexing, binary quantization with re-ranking, indexing a subvector for models that support it, or dimensionality reduction.

Filtering

With an approximate index, the WHERE clause is applied after the index scan. The README's example: if a condition matches 10% of rows, HNSW with the default ef_search of 40 returns about four matching rows on average. Since 0.8.0, iterative index scans keep scanning until enough rows qualify:

SET hnsw.iterative_scan = strict_order;   -- or relaxed_order for better recall
SELECT id FROM documents
WHERE tenant_id = 42
ORDER BY embedding <=> '[0.012, -0.034, ...]'
LIMIT 10;

For a filter on a few distinct values, a partial index per value works well; for many values, partition the table. Multi-tenant applications should know that a shared approximate index lets one tenant's vectors affect recall and speed for another.

Memory, vacuum and size

Index builds are much faster when the HNSW graph fits in maintenance_work_mem, and PostgreSQL logs a notice when it no longer does. Vacuuming an HNSW index can take a long time; the README suggests REINDEX INDEX CONCURRENTLY before VACUUM. A non-partitioned table is limited to 32 TB by default.

Scaling out

pgvector scales up with memory, CPU and storage, and scales reads with replicas. Sharding the vectors across servers is not built in; the README points to Citus or PgDog for that.

When pgvector is enough, and when a dedicated vector database fits better

The table below uses each project's own documentation. It deliberately leaves out performance numbers: vector benchmarks depend on dataset, dimensions, recall target and hardware, and the only ones worth trusting are your own.

OptionWhat it isLicenceWhat it adds over pgvector
pgvectorExtension inside your PostgreSQLPostgreSQL licenceBaseline: HNSW and IVFFlat, transactions, joins, PostgreSQL backups and replication
QdrantVector database written in Rust; self-hosted or Qdrant CloudApache 2.0Payload filtering with must, should and must_not; dense, sparse and multivector search; built-in quantization; sharding and replication
MilvusDistributed vector database under the LF AI & Data Foundation; Standalone mode and Milvus Lite for small setups; managed as Zilliz CloudApache 2.0Compute and storage scaled separately on Kubernetes; HNSW, IVF, FLAT, SCANN and DiskANN indexes; GPU indexing; BM25 full-text search alongside dense vectors
PineconeManaged database service, with a Bring Your Own Cloud optionCommercial serviceFull-text, semantic, sparse and hybrid search from one index; metadata filters; namespaces to separate tenants; no servers to run
OpenSearch k-NNVector fields inside OpenSearchApache 2.0HNSW and IVF through the Faiss engine (the default) or HNSW through Lucene, with Lucene's efficient filtering; sits beside OpenSearch text search and analytics

Stay on pgvector when

  • The vectors belong to rows you already keep in PostgreSQL, and you want one transaction to update both.
  • The working set fits on one server, with replicas for read traffic.
  • Your team already knows how to back up, restore, monitor and upgrade PostgreSQL. A second database is a second on-call rotation.

Choose a dedicated vector database when

  • The index needs to be sharded across many machines and you do not want to run Citus or a sharding proxy (Milvus, Qdrant, Pinecone).
  • You need GPU-built indexes or DiskANN-style indexes for very large collections (Milvus).
  • You want sparse and dense retrieval fused in one engine, or multivector models such as ColBERT (Qdrant, Milvus, Pinecone).
  • You already run OpenSearch for search or logs and want vectors there rather than in the database (OpenSearch k-NN).
  • You do not want to operate anything (Pinecone).

Running pgvector in production

Install it per major version

pgvector is packaged for each PostgreSQL major version: postgresql-18-pgvector from the PostgreSQL APT repository, pgvector_18 from the Yum repository, and the pgvector/pgvector:pg18-trixie image, which adds pgvector to the official postgres image. Many hosted PostgreSQL services include it, but each decides which pgvector version it ships, so check before relying on a recent feature such as iterative scans.

Upgrade the extension separately

A PostgreSQL minor update does not change the pgvector version inside your databases. Install the newer package with the same method you used originally, then run this in every database that uses it:

ALTER EXTENSION vector UPDATE;
SELECT extversion FROM pg_extension WHERE extname = 'vector';

The changelog is worth reading for operations, not only features. 0.8.2 fixed a buffer overflow in parallel HNSW builds, 0.8.3 fixed possible index corruption during HNSW vacuuming, and 0.8.7 fixed a buffer overflow in IVFFlat builds. These fixes arrive on pgvector's schedule, not PostgreSQL's quarterly one. Our guide to PostgreSQL minor version upgrades covers the server side.

Major version upgrades

Install the pgvector package built for the target major version before running pg_upgrade, and check that every extension you use is available there. The PostgreSQL major version upgrade guide walks through the extension audit.

Backups and restores

Physical backups, WAL archiving and streaming replicas carry vectors and indexes like any other data. A logical dump restores the data and then rebuilds each index with CREATE INDEX, so a large HNSW index adds its full build time to the restore. Measure that during a restore test rather than during an incident.

Where OSSeva fits

OSSeva for PostgreSQL supports the PostgreSQL server you run pgvector on: current versions, and patched, signed builds for 11, 12 and 13 today and for 14 after its community end of life on 12 November 2026. Extension compatibility is part of the Patch tier: OSSeva tests that the extensions you run, pgvector included, still load and pass their checks against each patched build. The extension's own code is a different matter. Security fixes inside a third-party extension sit with its maintainers, and OSSeva tells you which fixes it carries and which it does not before you sign, so raise pgvector on the discovery call. PostgreSQL sits under one contract with MySQL, MariaDB, Redis, Valkey and Kafka, priced per cluster. Book a discovery call for a quote.

Frequently asked questions

Is pgvector production ready?

It runs inside PostgreSQL, so it inherits PostgreSQL's durability, replication and point-in-time recovery. What needs production care is the index: choose HNSW or IVFFlat deliberately, size maintenance_work_mem for builds, test recall with your own queries, and keep the extension updated.

Should I use HNSW or IVFFlat in pgvector?

HNSW for most workloads, because it gives better speed for a given recall and can be created before the data arrives. IVFFlat when build time or memory matters more, and only after the table holds representative data.

How many dimensions does pgvector support?

A vector column stores up to 16,000 dimensions, but indexes cover up to 2,000. Use halfvec to index up to 4,000, or binary quantization to index up to 64,000.

pgvector vs Qdrant: which should I use?

pgvector when the vectors belong with relational data you already keep in PostgreSQL. Qdrant when you want a dedicated engine with rich payload filtering, sparse and multivector search, and sharding built in, and you are prepared to run a second system.

pgvector vs Pinecone?

Pinecone is a managed service, so the choice is mostly operational. pgvector keeps vectors inside a database you control and pay for already; Pinecone removes the servers but adds a vendor and a second copy of the data.

pgvector vs Milvus?

Milvus is built for collections spread across many nodes, with GPU and DiskANN indexes. If one PostgreSQL server and its replicas can hold the workload, pgvector is simpler to run.

pgvector vs OpenSearch k-NN?

If OpenSearch already holds your searchable text, k-NN fields keep vector and keyword search in one place. If the source of truth is PostgreSQL, pgvector avoids synchronising data into a second system.

Does pgvector work with replication and backups?

Yes. pgvector uses the write-ahead log, so streaming replication, WAL archiving and point-in-time recovery include vector data and indexes.

Tags

PostgreSQLpgvectorVector SearchQdrantMilvus

Ready to get your open source under control?

Talk to an OSSeva engineer about CVE coverage, compliance, and migration support for your stack.