~/rivestack/bench $ _

Managed pgvector benchmarks.
QPS with recall. Not marketing.

// Real HNSW search numbers across all three dedicated tiers on local NVMe, measured per tier, per dataset size, per recall target, and per concurrency level. Every QPS figure states its recall@10 and its concurrency, because without those two numbers a throughput claim means nothing. All results are reproducible with the open-source pgvector-bench CLI.

Honest numbers · HNSW · dedicated NVMe · reproducible with pgvector-bench

pgvector-bench --tier solo

bench> Solo · 1 vCPU / 2 GB · 250k × 1536

~983QPS · 4 clients · ef_search=80 · recall@10 0.93

3.8 ms

p50

6.6 ms

p95

8.3 ms

p99

// One measured operating point. Every number on this page is a (tier, dataset, ef_search, recall@10, concurrency, QPS, latency) tuple.

# key numbers

// One representative operating point per tier. Each cell shows the recommended recall-0.93 setting where available, or the largest-dataset row for tiers where a bigger index was measured. The p50/p95/p99 triple and the recall@k are always included, because they are what the benchmark actually measures.

Solo · 1 vCPU / 2 GB

$29/mo · 250k × 1536

recall 0.93

~983

QPS · 4 clients · ef_search=80

3.8 ms

p50

6.6 ms

p95

8.3 ms

p99

Growth · 4 vCPU / 8 GB

$85/node · 250k × 1536

recall 0.94

~2,949

QPS · 16 clients · ef_search=80

5.1 ms

p50

8.7 ms

p95

11.4 ms

p99

Scale · 8 vCPU / 16 GB

$159/node · 250k × 1536

recall 0.95

~4,465

QPS · 16 clients · ef_search=80

3.4 ms

p50

5.3 ms

p95

6.6 ms

p99

# full results table

// All measured (tier, dataset, ef_search, recall@10, concurrency) combinations. Cells shown as "—" were not captured for that configuration. The Solo row at ef_search=200 / recall 0.99 is the known collapse point where the working set spills past the 4 GB cache and latency surges to 43 ms p50.

tiervectorsef_searchrecall@10clientsQPSp50p95p99
Solo250k × 1536100.7441,446 2.7 ms4.0 ms5.6 ms
Solo250k × 1536800.931712 lowest latency1.3 ms2.0 ms2.7 ms
Solo250k × 1536800.934983 3.8 ms6.6 ms8.3 ms
Solo250k × 15361200.964832 4.4 ms7.8 ms9.7 ms
Starter250k × 1536100.75162,088 peak throughput7.1 ms13.0 ms17.3 ms
Starter250k × 1536800.9341,185 3.2 ms4.8 ms6.0 ms
Starter250k × 1536800.93161,599 9.3 ms17.1 ms22.3 ms
Growth250k × 1536100.82163,961 peak throughput3.7 ms6.7 ms8.7 ms
Growth250k × 1536800.9441,989 1.9 ms2.7 ms3.2 ms
Growth250k × 1536800.94162,949 5.1 ms8.7 ms11.4 ms
Scale250k × 1536100.80165,815 peak throughput2.6 ms4.1 ms5.1 ms
Scale250k × 1536800.9583,323 2.3 ms3.1 ms3.8 ms
Scale250k × 1536800.95164,465 3.4 ms5.3 ms6.6 ms
Scale1M × 1536800.74163,600 1M index hot in RAM4.2 ms6.9 ms8.9 ms
Scale1M × 15362000.85162,410 1M, higher recall6.2 ms10.7 ms14.0 ms
// HNSW m=16 · ef_construction=64 · cosine distance · same-region client via TLS · Hetzner EU Falkenstein · PostgreSQL 17.10 · pgvector 0.8.2 · synthetic clustered 1536-dim vectors (Gaussian mixture, cosine-normalized) · recall@10 vs brute-force KNN ground truth

# build ceiling

// HNSW index builds are memory-bound. Each tier has a hard size limit beyond which the build takes hours or fails entirely. The throughput numbers above are irrelevant if the index will not build. Size the node by index build memory first, QPS second.

tier (RAM)builds finebuilds, but slow / unusablewill not build
Solo (2 GB)250k (~15 min)500k (RAM-limited)1M (OOM)
Starter (4 GB)250k (~5 min)500k (tight)1M (OOM)
Growth (8 GB)250k (~2 min)~500-750k1M (untested)
Scale (16 GB)250k (~80 s), 1M (~9 min)
// HNSW m=16 · ef_construction=64 · 1536-dim vectors · a 1M × 1536 index requires approximately 6 GB of build memory

# methodology

// Exactly what was measured and how, for anyone who wants to understand or reproduce it.

Client and network path

The benchmark client ran on a separate VM in the same region (Hetzner EU, Falkenstein), connecting over the public endpoint with TLS (sslmode=require). That is exactly the path a same-region application takes. On Solo the client connects through PgBouncer (port 6432, transaction pooling); on Growth and Scale through the load balancer (port 5432). Bare SELECT 1 round-trip overhead from the same region is about 0.4 ms on Solo and 0.9 ms on Growth/Scale. Cross-region latency would add roughly 90 ms per query and would dominate all these numbers.

Dataset and index

Vectors are synthetic but clustered: a 1536-dimensional Gaussian mixture, normalized for cosine distance. A clustered mixture is used rather than uniform random noise so that recall is representative of real embedding structure, and real embeddings may recall slightly higher. The index is HNSW with m=16 and ef_construction=64 (the pgvector defaults for 1536-dim). Recall@10 is measured against exact brute-force KNN ground truth computed with index scans disabled.

Why recall and concurrency are non-negotiable

Approximate nearest-neighbor search trades recall for speed. The hnsw.ef_search parameter controls the tradeoff. A QPS number without a recall number and a concurrency count is an advertisement, not a measurement. Every result on this page is a (tier, dataset, ef_search, recall@10, concurrency, QPS, latency) tuple. A single headline number without those qualifiers tells you nothing about what the system actually delivers for your workload.

Reproducibility

All numbers were produced with the open-source pgvector-bench CLI, released under the MIT license and available at github.com/Rivestack/pgvector-bench. A single Go binary with an interactive wizard and a self-contained HTML report. Run it against any PostgreSQL instance (including a Rivestack free tier) to measure your own p50/p95/p99, QPS curve, and recall@k against brute-force ground truth on your actual data.

// related: NVMe vs cloud SSD benchmarks (full blog post) is the source of every number on this page, including the latency curves, the ef_search=200 collapse, and per-tier analysis. Run the benchmarks yourself with the pgvector-bench CLI. Try managed pgvector against a free tier to measure your own index.

# frequently asked questions

Get started

Stop overpaying for pgvector you don't control.

Start on the free tier, with pgvector ready in 60 seconds. Or send us your current setup and we'll tell you in 48 hours whether Rivestack is cheaper, faster, and less painful than what you have today.

No credit card required.