AWS RDS for PostgreSQL vs ServersCamp managed PostgreSQL: pgbench TPC-B
TL;DR
We compared managed PostgreSQL 18.3 on comparable hardware (2 vCPU / 8 GB / 500 GB) across two providers: AWS RDS db.m6i.large and ServersCamp db-m-8g. One workload - pgbench TPC-B-like, three database sizes (in-cache, partial cache, disk-bound), three runs each. No tuning - both out of the box.
About the CPUs. AWS m6i.large runs on Intel Xeon Platinum 8375C (Ice Lake, 2021, 2.9 / 4.0 GHz base / max turbo, per AWS). ServersCamp runs on Intel Xeon Gold 6132 (Skylake-SP, 2017, 2.6 / 3.7 GHz base / max turbo, per Intel ARK) - that's four years and one or two architecture generations of difference. This gives AWS a theoretical ~10-15% IPC advantage plus +8-11% clock headroom on CPU-bound workloads. We ran the test with that gap on the table on purpose, to see whether AWS's managed-PG overhead eats into that lead on disk-bound scenarios. For a clean CPU pairing we'd want AWS db.m5.large (also Skylake) - that's planned for the next round, see the end of the article.
ServersCamp was tested in two storage QoS configurations (everything else - vCPU, RAM, PG version - identical):
- Test 1 - 5,000 IOPS / 225 MB/s. Deliberately constrained, well below the current default (10,000 IOPS / 500 MB/s). The point of running it: probe how much storage QoS contributes to the disk-bound result by halving both IOPS and throughput vs Test 2.
- Test 2 - 12,000 IOPS / 500 MB/s, manually aligned with AWS RDS gp3's free baseline for 400+ GiB volumes. The point: isolate the storage tier's contribution and evaluate managed-PG as a product on its own at storage parity.
Why 12,000 IOPS / 500 MB/s. AWS RDS gp3 ships with 3,000 IOPS / 125 MB/s baseline on volumes up to 400 GiB. From 400 GiB upward the baseline auto-scales to 12,000 IOPS / 500 MB/s included in the price - and stays there with no extra payment. So we sized the AWS RDS volume at 500 GiB on purpose: that's the typical, sensible move for a customer who wants the maximum "free" storage baseline. ServersCamp Test 2 is calibrated to that exact baseline - a fair pairing against what AWS gives away without provisioned IOPS upcharges.
The numbers (median TPS, Δ ServersCamp T2 vs AWS):
| Scenario | AWS m6i | Scamp T2 | Δ | Lead |
|---|---|---|---|---|
| bench100 (in-cache, CPU-bound) | 3,976 | 3,745 | -5.8% | AWS |
| bench1000 (partial cache, mixed) | 2,762 | 2,818 | +2.0% | ServersCamp |
| bench10000 (disk-bound, ~150 GB) | 2,276 | 2,481 | +9.0% | ServersCamp |
Key findings:
- On CPU-bound, AWS is ahead ~6% thanks to the newer CPU (Ice Lake 8375C vs Skylake 6132).
- On disk-bound, ServersCamp T2 leads by 9% TPS and p95 latency is roughly half of AWS (13.5 vs 26 ms).
- On stability, ServersCamp T2 dramatically outperforms AWS: CoV TPS 4% vs 23%, max dip -15% vs -68%.
- ServersCamp Test 1 (5k/225, sub-default) trails AWS on disk-bound - that's the storage QoS we deliberately constrained, not managed-PG. Going back to default-or-above (10k/500 or 12k/500) inverts the result.
Raw data. Run scripts, every run.txt summary, and per-interval aggregate logs from all three scenarios are published at github.com/serverscamp/pgbench-aws-vs-serverscamp. Every number in this article is verifiable - clone the repo and run the same scripts on your own AWS RDS and ServersCamp accounts.
The same numbers as bars below. AWS leads on bench100 (in-cache, where only CPU matters); ServersCamp T2 leads on bench1000 and bench10000 (where storage starts to dominate). The grouping at bench100 - where all three bars are within ~6% of each other - is essentially CPU-generation noise.
Latency tracks TPS in mirror image - more transactions per second means lower per-transaction latency. The gap on bench10000 (disk-bound) is what later in the article shows up as p95 latency cut roughly in half for ServersCamp T2.
Disclaimer
What this is. A reproducible comparison of two managed PostgreSQL services: AWS RDS (db.m6i.large) and ServersCamp (db-m-8g). One workload - standard pgbench TPC-B-like, three database sizes, three runs each. The test was run by one engineer in one day. It does not claim to be the industry's last word.
What this is NOT.
- Not a production-grade SLA test. Three runs per scenario is statistically thin for final calls.
- Not a read-only / point-lookup / bulk-load workload. Only TPC-B (write-heavy mixed).
- Not a fault-tolerance / replica / failover test.
- Not a comprehensive cost analysis. The Cost section shows on-demand list prices for the configurations we tested, but doesn't model backup, data transfer, support tier upgrades, or volume-discount agreements.
About latency and percentile metrics. pgbench was run with --aggregate-interval=10, which records average, min, and max latency per 10-second window - but not the per-transaction distribution. Every "p95 latency" mentioned in this article is the p95 of the per-window average across 60-120 windows per run, not p95/p99/p999 across individual transactions (which number in the millions). We made that trade-off deliberately for log volume and simplicity. It's a weaker signal than a "real" p99. For production SLA claims on tail latency you'd want a per-transaction logging run - that's a separate iteration, not in this article.
Principle. We test managed PG as the product, as it ships. No postgresql.conf tuning, no custom parameter groups. The provider's default is part of the product.
About the two ServersCamp configurations. Test 1 (5k IOPS / 225 MB/s) is below the current default tier (10k IOPS / 500 MB/s) - we ran it deliberately constrained to probe how strongly storage QoS drives the result. Test 2 (12k / 500) is calibrated to AWS gp3's free baseline at 400+ GiB volumes, so the comparison there is at storage parity. Neither test represents a "default product"; they're two points along the storage-QoS axis chosen to bracket the question.
What's left out of frame. AWS db.m5.large (Skylake-equivalent for a clean CPU pairing with ServersCamp) is planned, but not in this article.
Test standtop
Equipment overview
| AWS | ServersCamp | |
|---|---|---|
| Instance class | RDS PostgreSQL db.m6i.large | Managed PostgreSQL db-m-8g |
| vCPU / RAM | 2 vCPU / 8 GiB | 2 vCPU / 8 GB |
| CPU generation | Intel Xeon Platinum 8375C - Ice Lake (3rd gen Xeon Scalable, 2021) | Intel Xeon Gold 6132 - Skylake-SP (1st gen Xeon Scalable, 2017) |
| Clock (base / max turbo, per Intel ARK) | 2.9 / 4.0 GHz | 2.6 / 3.7 GHz |
| Disk size | 500 GiB | 500 GB |
| Storage tech | EBS gp3 - networked SDS | Managed-volume - networked SDS |
| Storage IOPS | 12,000 (RDS gp3 baseline for 400+ GiB volumes, no upcharge) | 5,000 (Test 1) / 12,000 (Test 2 - manually aligned) |
| Storage throughput | 500 MB/s (RDS gp3 baseline for 400+ GiB) | 225 MB/s (Test 1) / 500 MB/s (Test 2) |
| PostgreSQL version | 18.3-R1 (Amazon-build) | 18.3 (PGDG-build) |
| pgbench version (client) | 18.3 | 18.3 |
| SSL in tests | require | require |
| max_connections | 200 | 200 |
| Topology | single instance (Single-AZ, no replicas) | single instance, no replicas |
| Backup | defaults, untouched | defaults, untouched |
| Region | Frankfurt (eu-central-1) | Bucharest (eu-east-ro-1) |
Client VM (pgbench generator)
| Parameter | AWS EC2 c6i.xlarge | ServersCamp burst-l |
|---|---|---|
| Hostname | ip-172-31-25-222 | vm-ff4fd7714b235241 |
| vCPU / RAM | 4 / 8 GiB | 4 / 8 GB (burst limits removed) |
| OS | Ubuntu 26.04 LTS (kernel 7.0.0-1004-aws) | Ubuntu 25.04 |
| Disk | 100 GiB gp3 (SDS) | 100 GB standard SDS |
| Network | up to 12.5 Gbps | 1 Gbit (initial), later upped to 10 Gbit |
| pgbench version | 18.3 | 18.3 |
| Placement | same AZ as RDS (eu-central-1a) | same network as the DB |
Methodologytop
Principles
- Out of the box - no
ALTER SYSTEM, no custom parameter groups, no tuning. The provider's default is the product. - Identical pgbench parameters on both sides:
-c 32 -j 4 -M prepared --no-vacuum. - Three database sizes, covering different regimes:
scale=100(~1.5 GB) - fits entirely in page cache, measures CPU and locks.scale=1000(~15 GB) - partial cache, mixed CPU + disk.scale=10000(~150 GB) - disk-bound, random IO dominates.
- Three runs per size. Before each run:
CHECKPOINT+ wait for autovacuum to finish. - Warmup before the series: 5 min for bench100/1000, 10 min for bench10000.
- Measurement: 10 min for bench100/1000, 20 min for bench10000.
--log --aggregate-interval=10- TPS/latency captured every 10 seconds for stability analysis.
1. Environment
Set once per shell session on each pgbench client VM. Connection details below are anonymised; substitute your own RDS endpoint and ServersCamp managed-PG endpoint from the respective control panels.
AWS EC2 client
export PGHOST="<your-instance>.eu-central-1.rds.amazonaws.com"
export PGPORT="5432"
export PGUSER="postgres"
export PGPASSWORD='<your-password>'
export PGSSLMODE="require"
ServersCamp VM client
export PGHOST="postgres-<id>.apps.serverscamp.com"
export PGPORT="5432"
export PGUSER="<your-user>"
export PGPASSWORD='<your-password>'
export PGSSLMODE="require"
Both sides use PGSSLMODE=require so encryption overhead is comparable. Default libpq settings otherwise - no statement timeouts, no client-side pooling, no application_name games.
2. Creating the test databases and loading data
One-time setup before the run scripts. pgbench -i -s N creates the four standard pgbench tables and loads N×100k accounts. Wrap the bench10000 init in screen or tmux - it takes ~26 minutes on AWS, ~38 minutes on the slowest ServersCamp config.
AWS
psql -d postgres <<'SQL'
CREATE DATABASE bench100;
CREATE DATABASE bench1000;
CREATE DATABASE bench10000;
SQL
time pgbench -d bench100 -i -s 100
time pgbench -d bench1000 -i -s 1000
time pgbench -d bench10000 -i -s 10000
ServersCamp
Same shape. Note that on ServersCamp the per-tenant user does not have access to the maintenance postgres database; the platform provisions a default database per user (here db_<id>) which we connect to for DDL.
psql -d db_<id> <<'SQL'
CREATE DATABASE bench100;
CREATE DATABASE bench1000;
CREATE DATABASE bench10000;
SQL
time pgbench -d bench100 -i -s 100
time pgbench -d bench1000 -i -s 1000
time pgbench -d bench10000 -i -s 10000
Before Test 2 on ServersCamp (after raising storage QoS from 5k/225 to 12k/500), drop and recreate to measure init from a clean slate:
psql -d db_<id> <<'SQL'
DROP DATABASE IF EXISTS bench100;
DROP DATABASE IF EXISTS bench1000;
DROP DATABASE IF EXISTS bench10000;
CREATE DATABASE bench100;
CREATE DATABASE bench1000;
CREATE DATABASE bench10000;
SQL
time pgbench -d bench100 -i -s 100
time pgbench -d bench1000 -i -s 1000
time pgbench -d bench10000 -i -s 10000
3. Run script
Same structure for all three scenarios. Only the database name, the warmup duration, and the measurement duration change.
bench100 - AWS variant
settle() {
echo "[$(date +%H:%M:%S)] CHECKPOINT + waiting for autovacuum..."
psql -d postgres -c "CHECKPOINT;" >/dev/null
while [ "$(psql -d postgres -tAc "SELECT count(*) FROM pg_stat_activity WHERE backend_type='autovacuum worker'")" != "0" ]; do
echo " autovacuum still working..."
sleep 10
done
echo "[$(date +%H:%M:%S)] ready"
}
mkdir -p ~/results/aws/bench100 && cd ~/results/aws/bench100
settle
echo "=== WARMUP ==="
pgbench -d bench100 -c 32 -j 4 -T 300 -M prepared --no-vacuum
for i in 1 2 3; do
settle
echo "=== RUN $i ==="
pgbench -d bench100 -c 32 -j 4 -T 600 -M prepared --no-vacuum \
--log --log-prefix=run${i} --aggregate-interval=10 \
2>&1 | tee run${i}.txt
done
echo "=== DONE bench100 ==="
bench100 - ServersCamp variant
Identical to the AWS version, except settle() uses psql -d bench100 (the per-tenant user does not have access to postgres), and results live under ~/results/serverscamp/bench100:
settle() {
echo "[$(date +%H:%M:%S)] CHECKPOINT + waiting for autovacuum..."
psql -d bench100 -c "CHECKPOINT;" >/dev/null
while [ "$(psql -d bench100 -tAc "SELECT count(*) FROM pg_stat_activity WHERE backend_type='autovacuum worker'")" != "0" ]; do
echo " autovacuum still working..."
sleep 10
done
echo "[$(date +%H:%M:%S)] ready"
}
mkdir -p ~/results/serverscamp/bench100 && cd ~/results/serverscamp/bench100
settle
echo "=== WARMUP ==="
pgbench -d bench100 -c 32 -j 4 -T 300 -M prepared --no-vacuum
for i in 1 2 3; do
settle
echo "=== RUN $i ==="
pgbench -d bench100 -c 32 -j 4 -T 600 -M prepared --no-vacuum \
--log --log-prefix=run${i} --aggregate-interval=10 \
2>&1 | tee run${i}.txt
done
echo "=== DONE bench100 ==="
bench1000
Same structure. Only changes:
- Database name:
bench100→bench1000everywhere (including insettle()on the ServersCamp side). - Output folder:
~/results/{aws,serverscamp}/bench1000. - Same warmup (5 min,
-T 300) and same measurement (10 min,-T 600) as bench100.
bench10000
Same structure, but longer runs because cold-cache disk-bound TPS takes longer to stabilise:
- Database name:
bench10000. - Output folder:
~/results/{aws,serverscamp}/bench10000. - Warmup: 10 min (
-T 600) instead of 5. - Measurement: 20 min (
-T 1200) instead of 10.
# settle() identical, paste from above
mkdir -p ~/results/aws/bench10000 && cd ~/results/aws/bench10000 # or /serverscamp/
settle
echo "=== WARMUP ==="
pgbench -d bench10000 -c 32 -j 4 -T 600 -M prepared --no-vacuum
for i in 1 2 3; do
settle
echo "=== RUN $i ==="
pgbench -d bench10000 -c 32 -j 4 -T 1200 -M prepared --no-vacuum \
--log --log-prefix=run${i} --aggregate-interval=10 \
2>&1 | tee run${i}.txt
done
echo "=== DONE bench10000 ==="
4. What settle() does
Called before warmup and before every measurement run - never skipped, never reused. Two steps:
CHECKPOINT- forces the server to flush all dirty shared buffers to disk. Without this, an in-flight checkpoint started by the previous run can leak IO contention into the current measurement.- Poll
pg_stat_activityevery 10 seconds until noautovacuum workerbackends are running. Autovacuum competes for the same CPU and IO as pgbench - measuring while it's working would understate steady-state TPS.
Note: on the ServersCamp side, settle() connects to the bench database itself (e.g. psql -d bench100) rather than to a maintenance database, because the per-tenant user does not have access to the postgres DB. pg_stat_activity is server-wide, so the count of autovacuum workers is the same regardless of which DB you connect from.
5. pgbench parameters - identical on both sides
| Flag | Value | Why this value |
|---|---|---|
-c 32 | 32 concurrent clients | OLTP-grade concurrency. High enough to saturate a 2-vCPU instance and surface lock contention; not so high that connection overhead dominates. |
-j 4 | 4 worker threads | Matches the 4 vCPUs on the pgbench client VM. One worker per core avoids client-side scheduling becoming the bottleneck before the server is. |
-M prepared | prepared statements | Realistic for any application using a connection pool (PgBouncer, JDBC, asyncpg) - the parse/plan cost is paid once. Simple-protocol mode would mostly measure parser overhead, not the database engine. |
--no-vacuum | do not VACUUM before run | The pre-run VACUUM warms the cache and skews the first minute of the measurement. Skipping it lets autovacuum behave naturally and the measurement starts from the actual steady state we just settled into. |
--log | per-thread log | Enables post-hoc analysis: TPS over time, latency variance, dip detection. Without this, only the summary line at the end of the run is available. |
--log-prefix=runN | filename prefix | Keeps three runs' logs distinct in the same folder (otherwise pgbench overwrites pgbench_log.PID). |
--aggregate-interval=10 | 10-second aggregates | Trade-off between log volume and resolution. 10 s is dense enough to see checkpoint stalls and dips; per-transaction logging would multiply the log size by ~30,000 per run with no analytical gain at this stage. |
6. Output files
After each scenario, ~/results/{provider}/{benchN}/ contains:
runN.txt- the pgbench summary printed at the end of run N: TPS, latency average, transactions processed, failed transactions. This is what we copy into the article body and the comparison tables.runN.<PID>andrunN.<PID>.1,runN.<PID>.2,runN.<PID>.3- per-thread aggregate logs (4 files per run, one per-jworker). Each line is a 10-second window with timestamp, transaction count, latency mean/min/max. These feed the stability analysis later in the article.
All of the above - run scripts, raw runN.txt summaries, and per-interval aggregate logs from every run - is checked in at github.com/serverscamp/pgbench-aws-vs-serverscamp. Clone it, run the same scripts on your own AWS RDS and ServersCamp accounts, and verify any number in this article.
Two ServersCamp configurations - read this
ServersCamp goes through the full benchmark cycle twice with different storage QoS, deliberately, to isolate how much of the disk-bound result is storage-driven vs database-driven. The current ServersCamp default storage tier is 10,000 IOPS / 500 MB/s; both Test configurations bracket that.
| Test 1 | Test 2 | |
|---|---|---|
| Storage IOPS | 5,000 | 12,000 |
| Storage throughput | 225 MB/s | 500 MB/s |
| Position vs default | ~½ IOPS, ~½ throughput - deliberately constrained | ~20% above default IOPS, default throughput - matched to AWS gp3 free baseline |
| Why this point | probe how much storage QoS alone drives the disk-bound result | "at storage parity with AWS, how does managed-PG itself stack up?" |
Everything else - vCPU, RAM, PG version, pg_hba - is the same across both. CPU and RAM on both ServersCamp runs are identical.
Init phase: loading datatop
pgbench -i -s N loads the test tables. It's not part of the TPS run, but it gives a baseline sense of disk and CPU throughput under sequential pressure.
Init results
| Database | scale | Size | AWS m6i | Scamp T1 (5k/225) | Scamp T2 (12k/500) |
|---|---|---|---|---|---|
| bench100 | 100 | ~1.5 GB | 12.69 s | 20.57 s | 14.97 s |
| bench1000 | 1000 | ~15 GB | 157.74 s | 241.85 s | 175.61 s |
| bench10000 | 10000 | ~150 GB | 1561.67 s (26m1s) | 2280.92 s (38m1s) | 1479.85 s (24m40s) |
niced (visible in OS metrics later in the article), which de-prioritises it under contention, and the EBS write path adds overhead on long sequential PK builds. Reproduce-it-yourself caveat: this is one observation, not a robust finding.
Excerpts from init logs (bench10000)
AWS RDS
done in 1561.67 s (drop tables 0.00 s, create tables 0.01 s, client-side generate 876.29 s, vacuum 1.59 s, primary keys 683.78 s). real 26m1.711s
ServersCamp T1 (5k IOPS)
done in 2280.92 s (drop tables 0.00 s, create tables 0.02 s, client-side generate 1337.61 s, vacuum 5.95 s, primary keys 937.32 s). real 38m1.080s
ServersCamp T2 (12k IOPS)
done in 1479.85 s (drop tables 0.00 s, create tables 0.01 s, client-side generate 941.12 s, vacuum 3.33 s, primary keys 535.40 s). real 24m39.970s
Storage bottleneck, demonstrated
Disk Throughput readings from the ServersCamp panel during PK build:
| Configuration | Read | Write | Limit utilization |
|---|---|---|---|
| ServersCamp Test 1 (limit 225 MB/s) | 224.6 MB/s | 30.2 MB/s | 99.8% |
| ServersCamp Test 2 (limit 500 MB/s) | 498.3 MB/s | 64.8 MB/s | 99.7% |
In both cases, the disk hits its ceiling - at 225 MB/s on Test 1, at 500 MB/s on Test 2. So Test 1's storage QoS really is the bottleneck, and ServersCamp's managed-PG is capable of consuming the same 500 MB/s as AWS gp3.
pgbench runs: TPS and latencytop
bench100 (~1.5 GB, in-cache, CPU-bound)
Database fits in shared_buffers + page cache entirely. We're measuring CPU and locks. Storage shouldn't matter.
What this measures in practice. This is not a typical production scenario - real databases rarely fit entirely in 8 GB of RAM. What we're effectively isolating here is the gap between two CPU generations (Ice Lake vs Skylake), not anything about how the database is managed or how storage is provisioned. AWS's lead is silicon, not architecture. Useful as a control measurement, but if you're choosing a managed-PG provider for a real workload, this row tells you almost nothing about what you'll actually get.
| Metric | AWS m6i | Scamp T1 (5k/225) | Scamp T2 (12k/500) |
|---|---|---|---|
| Run 1 TPS | 3,976.44 | 3,713.25 | 3,744.75 |
| Run 2 TPS | 3,967.67 | 3,729.28 | 3,665.94 |
| Run 3 TPS | 4,020.64 | 3,782.45 | 3,758.20 |
| Median TPS | 3,976.44 | 3,729.28 | 3,744.75 |
| Latency avg (Run 2) | 8.065 ms | 8.581 ms | 8.729 ms |
| Variance (across runs) | ±0.7% | ±0.9% | ±1.2% |
| Δ TPS vs AWS | - | -6.2% | -5.8% |
The raw pgbench output below is included for transparency - the table summarises it, but if you want to verify the numbers yourself, here it is. The two lines that actually matter are tps = ... (transactions per second, the headline number) and latency average = ... (mean per-transaction latency). Everything else describes the run setup. Full run.txt for every run of every scenario, plus the per-interval aggregate logs, lives in the repo: github.com/serverscamp/pgbench-aws-vs-serverscamp.
pgbench summary, AWS Run 2
pgbench (18.3) transaction type: <builtin: TPC-B (sort of)> scaling factor: 100 query mode: prepared number of clients: 32 number of threads: 4 duration: 600 s number of transactions actually processed: 2380108 number of failed transactions: 0 (0.000%) latency average = 8.065 ms initial connection time = 143.238 ms tps = 3967.670593 (without initial connection time)
ServersCamp Test 2 Run 1
pgbench (18.3 (Ubuntu 18.3-1.pgdg24.04+1)) transaction type: <builtin: TPC-B (sort of)> scaling factor: 100 query mode: prepared number of clients: 32 number of threads: 4 duration: 600 s number of transactions actually processed: 2246325 number of failed transactions: 0 (0.000%) latency average = 8.545 ms initial connection time = 183.938 ms tps = 3744.748138 (without initial connection time)
bench1000 (~15 GB, partial cache)
Database ~15 GB on ~8 GB available cache. Some pages get read from disk - storage starts to matter.
What this measures in practice. This is the regime smaller production systems actually live in: DB a few times bigger than RAM, hot pages cached, cold pages on disk. Both CPU and storage influence the result, which makes this the noisiest scenario - run-to-run variance jumps to 5-8% as cache warmth and autovacuum timing shift between runs. Useful as a transition view between "all in cache" and "all on disk", but for buying decisions the bench10000 row below carries more weight.
| Metric | AWS m6i | Scamp T1 (5k/225) | Scamp T2 (12k/500) |
|---|---|---|---|
| Run 1 TPS | 2,577.80 | 2,060.84 | 2,683.57 |
| Run 2 TPS | 2,761.72 | 2,265.01 | 2,817.91 |
| Run 3 TPS | 2,883.51 | 2,447.14 | 2,884.13 |
| Median TPS | 2,761.72 | 2,265.01 | 2,817.91 |
| Latency avg (Run 2) | 11.587 ms | 14.128 ms | 11.356 ms |
| Variance (across runs) | ±5.6% | ±8.4% | ±3.6% |
| Settle between runs | 2-18 s | 40-47 s | 4-20 s |
| Δ TPS vs AWS | - | -18.0% | +2.0% |
Especially telling: Run 3 (with cache fully warm):
- AWS Run 3: 2,883.51 TPS
- ServersCamp T2 Run 3: 2,884.13 TPS
Difference: 0.02% - statistical noise. With a warm cache the two providers run identically.
Raw pgbench output for that warm-cache run, side by side. Compare the tps and latency average lines - the rest is metadata.
AWS Run 3
scaling factor: 1000 duration: 600 s number of transactions actually processed: 1729727 latency average = 11.098 ms tps = 2883.514321 (without initial connection time)
ServersCamp Test 2 Run 3
scaling factor: 1000 duration: 600 s number of transactions actually processed: 1730230 latency average = 11.095 ms tps = 2884.131092 (without initial connection time)
bench10000 (~150 GB, disk-bound)
Database ~150 GB on 8 GB RAM - most of it cold. Every transaction takes a page miss and goes to disk. The scenario closest to a real production workload.
What this measures in practice. This is what production looks like for any database past the small-side: tens or hundreds of GB on a host with 8-16 GB RAM, hot set bigger than cache, page miss on most transactions. Storage characteristics and managed-PG overhead dominate the result - the CPU advantage AWS had on bench100 gets neutralised because the workload spends most of its time waiting for disk anyway. If you're picking between providers, this row is the one to weight most heavily. It's also where the stability difference (CoV, latency tails) shows up clearly.
| Metric | AWS m6i | Scamp T1 (5k/225) | Scamp T2 (12k/500) |
|---|---|---|---|
| Run 1 TPS | 2,196.37 | 1,896.81 | 2,409.97 |
| Run 2 TPS | 2,275.58 | 1,956.72 | 2,481.38 |
| Run 3 TPS | 2,339.69 | 1,983.08 | 2,561.34 |
| Median TPS | 2,275.58 | 1,956.72 | 2,481.38 |
| Latency avg (Run 2) | 14.062 ms | 16.354 ms | 12.896 ms |
| Variance (across runs) | ±3.1% | ±2.2% | ±3.0% |
| Settle between runs | 19-20 s | 41-53 s | 19-23 s |
| Δ TPS vs AWS | - | -14.0% | +9.0% |
Raw pgbench output, mid-series run on each side. Look at tps: ServersCamp processed ~265,000 more transactions in the same 1200-second window, with lower average latency. Same workload, same client, same connection settings - the difference is on the database side.
AWS Run 3
scaling factor: 10000 duration: 1200 s number of transactions actually processed: 2807282 latency average = 13.677 ms tps = 2339.689575 (without initial connection time)
ServersCamp Test 2 Run 3
scaling factor: 10000 duration: 1200 s number of transactions actually processed: 3073180 latency average = 12.493 ms tps = 2561.339411 (without initial connection time)
Stability and latency tails: the real findingtop
Median TPS is only part of the picture. Reading the per-interval logs (TPS/latency every 10 s via --aggregate-interval=10) revealed a qualitative difference between the providers that the summary numbers hide.
1. Within-run stability (CoV TPS)
Coefficient of Variation = stdev(TPS_per_interval) / mean(TPS) × 100%. Smaller = more uniform performance during the run.
| Scenario | AWS m6i | Scamp T1 (5k/225) | Scamp T2 (12k/500) |
|---|---|---|---|
| bench100 run1/2/3 | 5.5 / 6.9 / 7.0% | 5.3 / 5.8 / 4.2% | 5.3 / 5.1 / 5.1% |
| bench1000 run1/2/3 | 16.3 / 11.9 / 13.5% | 15.4 / 15.9 / 14.8% | 4.4 / 5.8 / 4.3% |
| bench10000 run1/2/3 | 23.3 / 20.1 / 19.7% | 12.3 / 11.2 / 11.1% | 3.8 / 4.4 / 4.4% |
2. TPS dips - minimum over a 10-second window
| Scenario | Mean TPS | AWS min | Scamp T2 min |
|---|---|---|---|
| bench1000 run1 | ~2576 | 1505 (-42%) | 2444 (-5%) |
| bench10000 run1 | ~2300 | 828 (-63%) | 2190 (-5%) |
| bench10000 run2 | ~2300 | 718 (-68%) | 2129 (-14%) |
| bench10000 run3 | ~2300 | 1006 (-57%) | 2188 (-15%) |
Across three bench10000 runs, AWS dropped to 700-1000 TPS in individual 10-second windows (nearly -70% from the mean). Without direct EBS/RDS telemetry we can't pin the mechanism - candidates: checkpoint stalls flushing dirty pages, multi-tenant noisy neighbours on EBS, periodic internal RDS-agent activity. ServersCamp T2 didn't show comparable dips in the same runs - TPS stayed in a narrow band.
Note on gp3: unlike gp2, gp3 has no burst credits - it's a steady provisioned baseline by spec. So calling these dips "gp3 burst exhaustion" would be technically wrong. Whatever's causing them is on the AWS side, but without EBS CloudWatch metrics we won't claim more than that.
3. Latency tails: p95 of average latency over 10-second windows
Important note on the metric. This is not p95 latency over individual transactions. This is p95 of "average latency in a 10-second window" across 60-120 windows per run. With --aggregate-interval=10, the per-transaction distribution wasn't recorded. For real per-transaction p99/p999 you'd need a separate run with --log and no aggregation.
| Scenario | AWS p95 latency | Scamp T2 p95 latency | AWS max single-tx | Scamp T2 max single-tx |
|---|---|---|---|---|
| bench1000 run3 | 14.50 ms | 11.86 ms | 293 ms | 225 ms |
| bench10000 run1 | 26.12 ms | 14.08 ms | 518 ms | 243 ms |
| bench10000 run2 | 20.48 ms | 14.13 ms | 522 ms | 201 ms |
| bench10000 run3 | 21.43 ms | 13.53 ms | 508 ms | 267 ms |
4. bench10000 summary (real-world, disk-bound)
| AWS m6i | Scamp T2 | |
|---|---|---|
| Avg latency | 14.4 - 15.6 ms | 12.5 - 13.3 ms |
| p95 latency | 20.5 - 26.1 ms | 13.5 - 14.1 ms |
| Max single-tx | 508 - 522 ms | 200 - 267 ms |
| TPS dips (min vs mean) | down to -68% | down to -15% |
| CoV TPS | 20-23% | 3.8-4.4% |
ServersCamp T2 isn't just 9% faster on the median - it's qualitatively more stable on disk-bound work. Vendors don't usually publish numbers like these because they spoil the sleekness of marketing-grade benchmarks.
Sidenote: top and Performance Insightstop
You can skip this section - it's loose observations from the server side without strong interpretation. Including it because the numbers are interesting; if anyone has a sharper read on the mechanism, we'd love to hear it.
Top waits on AWS RDS during bench1000/bench10000 (Performance Insights)
LWLock:WALWrite 5.82 ████████████████ IO:DataFileRead 3.61 ██████████ IO:WalSync 0.70 ██ Client:ClientRead 0.57 █ CPU 0.54 █ Lock:transactionid 0.33 █ Timeout:VacuumDelay 0.04 IPC:ProcarrayGroupUpdate 0.03 IO:DataFileWrite 0.03 Lock:tuple 0.02
OS CPU breakdown under the same workload (bench10000)
AWS RDS
%Cpu(s): 7.2 us, 22.8 sy, 48.6 ni, 9.2 id, 10.6 wa, 0.0 hi, 1.6 si, 0.0 st
ServersCamp
%Cpu(s): 51.8 us, 32.3 sy, 0.0 ni, 0.7 id, 2.5 wa, 0.0 hi, 12.7 si, 0.0 st
What jumped out:
- On AWS,
userCPU ~7%,nice~49%. On ServersCamp,user~52%,nice0%. iowait: AWS ~10.6%, ServersCamp ~2.5%.
Adding user+nice, total CPU usage looks comparable on both sides (~52-55%). But how the kernel scheduler accounts for that work in each case - we won't try to interpret. If anyone with deeper knowledge of either provider's managed internals can explain, we'd take the comment.
Summary tabletop
TPS gap: ServersCamp Test 2 vs AWS m6i across scenarios
| Scenario | AWS m6i TPS | Scamp T2 TPS | Δ | Lead |
|---|---|---|---|---|
| bench100 (CPU-bound, in-cache) | 3,976.44 | 3,744.75 | -5.8% | AWS |
| bench1000 (mixed) | 2,761.72 | 2,817.91 | +2.0% | ServersCamp T2 |
| bench10000 (disk-bound) | 2,275.58 | 2,481.38 | +9.0% | ServersCamp T2 |
Storage QoS contribution (T1 → T2)
| Scenario | T1 (5k/225) | T2 (12k/500) | Lift from storage upgrade |
|---|---|---|---|
| bench100 | 3,729.28 | 3,744.75 | +0.4% |
| bench1000 | 2,265.01 | 2,817.91 | +24.4% |
| bench10000 | 1,956.72 | 2,481.38 | +26.8% |
On bench100 the storage upgrade does nothing (DB in cache). On bench1000/10000 the lift is 24-27% - direct effect of higher IOPS/throughput.
Cost at this configurationtop
Same hardware spec on both sides (2 vCPU / 8 GB / 500 GiB), same EU region, on-demand pricing, no commitments. Excludes backup storage, data transfer, and support tier on both sides.
AWS prices are billed in USD; converted to EUR using the ECB reference rate of 1 USD = €0.92 as of 2026-05-05 for like-for-like comparison. Original USD figures shown in parentheses.
AWS RDS for PostgreSQL db.m6i.large
| Line item | Monthly |
|---|---|
DB instance (db.m6i.large, 2 vCPU / 8 GiB) | €142.38 ($154.76) |
| Storage (500 GiB gp3, 12k IOPS / 500 MB/s baseline included) | €63.02 ($68.50) |
| Total | €205.40 ($223.26) |
ServersCamp managed PostgreSQL db-m-8g
| Line item | Monthly |
|---|---|
DB instance (db-m-8g, 2 vCPU / 8 GB) | €36.36 |
| Storage (500 GB at €0.15/GB) | €75.00 |
| Total | €111.36 |
AWS at €205.40 is roughly 1.85× more expensive than ServersCamp at €111.36 for the same configuration on-demand.
Price per TPS (disk-bound, the production-realistic scenario)
Dividing monthly cost by median TPS on bench10000 - the scenario closest to a real production workload:
| TPS | Monthly cost | Cost per TPS / month | |
|---|---|---|---|
| AWS RDS m6i | 2,276 | €205.40 | ~€0.090 |
| ServersCamp T2 (12k IOPS) | 2,481 | €111.36 | ~€0.045 |
ServersCamp delivers ~2× more transactions per euro at the most production-relevant scenario. On the upcoming price update we mention in the roadmap, that gap widens further.
The honest caveat: AWS Savings Plans / Reserved Instances
AWS pricing can come down if you commit money up front. Approximate Reserved Instance pricing for db.m6i.large in EU regions, converted at the same ECB rate:
- 1-year, No Upfront: roughly €89/mo instance (~37% off, ~$97)
- 1-year, All Upfront: roughly €76/mo instance (~46% off, ~$83)
- 3-year, All Upfront: roughly €57/mo instance (~60% off, ~$62)
Add the same €63/mo of storage and the cheapest plan (3-year all-upfront) gets AWS RDS down to ~€120/mo total. That's still ~8% more than ServersCamp's on-demand price (€111.36), and you've committed 36 months of spend up front.
Why we benchmark on-demand
The whole point of cloud is "scale up when you grow, scale down when you don't." Reserved Instances and Savings Plans break that promise - in exchange for a discount, you commit to a fixed amount of compute for 12 or 36 months.
We approached this benchmark from the pay-as-you-grow perspective because that's the model most cloud customers actually live in - startups, small SaaS teams, indie devs, and frankly the majority of teams whose next quarter's load isn't already locked in. For that reality, fixed-commitment pricing is the wrong shape:
- You don't know if next month brings 200 users or 2,000. A 1-year RI assumes you do.
- If traffic doubles tomorrow, on-demand absorbs it. A reserved instance sized for last year's load doesn't.
- If traffic dies, on-demand stops billing the moment you stop the instance. An all-upfront RI is already paid.
- "Save 60% with 3 years upfront" is a discount that applies to teams who already know exactly how much they'll need for 36 months. Most don't.
So the comparison that's relevant to our reader is on-demand vs on-demand. That's the €111.36 vs €205.40 above. RIs are an option AWS gives you, not a price you'd pay if you came in cold today.
Conclusionstop
What we proved
- Managed PG as a product is equivalent across both providers. At identical vCPU/RAM/storage QoS, both deliver close or identical TPS on the same workload. Run 3 of bench1000 (TPS 2883.51 AWS vs 2884.13 ServersCamp T2) - 0.02% difference, perfect parity.
- On CPU-bound workload, AWS m6i wins thanks to the newer CPU. Ice Lake 8375C edges Skylake 6132 by ~6% TPS on bench100 - within the theoretical IPC + clock advantage.
- On disk-bound workload, ServersCamp T2 wins thanks to less managed-PG overhead. On bench10000, at storage parity ServersCamp leads by 9% TPS and cuts p95 latency roughly in half. Likely contributors: nice-priority cost on RDS, Nitro+EBS overhead, WAL contention.
- The stability trend on disk-bound favours ServersCamp T2. Across three runs, CoV TPS ~4% vs ~23% for AWS, and we didn't see the kind of large TPS dips on T2 that AWS produced (min -15% from mean vs -68% on AWS). It's a directional signal in our sample - for a categorical claim, 10+ runs at different times of day are needed.
- Storage QoS is the dominant lever for disk-bound work. When we ran a deliberately constrained probe (Test 1, 5k IOPS / 225 MB/s, well below the current 10k/500 default), TPS dropped 14-18% on bench1000/bench10000. Same managed-PG, same CPU, same RAM - just half the IOPS and half the throughput. Doubling them back inverts the result. This is the underlying reason AWS's "free 12k/500 baseline at 400+ GiB" is such a load-bearing detail of the comparison.
- On the price-performance dimension, the gap is wider than the raw TPS gap. On-demand, same hardware: €111.36 (ServersCamp) vs €205.40 (AWS). Cost per TPS at disk-bound: ~€0.045 vs ~€0.090. ServersCamp delivers ~2× more transactions per euro, no commitment needed to access that price.
The pattern
The more disk-bound the workload, the more AWS's CPU advantage gets neutralised, and the more its managed-PG overhead shows up. Linear relationship:
| Disk weight in workload | Δ ServersCamp T2 vs AWS |
|---|---|
| 0% (in-cache, bench100) | -5.8% (AWS ahead) |
| ~50% (mixed, bench1000) | +2.0% (ServersCamp ahead) |
| ~95% (disk-bound, bench10000) | +9.0% (ServersCamp ahead) |
Practical takeaway
If your DB fits in RAM - AWS m6i is ~6% faster on CPU. You pay roughly 2× the price for that ~6%. Most teams won't make that trade.
If your DB is bigger than RAM (typical production) - ServersCamp at storage parity delivers +9% TPS, half the p95 latency, ~5× lower TPS variance, and ~half the bill. That's not a marginal advantage on any single axis - it's a stack of them.
Storage QoS matters - more than CPU or memory for disk-bound work. ServersCamp's current default tier (10,000 IOPS / 500 MB/s) sits ~17% below AWS gp3's free 12k/500 baseline on IOPS, identical on throughput. In practice that lands much closer to Test 2's behaviour than Test 1's. For very large databases or read-heavy production you may want a higher tier - but the math still ends up cheaper than RDS at parity.
A note on AWS sizing - this matters. Our 150 GB database sits on a 500 GiB RDS volume on purpose. AWS gp3 starts at only 3,000 IOPS / 125 MB/s baseline; the 12,000 IOPS / 500 MB/s tier we benchmarked against kicks in for free only at 400+ GiB volumes. We sized up so the comparison would land against AWS at its free best. If you provision a smaller disk (say 200 GiB) and don't pay extra for provisioned IOPS on top, your disk-bound TPS on RDS will be substantially worse than what we measured here - and the gap to ServersCamp gets bigger, not smaller.
Bottom line
Under real production load - database bigger than RAM, hot pages on disk - ServersCamp is faster, dramatically more stable on tails, and roughly half the price.
The full scorecard, on-demand pricing, no commitments either side:
| Dimension (vs AWS RDS m6i, same hardware) | ServersCamp T2 |
|---|---|
| TPS on disk-bound (production-realistic) | +9.0% |
| p95 latency | -48% (~half) |
| TPS variance (CoV) | ~5x lower (4% vs 23%) |
| Worst TPS dip in a run | -15% vs -68% |
| Monthly price on-demand | -46% (€111 vs €205) |
| Cost per TPS (disk-bound) | ~½ (€0.045 vs €0.090) |
| TPS on small in-memory DBs | -5.8% (newer AWS CPU) |
The one column AWS wins is the one most production workloads don't even hit - small DB, fits entirely in RAM, CPU-bound. Everywhere else, ServersCamp is faster, cheaper, more predictable on tails, and you can leave next month with no penalty.
If the goal is performance per euro without locking in spend - this isn't a hard call.
On the roadmaptop
- The same pgbench across the other major managed Postgres providers - DigitalOcean, Render, Supabase, Neon, Aiven, Crunchy Bridge. One workload, identical settings, the same disclaimer template. The point isn't to crown a winner, it's to give you a like-for-like grid you can read across.
- Price update on our managed PostgreSQL. We've already done the deeper price-per-TPS and price-per-p95 numbers on the back of this benchmark. The honest takeaway: our PostgreSQL pricing has room to come down. Expect an adjustment before the next benchmark drops - if we ask people to compare on numbers, we don't get to hide behind soft pricing.
What's next
- Try ServersCamp - spin up a managed Postgres on the free tier and run pgbench yourself
- Migrating from AWS RDS? - see the migration page; we'll handle the move for you
- Next benchmark: same workload against DigitalOcean, Render, Supabase, Neon, Aiven, and the other major managed Postgres providers