Migrating production infrastructure? Get up to ₹10,000 in migration credits. Apply now
voroaBlog
PostgreSQL

What Is pgvector? The Postgres Vector Database, Explained

· · 15 min read

Postgres as a vector database with pgvector - embeddings next to your rows, in Mumbai

pgvector is the simplest way to build a Postgres vector database: one free extension adds a vector column type and similarity search to PostgreSQL, so embeddings live in the same table as the rows they describe and obey the same filters and permissions. This guide answers what pgvector is and what it is used for, then shows how to install it, which index to pick, how much memory it needs, and what it actually does on a small machine - we loaded 100,000 vectors and timed every query.

Facts are from the official pgvector repository and its changelog: the current release is 0.8.7, published on 1 October 2026, and this page was last checked on 14 November 2026. Benchmark numbers are our own, measured on 16 October 2026.

Advertisement

What is pgvector?

pgvector (also written “pg vector”) is a free, open-source PostgreSQL extension that stores AI embeddings in a vector column and finds the most similar ones with an ordinary SQL query. It does exact and approximate nearest-neighbour search, with HNSW and IVFFlat indexes and six distance functions, on Postgres 13 and later. You turn it on with CREATE EXTENSION vector;.

Diagram: text or an image goes to an embedding model, the result is stored in a vector column with an HNSW index, and one SQL query returns similar rows with normal filters

In one line each:

  • What it stores: embeddings - lists of numbers an AI model produces from text, images or audio, where similar meanings sit close together.
  • What it adds to Postgres: four column types (vector, halfvec, bit, sparsevec), distance operators such as <=> for cosine, and two index types that make search fast.
  • What it does not do: create embeddings. You get those from a model, such as an embedding API or an open-source model you run yourself, and insert them like any other value.
  • Licence and cost: the PostgreSQL License. The extension is free; you pay only for the database machine.

What is pgvector used for?

Anything that needs “find the rows most like this one”, next to data you already keep in Postgres:

  1. Retrieval for AI chatbots (RAG). Find the five document chunks closest to a user’s question and pass them to a language model as context.
  2. Semantic search. Search help articles, products or tickets by meaning, so “refund not received” also finds “money not credited back”.
  3. Recommendations. “Customers who viewed this also viewed” from product or content embeddings.
  4. Duplicate and near-duplicate detection. Flag support tickets, listings or uploads that say the same thing in different words.
  5. Image similarity. Find visually similar product photos from image embeddings.
  6. Classification and anomaly checks. Label a new item by its nearest labelled neighbours, or flag one that is far from everything seen before.

The reason to do these in Postgres rather than in a separate vector database is the join: one query can filter by tenant, permissions and metadata and rank by similarity at the same time, and deleting a row deletes its embedding in the same transaction.

Does Postgres support vector databases?

Not on its own. Core PostgreSQL (version 18 is current) has no vector type or similarity index: you can keep numbers in a real[] array, but there is no distance operator or index to search them. pgvector is the extension that adds them, and it is what most managed Postgres services mean when they say they support vector search. Once it is enabled, Postgres works as a vector database for most application workloads; the comparison further down covers where a dedicated system starts to make sense.

On this page

How pgvector works: types and operators

An embedding model turns text or an image into a list of numbers; similar meanings produce lists that are close together. pgvector stores the list in a column and finds the nearest ones:

sql
CREATE EXTENSION IF NOT EXISTS vector;

CREATE TABLE chunks (
  id bigserial PRIMARY KEY,
  document_id bigint NOT NULL,
  tenant_id bigint NOT NULL,
  content text NOT NULL,
  embedding vector(1536)
);

CREATE INDEX ON chunks USING hnsw (embedding vector_cosine_ops);

Distance operators

Operator Distance Typical use
<=> Cosine Text embeddings; the usual default
<-> Euclidean (L2) Image and other embeddings trained for L2
<#> Negative inner product Normalised vectors, slightly faster than cosine
<+> Manhattan (L1) Less common
<~> and <%> Hamming and Jaccard Binary vectors

Match the operator to the index’s operator class, such as vector_cosine_ops for <=>, or the index will not be used.

Which vector type to use

Type Bytes per number Most dimensions you can store Most dimensions you can index
vector 4 16,000 2,000
halfvec 2 16,000 4,000
bit 1 bit not stated 64,000
sparsevec only non-zero values 16,000 non-zero 1,000 non-zero

