Databases·8 min read·

Tuning pgvector HNSW Indexes for Sub-Millisecond Search in 2026

Optimize pgvector 0.7+ HNSW indexes on PostgreSQL 16 with memory-tuned maintenance_work_mem, vector quantisation, and calibrated ef_search settings.

NB

Netbay Engineering

Netbay Engineering

On this page

Vector similarity search inside relational databases has matured rapidly, with pgvector emerging as the de facto standard for developers combining relational records with high-dimensional embeddings. On PostgreSQL 16, pgvector 0.7+ delivers hierarchical navigable small world (HNSW) indexing capable of sub-millisecond approximate nearest neighbor (ANN) lookups over millions of vectors. However, default out-of-the-box configurations often lead to severe query latency, excessive memory consumption, or prolonged index construction times that can lock maintenance workers.

Tuning pgvector for production workloads requires balancing index build parameters, runtime search parameters, and memory allocation. When deploying on a dedicated Linux VPS with Intel Xeon Platinum processors and High-Speed SSD storage in Lucknow DC01, the primary objective is keeping index graphs resident in shared buffers while avoiding sequential scan fallbacks under concurrent load.

Choosing HNSW Parameters: m and ef_construction

The HNSW graph algorithm operates across multi-layer graphs where queries jump across coarse top layers before navigating fine-grained lower layers. The primary construction parameters dictate accuracy and graph connectivity:

  • **m**: The maximum number of bidirectional connection links created per vector node. Higher values increase search recall and index quality at the cost of a larger index footprint and longer build times. The standard range is 16 to 64. A setting of 16 works well for 384-dimensional models, while 24 to 32 is optimal for 1536-dimensional OpenAI or Cohere embeddings.
  • **ef_construction**: The size of the dynamic candidate list evaluated during index construction. A higher value builds a more accurate graph hierarchy by evaluating more potential neighbors. In production, set ef_construction to at least 128 (default 64) for high-recall production services.
sql
-- Enable the vector extension
CREATE EXTENSION IF NOT EXISTS vector;

