# Turbopuffer vs. Manticore Search on a couple of cheap VPS

A head-to-head benchmark on 975K 1536-dim vectors: turbopuffer's managed service vs Manticore on two $8.25/mo VPS. Matched recall (~0.966), ~4.5× lower cost, and comparable latency and throughput, with an honest look at where each approach wins.

Serverless vector databases are marketed on a simple promise: you don't run anything, you don't tune anything, and someone else worries about storage, scaling and uptime. [turbopuffer](https://turbopuffer.com/) is one of the better examples of the category: a fast search engine built on object storage, used by Cursor, Notion, Linear and others.

The promise is real, but it isn't free. So the obvious question is: for a small, well-defined workload, how much of that do you actually need, and what does the same workload cost and perform like on two cheap VPS running Manticore Search?

This article answers that with numbers from an apples-to-apples benchmark on the same dataset.

## The workload

We took a concrete, modest workload, the kind a lot of real applications actually have, and sized turbopuffer for it:

| Parameter | Value |
|---|---|
| Dataset | [DBpedia](https://storage.googleapis.com/ann-filtered-benchmark/datasets/dbpedia_openai_1M.tgz) (OpenAI embeddings) |
| Stored documents | 975k |
| Vector dimensions | 1536 (cosine similarity) |
| turbopuffer latency P50 / P90 / P99 (advertised, warm namespace) | 14 / 17 / 27 ms |
| turbopuffer estimated cost | $75 / month (July 2026) |

The two turbopuffer numbers in that table are its advertised latency and its calculator-estimated cost, not measurements. The cost comes from turbopuffer's [cost calculator](https://turbopuffer.com/pricing), and the latency is turbopuffer's published target: specifically the warm-namespace P50/P90/P99 from its own benchmark:

![turbopuffer's advertised vector-search latency: warm namespace 14/17/27 ms vs cold namespace 874/1214/1686 ms](./turbopuffer-vs-manticore/turbopuffer_advertised_latency.png)

Two caveats come straight from that screenshot. First, those 14/17/27 ms are for a warm namespace; the same benchmark reports 874 / 1214 / 1686 ms for a cold one. A namespace that has aged out of cache is ~50–60× slower on its first queries, a direct consequence of turbopuffer's object-storage tiering. Second, turbopuffer's benchmark runs a different workload (1024 dimensions, 10M docs) than ours (1536 dimensions, 975k docs). turbopuffer's actually measured latency and throughput on our dataset are reported further down, and they differ from these advertised figures. Our benchmark dataset is 975k docs (DBpedia); both systems were loaded with the same data.

That $75/month comes straight from turbopuffer's own cost calculator for 1536-dim vectors with no attributes (the calculator's "Attributes: None"). This matches our benchmark, which was pure vector search: no stored attributes and no metadata filtering on either side. Both engines support attribute filtering and full-text search (Manticore also adds [hybrid search](https://manual.manticoresearch.com/Searching/Hybrid_search)); we simply didn't exercise it here, so the comparison is like-for-like on plain KNN. The pricing is usage-based, and the breakdown is the interesting part:

| Component | Rate | This workload | Cost |
|---|---|---|---|
| Storage | ≤ $0.33 / GB | 1M docs (~3 GB) | $1.01 |
| Writes | ≤ $2.00 / GB | 10M writes, ~4 WPS (~61 GB) | $61.44 |
| Queries | ≤ $1.00 / PB | 10M queries, ~4 QPS (~13 PB) | $12.80 |
| Namespaces | n/a | 100 × 10K docs | Included |
| Total | | | $75 / month (min $16 Launch plan) |

![turbopuffer cost calculator for 1536-dim vectors with no attributes: $75/month, dominated by writes](./turbopuffer-vs-manticore/turbopuffer_cost_calculator.png)

The headline number is almost entirely writes: $61.44 of the $75 is the cost of ingesting ~61 GB of vector data, while storage (3 GB) is a rounding error.

![Monthly cost breakdown: turbopuffer $75 (mostly writes) vs Manticore's flat $16.50](./turbopuffer-vs-manticore/tpuf_vs_manticore_cost.svg)

The "~13 PB" against the query line looks alarming but is a billing abstraction, not bandwidth. turbopuffer meters queries by "GB queried" with a minimum billable of 1.28 GB per query, so the calculator simply computes `10M queries * 1.28 GB ≈ 13 PB`. The data actually moving per query is tiny (a 1536-dim float32 query vector is ~6 KB in, a top-10 result is a few KB out), yet every query on a namespace this small just bills at the 1.28 GB floor. At ≤$1/PB that whole 13 PB costs only $13, so query pricing is trivial here.

This matters for the comparison because on a self-hosted engine none of these meters exist: writes are just CPU time, queries have no per-query floor, and there is no per-GB charge at all. ([turbopuffer pricing](https://turbopuffer.com/pricing), [pricing changelog](https://turbopuffer.com/docs/pricing-log))

## The contenders

**turbopuffer** is serverless for the user: storage lives on S3/GCS/Azure object storage (~$0.02/GB), with hot data tiered through NVMe and RAM caches. You never provision or patch a server, never manage replication, never do capacity planning. You pay per GB stored / written / queried, and query compute runs on turbopuffer's managed infrastructure rather than on hardware you provision.

**Manticore Search** is software you run yourself. For this comparison we used the cheapest realistic setup that still gives you redundancy:

| Parameter | Value |
|---|---|
| Nodes | 2 × Hetzner CX23 (region "eu-central") in a cluster |
| Cost per VPS | $8.25 / month |
| CPU per VPS | 2 cores |
| Disk per VPS | 40 GB (table data uses ~6 GB) |
| Role of 2nd node | Replica for redundancy |
| Manticore total cost | $16.50 / month (or $8.25 for a single node if you don't need redundancy) |

Manticore stores vectors in an HNSW index (via the [Manticore Columnar Library](https://github.com/manticoresoftware/columnar)) and supports scalar quantization (8-bit and 1-bit) with oversampling and rescoring to trade memory and speed against recall. For this test we used 1-bit (binary) quantization with `oversampling=2.0` and rescoring, settings chosen specifically to land at roughly the same recall as turbopuffer, so **the latency and throughput numbers are compared at equal quality**.

## Bottom line first

| | turbopuffer | Manticore (2× cheap VPS) |
|---|---|---|
| Monthly cost | $75 | **$16.50** (high-availability mode) / $8.25 (single node) |
| recall@10 (5k queries) | 0.9622 | **0.9663** |
| Query latency P50 / P90 / P99 (end-to-end) | 21 / 24 / 30 ms (incl. network round-trip) | **10 / 13 / 16 ms** (local, no network hop, fully compacted) |
| Read (query) throughput ceiling | ~300 QPS unbatched (concurrency=8); **~800–1,100 QPS** with query batching | **~330 QPS** (concurrency=2, unbatched) |
| Sustained write throughput | **~1150 docs/s** (bulk load) | 490-1100 docs/s (bulk load) |
| Ops burden | **None (fully managed)** | You run & maintain it |

On this workload, Manticore on two $8.25 VPS is ~4.5× cheaper, matches turbopuffer on recall, and, once the table is compacted (its steady state), is actually faster end-to-end (~10–13 ms vs turbopuffer's ~21 ms in-region), because a local query skips the network round-trip turbopuffer always pays. turbopuffer, in turn, reaches a higher query-throughput ceiling, but only with query batching (~800–1,100 QPS vs ~330 on two 2-core VPS); unbatched from a single machine the two are comparable (~300 QPS each). For a 4 QPS workload both have enormous headroom regardless. What you give up with Manticore is the "zero operations" part, which is the whole point of the rest of this article.

## Quality: matched recall

There's no point comparing latency or cost unless both systems return results of comparable quality. Using the [DBpedia](https://storage.googleapis.com/ann-filtered-benchmark/datasets/dbpedia_openai_1M.tgz) dataset (1536-dim OpenAI embeddings, cosine similarity, 975k docs) and 5,000 queries:

| System | recall@10 |
|---|---|---|
| turbopuffer | 0.9622 |
| Manticore | **0.9663** |

The two are within ~0.4 points of each other, close enough that the rest of the comparison is fair. Manticore reached this with [binary quantization](/blog/quantization/) + `oversampling=2.0` + rescoring: the binary vector representation is 32× smaller than float32, while rescoring recomputes final distances on the full-precision vectors for the top candidates.

## Latency

The number that matters is the one you actually observe: end-to-end, from issuing the query to getting the answer. turbopuffer is a remote service, so that includes the network round-trip to the namespace's region. Measured from a machine in the same region as the namespace (`gcp-europe-west3`, same country as the query host):

```
turbopuffer (in-region, end-to-end):   P50=21ms   P90=24ms   P99=30ms
```

Run your application farther from the region and this rises with the distance (easily 100 ms+ across continents), so co-locating the two matters, and you always pay at least the in-region round-trip.

turbopuffer also reports two of its own internal timers: the time it spent running the search (~12 ms, and stable even under heavy load), and the total time on its side including requests waiting in line for an execution slot. We lean on those only to diagnose the throughput results below (the engine's own run-time stays fast under load), not as a latency figure, because any time a request waits is real latency you pay. And one point keeps the comparison honest: that ~12 ms is turbopuffer's own self-reported, engine-only time, excluding its internal queuing. Manticore has query profiling too, but we didn't isolate a single engine-only number equivalent to it; every Manticore figure here is end-to-end and already includes any internal queuing it does. So throughout this section we compare end-to-end against end-to-end, never turbopuffer's engine-only figure against Manticore's total.

Manticore's latency (queried locally, so end-to-end with negligible network) under a mixed 4 QPS + 4 WPS load (10-minute run) depends heavily on how many disk chunks the table has been compacted into. A freshly bulk-loaded real-time table is split into many chunks; Manticore's auto-optimize merges them over time, and each KNN query has to consult every chunk, so fewer chunks means faster queries:

![Manticore query latency falls as the table compacts, crossing below turbopuffer's in-region band at ~2 chunks](./turbopuffer-vs-manticore/tpuf_vs_manticore_latency_chunks.svg)

The 32 / 16 / 8 / 4-chunk states above were produced deliberately, by optimizing to a fixed chunk count, to map out the latency curve. A real bulk load lands at far fewer: at our benchmark's batch=1000 the table settled at 7 chunks (1 insert thread) or 8 chunks (2 threads), so a freshly loaded table sits around the 8-chunk mark (~28 ms). The number you end up with is really a race between two processes: inserts flush RAM chunks to disk, and auto-optimize merges those disk chunks in the background. The faster you ingest, the faster new disk chunks pile up, so a high-throughput load (batch=1000) out-runs the background merger and leaves more chunks behind (7–8), while slow single-document inserts (batch=1) produce chunks slowly enough that auto-optimize keeps pace and the table ends at just 3 chunks, at a far lower ingest rate. So it's ingest rate, not concurrency per se, that drives the final chunk count. From whatever the load leaves behind, Manticore's auto-optimize then continues compacting toward 2 chunks (~13 ms avg).

The takeaway: chunk count is the single biggest latency lever. At 32 chunks Manticore is several times slower than turbopuffer's ~21 ms end-to-end. But auto-optimized to its default floor of 2 chunks (~12–13 ms) it's already faster end-to-end than turbopuffer, and fully compacted to a single chunk (~10 ms) faster still, all on $8.25 hardware. The reason is mostly structural rather than a difference in engine speed: both do the core search in roughly the same ~10–12 ms, but Manticore runs on your own VPS and can be queried over localhost/LAN, so it pays no network round-trip, while turbopuffer always pays at least the ~9 ms in-region hop. The end-to-end gap is essentially that network hop.

Read (query) latency, not write latency, tracks how compacted the table is, and keeping it compacted is Manticore's job, not yours. Auto-optimize runs in the background and merges chunks down to a target it maintains automatically (our table settled at 2 chunks). That target is a setting, not a chore: the [`optimize_cutoff`](https://manual.manticoresearch.com/Creating_a_table/Local_tables/Plain_and_real-time_table_settings#optimize_cutoff) option (per-table via `CREATE`/`ALTER`, or globally in `searchd`) controls it, so if you want the very best latency you can set `optimize_cutoff=1` and the background optimizer will keep the table at a single chunk for you, with no manual step in steady state. The only real catch is timing: right after a large bulk load the table is still at many chunks and queries are slower until auto-optimize catches up. In our run, with 8 chunks left after the concurrency=2 load, the background optimizer took about 15 minutes to compact down to 2 chunks, during which query latency steadily improved. turbopuffer has no equivalent step at all, which is part of what you're paying the premium for.

## Throughput

Because each query costs the ~12 ms of engine time plus a network round-trip, a single connection is limited to roughly `1 / (engine + round-trip)` queries per second, so throughput is recovered by issuing queries concurrently, and the in-region numbers below reflect that.

**turbopuffer, in-region, plain single queries** (no batching). Adding concurrent connections lifts throughput, but only up to a point:

![Unbatched throughput vs concurrency: both engines peak then decline (turbopuffer ~296 QPS at 8 connections, Manticore 329 at 2 connections)](./turbopuffer-vs-manticore/tpuf_vs_manticore_throughput.svg)

Unbatched throughput peaks around 8 connections (~296 QPS) and then falls (233 QPS at 16, 153 at 32) while end-to-end latency climbs steeply (P90 33 → 108 → 423 ms at 8 → 16 → 32 connections). Through all of it, turbopuffer's own reported server-side time stays flat at ~12 ms. Because that reported time doesn't move while end-to-end latency rises, the extra delay isn't captured by turbopuffer's own timers: the bottleneck could be on our machine, the transport path, or some portion of request handling outside those timers, and this benchmark can't distinguish them. What it does show is that simply raising unbatched concurrency past 8 didn't improve throughput from this one machine.

![turbopuffer latency vs concurrency: end-to-end latency climbs past ~8 connections while turbopuffer's self-reported engine time stays flat at ~12 ms](./turbopuffer-vs-manticore/tpuf_throughput_latency.svg)

We therefore treat ~300 QPS as the observed unbatched ceiling from this single machine, not a documented turbopuffer service limit. (Whether more machines would push higher depends on what's actually saturating, a per-machine resource versus the shared path, which we didn't test.) The lever that took the same single machine well past ~300 was query batching.

Query batching, packing several queries into one request, amortizes the network round-trip and lifts throughput much higher, with a clear quality-of-service gradient:

- **~800 QPS cleanly**: concurrency=4 / batch=8 held 821 QPS with total server-side time P50=15 / P90=20 / P99=29 ms.
- **~1,000–1,100 QPS with higher tail latency**: concurrency=8 / batch=8 reached 1,015 QPS but P99 rose to 58 ms; conc 4 / batch 16 hit 1,105 QPS at P99=61 ms.
- **~1,170 QPS oversaturated**: concurrency=8 / batch=16 served 1,169 QPS, but the total time per request ballooned (P99=455 ms, avg ~70 ms) even though turbopuffer's reported server search time was still only ~12 ms, i.e. requests spent hundreds of milliseconds waiting in line on the server. Not a sustainable operating point.

So the practical batched ceiling we observed on this dataset is roughly ~800 QPS cleanly, rising to ~1,100 QPS with elevated server-side tails, comfortably above the two VPS either way.

Three caveats on batching, though:

- **It only helps if you have many independent queries arriving at once** to bundle into a single request: a high-traffic search backend can, but a typical "one user, one query" path can't, so for many real apps the relevant number is the non-batched ceiling above (~300 QPS), not the ~1,000+ headline.
- **The per-query latencies in batched mode are amortized, not wall-clock.** A query that has to wait for a batch to fill experiences the batch-formation delay plus the request's own latency, so end-user latency under batching is higher than the amortized figure suggests.
- **It pushes complexity onto your application.** You have to collect queries, hold them briefly to form a batch, fan the combined response back out to the right callers, and reason about partial failures, often inconvenient enough that teams simply don't do it.

**Manticore, on the two 2-core VPS**, after the initial load, serving queries **while also taking 4 WPS of writes** (queried locally, so no network round-trip):

| Concurrent connections | Queries per second |
|---|---|
| 1 | 154 |
| 2 | **329** |
| 4 | 287 |

Manticore tops out around 329 QPS at 2 connections; beyond that you're oversubscribing the 2 cores, so 4 connections is slightly slower. Unlike turbopuffer's unbatched decline, this is a hard ceiling: it's the engine on 2 cores, and the only way past it is more cores or more nodes, which costs more money.

These Manticore figures are unbatched, plain one-query-per-request, the same shape as turbopuffer's unbatched sweep above. Batching wouldn't lift Manticore's ceiling the way it does turbopuffer's: batching helps turbopuffer mainly by amortizing its ~9 ms network round-trip across many queries, but a local Manticore query has no such round-trip to amortize, and the real limit here is CPU on 2 cores, which batching doesn't relieve. So the fair like-for-like is unbatched-vs-unbatched, and there the two land close: **turbopuffer ~296 QPS vs Manticore ~329**. What differs is what each number means: Manticore's ~329 is a hard 2-core ceiling, while turbopuffer's ~296 is simply where our single machine topped out. Adding concurrency only made it worse, and we couldn't localize the cause (turbopuffer's self-reported engine time stayed flat, but that's its own number, not something this benchmark can verify). So ~300 was the practical unbatched limit in our setup, and the proven path beyond it on turbopuffer was query batching (~800–1,100 QPS from the same machine); on Manticore it's more hardware.

So on query throughput, **turbopuffer's batched ceiling (~800–1,100 QPS, on its managed compute) is higher than two 2-core VPS (~330 QPS)**, **while unbatched the two are comparable**, and turbopuffer's query pricing is cheap (the whole workload's query bill was ~$13). Manticore scales by buying hardware; turbopuffer scales on infrastructure you don't manage. The one nuance in Manticore's favor: at low concurrency **Manticore is actually faster per connection (154 QPS at 1 connection vs turbopuffer's 40)**, because a local query has no ~9 ms network round-trip to pay.

But for this workload the throughput axis is close to irrelevant. The target is 4 QPS, and both systems clear it by a huge margin: ~200× headroom for turbopuffer at clean latency, ~80× for Manticore. Throughput only becomes a deciding factor if you expect sustained query volume in the hundreds of QPS, at which point turbopuffer's higher batched ceiling is the safer bet and Manticore needs a bigger (pricier) cluster.

On the write side the two land closer together:

- **turbopuffer** bulk load: 975k docs at ~1150 docs/s. This is the end-to-end rate we observed; what bounds it (its ingestion path, the network, or its own indexing) we can't tell from the outside.
- **Manticore** bulk load (batch=1000): 750–1100 docs/s until the first disk chunk is flushed. Here the bottleneck is visible: HNSW graph construction on 2 cores, since adding insert workers barely helps (451 → 490 docs/s from 1 → 2 threads).

## Ingestion in practice

Loading the full DBpedia set into Manticore (975k docs, 1536 dims, building the HNSW graph) took:

| Concurrency | Batch | Rate | Wall time |
|---|---|---|---|
| 1 | 1 | 365 docs/s | ~45 min |
| 1 | 1000 | 451 docs/s | ~36 min |
| 2 | 1000 | 490 docs/s | ~33 min |

For comparison, turbopuffer loaded the same data at ~1150 docs/s, roughly 2.3x faster than Manticore on these two cheap VPS. That's expected: turbopuffer builds the index on its own (larger, managed) compute, while Manticore is doing the HNSW graph construction on the 2 inexpensive CPU cores. So turbopuffer wins clearly on raw ingest speed, though for a one-time initial load, the difference is ~14 minutes vs ~33–36 minutes, a one-off cost either way.

After the initial load at batch=1000 the Manticore table sat at 7 disk chunks (1 insert thread) or 8 chunks (2 threads); adding insert workers doesn't help on a 2-core VPS. The final chunk count is driven by ingest rate rather than concurrency: inserts flush disk chunks while auto-optimize merges them in the background, so the faster you load, the more chunks the background merger is left behind by. A fast batch=1000 load leaves 7–8 chunks; slow single-document inserts (batch=1) produce chunks gently enough that auto-optimize keeps up and the table ends at only 3 chunks, but at a lower 365 docs/s instead of ~450–490. From there, Manticore's auto-optimize keeps compacting toward 2 chunks (its default target); lowering `optimize_cutoff` to `1` makes it maintain a single chunk, the lowest-latency state, automatically.

## Storage and headroom

Manticore's table data for this workload is ~6 GB, against 40 GB of disk on each cheap VPS, so **a single node has room for several times this dataset before you'd need a bigger plan**. On turbopuffer, storage is billed at ≤$0.33/GB and came to just $1.01 for this dataset, genuinely negligible. As the cost breakdown above shows, the difference between $75 and $16.50 is not about storage; it's the per-GB write charge ($61.44) that Manticore simply doesn't have.

## What you actually trade

The numbers favor Manticore on cost and on latency when the table is compacted. But the comparison isn't only numbers: turbopuffer's price buys things that don't show up in a latency table.

**You give up (by self-hosting Manticore):**
- **Zero operations.** turbopuffer has no servers to patch, no replica to fail over, no disk to outgrow. With Manticore, that's your job (though compaction itself is automatic, see below).
- **Elastic scale and a higher throughput ceiling.** turbopuffer separates storage and compute and scales each independently; you can go from 1M to 1B docs without re-architecting, and its managed compute reached ~800–1,100 QPS with batching where two 2-core VPS cap at ~330. Two fixed VPS have a fixed ceiling: raising it means buying more hardware.
- **Scale-to-zero economics.** For spiky or tiny workloads, usage-based billing can undercut even a $16.50/month fixed cost: you pay for what you query.
- **A managed SLA** (uptime guarantees, support) on the higher tiers.

**You gain (with Manticore):**
- **~4.5× lower cost** on this workload: a fixed, predictable bill with no per-GB write charge and no per-query metering.
- **No network round-trip.** Querying over localhost/LAN avoids the round-trip a remote service always costs you (a ~9 ms floor even when co-located, more across regions), so a compacted table is faster end-to-end.
- **Full control and data locality**: your data on your servers, plus full-text search, filtering, [hybrid search](https://manual.manticoresearch.com/Searching/Hybrid_search) and SQL in the same engine.
- **Security and data sovereignty**: your vectors and source documents never leave infrastructure you control, so nothing is sent to or stored by a third party. For teams with data-residency, GDPR, or confidentiality requirements that's often decisive: you own the encryption, network isolation, and access policy, and there's no external service to vet, trust, or breach. With a managed service the same data lives in the provider's cloud by design.
- **Dedicated compute on your own VPS**: fixed, predictable resources that aren't shared with other tenants.

## Conclusion

For a large, elastic, or bursty workload, or for a team that simply doesn't want to operate a database, turbopuffer's $75/month (and the managed, serverless model behind it) is easy to justify.

But for the very common case of a bounded workload (around 1M vectors, a few QPS/WPS) where someone is willing to run a couple of servers, two $8.25 VPS running Manticore deliver the same recall, comparable latency after auto-optimize (and better latency when compacted to one chunk, with no network round-trip), ample throughput headroom for the workload, and a ~4.5× lower bill. The catches are that you run and keep the cluster healthy, and that if you ever need many hundreds of QPS, turbopuffer's managed compute scales past two fixed cores.

The serverless premium is real value, not a markup. The question this benchmark answers is just how much of that value a small, well-understood workload actually consumes, and the answer is: often, not $75 worth.
