Benchmarks
Measured latency and throughput numbers - what one query costs, what a connection costs, and what a noisy neighbor actually does - with full methodology and a reproducible harness.
How we measure (and how to read this page)
All numbers below come from capybench, our open-source managed-Postgres benchmark harness, run against production CapyDB infrastructure - the same nodes that serve real customer cells, not an idle lab box. The load client is colocated in the same region as the database (0.6 ms network round-trip). That matters: latency measured across the public internet measures your ISP, not the database. Expect your numbers to be these numbers plus your app-to-region round-trip.
Test cells are standard business-plan projects created through the public API, connected through the same TLS proxy every customer uses, with full certificate verification on. Nothing is special-cased.
Latest campaign: 2026-08-17, Postgres 17.10, Helsinki region.
What one query costs
Measured with 500 repeats per variant on a warm connection (driver auto-prepare disabled, so the numbers hold on both ports), plus 30 full fresh connects:
| Variant | Direct (5432) | Pooled (6432) |
|---|---|---|
SELECT 1, warm connection - p50 / p95 | 0.57 / 0.67 ms | 0.66 / 0.77 ms |
| Indexed point-select, warm connection - p50 / p95 | 0.70 / 0.81 ms | 0.83 / 1.04 ms |
| Fresh connection (TCP + TLS + SCRAM) + first query - p50 / p95 | 25.7 / 46.3 ms | 15.3 / 18.5 ms |
Two things worth internalizing:
- A query on a warm connection has a sub-millisecond p95. There is no HTTP gateway, no REST translation layer, and no per-request auth handshake between your app and Postgres - the wire protocol is the API.
- The pooled port connects faster than the direct port (the pooler already holds authenticated server connections) and costs ~0.2 ms extra per query. If your platform opens connections per request - serverless, edge workers - the pooled URL is the right default, exactly as the pooling guide says.
Full-transaction latency and throughput
pgbench read-write transactions (TPC-B-style, roughly seven statements each, writes included) on the direct port. Percentiles are exact, computed from per-transaction logs:
| Concurrent clients | Transactions/sec | p50 | p95 | p99 |
|---|---|---|---|---|
| 1 | 138 | 7.1 ms | 9.5 ms | 11.7 ms |
| 8 | 915 | 7.9 ms | 13.6 ms | 17.6 ms |
| 16 | 1 700 | 8.2 ms | 14.9 ms | 21.1 ms |
| 32 | 2 301 | 11.9 ms | 25.8 ms | 38.7 ms |
Through the pooled port the same sweep gives up 10-20 % of throughput and, at high concurrency, actually posts a lower p95 (24.2 ms at 32 clients) - transaction pooling queues excess work instead of letting it pile onto the server, which caps peak throughput and smooths the tail. That is the trade it exists to make.
Scale-to-zero wake
Twelve forced sleep→wake cycles, timing a cold connect + SELECT 1 (the first connection is what wakes a paused cell):
- p50 ≈ 308 ms, worst observed 358 ms.
A paused cell answers its first query in about a third of a second. Budget one extra round-trip-ish pause on the first request after idleness, not a multi-second cold boot; connections after that first one are at steady-state latency immediately.
Preview database creation
Creating a preview (copy-on-write branch) of a parent cell, timed from API call to the branch answering SELECT 1:
| Parent size | Time (avg of 3) |
|---|---|
| 256 MB | 10.2 s |
| 1 GB | 15.6 s |
Four times the data costs about 50 % more time - creation is dominated by orchestration and starting the new Postgres, not by copying your data. Multi-gigabyte parents do not turn preview creation into a coffee break.
Previews wake up warm
Because a preview is a copy-on-write branch, it shares its storage blocks with the parent - and the node's cache works at the block level, so a fresh preview inherits the parent's hot cache instead of starting cold. Measured on a ~500 MB parent whose data was in active use: the preview's first-ever full-table scan ran in 255 ms, the same as a warm scan on the parent itself and about 2.6× faster than a fully cold read of the same data. Your CI run against a just-created preview starts at production cache temperature.
Noisy neighbors: what we claim and what we measured
Standard cells share nodes. We publish this number precisely because most providers do not: with an aggressor cell on the same node sustaining ~1 640 transactions/sec for the whole measurement window, a victim cell kept 49 % of its solo throughput and its p99 moved from 28 ms to 72 ms (×2.55).
That is proportional fair-share, not isolation. Equal-plan cells under contention each keep roughly their share of the node; a neighbor's burst cannot starve you out, and the per-cell CPU clamp means no single tenant can take the whole node. If your workload cannot tolerate any neighbor effect, that is what dedicated placement is for - talk to us.
We consider an honestly-measured ×2.55 more useful to you than an unmeasured "fully isolated".
Reproducing
git clone https://github.com/capy-base/capybench && cd capybench
uv sync && sudo apt-get install sysbench postgresql-client
# provision two projects, put their URLs in capybench.toml, then - from a client
# colocated with your region:
uv run capybench run --config capybench.toml
uv run capybench report --config capybench.toml --out report.htmlThe harness records every sample with the client's region and machine info, refuses to hide a dead load generator (the noisy-neighbor scenario hard-fails if the aggressor was not actually sustaining load), and produces a self-contained HTML report. Community runs - including against other providers - are welcome.