This decides a practical question. OpenAI’s embeddings guide lists text-embedding-3-small at 1,536 dimensions, which fits vector, and text-embedding-3-large at 3,072, which is too many to index as vector. For the large model, either store halfvec(3072), or ask the API for fewer dimensions with its dimensions parameter, for example 1,024, at a small cost in accuracy.

How to install pgvector

Most people do not compile pgvector. Pick the line that matches where your database runs.

Docker. The official image already contains it. Tags follow pg<version>-<debian release>:

bash
docker run -d --name pgvector -e POSTGRES_PASSWORD=secret -p 5432:5432 pgvector/pgvector:pg17

Linux, from source. You need the Postgres server headers (postgresql-server-dev-17 on Debian and Ubuntu):

bash
cd /tmp
git clone --branch v0.8.7 https://github.com/pgvector/pgvector.git
cd pgvector
make
make install   # may need sudo

Windows. Open the “x64 Native Tools Command Prompt for VS” as administrator, point PGROOT at your install and build with nmake:

bat
set "PGROOT=C:\Program Files\PostgreSQL\17"
cd %TEMP%
git clone --branch v0.8.7 https://github.com/pgvector/pgvector.git
cd pgvector
nmake /F Makefile.win
nmake /F Makefile.win install

Managed Postgres. Nothing to install: enable it from the provider’s console, then run CREATE EXTENSION vector; if the console does not do that for you. The Voroa steps, with screenshots, are further down.

Then check it worked:

sql
SELECT extversion FROM pg_extension WHERE extname = 'vector';

HNSW or IVFFlat

HNSW IVFFlat
Search speed and recall Better Good, depends on tuning
Build time Slower Much faster
Memory while building Higher Lower
Can be created on an empty table Yes No; build after loading data
Main tuning settings m (default 16), ef_construction (default 64), hnsw.ef_search (default 40) lists when building, ivfflat.probes (default 1) when searching

HNSW builds a layered graph in which every vector links to its closest neighbours. A search enters at the top layer, jumps towards the query, and drops a layer each time it gets close, so it touches only a small part of the data. That is why it is fast and accurate, and also why it uses more memory and takes longer to build: every insert has to find its place in the graph.

IVFFlat splits the vectors into groups around centre points and searches only the groups nearest to the query. It builds in a fraction of the time and needs less memory, which makes it a fair choice for a large table you rebuild often or a machine with little RAM, as long as you accept tuning probes to get recall up.

Start with HNSW. Raise hnsw.ef_search per query when you need better recall, at some cost in speed:

sql
SET hnsw.ef_search = 100;

For IVFFlat, pgvector’s guidance is lists of about rows / 1,000 up to a million rows, and the square root of the row count above that; then raise ivfflat.probes until recall is good enough.

pgvector benchmark: 100,000 vectors on a small instance

Most guides stop at “HNSW is faster”. We wanted the numbers on the kind of machine a small team starts with, so we created a free Postgres 17 database with pgvector 0.8.0 on a 0.25 compute-unit instance (Neon’s free tier, with 64 MB of maintenance_work_mem), loaded 100,000 vectors of 384 dimensions - the size of common open-source text embedding models - and timed top-10 cosine queries.

Times are server-side (EXPLAIN ANALYZE), so the network is not in them. Recall is checked against an exact scan for 50 queries.

Bar chart: top-10 query over 100,000 vectors - 61.3 ms with no index, 0.34 ms with HNSW at ef_search 40 (91.2% recall), 0.53 ms at ef_search 100 (99.2%), 1.83 ms at ef_search 200 (99.2%)

What we measured Result
Table size, 100,000 x 384-dimension vector 158 MB
Exact search, no index 61.3 ms median
HNSW index build (defaults, m 16, ef_construction 64) 89 seconds
HNSW index size 195 MB
HNSW, ef_search 40 (default) 0.34 ms, 91.2% recall
HNSW, ef_search 100 0.53 ms, 99.2% recall
HNSW, ef_search 200 1.83 ms, 99.2% recall
Same vectors as halfvec: index size 112 MB, 89.6% recall at ef_search 40

