Benchmark methodology

Exact methodology behind the PostgreSQL-on-EC2 numbers on this site: versions, configuration deltas, instance and storage specs, dataset sizes, the measurement procedure, and the limitations that bound it.

The benchmarking tool that provisions the hosts, runs the workload, and produces the data on this site is open source: github.com/anivaniuk/sanebench.

Benchmark runs: (us-east-1). 414 measured repetitions across 23 instance types.

What we measure

Self-managed PostgreSQL 17.8 on EC2 (not RDS), one workload, one access pattern: 90% primary-key reads, 10% single-row inserts – the shape behind a typical OLTP service. Every figure is a real measurement against a real instance.

Environment

PostgreSQL17.8, from the Amazon Linux 2023 postgresql17 package. The bootstrap aborts if the running server is not major 17, so no run can silently record the wrong version.
OS / AMIAmazon Linux 2023, official AWS AMIs (x86-64 and arm64).
Region / AZus-east-1, single AZ us-east-1a (DB and client co-located).
FilesystemXFS on a dedicated EBS volume mounted at /pgdata; PostgreSQL is the only writer.
Connection pathClient → pgbouncer 1.24.1 (transaction pooling) → PostgreSQL on localhost:5432. Pool size is vCPUs × 4.
Client / driverA separate c7g.large (Graviton) load generator in the same AZ, identical for every run, using the Go pgx driver (extended protocol, prepared statements). Keeping the client off the DB host and constant across runs keeps comparisons fair.

Workload

Patternmixed_90_10 – 90% random primary-key SELECTs, 10% single-row INSERTs.
SchemaOne logged, primary-keyed table (sanebench_kv), 512 bytes per row. Inserts append past the preloaded max via a dedicated sequence, hitting the same B-tree the reads contend on.
Concurrency32 concurrent connections.
Dataset sizes1 GB (1,953,125 rows), 10 GB (19,531,250), 50 GB (97,656,250) – run on each instance to show behaviour as the working set crosses the memory boundary. The table is fully preloaded before any measurement.

Storage

PGDATA lives on a 200 GB gp3 EBS volume. Each instance is benchmarked against two provisioned profiles so you can separate compute limits from I/O limits:

ProfileSizeIOPSThroughput
Baseline200 GB3,000125 MB/s
High200 GB16,0001,000 MB/s

PostgreSQL configuration

Identical policy on every host. Everything not listed below is left at the PostgreSQL default; the worker dumps the full effective pg_settings into each run’s output, so any value can be verified after the fact.

SettingValueNote
shared_buffers25% of RAMSized per instance from this host’s memory.
effective_cache_size70% of RAMSized per instance.
huge_pagestry (effectively off)Left at the PostgreSQL default. No huge pages are reserved at the OS level (vm.nr_hugepages is 0), so try silently falls back and shared_buffers runs on standard 4 KB pages.
max_connections100Pooled access stays well under this.
wal_compressionon (pglz)Default is off.
checkpoint_timeout15minDefault is 5min.
table autovacuumdisabledSet on sanebench_kv after load – see limitations.

Procedure

For each instance type × disk profile, an automated harness (over AWS SSM, no SSH) provisions a fresh, isolated pair of hosts and runs:

Each repetition is stored as its own record (with full metadata and a methodology fingerprint), so every published number traces back to an individual measurement.

What the numbers mean

Each figure is the mean of the 3 repetitions; min, max, and standard deviation are kept in the dataset. Throughput is requests per second. Latency is reported as average plus the 95th and 99th percentiles, because tail latency is what users feel. Cost-efficiency divides throughput by the us-east-1 on-demand price of the instance plus its EBS volume.

Limitations

Found a problem, or want a workload or instance type covered? The blog is the place to reach me.