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
- All libraries execute queries using prepared statements (see Prepared statement)
- All libraries run the exact same query text with the
LIMITas 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 1The 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;On Docker:
docker-compose build
docker-compose upOn Ubuntu/Debian:
apt-get install libpq-dev g++ python3 make
npm install
npm run benchRunning 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)