100% found this document useful (4 votes)
1K views

Odoo Performance

The document discusses optimizing performance of Odoo deployments. It provides recommendations on hardware sizing, PostgreSQL tuning, monitoring performance metrics, analyzing logs and queries to identify issues, and addressing common problems like large databases and inefficient queries. Key aspects covered include using appropriate servers, monitoring tools like Munin, optimizing PostgreSQL settings, analyzing query logs and PostgreSQL statistics, and reducing database size by using the filestore for attachments.

Uploaded by

mcolomerav
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
100% found this document useful (4 votes)
1K views

Odoo Performance

The document discusses optimizing performance of Odoo deployments. It provides recommendations on hardware sizing, PostgreSQL tuning, monitoring performance metrics, analyzing logs and queries to identify issues, and addressing common problems like large databases and inefficient queries. Key aspects covered include using appropriate servers, monitoring tools like Munin, optimizing PostgreSQL settings, analyzing query logs and PostgreSQL statistics, and reducing database size by using the filestore for attachments.

Uploaded by

mcolomerav
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 44

High Performance

Odoo
Olivier Dony

@odony

Odoo can handle large data


and transaction volumes out
of the box!

On Odoo Online, a typical


server hosts more than
3000 instances
100/200 new ones/day

Typical size of large deployments


Multi-GB database (10-20GB)
Multi-million records tables
o Stock moves
o Journal items
o Mails / Leads

On a single Odoo server!

Performance issues
can be (easily) solved
With the right tools
And the right facts

Odoo Performance
o1 Some Facts
2

Deployment Architecture

o3 Monitor & Measure


o4 Analyze
o5 Top 5 Problems in Custom Apps

Some Facts

PostgreSQL
o Is the real workhorse of your Odoo server
o Powers large cloud services
o Can handle terabytes of data efficiently
o Should be fine-tuned to use your hardware
o Cannot magically fix algorithmic/complexity
issues in [y]our code!

Hardware Sizing
o 2014 recommandation for single user
server for up to ~100 active users
o Intel Xeon E5 2.5Ghz 6c/12t (e.g. E5-1650v2)
o 32GB RAM
o SATA/SAS RAID-1

o On Odoo online, this spec handles 3000 dbs


with a load average 3

Transaction Sizing
o Typical read transaction takes ~100ms
o A single process can handle ~6 t/s
o 8 worker processes = ~50 t/s
o 1 interactive user = ~50 t/m peak = ~1 t/s
o Peak use with 100 users = 100 t/s
o On average, 5-10% of peak = 5-10 t/s

SQL numbers
o Most complex SQL queries should be under
100ms, and the simplest ones < 5ms
o RPC read transactions: <40 queries
o RPC write transactions: 200+ queries
o One DB transaction = 100-300 heavy locks

Sizing

For anything else, appropriate load testing


is a must before going live!
Then size accordingly...

Deployment

Odoo Architecture
Front-end pages

Back-end JS client

HTTP Routing

User Interface
Controllers

Controllers (Front-end, Back-end)


Business Logic (Apps)
Messaging, Notifications (mail)

Models

ORM

PostgreSQL Store

Persistence

Deployment Architecture
Single server, multi-process

HTTP worker
Requests

HTTP worker
HTTP worker

PostgreSQL
Store

Cron worker
gevent worker

Rule of thumb: --workers=$[1+$CORES*2]

Deployment Architecture
Multi-server, multi-process

HTTP worker
HTTP worker
HTTP worker
Requests

Load
balancer

Cron worker
gevent worker

HTTP worker
HTTP worker
HTTP worker
Cron worker
gevent worker

PostgreSQL
Store

PostgreSQL Deployment
o Use PostgreSQL 9.2/9.3 for performance
o Tune it:

http://wiki.postgresql.org/wiki/Tuning_Your_PostgreSQL_Server

o Avoid deploying PostgreSQL on a VM


o If you must, optimize the VM for IOPS
o Check out vFabric vPostgres 9.2
o Use separate disks for SYSTEM/DATA/WAL
o shared_buffers: more than 55% VM RAM
o Enable guest memory ballooning driver

Monitor
& Measure

You cannot improve what


you cannot measure!

Monitor & Measure


o Get the pulse of your deployments
o System load
o Disk I/O
o Transactions per second
o Database size

o Recommended tool: munin


