4. Odoo logs
Easiest starting point
"POST /mail/init_messaging HTTP/1.0" 200 - 129 0.542 0.952
Last 3 figures are:
● Number of SQL queries
● Time spent on SQL queries
● Time spent on method excluding sql
5. Odoo profiling method
● Built in profiler
from odoo.tools.misc import profile
from datetime import datetime
[...]
@profile("/tmp/profile_my_method%s.cprof" % datetime.now().isoformat())
def my_method(self):
[...]
● Open result in snakeviz
8. log_destination = 'csvlog'
logging_collector = on
log_directory =
'/var/log/postgresql/pg_log/'
log_filename = 'postgresql-%Y-%m-
%d_%H%M%S.log'
log_min_duration_statement = 0
log_line_prefix = '%t [%p-%l] %q%u@%d '
lc_messages = 'en_US.UTF-8'
And analyse with pgbadger
➔ Require postgresql config access
➔ Choose level with
log_min_duration_statement
➔ HTML report as output
➔ Time consuming queries
◆ Per execution
◆ Due to the number of executions
Log with
PostgreSQL
9.
10. Most Odoo SQL queries
were simple
SELECT id
FROM module_table
WHERE search_domain
AND record_rules
ORDER BY order_by;
SELECT field
FROM module_table
WHERE id=ANY([...]);
UPDATE module_table
SET field=value
WHERE id IN ([...]);
➔ How is
self.search([(‘field1_id.field2_ids.name’,
‘=’, ‘whatever’)]) evaluated?
◆ 13.0 VS 14.0
◆ https://github.com/odoo/odoo/pull/52403
◆ JOIN
➔ SELECT id with criterias
➔ SELECT field WHERE id
11. Optimise SQL Query
Need to know how PostgreSQL executes the query
explain analyse
SELECT sequence_prefix
FROM account_move
WHERE journal_id = 140
AND name != '/'
AND date_trunc('year', date) = date_trunc('year', '2017-10-16'::date)
AND id != 1129331
ORDER BY id DESC;
● Explain
● Explain (analyse/analyze, buffers, format=json)
QUERY PLAN
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Index Scan using account_move_sequence_index2 on account_move (cost=0.42..94250.51 rows=550 width=12) (actual time=58.451..59.917 rows=1494 loops=1)
Index Cond: (journal_id = 140)
Filter: (((name)::text <> '/'::text) AND (id <> 1129331) AND (date_trunc('year'::text, (date)::timestamp with time zone) = date_trunc('year'::text, ('2017-10-16'::date)::timestamp
with time zone)))
Rows Removed by Filter: 110010
Planning time: 0.920 ms
Execution time: 59.973 ms
(6 rows)
12.
13.
14. explain analyse
SELECT sequence_prefix
FROM account_move
WHERE journal_id = 140
AND name != '/'
AND date_trunc('year', date) = date_trunc('year', '2017-10-16'::date)
AND id != 1129331
ORDER BY id DESC;
CREATE INDEX ON account_move(
journal_id,
date_trunc('year'::text, date::timestamp with time zone),
id DESC
)
WHERE name::text <> '/'::text;
QUERY PLAN
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Index Scan using account_move_sequence_index2 on account_move (cost=0.42..94250.51 rows=550 width=12) (actual time=58.451..59.917 rows=1494 loops=1)
Index Cond: (journal_id = 140)
Filter: (((name)::text <> '/'::text) AND (id <> 1129331) AND (date_trunc('year'::text, (date)::timestamp with time zone) = date_trunc('year'::text, ('2017-10-16'::date)::timestamp
with time zone)))
Rows Removed by Filter: 110010
Planning time: 0.920 ms
Execution time: 59.973 ms
(6 rows)
ERROR: functions in index expression must be marked IMMUTABLE
15. explain analyse SELECT sequence_prefix FROM account_move WHERE journal_id = 140 AND name != '/' AND date_trunc('year', date) =
date_trunc('year', '2017-10-16'::date) AND id != 1129331 ORDER BY id DESC;
VS
explain analyse SELECT sequence_prefix FROM account_move WHERE journal_id = 140 AND name != '/' AND date_trunc('year',
date::timestamp without time zone) = date_trunc('year', '2017-10-16'::date) AND id != 1129331 ORDER BY id DESC;
Execution time: 4.777 ms
VS
explain analyse SELECT sequence_prefix FROM account_move WHERE journal_id = 140 AND name != '/' AND date_trunc('year',
date::timestamp without time zone) = date_trunc('year', '2017-10-16'::date) AND id != 1129331 ORDER BY id DESC LIMIT 1;
Execution time: 49.815 ms
16. # create index on account_move(journal_id, date_trunc('year'::text, date::timestamp without time zone)) WHERE name::text <>
'/'::text;
CREATE INDEX
# explain analyse SELECT sequence_prefix FROM account_move WHERE journal_id = 140 AND name != '/' AND date_trunc('year',
date::timestamp without time zone) = date_trunc('year', '2017-10-16'::date) AND id != 1129331 ORDER BY id DESC;
QUERY PLAN
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Sort (cost=570.59..571.94 rows=538 width=12) (actual time=3.479..3.520 rows=1494 loops=1)
Sort Key: id DESC
Sort Method: quicksort Memory: 165kB
-> Index Scan using account_move_journal_id_date_trunc_idx on account_move (cost=0.43..546.19 rows=538 width=12) (actual time=0.179..2.841 rows=1494 loops=1)
Index Cond: ((journal_id = 140) AND (date_trunc('year'::text, (date)::timestamp without time zone) = date_trunc('year'::text, ('2017-10-16'::date)::timestamp with time
zone)))
Filter: (id <> 1129331)
Planning time: 1.048 ms
Execution time: 3.620 ms
(8 rows)
# explain analyse SELECT sequence_prefix FROM account_move WHERE journal_id = 140 AND name != '/' AND date_trunc('year',
date::timestamp without time zone) = date_trunc('year', '2017-10-16'::date) AND id != 1129331 ORDER BY id DESC limit 1;
QUERY PLAN
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Limit (cost=0.42..172.74 rows=1 width=12) (actual time=42.422..42.423 rows=1 loops=1)
-> Index Scan using account_move_sequence_index2 on account_move (cost=0.42..92705.85 rows=538 width=12) (actual time=42.421..42.421 rows=1 loops=1)
Index Cond: (journal_id = 140)
Filter: (((name)::text <> '/'::text) AND (id <> 1129331) AND (date_trunc('year'::text, (date)::timestamp without time zone) = date_trunc('year'::text, ('2017-10-
16'::date)::timestamp with time zone)))
Rows Removed by Filter: 109954
Planning time: 0.263 ms
Execution time: 42.443 ms
(7 rows)
Beware the “limit 1”
18. Update once per
transaction
# timing
Timing is on.
# begin;
BEGIN
Time: 0.147 ms
# update account_move set name='nse'
where id=6;
UPDATE 1
Time: 2.696 ms
# update account_move set name='nse'
where id=6;
UPDATE 1
Time: 377.396 ms
# timing
Timing is on.
# update account_move set name='nse' where
id=6;
UPDATE 1
Time: 2.656 ms
# update account_move set name='nse' where
id=6;
UPDATE 1
Time: 2.621 ms
.flush() !
Instead of the usual quote “you cannot improve what you cannot measure”
Know what to improve.
“It’s slow” may vary from people to people.
Improving a piece of software without degrading other ones.
Odoo provide tools out of the box to achieve this.
External softwares will be used during this talk.
Deploy Odoo
Deploy Munin
Configure PostgreSQL
Improve PostgreSQL
Which kind of index to use in PostgreSQL
As well as other people talks regarding ORM, performance, ...
Works out of the box!
Add extra overhead! Alot! -> not in prod
On Odoo.sh? Just _inherit your model, the method, decorate the method, and… that’s it!
Place your own screenshot
Select screenshot
Click ‘Replace Image’
Select ‘Upload from computer’ from dropdown menu
Choose photo
Adjust accordingly, without distorting (stretching) photo
Watch the RCO talks for python code optimisation
Can do it using odoo but easier to analyse with Postgresql (thanks to pgbadger)
Place your own screenshot
Select screenshot
Click ‘Replace Image’
Select ‘Upload from computer’ from dropdown menu
Choose photo
Adjust accordingly, without distorting (stretching) photo
Place your own screenshot
Select screenshot
Click ‘Replace Image’
Select ‘Upload from computer’ from dropdown menu
Choose photo
Adjust accordingly, without distorting (stretching) photo
Computing a stored field based on a non-stored one is equivalent
Computing a stored field based on a non-stored one is equivalent