PostgreSQL  

PostgreSQL 19 JSONB Query Performance: What Changed?

PostgreSQL has supported jsonb for many years, and it has become a practical choice for applications that need flexible document-style data without giving up relational database capabilities. It is common to see JSONB columns used for product attributes, application metadata, event payloads, configuration, audit information, and other semi-structured data.

With PostgreSQL 19, the interesting performance story is not a single change that suddenly makes every JSONB query faster. Instead, PostgreSQL 19 introduces several database-level improvements that can affect JSONB-heavy workloads indirectly, including changes around TOAST compression, GIN index maintenance, sorting, optimizer behavior, and JSON path functionality. PostgreSQL 19 is still in the beta stage as of August 2026, so benchmark results should be treated as version-testing results rather than production guarantees. The PostgreSQL project released Beta 2 on July 16, 2026 and specifically encourages testing application workloads against the beta while advising against production use of beta releases.

That makes PostgreSQL 19 a good candidate for a controlled JSONB benchmark.

The goal is not simply to compare two version numbers. The goal is to determine which JSONB workloads actually benefit, which remain unchanged, and whether a migration introduces different storage or query-planning behavior for your application.

Why JSONB Performance Is Different

A JSONB query can involve several layers of work.

For example:

SELECT id
FROM products
WHERE attributes @> '{"brand": "Contoso"}';

The database may need to parse the query, identify an appropriate index, search the index, inspect heap or table pages, decompress stored JSONB data when necessary, and return the matching rows.

A simplified execution path looks like this:

SQL query
   |
   v
Query planner
   |
   v
GIN / other index
   |
   v
Table or heap access
   |
   v
JSONB evaluation
   |
   v
Result

Performance therefore depends on much more than the JSONB operator itself.

The dataset, document size, index type, selectivity, compression, cache state, query shape, and PostgreSQL version can all influence the result.

What PostgreSQL 19 Changes for JSONB Workloads

The PostgreSQL 19 release notes do not describe the release as a wholesale rewrite of the JSONB storage or query engine. Instead, several general database improvements can influence JSONB-heavy applications.

One notable change is that PostgreSQL 19 changes the default TOAST compression method from pglz to lz4. TOAST is used to store large values, including large JSONB documents, outside the main table row when appropriate. This means applications with large JSONB values should specifically benchmark storage size, read performance, write performance, and compression behavior after migration.

PostgreSQL 19 also improves GIN index vacuuming using streaming reads. This is relevant because GIN is one of the primary indexing mechanisms used with JSONB. The release also includes broader performance improvements such as asynchronous I/O read-ahead scheduling, radix-sort improvements, and other internal performance work that can influence workloads containing substantial JSONB processing.

The important distinction is that these changes do not mean every JSONB query will automatically become faster. Their impact depends heavily on the workload.

JSONB Indexing Still Matters

PostgreSQL provides two primary GIN operator classes for JSONB: jsonb_ops and jsonb_path_ops.

The default jsonb_ops operator class supports a broader set of operators, while jsonb_path_ops supports fewer operations but can provide better performance for the operations it supports. PostgreSQL's documentation explicitly describes jsonb_path_ops as offering better performance for its supported operators.

For example:

CREATE INDEX ix_products_attributes
ON products
USING GIN (attributes);

This creates a default GIN index.

A path-oriented index can instead be created with:

CREATE INDEX ix_products_attributes_path
ON products
USING GIN (attributes jsonb_path_ops);

Do not assume the second index is always better.

The correct choice depends on the operators used by the application.

For example, a containment-heavy workload may behave differently from one that frequently uses key-existence operators such as ?.

Benchmark JSONB Containment Queries

Containment is one of the most common JSONB access patterns.

Consider:

SELECT id, name
FROM products
WHERE attributes @> '{"brand": "Contoso"}';

Benchmark this query against PostgreSQL 18 and PostgreSQL 19 using exactly the same dataset and index definitions.

The benchmark should capture:

  • Execution time.

  • Planning time.

  • Rows returned.

  • Shared buffer hits.

  • Shared buffer reads.

  • Index usage.

  • CPU utilization.

  • Storage size.

  • Query plan.

Use:

EXPLAIN (ANALYZE, BUFFERS)
SELECT id, name
FROM products
WHERE attributes @> '{"brand": "Contoso"}';

The important part is not simply the final execution time. BUFFERS helps determine whether a difference comes with different memory or disk access behavior.

Benchmark JSON Path Queries Separately

JSON path queries should be treated as a separate workload.

For example:

SELECT id
FROM products
WHERE attributes @?
      '$.specifications.weight ? (@ > 10)';

PostgreSQL 19 adds additional string methods to jsonpath, including functions such as lower(), upper(), replace(), split_part(), ltrim(), rtrim(), and btrim(). These methods are useful from a functionality perspective, but their performance should be measured for the actual expressions used by an application rather than assuming that a new function is faster simply because it is new.