o --log-level=debug_rpc in Production!
2014-05-03 12:22:32,846 9663 DEBUG test openerp.netsvc.rpc.request:
object.execute_kw time:0.031s mem: 763716k -> 763716k (diff: 0k)('test', 1,
'*', 'sale.order', 'read', (...), {...})

Monitor & Measure


o Build your munin
dashboard
o Establish what the usual
level of performance is
o Add your own specific
metrics
o It will be invaluable later,
even if you don't know yet

Monitor & Measure


Munin plugin for transactions/minute
#!/bin/sh
#%# family=manual
#%# capabilities=autoconf suggest
case $1 in
autoconf)
exit 0
;;
suggest)
exit 0
;;
config)
echo graph_category openerp
echo graph_title openerp rpc request count
echo graph_vlabel num requests/minute in last 5 minutes
echo requests.label num requests
exit 0
;;
esac
# watch out for the time zone of the logs => using date -u for UTC timestamps
result=$(tail -60000 /var/log/odoo.log | grep "object.execute_kw time" | awk "BEGIN{count=0} (\$1 \" \"
\$2) >= \"`date +'%F %H:%M:%S' -ud '5 min ago'`\" { count+=1; } END{print count/5}")
echo "requests.value ${result}"
exit 0

Monitor & Measure


Munin plugin for response time
#!/bin/sh
#%# family=manual
#%# capabilities=autoconf suggest
case $1 in
config)
echo graph_category openerp
echo graph_title openerp rpc requests min/average response time
echo graph_vlabel seconds
echo graph_args --units-exponent -3
echo min.label min
echo min.warning 1
echo min.critical 5
echo avg.label average
echo avg.warning 1
echo avg.critical 5
exit 0
;;
esac
# watch out for the time zone of the logs => using date -u for UTC timestamps
result=$(tail -60000 /var/log/openerp.log | grep "object.execute_kw time" | awk "BEGIN{sum=0;count=0} (\
$1 \" \" \$2) >= \"`date +'%F %H:%M:%S' -ud '5 min ago'`\" {split(\$8,t,\":\");time=0+t[2];if (min==\"\")
{ min=time}; sum += time; count+=1; min=(time>min)?min:time } END{print min, sum/count}")
echo
echo
echo
echo
exit

-n "min.value "
${result} | cut -d" " -f1
-n "avg.value "
${result} | cut -d" " -f2
0

Monitor PostgreSQL
o Munin has many builtin plugins (enabled with
symlinks)
o Enable extra logging in postgresql.conf
o log_min_duration_statement = 50

Set to 0 to log all queries

Instagram gist to capture sample + analyze

o lc_messages = 'C'

For automated log analysis

Analyze

Analysis Where to start?


o Many factors can impact performance
o Hardware bottlenecks (check munin graphs!)
o Business logic burning CPU

use `kill -3 ${odoo_pid}` for live traces

o Transaction locking in the database


o SQL query performance

Analysis SQL Logs


o Thanks to extra PostgreSQL logging you can use
pg_badger to analyze the query log
o Produces a very insightful statistical report
o Use EXPLAIN ANALYZE to check the behavior
of suspicious queries
o Keep in mind that PostgreSQL uses the fastest way,
not necessarily the one you expect (e.g. indexes not
always used if sequential scan is faster)

PostgreSQL Analysis
o Important statistics tables
o pg_stat_activity: real-time queries/transactions
o pg_locks: real-time transaction heavy locks
o pg_stat_user_tables: generic use stats for tables
o pg_statio_user_tables: I/O stats for tables

Analysis Longest tables


# SELECT schemaname || '.' || relname as table, n_live_tup as
num_rows
FROM pg_stat_user_tables
ORDER BY n_live_tup DESC LIMIT 10;
table
public.stock_move
public.ir_translation
public.wkf_workitem
public.wkf_instance
public.procurement_order
public.ir_property
public.ir_model_data
public.stock_move_history_ids
public.mrp_production_move_ids
public.mrp_bom

num_rows
179544
134039
97195
96973
83077
69011
59532
58942
49714
46258

Analysis Biggest tables


# SELECT nspname || '.' || relname AS "table",
pg_size_pretty(pg_total_relation_size(C.oid)) AS
"total_size"
FROM pg_class C
LEFT JOIN pg_namespace N ON (N.oid = C.relnamespace)
WHERE nspname NOT IN ('pg_catalog', 'information_schema')
AND C.relkind <> 'i'
AND nspname !~ '^pg_toast'
ORDER BY pg_total_relation_size(C.oid) DESC
LIMIT 10;

