A Postgres Benchmark Found jsonb Read One Key 2.7 to 10.6 Times Faster, and Larger on Disk in 6 of 11 Cases
Software / analysis
A Postgres Benchmark Found jsonb Read One Key 2.7 to 10.6 Times Faster, and Larger on Disk in 6 of 11 Cases
Chris van Eijk of Now-Next loaded 1 million small documents and 5,000 large ones into PostgreSQL 17.11 and 18.6. The speed result is consistent. The storage result depends on whitespace and the 2 kB compression threshold.
Reading one key from a jsonb column took 204 ms across 1,000,000 small documents in a benchmark published on 9 November. The same read against a json column took 1,379 ms. Chris van Eijk of Now-Next ran the test on PostgreSQL 17.11 and 18.6, and the piece's own conclusion is that the speed gap held in every case he timed, while the size difference did not.
That split is the useful part. The PostgreSQL documentation tells readers to prefer jsonb for most applications, and the benchmark supports that on speed. It also shows the folk belief that jsonb is always heavier on disk is wrong in four of eleven shapes, and right in six.
How the benchmark was run
Van Eijk used throwaway containers from the official PostgreSQL images on a host with an 8-core Ryzen 7 3700X and 62 GiB of memory. He set shared_buffers to 2 GB, switched off autovacuum, JIT and parallel query, and left data checksums on. The documents came from a fixed seed in six shapes: small, small with long keys, medium, large, flat (780 random strings) and an array of 3,500 integers. Each was written compact and pretty-printed.
Row counts were 1,000,000 for small documents, 100,000 for medium and 5,000 for large, flat and array. Query timings are the median of five runs after one warm-up. Load, index and update timings are the median of three. Sizes come from pg_column_size and from total table size after VACUUM. Every figure in this piece is his; nothing here was re-run.
Reading one key: 2.7 to 10.6 times faster
The read was sum(length(doc->>'first')) over the whole table with no index. The article's summary gives the range as 2.7 to 10.6 times faster for jsonb. The rows printed in the article's headline table span a narrower band, from 2.7 times (medium compact, 850 ms to 310 ms) to 7.2 times (small pretty-printed, 1,498 ms to 207 ms). On PostgreSQL 18.6 the times were within 6% of the 17.11 numbers, except that the small jsonb tables read 6 to 15% faster.
The reason is in the documentation: json stores an exact copy of the input text, "which processing functions must reparse on each execution", while jsonb is stored in a decomposed binary format "that makes it slightly slower to input due to added conversion overhead, but significantly faster to process, since no reparsing is needed." The benchmark measures the read side of that sentence. Van Eijk's article lists load, update and index timings in tables this piece does not reproduce.
- small compact, json (1,000,000 rows)1379 ms
- small compact, jsonb204 ms
- large compact, json (5,000 rows)541 ms
- large compact, jsonb146 ms
Source: Now-Next, PostgreSQL json vs jsonb measured, 9 November 2026
Table size: no consistent winner at 17.11
With the default pglz compression, the size of the whole table including TOAST, after VACUUM, moved in both directions:
| Documents | Average input | jsonb vs json, pglz |
|---|---|---|
| small, compact | 203.4 bytes | +17.8% |
| small, pretty-printed | 248.4 bytes | -3.6% |
| medium, compact | 1,517.9 bytes | -16.6% |
| medium, pretty-printed | 2,381.9 bytes | +16.6% |
| large, compact | 20,532.0 bytes | +33.0% |
| flat, compact | 21,129.0 bytes | -22.6% |
| array of 3,500 integers, compact | 20,692.3 bytes | +3.2% |
The table is a selection of seven of the eleven rows. The four left out are small with long keys (+6.3%), large pretty-printed (the same), flat pretty-printed (-5.5%) and array pretty-printed (+1.9%).
Switching to lz4 changed some rows a lot. The compact array of integers went from +3.2% to +40.9%, and the compact large documents from +33.0% to +22.2%. The medium and small rows did not move.
Why medium documents flip: the 2 kB threshold
PostgreSQL compresses a value only once it passes roughly 2 kB. Van Eijk's explanation of the medium rows follows from that. A compact json medium document is 1,521.9 bytes, so it stays uncompressed. The jsonb version is 2,071.9 bytes, crosses the threshold, gets compressed and ends up 16.6% smaller on disk. Pretty-print the same data and json is 2,385.9 bytes, so it is compressed too, and jsonb loses by 16.6%.
The PostgreSQL documentation page does not mention a 2 kB threshold or discuss compression for either type, so that claim rests on the Now-Next measurements alone.
The worst case before compression is the integer array. jsonb stored 3,500 integers in 54,701.6 bytes against 20,696.3 for compact json, a 164.3% increase, because each number is held as a numeric value rather than as one to four characters of text. The 17.11 table above shows compression pulls most of that back with pglz and little of it back with lz4.
What jsonb gives up and adds
The documentation lists the trade. jsonb does not preserve white space, does not preserve the order of object keys, and keeps only the last value when a key is duplicated. It also rewrites numbers through the numeric type, so 1.230e-5 comes back as 0.00001230. Anything that signs, hashes or diffs the raw text of a payload will see a different string.
In return, jsonb supports GIN indexes with two operator classes, jsonb_ops and jsonb_path_ops, and containment queries with @>. The documentation says of json: "There is no parallel set of facilities for the json type." jsonb_path_ops is usually smaller, but it does not support the key-exists operators ?, ?| and ?&.
What the benchmark does not settle
It is one author, one host and generated documents. The test did not use real production payloads, and the article's own advice is to measure your data above about 2 kB. The documentation also warns that any update locks the whole row, which matters more for large documents than a read benchmark can show.
The practical reading: if you store raw payloads you must reproduce byte for byte, json still has a job. If you query inside documents, the 2.7 to 10.6 times read gap makes jsonb the default, and the size cost is a measurement to take on your own tables, not a rule. PostgreSQL 18.6 results matched 17.11, so a major-version upgrade is not what changes the answer.
On the same desk, we covered another case where release timing, not raw speed, decided the choice: .NET 11 shipped on the day .NET 8 and 9 lost support, and the Git project's draft vote on AI-looking patches shows how maintainers settle a default by process.
Sources
More in Software
- 01Rivet, a YC W23 Company, Runs Agent Actors on S3 and Claims 72 KB EachThe Apache 2.0 orchestrator says an actor cold-starts in 4.99 ms at p99. Every figure is Rivet's own, and the Launch HN thread questioned both the premise and the AWS bill.
- 02GitHub Actions Lockfiles: What gh actions-lock Pins and What It Leaves LooseWorkflow lockfiles went generally available on October 28. The extension's own README lists four gaps, including Docker images and reusable workflows in other repositories.
- 03C++26 Reportedly Fails Its ISO Ballot, With Contracts the Sticking PointSecondary reports put the October 23 vote at 12 yes of 20 and nine no votes of 22 bodies. The numbers do not reconcile, and no ISO or WG21 record has been published to settle them.
- 04Meta's Astryx Design System Gained 2,842 GitHub Stars in a Day; Its Adoption Number Is Meta's Own 13,000 Internal AppsThe MIT-licensed React 19 and StyleX library is still in beta, lists no tagged releases on its repo page, and has 300 open issues. What the README claims, and what InfoQ found when it went looking for outside usage.