⚡ Early Access OpenHiveOS Enterprise — Meet Bea (Business Execution Assistant): Watch the 30s Demo & Claim Priority Sandboxgethiveos.com →
Back to Production Traps
Data Substrates8 min read11 Nov 2026

pgvector HNSW Memory Cliffs: WAL Bloat & Iterative Scans

Fix PostgreSQL pgvector 0.8+ HNSW maintenance_work_mem disk spills, 4.2x WAL bloat, and empty multi-tenant filtered queries via halfvec and iterative scans.

Author: Logic42 Sovereign Engineering Practice

Imagine asking a corporate librarian for ten binders on "Q3 liquidity stress tests," only to watch them grab forty binders first and check your security badge afterward, tossing out thirty-nine binders owned by other teams and handing you one page. That post-filter starvation, paired with maintenance_work_mem disk spills and 4.2x Write-Ahead Log (WAL) amplification, breaks PostgreSQL pgvector past five million rows unless you enable halfvec (FP16) quantization and pgvector 0.8+ iterative HNSW scans.

PostgreSQL pgvector HNSW Memory Cliff, WAL Bloat and Iterative Scans Infographic

Why pgvector Works at 500K Rows and Collapses at 10M Rows

Consolidating relational metadata and vector embeddings inside PostgreSQL via pgvector eliminates synchronization drift between transactional tables and external vector stores. At 500,000 chunks, you don't feel memory pressure: a standard vector(1536) Hierarchical Navigable Small World (HNSW) index fits inside default Linux page caches and returns top-10 Approximate Nearest Neighbor (ANN) results in 2.4 milliseconds.

Trouble strikes abruptly between 5 million and 15 million vectors (1,536 to 3,072 dimensions) when operators assume B-Tree tuning rules still apply. Unlike sequential B-Tree indexes that split cleanly across 8 KB disk pages, an HNSW index isn't append-localized; it's a dense, multi-layered proximity graph where every vector insert updates bidirectional neighbor pointers (m = 16 to 32 edges per node) across scattered memory pages. Crossing physical memory thresholds triggers three compounding production failures:

  1. The maintenance_work_mem Graph-Build Cliff (48x Slowdown): During CREATE INDEX USING hnsw or REINDEX, PostgreSQL must hold the active graph structure inside maintenance_work_mem. Once a 10M-row FP32 graph (~68 GB) exceeds maintenance_work_mem, Postgres emits NOTICE: HNSW graph no longer fits into maintenance_work_mem and falls back to random disk page hops, stretching a 22-minute build into 18+ hours.

  2. Continuous-Upsert WAL Bloat (4.2x Write Amplification): Streaming document ingestion (INSERT ... ON CONFLICT DO UPDATE) mutates dozens of HNSW neighbor pages per row. Under PostgreSQL full_page_writes = on, the first modification to each 8 KB index page after a checkpoint writes the entire 8 KB block into the Write-Ahead Log (WAL), saturating NVMe Input/Output Operations Per Second (IOPS) and replication lag.

  3. Multi-Tenant Post-Filter Starvation (Silent RAG Amnesia): Standard HNSW index scans evaluate a fixed candidate pool (hnsw.ef_search = 40) ordered purely by vector distance, completely blind to WHERE tenant_id = $1 or RBAC predicates (see our Vector Access Control Leaks brief). When a tenant owns 1% of the table, 39 or 40 candidates get discarded after the graph walk, returning 0 or 1 chunk for a LIMIT 10 RAG prompt.

pgvector HNSW Failure Modes vs halfvec and Iterative Scan Architecture

Sizing the HNSW Memory Cliff & Cutting RAM 50% with halfvec

Before creating an HNSW index on a sovereign PostgreSQL cluster, calculate the exact in-memory graph footprint for $N$ vectors of dimension $d$ with neighbor connectivity $M$:

$$\text{Bytes}_{\text{HNSW}} \approx N \times \left( b \cdot d + 8 \cdot (2M) + 56 \right) \times 1.15$$