table
total_size

public.stock_move
525 MB

public.wkf_workitem
111 MB

public.procurement_order
80 MB

public.stock_location
63 MB

public.ir_translation
42 MB

public.wkf_instance
37 MB

public.ir_model_data
36 MB

public.ir_property
26 MB

public.ir_attachment
14 MB

public.mrp_bom
13 MB

Reduce database size


o Enable filestore for attachments (see FAQ)
o No files in binary fields, use the filestore
Faster dumps and backups
Filestore easy to rsync for backups too

Analysis Most read tables


# SELECT schemaname || '.' || relname as table, heap_blks_read as disk_reads,
heap_blks_hit as cache_reads,
heap_blks_read + heap_blks_hit as total_reads
FROM pg_statio_user_tables
ORDER BY heap_blks_read + heap_blks_hit DESC LIMIT 15;

table
disk_reads cache_reads total_reads

public.stock_location

53796 60926676388 60926730184


public.stock_move

208763 9880525282 9880734045


public.stock_picking

15772 4659569791 4659585563


public.procurement_order

156139 1430660775 1430816914


public.stock_tracking

2621
525023173
525025794
public.product_product

11178
225774346
225785524
public.mrp_bom

27198
225329643
225356841
public.ir_model_fields

1632
203361139
203362771
public.stock_production_lot

5918
127915614
127921532
public.res_users

416
115506586
115507002
public.ir_model_access

6382
104686364
104692746
public.mrp_production

20829
101523983
101544812
public.product_template

4566
76074699
76079265
public.product_uom

18
70521126
70521144
public.wkf_workitem

129166
67782919
67912085

Analysis Most written tables


# SELECT schemaname || '.' || relname as table,
seq_scan,idx_scan,idx_tup_fetch+seq_tup_read lines_read_total,
n_tup_ins as num_insert,n_tup_upd as num_update,
n_tup_del as num_delete
FROM pg_stat_user_tables ORDER BY n_tup_upd DESC LIMIT 10;
table
public.stock_move
public.procurement_order
public.wkf_workitem
public.stock_location
public.stock_picking
public.stock_production_lot
public.mrp_production
public.sale_order_line
public.stock_tracking
public.ir_cron

seq_scan

idx_scan

1188095
226774
373
41402098
297984
190934
270568
30161
656404
246636

1104711719
22134417
17340039
166316501
71732467
28038527
13550371
4757426
97874788
818

lines_read_total

num_insert

num_update

num_delete

132030135782
11794090805
29910699
516216409246
5671488265
1124560295
476534514
60019207
5054452666
2467441

208507
92064
1958392
97
9008
4318
3816
2077
5914
0

9556574
6882666
3280141
2215107
1000966
722053
495776
479752
404469
169904

67298
27543
1883794
205
1954
0
1883
320
0
0

Analysis Locking (9.1)


-- For PostgreSQL 9.1
create view pg_waiter_holder as
select
wait_act.datname,
pg_class.relname,
wait_act.usename,
waiter.pid as waiterpid,
waiter.locktype,
waiter.transactionid as xid,
waiter.virtualtransaction as wvxid,
waiter.mode as wmode,
wait_act.waiting as wwait,
substr(wait_act.current_query,1,30) as wquery,
age(now(),wait_act.query_start) as wdur,
holder.pid as holderpid,
holder.mode as hmode,
holder.virtualtransaction as hvxid,
hold_act.waiting as hwait,
substr(hold_act.current_query,1,30) as hquery,
age(now(),hold_act.query_start) as hdur
from pg_locks holder join pg_locks waiter on (
holder.locktype = waiter.locktype and (
holder.database, holder.relation,
holder.page, holder.tuple,
holder.virtualxid,
holder.transactionid, holder.classid,
holder.objid, holder.objsubid
) is not distinct from (
waiter.database, waiter.relation,
waiter.page, waiter.tuple,
waiter.virtualxid,
waiter.transactionid, waiter.classid,
waiter.objid, waiter.objsubid
))
join pg_stat_activity hold_act on (holder.pid=hold_act.procpid)
join pg_stat_activity wait_act on (waiter.pid=wait_act.procpid)
left join pg_class on (holder.relation = pg_class.oid)
where holder.granted and not waiter.granted
order by wdur desc;

