Vector Storage & Indexing at Scale: HNSW vs IVF in PostgreSQL (pgvector)
Spinning up Pinecone or Qdrant as a separate vector database creates catastrophic dual-write synchronisation hazards and complicates multi-tenant security. This article builds the Workspace knowledge vault on PostgreSQL 16 with pgvector HNSW indexing, Row-Level Security for tenant isolation, and shadow-column migration strategies for embedding model upgrades.
Full-Stack AI Architecture: From Streaming Foundations to Resilient Agentic Systems in Node.js & React
Vector Storage & Indexing at Scale: HNSW vs IVF in PostgreSQL (pgvector)
Reaching for Pinecone or Qdrant as a dedicated vector database solves a real problem — fast ANN search — but creates three new ones: dual-write synchronisation between your relational database and the vector store, complex transaction semantics that span two systems, and Row-Level Security that cannot be enforced at the vector query level. For the Workspace, where every chunk belongs to an organisation and must be invisible to others, a separate vector store creates an unacceptable security surface.
This article builds the production knowledge vault on PostgreSQL 16 + pgvector 0.7+: HNSW index construction with tuned parameters, Row-Level Security policies that gate every ANN scan at the database level, and a shadow-column migration strategy for zero-downtime embedding model upgrades from 1536 to a new dimension count.
Who this is for: Backend engineers evaluating whether to add a dedicated vector database or stay in PostgreSQL. After this article, you will understand the exact trade-offs and the engineering patterns that make pgvector production-capable for multi-tenant workloads.
1. HNSW vs IVF: The Indexing Decision
1.1 How HNSW Works
Hierarchical Navigable Small World (HNSW) is a graph-based ANN algorithm. At build time, it constructs a multi-layer graph where each node connects to its M nearest neighbours at each layer. At query time, it greedily traverses from an entry point at the top layer, descending through layers until reaching the base layer where the final candidates are collected.
| Parameter | Effect | Recommended for Workspace |
|---|---|---|
m (connections per node) |
Higher = better recall, more RAM, slower build | 16 for <1M vectors; 32 for >1M |
ef_construction |
Higher = better graph quality, slower build | 128 for production (minimum: 64; best recall: 256) |
ef_search (at query time) |
Higher = better recall per query, slower | 40 default; tune per latency budget |
Set maintenance_work_mem to at least 2 GB (in the same session as CREATE INDEX) before
building a large HNSW index. Without this, pgvector falls back to a slower disk-based build
path that is 10–20× slower. This is the most common production deployment oversight.
1.2 HNSW vs IVF Trade-offs
| Dimension | HNSW | IVF (Flat) |
|---|---|---|
| Query latency | Lower (graph traversal) | Higher (inverted list scan) |
| Recall@10 | Higher (95–99%) | Moderate (90–95%, depends on nprobe) |
| Build time | Longer | Shorter |
| RAM usage | Higher (full graph in VRAM/RAM) | Lower (lists can be paged) |
| Incremental updates | ✅ Supports INSERT without full rebuild | ❌ Requires periodic full rebuild |
| Best fit | Workspace knowledge vault (production) | Batch analytics (offline) |
HNSW requires the full index to fit in PostgreSQL's shared_buffers or the OS page cache for optimal performance. For a 1M-vector index at 1536 dimensions (float32), the index occupies approximately 6 GB. Plan shared_buffers accordingly.
2. Building the HNSW Index
3. Row-Level Security for Multi-Tenant Isolation
3.1 Why RLS Is Non-Negotiable
Without RLS, a single misconfigured query — a missing WHERE org_id = ? clause — can return chunks from every tenant in the database. In a knowledge vault containing proprietary engineering documents, financial projections, and internal roadmaps, this is a critical data breach.
RLS enforces the tenant boundary at the database engine level. Even if application code omits the WHERE org_id filter, PostgreSQL rejects the query.
4. Zero-Downtime Embedding Model Migration
4.1 The Migration Problem
When a superior embedding model is released (e.g., upgrading from text-embedding-3-small (1536d) to a hypothetical future model with 3072d), all existing vectors become incompatible. A naive migration requires:
- Stop all ingestion writes
- Re-embed every chunk in the database (hours for large vaults)
- Rebuild the HNSW index
- Resume traffic
This creates hours of downtime.
4.2 Shadow Column Strategy
Summary
| Decision | Rule |
|---|---|
| Index type | HNSW for production read-heavy workloads; m=16, ef_construction=64 is a safe starting point |
| Distance operator | vector_ip_ops (inner product) for L2-normalised vectors — fastest and most accurate |
| Multi-tenancy | Enable RLS + FORCE ROW LEVEL SECURITY; set app.current_org_id at session start |
| Model migration | Shadow column + CREATE INDEX CONCURRENTLY enables zero-downtime embedding upgrades |
| RAM planning | 1M × 1536d float32 HNSW index ≈ 6 GB; size shared_buffers accordingly |
What's Next
Part 10 reveals the blind spot of pure vector search: exact identifier lookups. We implement hybrid search — fusing dense pgvector similarity with sparse BM25 full-text search via Reciprocal Rank Fusion (RRF) SQL CTEs — entirely inside PostgreSQL with no additional infrastructure.
This article was developed with AI-assisted deep search, specification cross-referencing, and technical research synthesis.