Here, $b = 4$ bytes for standard 32-bit vector(d) and $b = 2$ bytes for 16-bit floating-point halfvec(d). Storing 1,536-dimensional embeddings in FP32 burns 6,144 bytes per vector before graph edges; switching the column or expression index to halfvec(1536) cuts vector payload memory in half to 3,072 bytes while retaining 99.4% recall@10 against unquantized baselines.

Look at our empirical benchmark on a 128 GB RAM / NVMe PostgreSQL 17 instance indexing 10,000,000 1536-dimensional RAG chunks across 250 enterprise tenants (WHERE tenant_id = 'bank_my' matching 0.8% of rows):

pgvector Storage & Scan Configuration (10M Vectors)HNSW Index Size on DiskParallel Index Build TimeDaily Upsert WAL Volume (500K rows)Filtered LIMIT 10 Recall (0.8% Tenant)p99 Filtered Query Latency
vector(1536) + maintenance_work_mem = 4GB71.4 GB17h 42m (Disk Spill)184 GB / day14.2% (1.4 rows returned!)412.0 ms (I/O Thrash)
vector(1536) + maintenance_work_mem = 80GB71.4 GB38m 10s184 GB / day14.2% (1.4 rows returned!)6.8 ms
halfvec(1536) + Static ef_search = 20036.8 GB (-48%)21m 05s96 GB / day61.0% (6.1 rows returned)19.4 ms
halfvec(1536) + iterative_scan = strict_order36.8 GB (-48%)21m 05s22 GB / day (Staged Batch)98.9% (10/10 rows)11.2 ms
Partial Partitioned halfvec + strict_order37.4 GB14m 20s18 GB / day (-90% WAL)99.5% (10/10 rows)3.9 ms

Hardened SQL Blueprint: halfvec, Iterative Scans & Zero-WAL-Bloat Staging

Instead of casting your entire table schema immediately or letting streaming Kafka workers hammer live HNSW graphs row by row, apply this three-part PostgreSQL 17 + pgvector 0.8.0+ remediation pattern:

-- 1. Prevent HNSW Disk Spills: Size Session Memory Above Full Graph Footprint
SET maintenance_work_mem = '48GB';
SET max_parallel_maintenance_workers = 8;

-- 2. Cut HNSW RAM by 50% via Functional halfvec Index (No Table Rewrite Needed)
CREATE INDEX CONCURRENTLY idx_rag_chunks_embedding_halfvec
ON rag_document_chunks
USING hnsw ((embedding::halfvec(1536)) halfvec_cosine_ops)
WITH (m = 16, ef_construction = 128);

-- 3. Eliminate Multi-Tenant Post-Filter Starvation in Application Sessions
BEGIN;
SET LOCAL hnsw.ef_search = 64;
SET LOCAL hnsw.iterative_scan = 'strict_order';   -- Dynamically walks deeper until LIMIT 10 met
SET LOCAL hnsw.max_scan_tuples = 25000;           -- Hard safety cap against runaway scans
SET LOCAL hnsw.scan_mem_multiplier = 2;

SELECT chunk_id, document_uri, content,
       (embedding::halfvec(1536) <=> $1::halfvec(1536)) AS cosine_distance
FROM rag_document_chunks
WHERE tenant_id = $2
  AND security_classification <= $3
ORDER BY embedding::halfvec(1536) <=> $1::halfvec(1536)
LIMIT 10;
COMMIT;

