PostgreSQL introduced Just-In-Time (JIT) compilation to improve query execution performance by compiling parts of SQL execution into native machine code using LLVM. The feature is useful for complex analytical queries and large datasets where query execution time is high enough to benefit from runtime compilation.
In PostgreSQL 18, JIT is enabled by default. However, PostgreSQL 19 changes this behavior by disabling JIT by default. This change raises an important question for Odoo developers and PostgreSQL administrators:
Does JIT actually improve Odoo performance?
To understand this behavior, I performed a benchmark test on Odoo using PostgreSQL 18 with JIT enabled and disabled. The purpose of this test was to analyze whether Odoo benefits from JIT compilation in a real workload scenario. The benchmark was performed using:
- PostgreSQL 18
- Odoo database
- pg_stat_statements extension for query tracking
- Odoo Sales module installation workload
The benchmark focused on comparing query execution time between:
Step 1: Verify JIT Status
First, I checked whether JIT was enabled in PostgreSQL.
show jit;
Output:
jit
-----
on
Then I verified the PostgreSQL setting details.
select * from pg_settings where name = 'jit';
Result :
name | jit
setting | on
unit |
category | Query Tuning / Other Planner Options
short_desc | Allow JIT compilation.
extra_desc |
context | user
vartype | bool
source | configuration file
min_val |
max_val |
enumvals |
boot_val | on
reset_val | on
sourcefile | /etc/postgresql/18/main/postgresql.conf
sourceline | 474
pending_restart | f
This confirmed that JIT was enabled by default in PostgreSQL 18.
Step 2: Enable pg_stat_statements
To monitor query execution statistics, enable the pg_stat_statements extension. Connect to odoo database.
\c odoo
Create the pg_stat_statements extension
create extension pg_stat_statements;
Then we need to set the value for the shared_preload_libraries.
show shared_preload_libraries;
Initially, it was empty. Next, open the PostgreSQL configuration file and set values for the shared_preload_libraries postgres configuration.
So before that, check the path of the postgres configuration file like this.
Show config_file
Result :
config_file
-----------------------------------------
/etc/postgresql/18/main/postgresql.conf
(1 row)
Edit this file as;
sudo nano /etc/postgresql/18/main/postgresql.conf
Added value for the postgres parameter named shared_preload_libraries like the code below.
shared_preload_libraries = 'pg_stat_statements'
After modifying the configuration, restart the postgresql as;
sudo systemctl restart postgresql
Once restarted,verify it as;
show shared_preload_libraries;
Output:
pg_stat_statements
Step 3: Prepare the Benchmark
Before starting the benchmark, please ensure that the query statistics were clean.
select count(*) from pg_stat_statements;
The result returned zero rows, confirming no previous statistics existed.
Step 4: Run Odoo Workload With JIT Enabled
For the benchmark workload, install the Odoo Sales module and track the executed queries using pg_stat_statements.
After the installation is completed, check the top queries based on execution time.
select query,total_exec_time
from pg_stat_statements
order by total_exec_time desc
limit 5;
Result :
-[ RECORD 1 ]-------------------------------------------------------------------------
query | LOCK ir_attachment IN SHARE MODE
total_exec_time | 2481.721921
-[ RECORD 2 ]--------------------------------------------------------------------------
query | UPDATE "ir_attachment" +
| SET "checksum" = "__tmp"."checksum"::VARCHAR(40), "file_size" = "__tmp"."file_size"::int4, "index_content" = "__tmp"."index_content"::text, "mimetype" = "__tmp"."mimetype"::VARCHAR, "store_fname" = "__tmp"."store_fname"::VARCHAR, "write_date" = "__tmp"."write_date"::timestamp, "write_uid" = "__tmp"."write_uid"::int4+
| FROM (VALUES ($1, $2, $3, $4, $5, $6, $7::timestamp, $8)) AS "__tmp"("id", "checksum", "file_size", "index_content", "mimetype", "store_fname", "write_date", "write_uid") +
| WHERE "ir_attachment"."id" = "__tmp"."id"
total_exec_time | 413.388906
-[ RECORD 3 ]---+----------------------------------------------------------------------
query | SELECT column_name, udt_name, character_maximum_length, is_nullable +
| FROM information_schema.columns WHERE table_name=$1
total_exec_time | 281.66070899999994
-[ RECORD 4 ]---+----------------------------------------------------------------------
query | INSERT INTO ir_model_constraint +
| (name, create_date, write_date, create_uid, write_uid, module, model, type, definition, message) +
| VALUES ($1, +
| now() AT TIME ZONE $2, +
| now() AT TIME ZONE $3, +
| $4, $5, +
| (SELECT id FROM ir_module_module WHERE name=$6), +
| (SELECT id FROM ir_model WHERE model=$7), +
| $8, $9, $10) +
| RETURNING id
total_exec_time | 214.07505700000007
-[ RECORD 5 ]---+----------------------------------------------------------------------
query | INSERT INTO ir_model_data (module,name,model,res_id,noupdate) +
| VALUES ($1,$2,$3,$4,$5) +
| ON CONFLICT (module, name) +
| DO UPDATE SET (model, res_id, write_date) = +
| (EXCLUDED.model, EXCLUDED.res_id, now() at time zone $6) +
| WHERE (ir_model_data.res_id != EXCLUDED.res_id OR ir_model_data.model != EXCLUDED.model) +
| RETURNING module, name, model, res_id, create_date, write_date
total_exec_time | 175.8586729999996
The most expensive query consumed approximately 2481 ms. The UPDATE ir_attachment query alone consumed approximately 413 ms.
This indicated that enabling JIT introduced noticeable execution overhead during Odoo operations.
Step 5: Disable JIT
Next, disable the JIT from the PostgreSQL configuration.
sudo nano /etc/postgresql/18/main/postgresql.conf
I modified the configuration for jit like this;
jit = off
Then PostgreSQL was restarted again.
sudo systemctl restart postgresql
After restarting, verify the changed value is reflected in postgres correctly using;
show jit;
Output:
jit
-----
off
Step 6: Reset Statistics Before Retesting
Before running the benchmark again, reset the statistics.
select pg_stat_statements_reset();
Result :
pg_stat_statements_reset
----------------------------------
2026-05-26 21:50:24.203238+05:30
(1 row)
Then verify the statistics table was empty.
select count(*) from pg_stat_statements;
Result :
count
-------
2
(1 row)
The count returned zero again.
Step 7: Run Odoo Workload With JIT Disabled
Repeated the same Odoo Sales module installation workload and again captured the top queries.
select query,total_exec_time
from pg_stat_statements
order by total_exec_time desc
limit 5;
Result :
-[ RECORD 1 -----------------------------------------------------------------------
query | UPDATE "ir_attachment" +
| SET "checksum" = "__tmp"."checksum"::VARCHAR(40), "file_size" = "__tmp"."file_size"::int4, "index_content" = "__tmp"."index_content"::text, "mimetype" = "__tmp"."mimetype"::VARCHAR, "store_fname" = "__tmp"."store_fname"::VARCHAR, "write_date" = "__tmp"."write_date"::timestamp, "write_uid" = "__tmp"."write_uid"::int4+
| FROM (VALUES ($1, $2, $3, $4, $5, $6, $7::timestamp, $8)) AS "__tmp"("id", "checksum", "file_size", "index_content", "mimetype", "store_fname", "write_date", "write_uid") +
| WHERE "ir_attachment"."id" = "__tmp"."id"
total_exec_time | 200.01295800000003
-[ RECORD 2 ]---+----------------------------------------------------------------------
query | SELECT max(v.id) +
| FROM ir_ui_view v +
| LEFT JOIN ir_model_data md ON (md.model = $1 AND md.res_id = v.id) +
| WHERE md.module IN (SELECT name FROM ir_module_module) IS NOT TRUE +
| AND v.model = $2 +
| AND v.active = $3 +
| GROUP BY coalesce(v.inherit_id, v.id)
total_exec_time | 54.383687999999985
-[ RECORD 3 ]---+----------------------------------------------------------------------
query | INSERT INTO "ir_attachment" ("checksum", "company_id", "create_date", "create_uid", "db_datas", "file_size", "index_content", "mimetype", "name", "public", "res_id", "res_model", "store_fname", "type", "url", "write_date", "write_uid") VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17) RETURNING "id"
total_exec_time | 36.942833
-[ RECORD 4 ]---+----------------------------------------------------------------------
query | UPDATE ir_module_module_dependency SET auto_install_required = (name = any($1)) WHERE module_id = $2
total_exec_time | 31.09401399999998
-[ RECORD 5 ]---+----------------------------------------------------------------------
query | UPDATE ir_module_module_dependency SET auto_install_required = (name = any(ARRAY[$1 /*, ... */])) WHERE module_id = $2
total_exec_time | 24.822724999999995
This time the results were significantly different. The highest query execution time was approximately 200 ms.
Compared to the previous benchmark, the query execution time was much lower.
Benchmark Analysis
One of the clearest comparisons was the UPDATE ir_attachment query.
UPDATE "ir_attachment" +
SET "checksum" = "__tmp"."checksum"::VARCHAR(40), "file_size" = "__tmp"."file_size"::int4, "index_content" = "__tmp"."index_content"::text, "mimetype" = "__tmp"."mimetype"::VARCHAR, "store_fname" = "__tmp"."store_fname"::VARCHAR, "write_date" = "__tmp"."write_date"::timestamp, "write_uid" = "__tmp"."write_uid"::int4+
FROM (VALUES ($1, $2, $3, $4, $5, $6, $7::timestamp, $8)) AS "__tmp"("id", "checksum", "file_size", "index_content", "mimetype", "store_fname", "write_date", "write_uid") +
WHERE "ir_attachment"."id" = "__tmp"."id"
- With JIT enabled: Execution time : 413 ms
- With JIT disabled: Execution time : 200 ms
The query became approximately twice as fast after disabling JIT. This benchmark clearly showed that Odoo did not benefit from JIT in this workload.
Why Didn't JIT Improve Odoo Performance?
JIT compilation is mainly designed for:
- Large analytical queries.
- Complex aggregations.
- Data warehouse workloads.
- Long-running sequential scans.
Odoo workloads are very different. Odoo primarily generates:
- Many short queries.
- ORM-generated SQL.
- Transactional operations.
- Repeated inserts and updates.
- Metadata queries.
For these smaller queries, the cost of LLVM compilation becomes higher than the actual execution benefit. PostgreSQL 18 enables JIT by default. However, PostgreSQL 19 disables it by default.
This benchmark demonstrates why that decision makes sense for many OLTP applications such as Odoo. Since transactional workloads usually involve many short-running queries, disabling JIT can reduce unnecessary compilation overhead and improve response time.
The benchmark demonstrated several important findings.
With JIT enabled:
- Query execution time increased.
- Lock-related queries became expensive.
- ORM operations were slower.
- Additional runtime compilation overhead was introduced.
With JIT disabled:
- Query execution time decreased.
- Odoo operations became faster.
- Transactional queries executed more efficiently.
- Overall workload latency improved
The benchmark clearly showed that Odoo performed better with JIT disabled in this environment. For transactional applications like Odoo, disabling JIT can provide better real-world performance compared to enabling it.
In conclusion, PostgreSQL JIT can enhance Odoo performance for complex queries, but it may reduce efficiency for regular transactional workloads. As a result, PostgreSQL 19 disables JIT by default. The best approach is to benchmark and test configurations based on the specific Odoo environment and workload.