This distinction is important when writing a migration benchmark.

A new capability is not automatically a performance optimization.

Large JSONB Documents Are Especially Important

JSONB performance can change significantly as document size increases.

A useful benchmark matrix might contain documents around:

Document SizeExample Workload
1 KBSmall metadata
10 KBProduct/application attributes
100 KBDetailed event payload
1 MBLarge configuration or document
5 MB+Large archival payload

The exact sizes should match your application.

Large JSONB values are especially interesting because TOAST behavior becomes more relevant. Since PostgreSQL 19 changes the default TOAST compression method to LZ4, applications with large JSONB values should compare both storage characteristics and query behavior rather than looking only at CPU time.

Benchmark GIN Index Maintenance

JSONB read performance is only half of the story.

A GIN index can accelerate searches, but maintaining the index has a cost when JSONB documents are inserted or updated.

For a write-heavy workload, benchmark:

INSERT INTO products (...)
VALUES (...);

and:

UPDATE products
SET attributes = jsonb_set(
    attributes,
    '{inventory}',
    '100'::jsonb
)
WHERE id = 100000;

Measure:

  • Insert latency.

  • Update latency.

  • Transaction throughput.

  • WAL generation.

  • Index size.

  • Table size.

  • Vacuum behavior.

  • CPU and I/O utilization.

PostgreSQL 19 includes a GIN vacuuming performance improvement using streaming reads, so write-heavy JSONB workloads are particularly worth testing rather than relying on read-only query benchmarks.

Compare Query Plans, Not Just Timings

Suppose PostgreSQL 18 reports:

Bitmap Index Scan
  -> Bitmap Heap Scan

while PostgreSQL 19 chooses:

Index Scan

That difference is more important than simply seeing a lower execution-time number.

Record the complete plans.

For each benchmark, compare:

Planning Time
Execution Time
Rows
Index chosen
Heap blocks
Shared hits
Shared reads
Sort operations
Parallel workers

This can explain why performance changed.

Use Production-Style Data Distribution

A JSONB benchmark should not contain identical documents.

Imagine a products table where every row contains:

{
  "brand": "Contoso",
  "category": "Laptop"
}

That does not represent a realistic production dataset.

Instead, create distributions such as:

Brand A       35%
Brand B       20%
Brand C       10%
Other brands  35%

Likewise, include documents with missing keys, nested objects, arrays, null values, and different document sizes.

Selectivity has a major effect on index behavior.

A query returning 5 rows from 10 million records is very different from one returning 4 million rows.

Test Cached and Uncached Workloads

Database cache state can significantly affect results.

Run both warm and cold-style scenarios where practical.

A warm benchmark might look like:

Database startup
      |
      v
Warm-up queries
      |
      v
Measured queries

A cold-oriented experiment should explicitly document how the cache state is controlled.

Do not compare a PostgreSQL 18 test performed after hours of activity with a PostgreSQL 19 test against a freshly restarted database.

The result will not be a fair version comparison.

Benchmark Through the .NET Application Too

If the application uses EF Core, SQL-level benchmarking should be complemented by application-level benchmarking.

For example:

var products = await db.Products
    .AsNoTracking()
    .Where(x => x.Attributes.Contains(searchJson))
    .ToListAsync(cancellationToken);

The exact translation depends on the EF Core version and provider configuration, so inspect the generated SQL rather than assuming the LINQ expression maps to the desired PostgreSQL operator.

A useful test architecture is:

.NET application
      |
      v
EF Core
      |
      v
Npgsql
      |
      v
PostgreSQL 18 / 19

This lets you distinguish database performance changes from application or driver effects.

Keep the .NET runtime, EF Core version, Npgsql version, query code, connection settings, and dataset constant when comparing PostgreSQL versions.

A Practical Benchmark Matrix

A serious PostgreSQL 18 versus 19 JSONB benchmark can use a matrix such as:

DimensionValues
PostgreSQL18 / 19
Document size1 KB / 10 KB / 100 KB / 1 MB
Query@> / ? / @? / @@
IndexNone / jsonb_ops / jsonb_path_ops
Dataset1M / 10M rows
SelectivityLow / Medium / High
WorkloadRead / Write / Mixed
CacheWarm / Controlled cold
Concurrency1 / 10 / 50 / 100
ClientSQL / .NET + EF Core

This matrix can quickly become large, so start with the queries that represent the application's actual production workload.

Avoid Benchmarking Only the Fastest Query

A common mistake is to select one query where PostgreSQL 19 performs well and present that as the JSONB performance story.

That is not a meaningful migration analysis.

