DEV Community

Cover image for pg_vectorsearch vs pgvector HNSW: 42 Hours vs 49 Minutes. Here's the Catch
Alan West
Alan West

Posted on Originally published at blog.authon.dev

pg_vectorsearch vs pgvector HNSW: 42 Hours vs 49 Minutes. Here's the Catch

On a 64 vCPU, 512 GiB machine, pgvector took 42 hours to build an HNSW index over 100 million vectors. On the same box, Timescale's new pg_vectorsearch extension built its index in 49 minutes and answered queries in half the time.

Those are the numbers in the launch post from Tiger Data (formerly Timescale), published October 13, 2026. If you run RAG on Postgres, that headline is hard to ignore. I went through the post, the repo, the tuning docs, the benchmark tool's config files and the Hacker News thread to see how far the pg_vectorsearch vs pgvector claim holds. Short answer: the query-side win is real and big. The build-time comparison is tilted, and the extension is still alpha, so production is off the table for now.

What pg_vectorsearch actually is

The extension adds a new index access method called PRISM (Partitioned Routing Index for Similarity Matching). The README calls it "conceptually similar to pgvector's IVFFlat", which is the clearest way to think about it. It's a partition index:

  • a hierarchical centroid tree routes each query to a few clusters
  • vectors in those clusters are stored as RaBitQ codes, about one bit per dimension
  • candidates get filtered on the compressed codes, then reranked with full-precision vectors from the heap

That last part explains most of the memory difference. HNSW has to keep a big graph in memory to be fast, while PRISM only keeps a small tree there and scans compact posting lists for the rest.

It also reads pgvector's types. vec32 and vec16 are binary-compatible with vector and halfvec, so you can put a PRISM index on an existing table:

CREATE EXTENSION pg_vectorsearch;

CREATE INDEX documents_embedding_prism
    ON documents USING prism (embedding vec32_cosine_ops);

SET prism.nprobe = 40;  -- 0 = auto, roughly 0.95 recall
Enter fullscreen mode Exit fullscreen mode

The pg_vectorsearch vs pgvector HNSW benchmark numbers

All figures are p50 latency at recall@10 against brute force, from the launch post. HNSW ran on pgvector's 16-bit halfvec.

100M Cohere embeddings

PRISM, r8i.4xlarge (128 GiB) PRISM, r8i.16xlarge (512 GiB) HNSW, r8i.16xlarge (512 GiB)
Build time 1 h 40 min 49 min 42 h
Index size 25 GB 25 GB 180 GB
p50 @ 95% recall 6 ms 4 ms 8 ms
p50 @ 99% recall 21 ms 19 ms not reached

HNSW didn't finish building on the 128 GiB box. When they moved the index built on the big machine over to it, queries took over a second at 95% recall and never reached 99%.

1M vectors

PRISM, c8g.medium (2 GiB) PRISM, c8g.large (4 GiB) HNSW, c8g.large (4 GiB)
Build time ~3 min ~3.1 min ~21.7 h
Index size 280 MB 280 MB 2.05 GB
p50 @ 95% recall 9 ms 3 ms 122 ms
p50 @ 99% recall 15 ms 9 ms 392 ms

HNSW wasn't really tried on the 2 GiB box; the post says they "couldn't realistically try" it there, and the table marks it "did not finish".

The headline "6 ms at 100M on 4x less memory" is the 128 GiB PRISM number against the 512 GiB HNSW number. I think that's fair: both hit 95% recall, and one machine costs a quarter of the other.

Where the comparison is tilted

Start with the HNSW build settings. Tiger Data's benchmark tool, rekall, ships an HNSW config (hnsw-m16.toml) with m = 16 and ef_construction = 200. pgvector's default ef_construction is 64. Building with 200 buys a better graph and costs a lot more time. Reasonable if you're chasing 99% recall, but it inflates the build-time gap. The post doesn't say which config produced the published table, so I'm reading the repo, not the run.

The post also doesn't publish maintenance_work_mem, and pgvector's README is blunt about why that matters. Once the graph stops fitting in maintenance_work_mem, you get hnsw graph no longer fits into maintenance_work_mem after 100000 tuples and the build "will take significantly more time". A 2.05 GB HNSW index on a 4 GiB machine is going to hit that wall, and a 21.7 h build for 1M vectors is what hitting it looks like. I wouldn't call that normal HNSW performance. Same story for the 122 ms queries: the index doesn't fit in RAM next to the heap, so you're reading from disk.

The numbers aren't dishonest, though. "HNSW falls over when it doesn't fit in memory" is exactly the point Tiger Data is making. But if your HNSW index already fits in RAM with headroom, the 1M table doesn't describe your setup.