The index is the whole game. The same query went from 61 ms to a third of a millisecond, about 180 times faster. Without an index Postgres compares the query with every row; with HNSW it walks a graph and looks at a few hundred candidates instead of a hundred thousand.

Raise ef_search to 100 for production. The default of 40 returned 91% of the true nearest neighbours. At 100 it returned 99% and still answered in about half a millisecond. Past 100 we paid three times the time for no extra recall, so 100 is a sensible starting point for most apps.

halfvec cut the index by 43% for a 1.6-point recall loss. On a small machine that is often the difference between an index that stays in memory and one that keeps being read from disk, which is where the slow queries come from.

Your data changes everything, so test on it. We first ran the same benchmark on purely random vectors. The build took 386 seconds instead of 89, and recall at the default setting was 7.6%. Random vectors have no real neighbours, while real embeddings cluster around topics. Benchmark with a sample of your own embeddings, not with synthetic noise, or the result will mislead you in either direction.

Our vectors were synthetic but clustered the way embeddings are (1,000 groups of related points), and the queries were new points near those groups. A different dataset will give different absolute numbers; the ratios are what to take away.

Filtered search, and the catch

Real queries filter: only this customer’s documents, only this category. With an approximate index, Postgres first finds the nearest candidates, then applies the filter, so a selective filter can return fewer rows than you asked for. pgvector 0.8 added iterative index scans, which keep searching until enough rows pass the filter:

sql
SET hnsw.iterative_scan = relaxed_order;

For filters that remove most rows, such as one tenant out of thousands, an ordinary B-tree index on the filter column, or a partial index per large tenant, often helps more.

A retrieval query for a chatbot

The core of retrieval-augmented generation is one query: the closest chunks the user is allowed to see.

sql
SELECT c.content, d.title
FROM chunks c
JOIN documents d ON d.id = c.document_id
WHERE c.tenant_id = $2
ORDER BY c.embedding <=> $1
LIMIT 5;

The join and the tenant_id filter are why keeping vectors in Postgres is attractive: permissions, metadata and similarity are answered together, with no second system to keep in sync.

From Python, the pgvector package registers the type with psycopg, SQLAlchemy and Django, and LangChain’s Postgres vector store uses pgvector underneath, so the same table works from a framework.

Common pgvector mistakes

The query does not use the index. The ORDER BY has to be the bare distance expression with the same operator as the index, followed by a LIMIT. An index built with vector_cosine_ops is ignored by a query that orders by <->, and so is one that sorts by 1 - (embedding <=> $1). Run EXPLAIN once on every new query and look for the index name in the plan.

Statistics were never collected. After a bulk load, run ANALYZE on the table. Without it the planner can misjudge how many rows a filter keeps and choose a full scan for a query that should have used the index, which looks like pgvector being slow when it is the plan that is wrong.

The dimension does not match. A vector(1536) column rejects a 1,024-number embedding with an error, and switching embedding models later means re-embedding every row. Record the model and its dimension next to the data, and treat a model change as a migration rather than a config change.

Inner product on unnormalised vectors. The <#> operator is only a stand-in for cosine when every vector has length 1. Most hosted embedding APIs return normalised vectors; many local models do not. If you are unsure, use cosine.

Building the index on a busy table. A plain CREATE INDEX blocks writes until it finishes, which on a large table can be many minutes. Use CREATE INDEX CONCURRENTLY in production, and load data before building an IVFFlat index, because it learns its groups from the rows that exist at build time.

How much memory pgvector needs

Vector search is fast only when the index sits in memory. In our benchmark the HNSW index was 1.3 times the raw vector data, and table plus index came to about 350 MB per 100,000 vectors of 384 dimensions. Raw vector sizes for common embedding sizes:

Vectors 1,536 dimensions as vector 1,536 as halfvec 3,072 as halfvec
100,000 about 0.6 GB about 0.3 GB about 0.6 GB
1,000,000 about 6.1 GB about 3.1 GB about 6.1 GB

Add roughly the same again for the HNSW index, then leave room for the rest of your database. As a starting point on Voroa’s sizes:

Size RAM Comfortable for (HNSW, vector)
DB Starter 1 GB about 200,000 vectors of 384 dimensions
DB Standard 2 GB about 100,000 vectors of 1,536 dimensions
DB Pro 4 GB about 200,000 vectors of 1,536, or twice that as halfvec
DB Pro Max 8 GB about 400,000 vectors of 1,536, or twice that as halfvec

