Install pgvector via Pigsty, Docker, Linux packages, Homebrew, or source; enable the extension; and insert or bulk-load vectors.
This is the multi-page printable view of this section. .
pgvector Documentation
- 1: Getting Started
- 2: Querying & Filtering
- 3: HNSW & IVFFlat Indexing
- 4: Quantization & Sparse Vectors
- 5: Reference, Tuning & FAQ
pgvector is the foundational open-source vector similarity search extension for PostgreSQL. It provides the core vector, halfvec, bit, and sparsevec data types, distance operators, and built-in HNSW and IVFFlat index access methods.
vchordKey Features
- Exact and approximate nearest-neighbor search inside standard PostgreSQL tables with full ACID transactions, point-in-time recovery, joins, and filtering.
- Multiple vector representations:
- Single-precision floating-point vectors (
vector, up to 2,000 dimensions indexed; up to 16,000 dimensions stored) - Half-precision floating-point vectors (
halfvec, up to 4,000 dimensions indexed) - Binary vectors (
bit, up to 64,000 dimensions indexed) - Sparse vectors (
sparsevec, up to 1,000 non-zero elements across up to 1,000,000 dimensions)
- Single-precision floating-point vectors (
- Six distance metrics: L2 distance (
<->), inner product (<#>), cosine distance (<=>), L1 distance (<+>), Hamming distance (<~>), and Jaccard distance (<%>). - Iterative index scans (
0.8.0+) that automatically expand HNSW or IVFFlat candidate traversal whenWHEREfilters narrow the result set.
Documentation Sections
Distance operators, similarity calculations, vector aggregates, WHERE filtering with iterative index scans, and hybrid search.
Create and tune HNSW and IVFFlat indexes, parallel index build settings, progress monitoring, and scaling with vchord.
Half-precision (halfvec), binary quantization (bit + re-ranking), sparse vectors (sparsevec), and Matryoshka sub-vector indexing.
Complete SQL function and operator reference, GUC parameters, memory sizing, query plan debugging, and frequently asked questions.
Drop-in RaBitQ vchordrq and vchordg indexes on standard pgvector columns for 100M+ vectors without changing queries.
1 - Getting Started
Installation
Pigsty / Native Linux Packages (Recommended)
Pigsty provides prebuilt native pgvector packages (v0.8.2) for PostgreSQL 14–18 across Debian, Ubuntu, and RHEL/Rocky/AlmaLinux (x86_64 and ARM64):
If you also want disk-friendly RaBitQ vector indexes and BM25 ranking, install the full Vchord suite together:
Docker
Run the official pgvector container image:
Or use the all-in-one Vchord Suite image, which bundles pgvector, vchord, vchord_bm25, and pg_tokenizer:
Linux (PGDG APT / YUM)
- Debian / Ubuntu (via PostgreSQL APT Repository):
- RHEL / Rocky / AlmaLinux / Fedora (via PostgreSQL YUM Repository):
macOS (Homebrew)
Build from Source (Linux / macOS)
Ensure PostgreSQL development headers (postgresql-server-dev-* or postgresql*-devel), make, and a C compiler are installed:
To compile with -march=native for maximum SIMD performance on the current machine, pass OPTFLAGS="" only if needed for portability; by default pgvector’s Makefile adds -march=native when supported by the compiler. If building a portable binary for multiple CPU generations, run make OPTFLAGS="".
Enable the Extension
Connect to your target database and enable vector (this must be done once per database where you want to use it):
Check the installed version:
Upgrading pgvector
After installing a newer binary package on the server, upgrade the SQL catalog definitions inside each database:
Quick Start
1. Create a Table with a vector Column
Create a table storing 3-dimensional single-precision (float32) vectors:
You can also add a vector column to an existing table:
2. Insert, Upsert, Update, and Delete Vectors
Insert vectors using the string literal format '[x1,x2,x3]':
Upsert vectors with ON CONFLICT:
Update and delete rows with standard SQL:
3. Bulk Loading with COPY
For large datasets (millions of vectors), always prefer COPY over row-by-row INSERT:
4. Query Nearest Neighbors
Find the 5 closest vectors by L2 (Euclidean) distance:
Next, read Querying & Filtering for all distance metrics and filtering patterns, or HNSW & IVFFlat Indexing to accelerate queries over large tables.
2 - Querying & Filtering
Distance Operators
pgvector supports six distance operators for ordering (ORDER BY) and threshold filtering:
| Operator | Metric | Supported Types | Notes |
|---|---|---|---|
<-> |
L2 (Euclidean) distance | vector, halfvec, sparsevec |
|
<#> |
Negative inner product | vector, halfvec, sparsevec |
Returns because PostgreSQL index scans only support ASC order |
<=> |
Cosine distance | vector, halfvec, sparsevec |
(range [0, 2]) |
<+> |
L1 (Taxicab / Manhattan) distance | vector, halfvec, sparsevec |
(\sum |
<~> |
Hamming distance | bit |
Number of differing bits (XOR popcount) |
<%> |
Jaccard distance | bit |
(1 - \frac{ |
If your vectors are already normalized to unit length (such as OpenAI text-embedding-3-* or BGE embeddings), prefer inner product (<#>) over cosine distance (<=>) for the best CPU throughput.
Nearest-Neighbor Queries
Calculating Distances & Similarity Scores
Retrieve the distance alongside each row:
Distance Threshold Queries
Get all rows within a given distance threshold:
Combine with ORDER BY and LIMIT to use an HNSW or IVFFlat index:
vchordIf you frequently query by distance radius, vchord provides a dedicated <<->> sphere operator (WHERE embedding <<->> sphere('[3,1,2]'::vector, 5)) that evaluates range predicates directly inside the vchordrq or vchordg index without requiring ORDER BY ... LIMIT. See Similarity Filter in vchord.
Aggregates
Compute the mean (AVG) or component-wise sum (SUM) of vectors:
Filtering (WHERE Clauses) & Iterative Index Scans
Combine vector search with relational predicates:
Indexing Filter Columns
- Standard B-tree index on the filter column (good when
category_id = 123matches a very small fraction of rows): - Partial vector index (ideal when you only filter by a few known values):
- Partitioning (ideal when filtering across a bounded set of high-volume tenants or time ranges):
Iterative Index Scans (pgvector 0.8.0+)
By default, approximate vector index scans examine a fixed number of candidates (hnsw.ef_search or ivfflat.probes). If a WHERE filter rejects most of those candidates, the query may return fewer rows than LIMIT.
Starting in pgvector 0.8.0, enable iterative index scans so the index automatically continues scanning until enough matching rows are found (or max_scan_tuples / max_probes is reached):
When using relaxed_order, results may be slightly out of order by distance; wrap the query in a CTE or subquery to re-sort the final LIMIT rows:
Controlling Iterative Scan Budgets
Hybrid Search
You can combine pgvector semantic similarity search with lexical full-text search using Reciprocal Rank Fusion (RRF).
Hybrid Search with PostgreSQL tsvector
Hybrid Search with vchord_bm25 or pg_bestmatch
For true Okapi BM25 ranking with document-length normalization and corpus IDF:
- Pair
pgvector(orvchord) withvchord_bm25andpg_tokenizer— see the Hybrid Search Guide. - Or generate BM25-weighted
sparsevecvectors directly inside PostgreSQL usingpg_bestmatchand index them withpgvectorHNSW (sparsevec_ip_ops).
3 - HNSW & IVFFlat Indexing
By default, pgvector performs exact nearest-neighbor search (sequential scan), which delivers 100% recall. When your dataset grows beyond tens of thousands of rows, add an approximate nearest-neighbor (ANN) index to trade a small amount of recall for orders-of-magnitude higher query speed.
pgvector includes two built-in index access methods:
- HNSW (Hierarchical Navigable Small World): Multi-layer proximity graph. Higher recall-speed trade-off and no training step required, at the cost of slower build times and higher memory usage.
- IVFFlat (Inverted File with Flat Vectors): K-means clustering over vectors. Faster index builds and smaller index footprint, but requires data to exist before index creation and delivers lower recall per probe than HNSW.
When datasets exceed available RAM (e.g., 10M–1B+ vectors) or HNSW index build times become a bottleneck, install vchord alongside pgvector. vchord’s vchordrq and vchordg index access methods work directly on pgvector’s vector and halfvec columns, compressing vectors with RaBitQ to build up to 100x faster and run efficiently from disk/SSD.
HNSW Indexes
An HNSW index constructs a multilayer navigation graph. Unlike IVFFlat, it has no training phase, so you can create an HNSW index on an empty table before loading data.
Creating an HNSW Index
Create an index matching the distance operator used in your queries:
Supported dimensionality limits for HNSW:
vector: up to 2,000 dimensionshalfvec: up to 4,000 dimensionsbit: up to 64,000 dimensionssparsevec: up to 1,000 non-zero elements
HNSW Build Parameters
m(default16, range2–100): Maximum number of bidirectional links per node per layer. Highermimproves recall on high-intrinsic-dimensionality datasets at the cost of larger index size and longer build times.ef_construction(default64, range4–1000): Size of the dynamic candidate list during graph construction. Must be .
HNSW Query Options
Specify the size of the dynamic candidate list during search (hnsw.ef_search, default 40, range 1–1000). Higher values increase recall at the cost of latency:
Accelerating HNSW Index Builds
HNSW construction speed drops sharply once the graph no longer fits into maintenance_work_mem. Before building an HNSW index on a large table, increase maintenance_work_mem and max_parallel_maintenance_workers:
Always create HNSW indexes after bulk-loading initial data with COPY for significantly faster ingestion. In production, use CREATE INDEX CONCURRENTLY to avoid blocking writes.
IVFFlat Indexes
An IVFFlat index partitions vectors into lists Voronoi cells using k-means clustering, and at query time inspects the closest ivfflat.probes lists.
Because IVFFlat computes k-means centroids during CREATE INDEX, you must load representative data into the table before creating the index.
Creating an IVFFlat Index
Choose lists based on table size:
- Up to 1 million rows:
rows / 1000 - Over 1 million rows:
sqrt(rows)
Supported dimensionality limits for IVFFlat:
vector: up to 2,000 dimensionshalfvec: up to 4,000 dimensionsbit: up to 64,000 dimensions
IVFFlat Query Options
Set the number of lists probed per query (ivfflat.probes, default 1). A good starting point is sqrt(lists):
Index Build Progress & Maintenance
Monitoring Index Build Progress
Query pg_stat_progress_create_index while CREATE INDEX is running:
- HNSW phases:
initializingloading tuples - IVFFlat phases:
initializingperforming k-meansassigning tuplesloading tuples
Vacuuming HNSW Indexes
Vacuuming an HNSW index after heavy deletes can be slow because graph links must be repaired. To speed up DELETE + VACUUM cycles on HNSW tables, rebuild the index concurrently first:
4 - Quantization & Sparse Vectors
pgvector supports four vector representations so you can balance memory footprint, dimensionality limits, and recall:
| Data Type | Element Type | Max Indexed Dimensions | Storage Size |
|---|---|---|---|
vector(n) |
32-bit float (float32) |
2,000 | bytes |
halfvec(n) |
16-bit float (float16) |
4,000 | bytes |
bit(n) |
1-bit binary (0 / 1) |
64,000 | bytes |
sparsevec(n) |
Non-zero index:float32 pairs |
1,000 non-zero elements (up to 1,000,000 dims) | bytes |
vchordIn addition to pgvector’s built-in quantization types, vchord provides rabitq8 (8-bit) and rabitq4 (4-bit) quantized vector types and supports indexing up to 60,000 dimensions in vchordrq. See vchord Quantization Types.
Half-Precision Vectors (halfvec)
Use the halfvec type (pgvector 0.7.0+) to store 16-bit floating-point vectors, cutting table and index footprint in half compared to vector:
Half-Precision Expression Indexing (Scalar Quantization)
If you want to keep full-precision vector values in the table (or index vectors between 2,001 and 4,000 dimensions, such as OpenAI text-embedding-3-large at 3,072 dimensions), build an expression index casting vector to halfvec:
Binary Vectors (bit) & Binary Quantization
Use PostgreSQL’s built-in bit(n) type with pgvector’s Hamming (<~>) and Jaccard (<%>) operators to store and index binary vectors up to 64,000 bits:
Binary Quantization with Full-Precision Re-Ranking
Use binary_quantize(vector) to compress 32-bit floats to 1 bit per dimension (1 if , 0 otherwise) in an expression index, then re-rank the top candidates using the exact vector column:
Sparse Vectors (sparsevec)
Use sparsevec (pgvector 0.7.0+) to store vectors where most dimensions are zero (such as SPLADE, BGE-M3 sparse outputs, or BM25 term vectors generated by pg_bestmatch).
Sparse Literal Format
Format: '{index1:value1,index2:value2}/dimensions', where indices are 1-based:
Matryoshka Sub-Vector Indexing (subvector)
For models trained with Matryoshka Representation Learning (MRL), you can index only the leading dimensions with subvector(vector, start, count) and re-rank on the full vector:
5 - Reference, Tuning & FAQ
SQL Function Reference
Vector Functions
| Function | Return Type | Description |
|---|---|---|
l2_distance(a, b) |
float8 |
Euclidean (L2) distance between a and b (vector, halfvec, sparsevec) |
inner_product(a, b) |
float8 |
Inner (dot) product (vector, halfvec, sparsevec) |
cosine_distance(a, b) |
float8 |
Cosine distance (vector, halfvec, sparsevec) |
l1_distance(a, b) |
float8 |
Manhattan (L1) distance (\sum |
hamming_distance(a, b) |
float8 |
Hamming distance between two bit vectors |
jaccard_distance(a, b) |
float8 |
Jaccard distance between two bit vectors |
vector_dims(a) |
integer |
Number of dimensions in a (vector, halfvec, sparsevec) |
vector_norm(a) |
float8 |
Euclidean norm (vector, halfvec, sparsevec) |
l2_normalize(a) |
same as input | Normalize vector a to unit L2 length (vector, halfvec, sparsevec) |
binary_quantize(a) |
bit |
Quantize each element of vector or halfvec to 1 () or 0 () |
subvector(a, start, count) |
same as input | Extract count dimensions starting at 1-based index start (vector, halfvec) |
Element-Wise Arithmetic Operators
a + b: Element-wise addition (vector,halfvec)a - b: Element-wise subtraction (vector,halfvec)a * b: Element-wise multiplication (vector,halfvec)a || b: Vector concatenation (vector,halfvec)
GUC Parameters
| Parameter | Type / Allowed Values | Default | Description |
|---|---|---|---|
hnsw.ef_search |
integer (1–1000) |
40 |
Size of the dynamic candidate list during HNSW search |
hnsw.iterative_scan |
off, relaxed_order, strict_order |
off |
Automatically scan more HNSW tuples when WHERE filters reduce results below LIMIT |
hnsw.max_scan_tuples |
integer |
20000 |
Maximum tuples visited during an HNSW iterative scan |
hnsw.scan_mem_multiplier |
real |
1 |
Multiple of work_mem used for HNSW iterative scan state |
ivfflat.probes |
integer (1–lists) |
1 |
Number of inverted lists probed during an IVFFlat query |
ivfflat.iterative_scan |
off, relaxed_order |
off |
Automatically probe additional IVFFlat lists when WHERE filters reduce results below LIMIT |
ivfflat.max_probes |
integer |
32768 |
Maximum lists probed during an IVFFlat iterative scan |
Performance Tuning & Monitoring
1. Check Index Size
2. Inspect Query Execution Plans
Use EXPLAIN ANALYZE to confirm whether an index scan is chosen and measure execution latency:
3. Monitor Slow Queries with pg_stat_statements
4. Measure Recall
Compare approximate index results against exact sequential scan results inside a transaction:
Frequently Asked Questions (FAQ)
Why isn’t my query using the vector index?
- Missing
ORDER BY ... LIMIT: Index scans require both anORDER BY <operator>and aLIMITclause. - Mismatched operator class: The query operator must match the index operator class (
<->requiresvector_l2_ops,<#>requiresvector_ip_ops,<=>requiresvector_cosine_ops). - Small table cost estimation: On small tables, PostgreSQL’s planner may correctly determine that a sequential scan is cheaper than an index scan. To force index usage during testing, run
SET LOCAL enable_seqscan = off;.
How do I index vectors with more than 2,000 dimensions?
pgvector HNSW and IVFFlat indexes cap vector columns at 2,000 dimensions because PostgreSQL index tuples cannot exceed 8 KB page limits. You have three options:
- Install
vchord:vchord’svchordrqindex supports up to 60,000 dimensions directly onvectorandhalfveccolumns. - Half-precision expression indexing: Cast to
halfvec((embedding::halfvec(3072)) halfvec_l2_ops) to index up to 4,000 dimensions. - Binary quantization: Use
(binary_quantize(embedding)::bit(N)) bit_hamming_opsto index up to 64,000 dimensions.
Can I store vectors with different dimensions in the same column?
Yes, declare the column without a dimension modifier (embedding vector), though you can only create an index on rows that share the same dimension via a partial expression index:
Do vectors contain NULL or NaN / Infinity?
Vectors cannot contain NULL, NaN, or Infinity elements. A whole column value can be NULL; note that NULL vectors are not indexed in IVFFlat and are sorted last by default unless NULLS FIRST is specified.