Then there's what the post leaves out. No QPS, no p99, no concurrency (multi-client throughput testing is listed as GA work), no filtered search, no IVFFlat numbers even though the README compares PRISM to IVFFlat, and no published embedding dimension. Filtered search is where people actually suffer with pgvector, and PRISM's answer today is basic: per the tuning docs, the executor applies your WHERE clause after the index has emitted its top-k, and the scan inflates k based on estimated selectivity. Filters that are anti-correlated with vector distance can still return too few rows. The docs suggest a partial index on the filter. Streaming search with WHERE filters is on the GA roadmap.

On HN, esafak asked the obvious thing: "Shouldn't you lead with your benchmarks if you're fast? qps and precision/recall, please." jascha_eng replied that the benchmarks are in the post and the suite is open source. Both are right. They're still vendor numbers, and I haven't seen anyone outside Tiger Data publish a rerun yet.

pgvectorscale's fate worries me more than the benchmarks

pgvectorscale, Tiger Data's 2024 DiskANN-style index, "will enter maintenance mode," per the launch post. On HN, Tiger Data CTO mfreed said parts of pgvectorscale's graph could become unreachable and degrade recall, and when they couldn't find a fix they decided to start over with a partition index.

So the vendor that told you two years ago that its graph index was the answer is now telling you graph indexes are the problem. They might be right. They also showed you how fast a Postgres vector extension can go from flagship to maintenance mode.

On top of that, the practical blockers:

  • It's alpha. The README says "meant for testing, not production" and warns that you may have to drop and recreate the extension between prereleases.
  • It needs PostgreSQL 18+. pgvector supports 13+.
  • The hyperscalers don't offer it yet. On HN, devy pointed out it isn't on Azure's or the other big clouds' extension allow-lists, so on those you wait or self-host PG 18. When another commenter raised the same problem, jascha_eng's answer was that you can move to Tiger Cloud.
  • Index maintenance is manual and marked experimental. Posting lists grow on insert and "never split on their own." CALL prism_rebalance('items_idx'); splits them in place, but only on flat, single-level indexes built with centroid_fastscan = off. The default CREATE INDEX doesn't produce that shape, so for a default-built index the fix is a full REINDEX, which rewrites the whole index. A rebalance also leaves replaced lists on disk until the next REINDEX.
  • Don't build on an empty table. Index sizing uses the row count at CREATE INDEX, so load first and index second.
  • Be careful with high dimensions. The tuning docs say the packed SIMD fastscan layout always fits up to ~1900 dims at 8 KB pages, and auto falls back to a slower layout past that. On HN, devy said "PRISM caps at 1968 dimensions"; I couldn't find a hard cap in the docs. If you use 2048-dim embeddings (VoyageAI offers them), test before you plan on it.

What to do with pgvector today

If HNSW builds are killing you, fix the build before you change extensions. From the pgvector docs:

SET maintenance_work_mem = '8GB';          -- size it so the graph fits
SET max_parallel_maintenance_workers = 7;  -- plus leader

CREATE INDEX ON items
  USING hnsw ((embedding::halfvec(768)) halfvec_cosine_ops);
Enter fullscreen mode Exit fullscreen mode

Use halfvec (it halves the index and is what Tiger Data benchmarked against). Leave ef_construction at 64 unless you've measured that you need more. If filtered queries return too few rows, turn on hnsw.iterative_scan (pgvector 0.8.0+; current release is v0.8.7).

So when does switching pay off?

My take: pg_vectorsearch is the most interesting thing to happen to Postgres vector search since HNSW landed in pgvector. VectorChord took the same route of clustering plus quantized vectors, and its own benchmark says its QPS "could be twice that of pgvector" at equivalent recall on GIST 1M (960 dims). That's a vendor number too. Still, when two separate teams build partition indexes and both beat HNSW, I'm inclined to believe the direction.

Try it now, on a replica or a staging box, if:

  • you're past ~10M vectors and your HNSW index doesn't fit in RAM, or you're paying for a huge instance just so it does
  • you need 99% recall and HNSW can't reach it at your size
  • you're on PG 18, self-hosted, and comfortable rebuilding indexes

Stay on pgvector HNSW if:

  • your index fits in memory with headroom (most RAG apps under a few million chunks)
  • you're on RDS, Cloud SQL, Azure or another managed Postgres that doesn't list it
  • you rely on selective WHERE tenant_id = ... filters and can't add a partial index per tenant
  • you have heavy update churn and no time to schedule rebalances

Revisit at GA, when filtered streaming search and automatic, incremental rebalancing are supposed to ship. Until then, 42 hours vs 49 minutes is a great slide and a bad reason to migrate production.

What do you run vector search on right now: pgvector, VectorChord, pgvectorscale, or a dedicated vector DB like Qdrant? And if you've rerun rekall on your own hardware, did the numbers hold up? Tell me in the comments.

Sources

Top comments (1)

Collapse
 
alewx profile image
alex •

i'm curious how the build time advantage holds up when you bump the vector dimension, does pg_vectorsearch still outpace pgvector by an order of magnitude? and what's the strategy for handling live inserts without rebuilding the whole hnsw