jit
JIT compiles query plans to native code. Speeds up complex analytics; slows down OLTP by 135× when misconfigured with wrong threshold.
Tier 2 — Production Guide — Operational guidance is available; evidence scope is stated where relevant.
What We Observed
box
(PG 17.10, Intel Core Ultra 7 268V, WSL2), jit_test 500k rows
oltp with jit overhead
- setting
jit=on, jit_above_cost=0 (JIT fires on every query)
- query
SELECT * FROM jit_test WHERE id=250000 (1-row PK lookup)
- exec time ms reps
- 15.08
- 9.988
- 14.238
- median ms
13.2
- jit fires
true
- source file
oltp without jit
- setting
jit=off
- query
Same 1-row PK lookup
- exec time ms reps
- 0.102
- 0.082
- 0.098
- median ms
0.098
- jit fires
false
- source file
oltp overhead ratio
135× overhead (13.2ms vs 0.098ms)
analytics with jit
- setting
jit=on, jit_above_cost=0
- query
SELECT sum(a*b+c*d) FROM jit_test WHERE e LIKE 'A%'
- exec time ms reps
- 49.205
- 42.599
- 53.891
- median ms
49.2
- source file
analytics without jit
- setting
jit=off
- exec time ms reps
- 25.349
- 23.477
- 20.777
- median ms
23.5
- note
On this hardware (WSL2), JIT did NOT improve analytical query — 2× slower with JIT than without!
curve note
JIT adds LLVM compilation time upfront. Threshold behavior: below jit_above_cost, no JIT; above it, JIT compiles. On short OLTP, compilation always exceeds benefit.
What Was Expected
- framing
jit=on with jit_above_cost=0 fires JIT compilation on EVERY query. A 1-row PK lookup that normally takes 0.1ms takes 13ms with JIT (135× overhead). On OLTP servers doing 1000s of tiny queries/s, this is catastrophic: tps collapses.
- oltp evidence
- setting
jit=on, jit_above_cost=0
- query
1-row primary key lookup
- with jit ms
13.2
- without jit ms
0.098
- overhead factor
135×
- source file
- analytics note
On this hardware even analytical queries were SLOWER with JIT (49ms vs 23ms). jit_above_cost=100000 (default) prevents JIT on short queries but may still add overhead.
- production impact
If jit=on globally and workload is OLTP-heavy, throughput can drop 10-100× for short queries.
- source file
Common Pitfalls
- Treating jit=on as universally beneficial — on OLTP / short queries it is often net-negative, and on a fast query with a stale/over-estimated cost it can add hundreds of ms (measured 2.2x slower).
- Diagnosing a JIT regression as a JIT-tuning problem when the real cause is a bad ROW ESTIMATE — fix stats (ANALYZE / autovacuum / CREATE STATISTICS) first; the lab's regression was reltuples=10M vs 5,000 actual rows.
- Forgetting JIT compile cost is paid in EVERY parallel worker — high parallelism multiplies the tax on queries that do not benefit.
- Assuming jit=on means JIT is actually happening — a binary built without LLVM reports jit=on but pg_jit_available=false and never JITs; conversely JIT only fires when estimated cost also exceeds jit_above_cost.
- Lowering jit_above_cost to 'get more JIT' — this drags more small/fast queries across the gate and into the compile tax; the safer direction is usually to RAISE it (or the inline/optimize gates).
- Not reading the EXPLAIN ANALYZE 'JIT: . Timing' block — it is the only place the compile cost (Generation/Inlining/Optimization/Emission/Total) is visible; without it a JIT regression looks like an unexplained slowdown.
Tuning Guidance
Unlock Incident Pro
The free sections tell you what to do. Pro is the measured proof — the exact failure signature, safe-ceiling formula, and per-workload defaults, each captured on real PG 14–18 runs.
- Tuning Guidance
jit is the on/off master switch for Just-In-Time compilation of query expressions. When jit is on (the default), and a query's TOTAL estimated plan cost exceeds jit_above_cost (default 100,000), PostgreSQL hands that query's expression evaluation and tuple deforming to the LLVM JIT: instead of walking a generic expression-interpreter tree per row, it generates and runs machine code specialised to that exact query. For long, expression-heavy, many-row queries (analytics: big aggregates, complex WHERE/CASE arithmetic over millions of rows) this can cut CPU time substantially. JIT has THREE escalating stages, each with its own cost gate: basic code generation (fires above jit_above_cost), then Inlining (above jit_inline_above_cost, default 500,000) which pulls function bodies inline, then Optimization (above jit_optimize_above_cost, default 500,000) which runs LLVM optimisation passes. Each stage costs WALL-CLOCK TIME to compile BEFORE the query runs — and that compile tax is decided purely on the planner's COST ESTIMATE, not on how many rows the query actually touches. That asymmetry (real, up-front, estimate-driven cost vs only-sometimes-realised benefit) is the whole story of jit.
- name
jit
- setting default
on
- unit
(none — boolean)
- vartype
bool
- category
Query Tuning / Other Planner Options
- context
user
- min val
(n/a — bool)
- max val
(n/a — bool)
- enumvals
(n/a — bool; accepts on/off/true/false/1/0/yes/no)
- boot val
on
- reset val
on
- pending restart
false
- short desc
Allow JIT compilation.
- ground truth note
Captured literally from pg_settings on PG 14-18 (-18) on 2026-06-20. setting=on, boot_val=on, reset_val=on on EVERY version (default_changed=false, boot_fallback_changed=false). context=user (any session/role/database can set it, no restart, no superuser). vartype=bool. category='Query Tuning / Other Planner Options', short_desc='Allow JIT compilation.' pg_jit_available=true on all five lab images, so JIT is actually compiled in (a source build WITHOUT --with-llvm reports jit=on but pg_jit_available=false and silently never JITs). Companion cost gates on all versions: jit_above_cost=100000, jit_inline_above_cost=500000, jit_optimize_above_cost=500000.
Rock-stable across versions: default=on, context=user, vartype=bool on PG 14, 15, 16, 17 and 18 — no version cliff (default_changed=false, boot_fallback_changed=false). jit has defaulted to ON since PG 12. The cost gates (jit_above_cost=100000, jit_inline_above_cost=500000, jit_optimize_above_cost=500000) are also identical across 14-18. What HAS evolved underneath is the LLVM machinery and which expression/agg paths are JIT-able, plus general JIT-quality fixes — so the same query may compile slightly differently or faster on a newer major — but this GUC's value, type and meaning are unchanged. One thing to verify per-install rather than per-version: pg_jit_available — a binary built without LLVM (some minimal/managed builds) shows jit=on yet never JITs. Per-version detail in _guc_matrix_machine.
context=user, the most flexible scope: postgresql.conf + reload (cluster-wide), ALTER DATABASE x SET / ALTER ROLE r SET (per database / role), or SET jit = on|off (per session) — NO restart, NO superuser. Because it is per-session, the surgical pattern is to leave the cluster default at on and SET jit = off only on the specific connection/role running a known-pathological workload (or vice-versa). The lab toggled it per query via PGOPTIONS '-c jit=on' / '-c jit=off'. It is a master switch: with jit=off none of the jit_* sub-parameters matter; with jit=on the per-stage gates (jit_above_cost / jit_inline_above_cost / jit_optimize_above_cost) decide how much compilation a given query gets. BOUNDARY PROOF (captured literal): jit is a boolean, so a non-boolean value is rejected at set time -- ALTER SYSTEM SET jit = 'notabool'; -> ERROR: parameter "jit" requires a Boolean value. It is context=user, so a plain SET jit = off in a session succeeds immediately (session-scoped, no restart, no superuser); change it cluster-wide via postgresql.conf or ALTER SYSTEM + reload. Note jit=on only ENABLES JIT — a query is JIT-compiled only when its estimated cost also exceeds jit_above_cost AND the binary was built with LLVM (pg_jit_available=true).
References
- PostgreSQL docs: Runtime Config — Query Planning → Other Planner Options (jit) and JIT cost gates (jit_above_cost, jit_inline_above_cost, jit_optimize_above_cost).
- PostgreSQL docs: Chapter 'Just-in-Time Compilation (JIT)' — what gets compiled (expressions, tuple deforming), the cost-based decision, pg_jit_available, jit_provider.
- PostgreSQL docs: EXPLAIN — the 'JIT' / 'Timing' output block (Generation, Inlining, Optimization, Emission, Total).
- PostgreSQL docs: Statistics Used by the Planner; ANALYZE; CREATE STATISTICS — accurate row estimates are what keep JIT from firing on fast queries.
- PostgreSQL docs: Parallel Query — JIT compilation is performed per worker.
- Lab capture 2026-06-20: — 10M-row jit_t EFFECT (5.60 s -> 4.05 s, compile 345.888 ms) and jit_skew stale-stats DANGER (reltuples 10,000,806 vs 5,000 actual rows; jit=on ~2.2x slower; compile Timing Total 257.376 ms); pg_jit_available=true and jit/jit_*_cost ground truth PG 14-18 (-18).
Technical Reference
- Category
- Query Tuning / Other Planner Options
- Type
- bool ((none; a boolean on/off switch that decides whether the executor may JIT-compile a query whose total estimated cost crosses jit_above_cost))
- Blast radius
- query
- Severity
- Moderate
- Context
- user
- Versions
- 14, 15, 16, 17, 18
- Reviewed
- 2026-06-20
Severity scale: 1 Minimal · 2 Low · 3 Moderate · 4 High · 5 Critical.