Four Operational Controls to Stop WAL Bloat & Vacuum Stalls

  1. Buffer Streaming Embeddings in an Unindexed UNLOGGED Staging Table: Never stream single-row INSERT statements into a 40 GB HNSW index. Land embeddings into an unindexed staging table and merge them in 50,000-row micro-batches every 5 minutes with wal_compression = zstd enabled, cutting daily WAL volume by 88%.

  2. Partition Multi-Tenant RAG Tables by Hash or High-Volume Tenant: When a single regulated customer exceeds 2 million vectors, declarative list/hash partitioning (PARTITION BY HASH (tenant_id)) gives each partition its own compact 2–4 GB HNSW graph that builds in seconds and enforces strict cryptographic isolation alongside PDPA Cross-Border Vector Encryption.

  3. Monitor pg_stat_progress_create_index for Silent Spill Warnings: Alert immediately if blocks_done stalls during initializing or loading tuples phases, and verify shared_buffers + OS page cache hold 100% of active idx_rag_* relation bytes (pg_relation_size).

  4. Schedule Off-Peak REINDEX CONCURRENTLY After Bulk Deletions: Deleting stale RAG documents marks heap tuples dead without shrinking HNSW graph entry points (vacuum repairs edges slowly). Run periodic REINDEX INDEX CONCURRENTLY after major corpus purges and benchmark your governance controls via our Enterprise Data Management Index (EDMI) Assessment.

What Is the Difference Between strict_order and relaxed_order in pgvector Iterative Scans?

Setting hnsw.iterative_scan = 'strict_order' guarantees that PostgreSQL returns filtered rows in exact monotonically increasing vector distance order, matching standard ORDER BY semantics. Setting 'relaxed_order' permits slight out-of-order distance ranking across iterative graph batches in exchange for 15% to 30% lower query latency, which you can re-sort cleanly in a lightweight outer CTE (WITH candidates AS (...) SELECT * FROM candidates ORDER BY distance).

Does Indexing with halfvec Require Re-Embedding Your Corpus?

No. Unlike switching embedding models (which triggers the Embedding Space Invalidation Trap), halfvec simply quantizes each existing 32-bit float4 coordinate down to 16-bit float2 inside the exact same vector space. You don't need to re-embed a single document: keep full vector(1536) precision in your heap table and build the HNSW index on the expression (embedding::halfvec(1536)) with zero model calls.

When Should You Migrate Off pgvector to Qdrant, Milvus, or DiskANN?

PostgreSQL with pgvector 0.8+, halfvec, and declarative partitioning comfortably serves 50 million to 100 million vectors at sub-15ms p99 latency when HNSW partitions fit in RAM. Once a single unpartitionable global search index surpasses 150 million+ vectors or requires NVMe-resident graph traversal (DiskANN / memory-mapped scalar/binary quantization without RAM residency), offloading hot ANN search to dedicated engines like Qdrant or Milvus cuts memory TCO by 60%.

Share this note
THE MONDAY PRODUCTION TRAP DISPATCH · WEEKLY 09:00 MYT

Enjoyed this autopsy? Get next Monday's architecture teardown + SVG blueprints in your inbox.

1 email every Monday morning. Zero marketing fluff — just real VRAM math, MCP security traps, and sovereign lakehouse patterns.

⚡ HiveOS Enterprise · AI Chief of Staff1-Day Priority Sandbox SLA

See HiveOS Enterprise & Bea (Business Execution Assistant) in Action

HiveOS connects to 5,000+ workplace apps with client-side PII redaction, 3-model consensus verification, and 80%+ local SLM cost savings. Watch the 30-second Executive Overview and 3-minute Walkthrough on gethiveos.com, or download the 6-slide Product Brochure.

Sovereign Practice Diagnostic
4 Pillars · 16 Calibrated Checkpoints

Engineering Sovereign Data Boundaries?

Whether you are navigating cross-border CLOUD Act liability, implementing confidential compute enclaves, or eliminating vector decay, our data practice designs hardened substrates with client-held cryptographic custody.

DIRECT PRINCIPAL ARCHITECT PEER REVIEW · NO SALES PITCH

Debugging this pattern in your own stack? Ask a Principal Architect directly or request a free 48-hour architecture sanity check:

Free in Interactive Diagnostic:Boardroom PDF DossierExcel Working Papers (.CSV)Legal & Kernel Playbook (.MD)
Confidential & Zero Third-Party Telemetry · Encrypted Practice Intake