These are estimates scaled from our measurement; measure your own index with SELECT pg_size_pretty(pg_relation_size('your_index')); before you size up. HNSW builds are much faster when the graph fits in maintenance_work_mem - Postgres prints a notice when it does not - and CREATE INDEX CONCURRENTLY keeps a live table writable while it builds.

pgvector vs Pinecone and other vector databases

pgvector covers what most teams build: documentation search, support ticket matching, product recommendations and chatbot retrieval. A dedicated vector database such as Pinecone, Milvus or Weaviate starts to earn its place in three situations: when you store tens of millions of vectors and the index no longer fits in one machine’s memory, when search must stay equally fast while the data changes constantly at high volume, or when you need vector search with no relational data at all.

Below that, a second system mainly adds another bill, another network hop on every query and another copy of your data to keep in sync with the rows it describes. With pgvector, deleting a document deletes its embedding in the same transaction; with two systems, that is a job you have to build and watch.

Enable pgvector on Voroa, step by step

Voroa’s managed PostgreSQL runs in Mumbai and offers pgvector as Vector Search on paid sizes. Every new database is built to support it. Only some older databases first created on the free plan are not, and upgrading alone does not change that; if Vector Search is not listed on your paid database, contact support. The full rules are in PostgreSQL extensions on Voroa, and sizes are on the pricing page.

Step 1. Create a Postgres database on a paid size. In the dashboard choose New, then Postgres, and pick a paid instance type with enough RAM for your index (the table above).

Step 2. Open Settings and find Extensions. On the database, go to the Settings tab and scroll to Extensions. Vector Search is listed with the other extensions.

Extensions card on a Voroa Postgres database's Settings tab, listing UUID Generation, Case-insensitive Text, Trigram Search, Cryptographic Functions, Key-Value Store and Vector Search, each Available with an Enable button

Step 3. Select Enable next to Vector Search. The status changes to Pending while Voroa installs it, with no database restart, and to Enabled once it has checked the extension is really there.

Extensions card after selecting Enable: Vector Search now shows Pending

Step 4. Create your table and index. Connect with the DATABASE_URL from the Connect tab (the details are in PostgreSQL connection details) and run the CREATE TABLE and CREATE INDEX statements from the top of this page.

Coming from Supabase, which also ships pgvector? See moving off Supabase; existing data, vector columns included, comes in through the database import. Just trying things out first? Our comparison of free Postgres databases covers the free tiers, including the limits that matter once an index is in memory.

FAQ

Is pgvector free?

Yes. pgvector is open source under the PostgreSQL License. You pay only for the machine your database runs on.

Can pgvector handle 10 million vectors?

Yes, if the machine has the memory. Ten million 1,536-dimension embeddings are about 61 GB as vector or 31 GB as halfvec before the index, and an HNSW index adds roughly the same again, so plan for a large instance or shorter embeddings (10 million at 384 dimensions is about 15 GB). Build the index with plenty of maintenance_work_mem, use CREATE INDEX CONCURRENTLY, and test recall with your filters. When one machine’s memory is no longer enough, a dedicated vector database is worth comparing.

Is pgvector the same as pg_vector or pg vector?

Yes. The project is called pgvector, people often write it “pg vector” or “pg_vector”, and the extension you create inside Postgres is named vector.

Is pgvector good enough for production?

Yes, for most AI features. Plan the memory, use an HNSW index, set hnsw.ef_search to about 100, and test recall on your own data with your real filters.

Does pgvector work with Prisma or Django?

Yes. Prisma treats vector columns as an unsupported type and queries them with raw SQL. Django has the official pgvector Python package, with a field type and distance functions.

What embedding size should I use?

The smallest that is accurate enough on your data. Smaller embeddings need less memory and search faster; test a shortened embedding before paying for the largest.

Why does my filtered vector query return fewer results than LIMIT?

The index found the nearest candidates before the filter removed most of them. Turn on iterative index scans, or add an index on the filter column.

Add vector search next to your data

Create a paid PostgreSQL database in Mumbai, turn on Vector Search, and keep your embeddings in the same database as your app. For the wider choice of Postgres hosts, read Neon vs Supabase.

Advertisement

Updated November 14, 2026