Skip to content

Repository files navigation

Postgres benchmark

brianc/node-postgres VS porsager/postgres

A benchmark focusing on the client-side overhead/performance of Postgres client libraries for Node.js, brianc/node-postgres VS porsager/postgres

Dependencies:

The benchmark measures:

  • PostgreSQL client overhead
  • protocol parsing
  • type conversion
  • result consumption

Fair benchmark

  • All libraries execute queries using prepared statements (see Prepared statement)
  • All libraries run the exact same query text with the LIMIT as a SQL literal (no bound parameters) and consume the results through the same code path
  • The garbage collector is exposed and triggered before both the warmup and the measured run of each task, so every measurement starts from a clean heap and a GC pause during warmup cannot leak into the measured run (see tinybench)
  • Each query size is measured under all 6 execution orders (every permutation of the 3 clients) and the raw samples are pooled per client, so the execution order is fully removed as a confounder - no library benefits from systematically running first (cold cache/JIT) or last (warmed shared state). The per-run time budget is divided across the permutations, keeping the total sample count and wall-clock close to a single run
  • The winner is ranked by median latency (p50) and is only crowned when its confidence interval of the mean does not overlap any rival's; otherwise the run is reported as having no clear winner
  • Queries are warmed up before measurements
  • PostgreSQL is accessed through a Unix domain socket to reduce TCP overhead
  • postgres (porsager/postgres) supports PostgreSQL pipeline mode and uses it internally for query execution, pg (brianc/node-postgres) does not currently support PostgreSQL pipeline mode. This benchmark executes one query at a time (no concurrent query batching), so pipeline mode benefits are expected to be limited.

The database contains a pre-populated table with 500 rows. Benchmark queries only read existing rows using LIMIT 1, LIMIT 100 and LIMIT 500, eg.:

SELECT * FROM benchmark_rows ORDER BY id LIMIT 1

The data preparation of benchmark_rows:

CREATE TABLE IF NOT EXISTS benchmark_rows (
  id int PRIMARY KEY,
  int_value int,
  string_value text,
  null_value text,
  bool_value boolean
);

TRUNCATE benchmark_rows;

INSERT INTO benchmark_rows (id, int_value, string_value, null_value, bool_value)
  SELECT
    i,
    1337,
    'wat',
    NULL,
    false
  FROM generate_series(1, 500) i;

Run benchmark

On Docker:

docker-compose build
docker-compose up

On Ubuntu/Debian:

apt-get install libpq-dev g++ python3 make
npm install
npm run bench

Output

Running benchmarks...
GC is exposed
Pool size: 10                                                                                                                                               
Dependencies versions:                                                                                                                                      
{                                                                                                                                                           
  "tinybench": "6.0.2",                                                                                                                                     
  "pg": "8.22.0",                                                                                                                                           
  "pg-native": "3.8.0",                                                                                                                                     
  "postgres": "3.4.9"
}                                                                                                                                                           
Database connectivity verified through: socket at /var/run/postgresql
nodejs v24.18.0, CPU: Intel(R) Core(TM) i7-1065G7 CPU @ 1.30GHz Cores: 8, RAM: 7.57 GB


query_1 (pooled over 6 execution orders)
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ (index) β”‚ Task name                          β”‚ Latency avg (ns) β”‚ Latency med (ns) β”‚ Throughput avg (ops/s) β”‚ Samples β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ 0       β”‚ 'pg-native (brianc/node-postgres)' β”‚ '245014 Β± 0.67%' β”‚ '206295'         β”‚ 4081                   β”‚ 30000   β”‚
β”‚ 1       β”‚ 'pg (brianc/node-postgres)'        β”‚ '329153 Β± 1.24%' β”‚ '233539'         β”‚ 3038                   β”‚ 30000   β”‚
β”‚ 2       β”‚ 'postgres (porsager/postgres)'     β”‚ '272314 Β± 0.76%' β”‚ '225388'         β”‚ 3672                   β”‚ 30000   β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
πŸ† Winner: pg-native (brianc/node-postgres) (206295 ns median)


query_100 (pooled over 6 execution orders)
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ (index) β”‚ Task name                          β”‚ Latency avg (ns) β”‚ Latency med (ns) β”‚ Throughput avg (ops/s) β”‚ Samples β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ 0       β”‚ 'pg-native (brianc/node-postgres)' β”‚ '379889 Β± 0.51%' β”‚ '343703'         β”‚ 2632                   β”‚ 30000   β”‚
β”‚ 1       β”‚ 'pg (brianc/node-postgres)'        β”‚ '424718 Β± 0.59%' β”‚ '366348'         β”‚ 2355                   β”‚ 30000   β”‚
β”‚ 2       β”‚ 'postgres (porsager/postgres)'     β”‚ '415342 Β± 1.09%' β”‚ '357673'         β”‚ 2408                   β”‚ 30000   β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
πŸ† Winner: pg-native (brianc/node-postgres) (343703 ns median)


query_500 (pooled over 6 execution orders)
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ (index) β”‚ Task name                          β”‚ Latency avg (ns) β”‚ Latency med (ns) β”‚ Throughput avg (ops/s) β”‚ Samples β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ 0       β”‚ 'pg-native (brianc/node-postgres)' β”‚ '885226 Β± 0.35%' β”‚ '818698'         β”‚ 1130                   β”‚ 30000   β”‚
β”‚ 1       β”‚ 'pg (brianc/node-postgres)'        β”‚ '914526 Β± 0.60%' β”‚ '786653'         β”‚ 1093                   β”‚ 30000   β”‚
β”‚ 2       β”‚ 'postgres (porsager/postgres)'     β”‚ '888801 Β± 1.11%' β”‚ '767221'         β”‚ 1125                   β”‚ 30000   β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
🀝 No clear winner within margin of error - lowest median: postgres (porsager/postgres) (767221 ns); lowest mean: pg-native (brianc/node-postgres) (885226 ns)

About

A benchmark focusing on the performance of Postgres client libraries for Node.js, brianc/node-postgres VS porsager/postgres

Topics

Resources

Stars

Watchers

Forks

Used by

Contributors

Languages