Contents
layout: doc lang: en translation_key: benchmarks-node title: Node.js benchmarks description: “Local node-postgres results on Apple M3 Max: batch reads, concurrent updates, PostgreSQL CPU and memory.” section: Benchmarks permalink: /docs/benchmarks-node.html
last_modified_at: “2026-09-16”
Node.js benchmarks {#nodejs-benchmarks}
Overview · Node.js · Go and RESP
Node.js 24.18.0 with node-postgres 8.16.3. Machine and measurement method.
Measured on 14 September 2026 with extension build 67e5754, a 384 MiB cache
budget and clients on macOS through Docker’s published SQL port.
The SQL mget lane and all results below are historical 2.x measurements;
SQL mget was removed in 3.0.0 and RESP MGET is the supported cached-read
interface.
At 64 keys per request, median requests/s from three 10-second samples:
| Connections | Prepared SQL | 2.x SQL mget (removed in 3.0.0) |
|---|---|---|
| 4 | 7,023 | 7,528 |
| 64 | 15,577 | 16,616 |
| 256 | 15,459 | 16,574 |
At 64 connections, mget used 170 µs of server CPU per request, versus
286 µs for SQL. Server CPU averaged 2.80 versus 4.43 cores; sampled peak
memory was 215.0 versus 205.5 MiB. Client CPU was 0.84 versus 0.87 cores.
For one key at 64 connections, SQL was faster: 55,409 versus 53,646 requests/s. The batch result does not apply to single-key reads.
Raw measurements include latency percentiles, resource samples and source revisions.
Reads mixed with writes {#reads-mixed-with-writes}
The Node.js application runner uses 64 connections, 50,000 requests per sample and three repetitions. Reads cycle through 128 hot rows; 5% of operations in the mixed workload update rows.
| Workload | Keys/request | Prepared SQL requests/s | 2.x SQL mget JSON requests/s (removed in 3.0.0) |
|---|---|---|---|
| Warm reads | 1 | 56,104 (52,246–56,364) | 52,842 (52,494–53,399) |
| Warm reads | 16 | 37,391 (36,996–37,938) | 38,425 (38,340–38,617) |
| Warm reads | 64 | 15,178 (15,109–15,488) | 16,294 (16,131–16,368) |
| 5% updates | 1 | 55,768 (54,273–56,527) | 52,621 (51,432–53,216) |
| 5% updates | 16 | 38,669 (38,429–38,718) | 37,604 (37,481–37,916) |
| 5% updates | 64 | 16,049 (16,008–16,063) | 16,814 (16,771–17,229) |
Values are median requests/s with minimum–maximum in parentheses. Mixed
samples count reads and writes together. The JSON’s application_run includes
cold-fill and write-overhead cases. Cold fill at batch 64 has only 64 latency
observations, too few for a useful p99 estimate.
Historical SQL benchmark setup {#query-setup}
The historical SQL mget query is retained in the linked raw measurements for
reproducibility. It is not available in 3.0.0. The current benchmark runner
compares prepared SQL with RESP MGET; see the
Node.js example.
Reproduce {#reproduce}
Run the Node.js benchmarks
From the repository root, with Docker and Node.js 20+:
./examples/benchmark.sh node > node.json
Current defaults: 4/64/256 connections, 1/16/64 keys, three five-second samples
per case. Node.js runs prepared SQL and RESP MGET inside the Docker VM. The
script creates a disposable server and
separate client container, records resources, then removes both.
Optional overrides: CONNECTIONS, BATCHES, REPEATS, DURATION_SECONDS.
Use all to include Go in the same matrix.
For the recorded host-based setup, use the revisions in the measurements JSON.
For reads mixed with writes:
BATCHES=1,16,64 ./examples/benchmark.sh node-workload > benchmark.json
python3 scripts/benchmark_report.py benchmark.json
Defaults: 64 connections, 50,000 requests per sample and three repetitions.
The runner resets its demo tables between samples. CLIENTS, REQUESTS,
BATCHES and REPEATS are optional overrides.