Analysis Locking (9.2)


-- For PostgreSQL 9.2
create view pg_waiter_holder as
select
wait_act.datname,
wait_act.usename,
waiter.pid as wpid,
holder.pid as hpid,
waiter.locktype as type,
waiter.transactionid as xid,
waiter.virtualtransaction as wvxid,
holder.virtualtransaction as hvxid,
waiter.mode as wmode,
holder.mode as hmode,
wait_act.state as wstate,
hold_act.state as hstate,
pg_class.relname,
substr(wait_act.query,1,30) as wquery,
substr(hold_act.query,1,30) as hquery,
age(now(),wait_act.query_start) as wdur,
age(now(),hold_act.query_start) as hdur
from pg_locks holder join pg_locks waiter on (
holder.locktype = waiter.locktype and (
holder.database, holder.relation,
holder.page, holder.tuple,
holder.virtualxid,
holder.transactionid, holder.classid,
holder.objid, holder.objsubid
) is not distinct from (
waiter.database, waiter.relation,
waiter.page, waiter.tuple,
waiter.virtualxid,
waiter.transactionid, waiter.classid,
waiter.objid, waiter.objsubid
))
join pg_stat_activity hold_act on (holder.pid=hold_act.pid)
join pg_stat_activity wait_act on (waiter.pid=wait_act.pid)
left join pg_class on (holder.relation = pg_class.oid)
where holder.granted and not waiter.granted
order by wdur desc;

Analysis Locking
o Verify blocked queries
# SELECT * FROM waiter_holder;
relname | wpid | hpid |
wquery
| wdur
| hquery
---------+-------+-------+--------------------------------+------------------+----------------------------| 16504 | 16338 | update "stock_quant" set "s
| 00:00:57.588357 | <IDLE> in transaction
| 16501 | 16504 | update "stock_quant" set "f
| 00:00:55.144373 | update "stock_quant"
(2 lignes)
...
hquery
| hdur
|
wmode
|
hmode
|
... ------------------------------+-------------------+-----------+---------------|
... <IDLE> in transaction
| 00:00:00.004754
| ShareLock | ExclusiveLock |
... update "stock_quant" set "s
| 00:00:57.588357
| ShareLock | ExclusiveLock |

o Update to PostgreSQL 9.3 is possible


o More efficient locking for Foreign Keys

o Try pg_activity (top-like):

pip install pg_activity

Top 5
Problems
in Custom Apps

Top 5 Problems in Custom Apps


o 1. Wrong use of stored computed fields
o 2. Domain evaluation strategy
o 3. Business logic triggered too often
o 4. Misuse of the batch API
o 5. Custom locking

1. Stored computed fields


o Be vary careful when you add stored computed fields
(using the old API)
o Manually set the right trigger fields + func
store = {'trigger_model': (mapping_function,
[fields...],
priority) }
store = True is a shortcut for:
{self._name: (lambda s,c,u,ids,c: ids,
None,10)}

o Do not add this on master data (products, locations,


users, companies, etc.)

2. Domain evaluation strategy


o Odoo cross-object domain expressions do not use
JOINs by default, to respect modularity and ACLs
o e.g. search([('picking_id.move_ids.partner_id', '!=', False)])
o Searches all moves without partner!
o Then uses id IN <found_move_ids>!

o Imagine this in record rules (global security filter)


o Have a look at auto_join (v7.0+)
'move_ids': fields.one2many('stock.move', 'picking_id',
string='Moves', auto_join=True)

3. Busic logic triggered too often


o Think about it twice when you override
create() or write() to add your stuff
o How often will this be called? Should it be?

o Think again if you do it on a high-volume


object, such as o2m line records
(sale.order.line, stock.move, )
o Again, make sure you don't alter master data

4. Misuse of batch API


o The API works with batches
o Computed fields work in batches
o Model.browse() pre-fetches in batches
o See @one in the new API

5. Custom Locking
o In general PostgreSQL and the ORM do all the DB and
Python locking we need
o Rare cases with manual DB locking
o Inter-process mutex in db (ir.cron)
o Sequence numbers
o Reservations in double-entry systems

o Python locking
o Caches and shared resources (db pool)

o You probably do not need more than this!

Thank You

Odoo
[email protected]
+32 (0) 2 290 34 90
www.odoo.com

@odony

You might also like