Include at least:

  1. A highly selective indexed lookup.

  2. A moderately selective JSONB query.

  3. A low-selectivity query.

  4. A nested JSON path query.

  5. A large-document query.

  6. A write/update workload.

  7. A concurrent workload.

This gives a more balanced view.

What Results Should You Expect?

Do not start with a predetermined conclusion.

For example, your results might look like:

WorkloadPostgreSQL 18PostgreSQL 19Change
Small JSONB lookupMeasureMeasureCalculate
Large JSONB lookupMeasureMeasureCalculate
GIN containmentMeasureMeasureCalculate
JSONPath queryMeasureMeasureCalculate
JSONB updateMeasureMeasureCalculate
Concurrent readsMeasureMeasureCalculate
GIN maintenanceMeasureMeasureCalculate

The correct article conclusion should come from these measurements.

If PostgreSQL 19 improves one workload while another remains unchanged, report that honestly.

Performance engineering becomes much more useful when it describes where an optimization applies rather than claiming a universal improvement.

Common Mistakes

Assuming PostgreSQL 19 Makes Every JSONB Query Faster

The release contains several improvements that can affect JSONB workloads, but their impact is workload-dependent.

Comparing Different Indexes

If PostgreSQL 18 uses jsonb_ops and PostgreSQL 19 uses jsonb_path_ops, you are not performing a version-only comparison.

Ignoring Document Size

Small JSONB documents may not expose storage and TOAST behavior that becomes significant with larger values.

Measuring Only Reads

GIN maintenance and JSONB updates can be important in write-heavy systems.

Ignoring Query Plans

A timing difference without a plan comparison makes root-cause analysis difficult.

Using Synthetic Uniform Data

Uniform JSON documents often produce unrealistic selectivity and index behavior.

Testing a Beta as Production

PostgreSQL 19 Beta 2 is intended for testing and feature evaluation, not production deployment. The PostgreSQL project explicitly advises against running beta releases in production.

PostgreSQL 19 Migration Considerations

If you are evaluating PostgreSQL 19 for an existing .NET application, JSONB benchmarking should be part of a broader upgrade test.

Pay particular attention to applications that:

  • Store large JSONB documents.

  • Use GIN indexes heavily.

  • Perform frequent JSONB updates.

  • Depend on JSON path queries.

  • Run high-volume document searches.

  • Have storage-sensitive workloads.

  • Generate large amounts of WAL.

  • Depend heavily on query-plan stability.

Also remember that PostgreSQL 19 introduces other changes unrelated to JSONB that can affect application behavior. The release notes currently identify PostgreSQL 19 as a beta-era release, and some details can still change before general availability.

Frequently Asked Questions

Does PostgreSQL 19 introduce a completely new JSONB storage format?

No. The important PostgreSQL 19 changes affecting JSONB performance are broader database and storage improvements rather than a wholesale replacement of JSONB's fundamental storage model.

Is PostgreSQL 19 faster for all JSONB queries?

There is no basis for making that blanket claim. Performance depends on query shape, index choice, document size, selectivity, cache state, and workload characteristics.

Why is LZ4 relevant to JSONB?

Large JSONB values may be stored using TOAST. PostgreSQL 19 changes the default TOAST compression method to LZ4, so workloads containing large JSONB documents should benchmark both storage and access behavior.

Should I replace jsonb_ops with jsonb_path_ops?

Not automatically. jsonb_path_ops supports fewer operators but can perform better for the operations it supports. Choose based on the queries your application actually executes.

Does PostgreSQL 19 improve GIN indexes?

PostgreSQL 19 includes a performance improvement for GIN index vacuuming using streaming reads. That is particularly relevant to write-heavy JSONB workloads where GIN maintenance is significant.

Should PostgreSQL 19 Beta be used in production?

No. As of the current PostgreSQL 19 Beta 2 release, the project recommends testing typical workloads but advises against running beta releases in production.

Conclusion

PostgreSQL 19 does not turn JSONB into a universally faster data type overnight. The more interesting story is how several PostgreSQL improvements can influence different parts of a JSONB workload. The change to LZ4 as the default TOAST compression method is particularly relevant to large JSONB values, while GIN vacuuming improvements matter to systems that frequently maintain JSONB indexes. Other optimizer, I/O, sorting, and execution improvements can influence JSONB queries depending on their execution plans and workload characteristics.

For .NET teams evaluating PostgreSQL 19, the right approach is to benchmark the actual workload rather than relying on generalized version-to-version claims. Use the same dataset, indexes, application code, EF Core and Npgsql versions, and connection configuration across both PostgreSQL versions. Measure query latency, execution plans, buffer activity, storage, write performance, and concurrency. Most importantly, test the JSONB queries that matter to your application. That will tell you whether PostgreSQL 19 provides a meaningful performance improvement for your system and whether the upgrade is worth pursuing when the release reaches general availability.