-- Define embedding table with 1536-dimensional vectors
CREATE TABLE document_embeddings (
    id BIGSERIAL PRIMARY KEY,
    document_id UUID NOT NULL,
    metadata JSONB NOT NULL DEFAULT '{}',
    embedding vector(1536) NOT NULL,
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

-- Build the HNSW index using cosine distance
CREATE INDEX CONCURRENTLY idx_document_embeddings_hnsw_cosine
ON document_embeddings
USING hnsw (embedding vector_cosine_ops)
WITH (m = 24, ef_construction = 128);

Building with CONCURRENTLY avoids write locks on table inserts while pgvector constructs the multi-layer graph. For 100,000 vectors with 1536 dimensions, this configuration generates an index structure that reliably delivers above 98% recall without degrading concurrent queries.

Tuning maintenance_work_mem and Memory Allocation

Index builds fail or slow down by orders of magnitude when the graph cannot fit into memory during the build phase. PostgreSQL allocates maintenance_work_mem for each index build worker. If this setting is too low, pgvector spills intermediate graph nodes to temporary files on disk, resulting in excessive disk churn and extended build durations.

bash
# Inspect current maintenance memory in PostgreSQL
sudo -u postgres psql -c "SHOW maintenance_work_mem;"
sudo -u postgres psql -c "SHOW max_parallel_maintenance_workers;"

# Temporarily elevate memory for the index creation session
sudo -u postgres psql -c "SET maintenance_work_mem = '4GB';"
sudo -u postgres psql -c "SET max_parallel_maintenance_workers = 4;"

For persistent performance, update /etc/postgresql/16/main/postgresql.conf. Allocate 25% of available system RAM to shared_buffers so the active HNSW graph nodes stay hot in memory, and set work_mem sufficiently high so client sorting operations never drop to disk:

ini
# /etc/postgresql/16/main/conf.d/99-pgvector.conf
shared_buffers = 4GB
effective_cache_size = 12GB
maintenance_work_mem = 2GB
work_mem = 64MB
max_parallel_maintenance_workers = 4
checkpoint_completion_target = 0.9
wal_buffers = 16MB
default_statistics_target = 500
Expand
pgvector Architecture
pgvector HNSW Graph Traversal and Memory Allocation

At query time, the parameter hnsw.ef_search controls the candidate list size during graph traversal. This parameter gives you real-time control over the latency versus recall trade-off without rebuilding the index:

  • A lower value (e.g., 20) yields microsecond response times with lower recall (around 92-95%).
  • A balanced production value (e.g., 60 to 100) yields 98-99% recall with sub-millisecond response times.
  • A high value (e.g., 200+) approaches exact k-NN accuracy at the cost of additional buffer hits.
sql
-- Set dynamic search candidate window for current transaction
BEGIN;
SET LOCAL hnsw.ef_search = 80;

EXPLAIN (ANALYZE, BUFFERS)
SELECT id, document_id, 1 - (embedding <=> '[0.012, -0.045, 0.089, ...]'::vector) AS similarity
FROM document_embeddings
ORDER BY embedding <=> '[0.012, -0.045, 0.089, ...]'::vector
LIMIT 10;
COMMIT;

Check the EXPLAIN output to verify that an Index Scan using idx_document_embeddings_hnsw_cosine is performed rather than a Seq Scan. If pgvector defaults to Seq Scan, verify that enable_seqscan is not overriding your cost parameters and check that your statistics target is up to date:

sql
ALTER TABLE document_embeddings ALTER COLUMN embedding SET STATISTICS 1000;
ANALYZE document_embeddings;
pgvector HNSW Indexing vs IVFFlat Architecture HNSW Multi-Layer Graph - Layer 2: Sparse skip-list entry - Layer 1: Dense intermediate hops - Layer 0: Full dataset connectivity (m=24) - Zero training phase required - Sub-millisecond recall at ef_search=80 IVFFlat Inverted Lists - Requires k-means cluster training - Centroid lookups via probes - Smaller memory footprint - Slower queries during high concurrency - Degrades as new rows are inserted HNSW delivers consistent sub-millisecond lookups for production AI services Tuned on PostgreSQL 16 with High-Speed SSD & dedicated Xeon cores
PostgreSQL Database Architecture & Query Execution
YouTube

Vector Compression: Halfvec and Quantisation

In pgvector 0.7+, you can leverage half-precision vectors (halfvec) to cut index RAM requirements by 50% without meaningful loss in search precision. Instead of storing 32-bit floating point numbers for each dimension, halfvec stores 16-bit floats:

sql
-- Create table using half-precision floating point vectors
CREATE TABLE document_embeddings_half (
    id BIGSERIAL PRIMARY KEY,
    document_id UUID NOT NULL,
    embedding halfvec(1536) NOT NULL
);

-- Index build consumes half the memory footprint
CREATE INDEX idx_embeddings_half_hnsw
ON document_embeddings_half
USING hnsw (embedding halfvec_cosine_ops)
WITH (m = 24, ef_construction = 128);

For massive vector collections exceeding 10 million rows, binary quantisation (BQ) compresses vectors to single bits per dimension, accelerating vector comparisons via native popcount CPU instructions. This two-tier query strategy uses binary quantisation to retrieve candidate rows followed by full re-ranking using exact floating-point vectors.

Deploy your PostgreSQL 16 vector database on a high-speed Linux VPS from Netbay in under 60 seconds, leveraging Intel Xeon Platinum cores and High-Speed SSD storage in Lucknow DC01 — netbayhosts.in.

Keep reading

Follow along on a real VPS

Deploy Linux in under 60 seconds

These guides are written against Ubuntu, Debian, and RHEL-family images — the same ones on NetBay.

Deploy an instance