deniz.in

Markets

Weather

Loading weather

· via dev.to (home feed)

PostgreSQL 19 disables JIT compilation by default; analytical workloads must re-enable it

PostgreSQL 19 ships with JIT compilation off by default after its cost-based triggering was judged unreliable. Operators can find affected queries today using EXPLAIN and pg_stat_statements.

PostgreSQL 19 disables JIT compilation by default; analytical workloads must re-enable it

PostgreSQL 19 will arrive with JIT compilation switched off by default, reversing a setting that was on in PostgreSQL 18. The change is already in the release, which is at RC 1 with general availability scheduled for 19 November 2026, and it means sites running heavy analytical queries will have to turn JIT back on themselves. A detailed walkthrough of the change and how to prepare for it was published on dev.to by Gabriel Anhaia.

What changed

The flip comes from a commit authored by Jelte Fennema-Nio and pushed by Tomas Vondra on 2 April 2026. According to the commit message quoted in the dev.to post, the core problem is that the planner's cost estimate alone decides whether a query gets compiled, so a trivial event such as a few extra rows landing in a table can push an estimate just past the JIT threshold. A query that used to finish in milliseconds can then spend most of its runtime on compilation instead of execution, without anyone touching the query.

The PostgreSQL release notes, as cited in the post, describe the cost-based trigger as unreliable and state that deployments running many large analytical queries will now need to enable JIT manually. The commit also notes that most large deployments, including every hyperscaler, already run with JIT disabled.

How the old default worked

Postgres JIT compiles two things through LLVM: expression evaluation, covering WHERE clauses, aggregates and projections, and tuple deforming, which converts an on-disk row into its in-memory shape. It only exists on servers built with LLVM support.

Whether compilation fires is decided at plan time against three thresholds: jit_above_cost at 100,000 to compile at all, and jit_inline_above_cost and jit_optimize_above_cost at 500,000 each for inlining functions and running the expensive LLVM optimization passes. Those thresholds are unchanged in 19. What changes is the jit setting itself, which moves from on to off, so the thresholds only matter again once an operator turns JIT back on.

Two lists to build before upgrading

The dev.to post's central advice is to sort your workload into two groups before starting the upgrade. The first is the queries JIT was hurting, which get faster for free in 19. The second is the queries JIT was helping, which can get slower unless you act.

Start with SHOW jit;. If it already reports off, this default change does nothing for you. If it reports on, EXPLAIN ANALYZE on a suspect query prints a JIT block when compilation fired, showing how many functions LLVM produced and a timing breakdown across generation, inlining, optimization and emission. In an example from the official documentation cited in the post, JIT accounts for 7.104 ms of a 7.416 ms execution, while the same query without JIT runs in 0.365 ms.

For a fleet-wide view, pg_stat_statements exposes a count of compiled functions plus four millisecond timing columns for generation, inlining, optimization and emission. The post notes these columns are documented for PostgreSQL 18, so they are not new in 19. Summing the four times and ranking statements by total JIT time surfaces the biggest payers; the author also recommends re-running the ranking sorted by mean execution time so rarely run reporting queries are not crowded out by chatty statements.

Two patterns fall out of that ranking. Queries with a small mean execution time but a large JIT share are victims of the old default, mostly OLTP-style statements whose cost estimates drifted over the line; they simply win in 19. Queries with a large mean time but a small JIT share are the risk: compilation was cheap relative to the run, and the compiled expressions may have been saving real CPU work on every row processed.

One caveat worth noting: with track_planning off, the default, planning time is not captured, and JIT compile time lands inside execution time.

Testing the new default today

You do not need PostgreSQL 19 to preview its behaviour. On 18, SET jit = off reproduces it for a session, and a fuller comparison can be driven through pgbench using the PGOPTIONS environment variable to run the same script once with jit on and once with jit off, comparing latency. The post recommends the -n flag for custom scripts, a single client so the comparison stays about the query rather than concurrency, repeated runs, and a staging replica loaded with production-sized data.

Why it matters

A default flip in a major database release changes performance without any action by the operator, and this one cuts both ways: short queries stop paying a compilation tax, while long analytical queries can regress silently after an upgrade. That every major hyperscaler had already disabled JIT is a strong signal the cost model never worked broadly in practice. Any team planning a move to PostgreSQL 19 should audit JIT usage now, because the evidence needed to do it, in EXPLAIN output and pg_stat_statements, is already sitting in the current production system.

  • #postgresql
  • #databases
  • #performance
  • #query-optimization
  • #jit

Related posts