Skip to content

[18.0][ADD] queue_job: per DB channel managers with UI configuration and hot reload - #962

Open
guewen wants to merge 2 commits into
OCA:18.0from
guewen:18.0-queue-job-channel-config
Open

[18.0][ADD] queue_job: per DB channel managers with UI configuration and hot reload#962
guewen wants to merge 2 commits into
OCA:18.0from
guewen:18.0-queue-job-channel-config

Conversation

@guewen

@guewen guewen commented Aug 18, 2026

Copy link
Copy Markdown
Member

Implements #765 (comment)

Introduction of new configuration parameters

With this in the config file, the job runner uses the static server-side configuration as before, with no hot-reload (single, shared channel manager):

[queue_job]
channels = root:10,root.p1:2

With this in the config file, the job runner uses the channels configured in the database (the job runner builds one channel manager per database):

[queue_job]
max_capacity = 10

When capacity or other options of a channel (or channels are created/deleted, paused) change, the channel manager for the concerned database is hot reloaded.

Capacity can be constrained by database:

[queue_job]
max_capacity = 10
db_max_capacity = 2

Or with patterns

[queue_job]
max_capacity = 10
db_max_capacity = prod_*:6,staging:3,*:1

When db_max_capacity is not configured, each database gets a max capacity of the global max_capacity.
When there is more jobs pending than max_capacity across the databases, a round-robin allows each database to enqueue jobs in turns.

The channels option for server-side configuration takes precedence over the new configuration.

What does this bring?

  • Each database has its own channel tree, which can vary depending of installed addons
  • Pause and resume a channel
  • Create a new channel, assign a job function this channel, set a capacity at 1 (or anything) or sequential, all from the UI and applied live by the job runner for a single database

Migration path

Since the server-side configuration (ODOO_QUEUE_JOB_CHANNELS or channels in config file) is used by default, updating the addon has no effect. It allows to configure the channels capacity and other options on the UI / by script of the databases, then the server-side configuration can be replaced by the parameters max_capacity and db_max_capacity (or their environment variables counterparts).

Caveat

When using per DB channel managers, we cannot have a shared channel for several databases since each database has its own channels (use case mentioned in #765 (comment)). It could be possible to implement it using a single channel manager that coexists with the db channel managers, yet to define the specifics, e.g. use another root (shared:3, shared.foo:2) or a new option shared_channels = root.shared where the subchannels defined in this option are excluded from the DB channels.
In the meantime, using the server-side channels instead of per-db channels is a trade-off to accept if this use case is essential for a server.

@OCA-git-bot OCA-git-bot added the mod:queue_job Module queue_job label Aug 18, 2026
@OCA-git-bot

Copy link
Copy Markdown
Contributor

Hi @sbidoul,
some modules you are maintaining are being modified, check this out!

@guewen
guewen force-pushed the 18.0-queue-job-channel-config branch 4 times, most recently from 9b9e3fd to 934ccb4 Compare August 18, 2026 12:02
@guewen guewen changed the title [18.0][ADD] queue_job: add jobrunner config on channels [18.0][ADD] queue_job: per DB channel managers with UI configuration and hot reload Aug 18, 2026
@guewen

guewen commented Aug 27, 2026

Copy link
Copy Markdown
Member Author

Hi @sbidoul , can I have your thoughts on this, before I start completing the tests and docs?

@OCA-git-bot
OCA-git-bot deleted the branch OCA:18.0 August 27, 2026 14:44
@guewen guewen reopened this Aug 27, 2026
@OCA-git-bot

Copy link
Copy Markdown
Contributor

Hi @sbidoul,
some modules you are maintaining are being modified, check this out!

@guewen
guewen changed the base branch from 18.0-queue-job-pause to 18.0 August 27, 2026 15:19

@sbidoul sbidoul left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi Guewen,

I have not had time to do a proper review, but I think I'm on board with this.

Great solution!

I have some doubts about the round robin rule, but on the other hand I don't have serious multi-db use cases myself so this can be addressed later.

capacity: int = 0
sequential: bool = False
throttle: int = 0
paused: bool = False

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Handle the new default subchannel capacity too?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like the subchannel pull request (#767) has a merge conflict. I'll take a look at it today.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah I thought that was merged already.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I rebased this branch with the default subchannel capacity now that is is merged

@guewen

guewen commented Aug 31, 2026

Copy link
Copy Markdown
Member Author

I'm writing a benchmark script to have some comparison points (I'll open another PR for the script at some point). It also demonstrates an interesting effect of the round-robin (nothing surprising but nice to see it with numbers).

In the current state of my benchmark, the script enqueues 5k jobs in db0, then 5k in db1, then 5k in db2.

With the global channel manager, the jobrunner executes the jobs strictly by creation order, so the 5k jobs of db0 are executed, then those of db1 and finally those of db2.

The round-robin (yet to test in real situation, which I do not have as we have a single DB in production), might help in situations where a large DB clutters the queue with a large quantity of jobs and starve other databases.

Do not give much value to the number otherwise, it is only a single run of each.

global channel manager

The throughput per database is much higher than the round-robin version: this is because the results are based on when the 5000 jobs are started and when they are all done, and all jobs the bench0 are done before the others, the 5k jobs are done much earlier.

[bench0] jobs:             5000 (5000 done, 0 failed)
[bench0] wall time:        32.6s
[bench0] throughput:       9196 jobs/minute
[bench0] avg exec_time:    0.0063s
[bench0] p95 exec_time:    0.0100s
[bench0] max connections:  10
[bench1] jobs:             5000 (5000 done, 0 failed)
[bench1] wall time:        50.6s
[bench1] throughput:       5930 jobs/minute
[bench1] avg exec_time:    0.0098s
[bench1] p95 exec_time:    0.0184s
[bench1] max connections:  11
[bench2] jobs:             5000 (5000 done, 0 failed)
[bench2] wall time:        55.1s
[bench2] throughput:       5445 jobs/minute
[bench2] avg exec_time:    0.0108s
[bench2] p95 exec_time:    0.0165s
[bench2] max connections:  8
## summary
jobs:             15000 (15000 done, 0 failed)
wall time:        138.3s
throughput:       6506 jobs/minute
max connections (peak per db): {'bench0': 10, 'bench1': 11, 'bench2': 8}

round-robin

In the round-robin version, the throughput is equal for each database, because their jobs are all starting and ending at about the same points.

[bench0] jobs:             5000 (5000 done, 0 failed)
[bench0] wall time:        124.5s
[bench0] throughput:       2410 jobs/minute
[bench0] avg exec_time:    0.0077s
[bench0] p95 exec_time:    0.0142s
[bench0] max connections:  5
[bench1] jobs:             5000 (5000 done, 0 failed)
[bench1] wall time:        124.5s
[bench1] throughput:       2410 jobs/minute
[bench1] avg exec_time:    0.0075s
[bench1] p95 exec_time:    0.0137s
[bench1] max connections:  7
[bench2] jobs:             5000 (5000 done, 0 failed)
[bench2] wall time:        124.5s
[bench2] throughput:       2410 jobs/minute
[bench2] avg exec_time:    0.0076s
[bench2] p95 exec_time:    0.0139s
[bench2] max connections:  7
## summary
jobs:             15000 (15000 done, 0 failed)
wall time:        124.5s
throughput:       7229 jobs/minute
max connections (peak per db): {'bench0': 5, 'bench1': 7, 'bench2': 7}
memo for myself on the benchmark
odoo -c odoorc -d testqueue18 \                
    --load=web,queue_job --workers=0 --log-level=warn -i queue_job --stop-after-init
dropdb --if-exists -p 6432 -hlocalhost bench0
dropdb --if-exists -p 6432 -hlocalhost bench1
dropdb --if-exists -p 6432 -hlocalhost bench2
createdb -p 6432 -hlocalhost -T testqueue18 bench0
createdb -p 6432 -hlocalhost -T testqueue18 bench1
createdb -p 6432 -hlocalhost -T testqueue18 bench2

BENCHMARK_DATABASES=bench0,bench1,bench2 BENCHMARK_JOBS=5000 \
    odoo shell -c odoorc --log-level=warn < queue/queue_job/scripts/benchmark_jobs.py 

BENCHMARK_RUN='benchmark-2026-08-31-15:04:50' BENCHMARK_DATABASES=bench0,bench1,bench2 \
  odoo shell -c odoorc --log-level=warn < queue_job/scripts/benchmark_jobs.py

ODOO_QUEUE_JOB_CHANNELS=root:8 \
  odoo -c odoorc -d bench0,bench1,bench2 \
  --load=web,queue_job --workers=0 --log-level=warn

# or

ODOO_QUEUE_JOB_MAX_CAPACITY=8 \
  odoo -c odoorc -d bench0,bench1,bench2 \
  --load=web,queue_job --workers=0 --log-level=warn

@amh-mw

amh-mw commented Aug 31, 2026

Copy link
Copy Markdown
Member

With the current global channel manager, the jobrunner executes the jobs strictly by creation order, so the 5k jobs of db0 are executed, then those of db1 and finally those of db2.

A single database running multiple companies can also find itself at the mercy of strict creation order.

@guewen

guewen commented Aug 31, 2026

Copy link
Copy Markdown
Member Author

A single database running multiple companies can also find itself at the mercy of strict creation order.

This one won't be solved though

@guewen
guewen force-pushed the 18.0-queue-job-channel-config branch 3 times, most recently from 9578eb4 to b037b8f Compare September 11, 2026 14:08
Not used at this point by the jobrunner, but the changes on channels
trigger a notify to the jobrunner.
@guewen
guewen force-pushed the 18.0-queue-job-channel-config branch from b037b8f to 9d0b7f4 Compare September 12, 2026 15:22
@guewen

guewen commented Sep 12, 2026

Copy link
Copy Markdown
Member Author

I ran 4 times this benchmark (#981) for each mode: server-side channels / per-database channels (on my laptop, performance mode) with 8 concurrent jobs on 10 workers. The throughput is close has some variance in both modes but with close results:

Server-side channels:

  • 9615 jobs / minute
  • 9933 jobs / minute
  • 9284 jobs/minute
  • 9105 jobs/minute

Per-database channels (I had to exclude an outlier at 16'329 j/m I could not explain):

  • 9854 jobs/minute
  • 9291 jobs/minute
  • 9753 jobs/minute
  • 9467 jobs/minute

DB prepared with

odoo -c odoorc -d benchmark_template \       
--load=web,queue_job --workers=0 --log-level=warn -i queue_job --stop-after-init
server-side runs
dropdb --if-exists -p 6432 -hlocalhost bench0
dropdb --if-exists -p 6432 -hlocalhost bench1
dropdb --if-exists -p 6432 -hlocalhost bench2
createdb -p 6432 -hlocalhost -T benchmark_template bench0
createdb -p 6432 -hlocalhost -T benchmark_template bench1
createdb -p 6432 -hlocalhost -T benchmark_template bench2

ODOO_QUEUE_JOB_CHANNELS=root:8 \
BENCHMARK_DATABASES=bench0,bench1,bench2 \
BENCHMARK_JOBS=5000 \
odoo shell -c odoorc --log-level=warn < queue/queue_job/scripts/benchmark_jobs.py

enqueueing 5000 jobs in each of ['bench0', 'bench1', 'bench2'] (run '2026-09-12-16:22:16')...
  500 jobs enqueued in each of ['bench0', 'bench1', 'bench2']
  1000 jobs enqueued in each of ['bench0', 'bench1', 'bench2']
  1500 jobs enqueued in each of ['bench0', 'bench1', 'bench2']
  2000 jobs enqueued in each of ['bench0', 'bench1', 'bench2']
  2500 jobs enqueued in each of ['bench0', 'bench1', 'bench2']
  3000 jobs enqueued in each of ['bench0', 'bench1', 'bench2']
  3500 jobs enqueued in each of ['bench0', 'bench1', 'bench2']
  4000 jobs enqueued in each of ['bench0', 'bench1', 'bench2']
  4500 jobs enqueued in each of ['bench0', 'bench1', 'bench2']
  5000 jobs enqueued in each of ['bench0', 'bench1', 'bench2']
jobs enqueued
starting jobrunner with command: odoo -c odoorc -d bench0,bench1,bench2 --load=web,queue_job --log-level=warn --max-cron-threads=0

----configuration----
jobs per database: 5000 (duration 0.0s, channel 'root')
 workers=10 db_maxconn=64 max_cron_threads=0

polling run '2026-09-12-16:22:16' on ['bench0', 'bench1', 'bench2']...

[bench0] jobs:             5000 (5000 done, 0 failed)
[bench0] wall time:        93.6s
[bench0] throughput:       3206 jobs/minute
[bench0] avg queue time:   37.3398s
[bench0] p95 queue time:   91.7538s
[bench0] max queue time:   99.4077s
[bench0] avg exec time:    0.0066s
[bench0] p95 exec time:    0.0144s
[bench0] max connections:  12
[bench1] jobs:             5000 (5000 done, 0 failed)
[bench1] wall time:        93.6s
[bench1] throughput:       3205 jobs/minute
[bench1] avg queue time:   37.3460s
[bench1] p95 queue time:   91.7629s
[bench1] max queue time:   99.4169s
[bench1] avg exec time:    0.0065s
[bench1] p95 exec time:    0.0146s
[bench1] max connections:  12
[bench2] jobs:             5000 (5000 done, 0 failed)
[bench2] wall time:        93.6s
[bench2] throughput:       3206 jobs/minute
[bench2] avg queue time:   37.3524s
[bench2] p95 queue time:   91.7732s
[bench2] max queue time:   99.4265s
[bench2] avg exec time:    0.0066s
[bench2] p95 exec time:    0.0148s
[bench2] max connections:  12

Databases Aggregation
jobs:             15000 (15000 done, 0 failed)
wall time:        93.6s
throughput:       9615 jobs/minute
max connections (peak per db): {'bench0': 12, 'bench1': 12, 'bench2': 12}
stopping jobrunner...

----

dropdb --if-exists -p 6432 -hlocalhost bench0
dropdb --if-exists -p 6432 -hlocalhost bench1
dropdb --if-exists -p 6432 -hlocalhost bench2
createdb -p 6432 -hlocalhost -T benchmark_template bench0
createdb -p 6432 -hlocalhost -T benchmark_template bench1
createdb -p 6432 -hlocalhost -T benchmark_template bench2

ODOO_QUEUE_JOB_CHANNELS=root:8 \
BENCHMARK_DATABASES=bench0,bench1,bench2 \
BENCHMARK_JOBS=5000 \
odoo shell -c odoorc --log-level=warn < queue/queue_job/scripts/benchmark_jobs.py

enqueueing 5000 jobs in each of ['bench0', 'bench1', 'bench2'] (run '2026-09-12-16:24:42')...
  500 jobs enqueued in each of ['bench0', 'bench1', 'bench2']
  1000 jobs enqueued in each of ['bench0', 'bench1', 'bench2']
  1500 jobs enqueued in each of ['bench0', 'bench1', 'bench2']
  2000 jobs enqueued in each of ['bench0', 'bench1', 'bench2']
  2500 jobs enqueued in each of ['bench0', 'bench1', 'bench2']
  3000 jobs enqueued in each of ['bench0', 'bench1', 'bench2']
  3500 jobs enqueued in each of ['bench0', 'bench1', 'bench2']
  4000 jobs enqueued in each of ['bench0', 'bench1', 'bench2']
  4500 jobs enqueued in each of ['bench0', 'bench1', 'bench2']
  5000 jobs enqueued in each of ['bench0', 'bench1', 'bench2']
jobs enqueued
starting jobrunner with command: odoo -c odoorc -d bench0,bench1,bench2 --load=web,queue_job --log-level=warn --max-cron-threads=0

----configuration----
jobs per database: 5000 (duration 0.0s, channel 'root')
 workers=10 db_maxconn=64 max_cron_threads=0

polling run '2026-09-12-16:24:42' on ['bench0', 'bench1', 'bench2']...

[bench0] jobs:             5000 (5000 done, 0 failed)
[bench0] wall time:        90.6s
[bench0] throughput:       3312 jobs/minute
[bench0] avg queue time:   36.9280s
[bench0] p95 queue time:   86.9402s
[bench0] max queue time:   96.4197s
[bench0] avg exec time:    0.0064s
[bench0] p95 exec time:    0.0140s
[bench0] max connections:  12
[bench1] jobs:             5000 (5000 done, 0 failed)
[bench1] wall time:        90.6s
[bench1] throughput:       3312 jobs/minute
[bench1] avg queue time:   36.9340s
[bench1] p95 queue time:   86.9551s
[bench1] max queue time:   96.4291s
[bench1] avg exec time:    0.0064s
[bench1] p95 exec time:    0.0137s
[bench1] max connections:  13
[bench2] jobs:             5000 (5000 done, 0 failed)
[bench2] wall time:        90.6s
[bench2] throughput:       3311 jobs/minute
[bench2] avg queue time:   36.9402s
[bench2] p95 queue time:   86.9662s
[bench2] max queue time:   96.4396s
[bench2] avg exec time:    0.0064s
[bench2] p95 exec time:    0.0139s
[bench2] max connections:  12

Databases Aggregation
jobs:             15000 (15000 done, 0 failed)
wall time:        90.6s
throughput:       9933 jobs/minute
max connections (peak per db): {'bench0': 12, 'bench1': 13, 'bench2': 12}
stopping jobrunner...

----

dropdb --if-exists -p 6432 -hlocalhost bench0
dropdb --if-exists -p 6432 -hlocalhost bench1
dropdb --if-exists -p 6432 -hlocalhost bench2
createdb -p 6432 -hlocalhost -T benchmark_template bench0
createdb -p 6432 -hlocalhost -T benchmark_template bench1
createdb -p 6432 -hlocalhost -T benchmark_template bench2

ODOO_QUEUE_JOB_CHANNELS=root:8 \
BENCHMARK_DATABASES=bench0,bench1,bench2 \
BENCHMARK_JOBS=5000 \
odoo shell -c odoorc --log-level=warn < queue/queue_job/scripts/benchmark_jobs.py

enqueueing 5000 jobs in each of ['bench0', 'bench1', 'bench2'] (run '2026-09-12-16:27:01')...
  500 jobs enqueued in each of ['bench0', 'bench1', 'bench2']
  1000 jobs enqueued in each of ['bench0', 'bench1', 'bench2']
  1500 jobs enqueued in each of ['bench0', 'bench1', 'bench2']
  2000 jobs enqueued in each of ['bench0', 'bench1', 'bench2']
  2500 jobs enqueued in each of ['bench0', 'bench1', 'bench2']
  3000 jobs enqueued in each of ['bench0', 'bench1', 'bench2']
  3500 jobs enqueued in each of ['bench0', 'bench1', 'bench2']
  4000 jobs enqueued in each of ['bench0', 'bench1', 'bench2']
  4500 jobs enqueued in each of ['bench0', 'bench1', 'bench2']
  5000 jobs enqueued in each of ['bench0', 'bench1', 'bench2']
jobs enqueued
starting jobrunner with command: odoo -c odoorc -d bench0,bench1,bench2 --load=web,queue_job --log-level=warn --max-cron-threads=0

----configuration----
jobs per database: 5000 (duration 0.0s, channel 'root')
 workers=10 db_maxconn=64 max_cron_threads=0

polling run '2026-09-12-16:27:01' on ['bench0', 'bench1', 'bench2']...

[bench0] jobs:             5000 (5000 done, 0 failed)
[bench0] wall time:        96.9s
[bench0] throughput:       3095 jobs/minute
[bench0] avg queue time:   39.3638s
[bench0] p95 queue time:   95.1186s
[bench0] max queue time:   102.7449s
[bench0] avg exec time:    0.0069s
[bench0] p95 exec time:    0.0138s
[bench0] max connections:  12
[bench1] jobs:             5000 (5000 done, 0 failed)
[bench1] wall time:        96.9s
[bench1] throughput:       3095 jobs/minute
[bench1] avg queue time:   39.3701s
[bench1] p95 queue time:   95.1278s
[bench1] max queue time:   102.7542s
[bench1] avg exec time:    0.0069s
[bench1] p95 exec time:    0.0142s
[bench1] max connections:  12
[bench2] jobs:             5000 (5000 done, 0 failed)
[bench2] wall time:        96.9s
[bench2] throughput:       3095 jobs/minute
[bench2] avg queue time:   39.3768s
[bench2] p95 queue time:   95.1377s
[bench2] max queue time:   102.7701s
[bench2] avg exec time:    0.0068s
[bench2] p95 exec time:    0.0139s
[bench2] max connections:  12

Databases Aggregation
jobs:             15000 (15000 done, 0 failed)
wall time:        96.9s
throughput:       9284 jobs/minute
max connections (peak per db): {'bench0': 12, 'bench1': 12, 'bench2': 12}
stopping jobrunner...

----

dropdb --if-exists -p 6432 -hlocalhost bench0
dropdb --if-exists -p 6432 -hlocalhost bench1
dropdb --if-exists -p 6432 -hlocalhost bench2
createdb -p 6432 -hlocalhost -T benchmark_template bench0
createdb -p 6432 -hlocalhost -T benchmark_template bench1
createdb -p 6432 -hlocalhost -T benchmark_template bench2

ODOO_QUEUE_JOB_CHANNELS=root:8 \
BENCHMARK_DATABASES=bench0,bench1,bench2 \
BENCHMARK_JOBS=5000 \
odoo shell -c odoorc --log-level=warn < queue/queue_job/scripts/benchmark_jobs.py

enqueueing 5000 jobs in each of ['bench0', 'bench1', 'bench2'] (run '2026-09-12-16:29:35')...
  500 jobs enqueued in each of ['bench0', 'bench1', 'bench2']
  1000 jobs enqueued in each of ['bench0', 'bench1', 'bench2']
  1500 jobs enqueued in each of ['bench0', 'bench1', 'bench2']
  2000 jobs enqueued in each of ['bench0', 'bench1', 'bench2']
  2500 jobs enqueued in each of ['bench0', 'bench1', 'bench2']
  3000 jobs enqueued in each of ['bench0', 'bench1', 'bench2']
  3500 jobs enqueued in each of ['bench0', 'bench1', 'bench2']
  4000 jobs enqueued in each of ['bench0', 'bench1', 'bench2']
  4500 jobs enqueued in each of ['bench0', 'bench1', 'bench2']
  5000 jobs enqueued in each of ['bench0', 'bench1', 'bench2']
jobs enqueued
starting jobrunner with command: odoo -c odoorc -d bench0,bench1,bench2 --load=web,queue_job --log-level=warn --max-cron-threads=0

----configuration----
jobs per database: 5000 (duration 0.0s, channel 'root')
 workers=10 db_maxconn=64 max_cron_threads=0

polling run '2026-09-12-16:29:35' on ['bench0', 'bench1', 'bench2']...

[bench0] jobs:             5000 (5000 done, 0 failed)
[bench0] wall time:        98.8s
[bench0] throughput:       3036 jobs/minute
[bench0] avg queue time:   40.8788s
[bench0] p95 queue time:   97.1310s
[bench0] max queue time:   104.6529s
[bench0] avg exec time:    0.0068s
[bench0] p95 exec time:    0.0136s
[bench0] max connections:  12
[bench1] jobs:             5000 (5000 done, 0 failed)
[bench1] wall time:        98.8s
[bench1] throughput:       3036 jobs/minute
[bench1] avg queue time:   40.8852s
[bench1] p95 queue time:   97.1395s
[bench1] max queue time:   104.6648s
[bench1] avg exec time:    0.0071s
[bench1] p95 exec time:    0.0134s
[bench1] max connections:  12
[bench2] jobs:             5000 (5000 done, 0 failed)
[bench2] wall time:        98.8s
[bench2] throughput:       3035 jobs/minute
[bench2] avg queue time:   40.8919s
[bench2] p95 queue time:   97.1501s
[bench2] max queue time:   104.6811s
[bench2] avg exec time:    0.0070s
[bench2] p95 exec time:    0.0135s
[bench2] max connections:  12

Databases Aggregation
jobs:             15000 (15000 done, 0 failed)
wall time:        98.8s
throughput:       9105 jobs/minute
max connections (peak per db): {'bench0': 12, 'bench1': 12, 'bench2': 12}
stopping jobrunner...


per database channels runs
dropdb --if-exists -p 6432 -hlocalhost bench0
dropdb --if-exists -p 6432 -hlocalhost bench1
dropdb --if-exists -p 6432 -hlocalhost bench2
createdb -p 6432 -hlocalhost -T benchmark_template bench0
createdb -p 6432 -hlocalhost -T benchmark_template bench1
createdb -p 6432 -hlocalhost -T benchmark_template bench2

ODOO_QUEUE_JOB_MAX_CAPACITY=8 \
BENCHMARK_DATABASES=bench0,bench1,bench2 \
BENCHMARK_JOBS=5000 \
odoo shell -c odoorc --log-level=warn < queue/queue_job/scripts/benchmark_jobs.py

enqueueing 5000 jobs in each of ['bench0', 'bench1', 'bench2'] (run '2026-09-12-16:11:05')...
  500 jobs enqueued in each of ['bench0', 'bench1', 'bench2']
  1000 jobs enqueued in each of ['bench0', 'bench1', 'bench2']
  1500 jobs enqueued in each of ['bench0', 'bench1', 'bench2']
  2000 jobs enqueued in each of ['bench0', 'bench1', 'bench2']
  2500 jobs enqueued in each of ['bench0', 'bench1', 'bench2']
  3000 jobs enqueued in each of ['bench0', 'bench1', 'bench2']
  3500 jobs enqueued in each of ['bench0', 'bench1', 'bench2']
  4000 jobs enqueued in each of ['bench0', 'bench1', 'bench2']
  4500 jobs enqueued in each of ['bench0', 'bench1', 'bench2']
  5000 jobs enqueued in each of ['bench0', 'bench1', 'bench2']
jobs enqueued
starting jobrunner with command: odoo -c odoorc -d bench0,bench1,bench2 --load=web,queue_job --log-level=warn --max-cron-threads=0

----configuration----
jobs per database: 5000 (duration 0.0s, channel 'root')
 workers=10 db_maxconn=64 max_cron_threads=0

polling run '2026-09-12-16:11:05' on ['bench0', 'bench1', 'bench2']...

[bench0] jobs:             5000 (5000 done, 0 failed)
[bench0] wall time:        91.3s
[bench0] throughput:       3285 jobs/minute
[bench0] avg queue time:   37.0694s
[bench0] p95 queue time:   89.6072s
[bench0] max queue time:   97.1411s
[bench0] avg exec time:    0.0064s
[bench0] p95 exec time:    0.0134s
[bench0] max connections:  12
[bench1] jobs:             5000 (5000 done, 0 failed)
[bench1] wall time:        91.3s
[bench1] throughput:       3285 jobs/minute
[bench1] avg queue time:   37.0754s
[bench1] p95 queue time:   89.6169s
[bench1] max queue time:   97.1508s
[bench1] avg exec time:    0.0064s
[bench1] p95 exec time:    0.0136s
[bench1] max connections:  13
[bench2] jobs:             5000 (5000 done, 0 failed)
[bench2] wall time:        91.3s
[bench2] throughput:       3285 jobs/minute
[bench2] avg queue time:   37.0818s
[bench2] p95 queue time:   89.6261s
[bench2] max queue time:   97.1608s
[bench2] avg exec time:    0.0064s
[bench2] p95 exec time:    0.0135s
[bench2] max connections:  12

Databases Aggregation
jobs:             15000 (15000 done, 0 failed)
wall time:        91.3s
throughput:       9854 jobs/minute
max connections (peak per db): {'bench0': 12, 'bench1': 13, 'bench2': 12}
stopping jobrunner...


----

dropdb --if-exists -p 6432 -hlocalhost bench0
dropdb --if-exists -p 6432 -hlocalhost bench1
dropdb --if-exists -p 6432 -hlocalhost bench2
createdb -p 6432 -hlocalhost -T benchmark_template bench0
createdb -p 6432 -hlocalhost -T benchmark_template bench1
createdb -p 6432 -hlocalhost -T benchmark_template bench2

ODOO_QUEUE_JOB_MAX_CAPACITY=8 \
BENCHMARK_DATABASES=bench0,bench1,bench2 \
BENCHMARK_JOBS=5000 \
odoo shell -c odoorc --log-level=warn < queue/queue_job/scripts/benchmark_jobs.py

enqueueing 5000 jobs in each of ['bench0', 'bench1', 'bench2'] (run '2026-09-12-16:14:14')...
  500 jobs enqueued in each of ['bench0', 'bench1', 'bench2']
  1000 jobs enqueued in each of ['bench0', 'bench1', 'bench2']
  1500 jobs enqueued in each of ['bench0', 'bench1', 'bench2']
  2000 jobs enqueued in each of ['bench0', 'bench1', 'bench2']
  2500 jobs enqueued in each of ['bench0', 'bench1', 'bench2']
  3000 jobs enqueued in each of ['bench0', 'bench1', 'bench2']
  3500 jobs enqueued in each of ['bench0', 'bench1', 'bench2']
  4000 jobs enqueued in each of ['bench0', 'bench1', 'bench2']
  4500 jobs enqueued in each of ['bench0', 'bench1', 'bench2']
  5000 jobs enqueued in each of ['bench0', 'bench1', 'bench2']
jobs enqueued
starting jobrunner with command: odoo -c odoorc -d bench0,bench1,bench2 --load=web,queue_job --log-level=warn --max-cron-threads=0

----configuration----
jobs per database: 5000 (duration 0.0s, channel 'root')
 workers=10 db_maxconn=64 max_cron_threads=0

polling run '2026-09-12-16:14:14' on ['bench0', 'bench1', 'bench2']...

[bench0] jobs:             5000 (5000 done, 0 failed)
[bench0] wall time:        96.9s
[bench0] throughput:       3098 jobs/minute
[bench0] avg queue time:   39.6791s
[bench0] p95 queue time:   95.1035s
[bench0] max queue time:   102.6731s
[bench0] avg exec time:    0.0068s
[bench0] p95 exec time:    0.0142s
[bench0] max connections:  12
[bench1] jobs:             5000 (5000 done, 0 failed)
[bench1] wall time:        96.9s
[bench1] throughput:       3097 jobs/minute
[bench1] avg queue time:   39.6854s
[bench1] p95 queue time:   95.1120s
[bench1] max queue time:   102.6824s
[bench1] avg exec time:    0.0068s
[bench1] p95 exec time:    0.0143s
[bench1] max connections:  12
[bench2] jobs:             5000 (5000 done, 0 failed)
[bench2] wall time:        96.9s
[bench2] throughput:       3097 jobs/minute
[bench2] avg queue time:   39.6921s
[bench2] p95 queue time:   95.1301s
[bench2] max queue time:   102.6939s
[bench2] avg exec time:    0.0068s
[bench2] p95 exec time:    0.0142s
[bench2] max connections:  12

Databases Aggregation
jobs:             15000 (15000 done, 0 failed)
wall time:        96.9s
throughput:       9291 jobs/minute
max connections (peak per db): {'bench0': 12, 'bench1': 12, 'bench2': 12}
stopping jobrunner...

----

dropdb --if-exists -p 6432 -hlocalhost bench0
dropdb --if-exists -p 6432 -hlocalhost bench1
dropdb --if-exists -p 6432 -hlocalhost bench2
createdb -p 6432 -hlocalhost -T benchmark_template bench0
createdb -p 6432 -hlocalhost -T benchmark_template bench1
createdb -p 6432 -hlocalhost -T benchmark_template bench2

ODOO_QUEUE_JOB_MAX_CAPACITY=8 \
BENCHMARK_DATABASES=bench0,bench1,bench2 \
BENCHMARK_JOBS=5000 \
odoo shell -c odoorc --log-level=warn < queue/queue_job/scripts/benchmark_jobs.py

enqueueing 5000 jobs in each of ['bench0', 'bench1', 'bench2'] (run '2026-09-12-16:16:42')...
  500 jobs enqueued in each of ['bench0', 'bench1', 'bench2']
  1000 jobs enqueued in each of ['bench0', 'bench1', 'bench2']
  1500 jobs enqueued in each of ['bench0', 'bench1', 'bench2']
  2000 jobs enqueued in each of ['bench0', 'bench1', 'bench2']
  2500 jobs enqueued in each of ['bench0', 'bench1', 'bench2']
  3000 jobs enqueued in each of ['bench0', 'bench1', 'bench2']
  3500 jobs enqueued in each of ['bench0', 'bench1', 'bench2']
  4000 jobs enqueued in each of ['bench0', 'bench1', 'bench2']
  4500 jobs enqueued in each of ['bench0', 'bench1', 'bench2']
  5000 jobs enqueued in each of ['bench0', 'bench1', 'bench2']
jobs enqueued
starting jobrunner with command: odoo -c odoorc -d bench0,bench1,bench2 --load=web,queue_job --log-level=warn --max-cron-threads=0

----configuration----
jobs per database: 5000 (duration 0.0s, channel 'root')
 workers=10 db_maxconn=64 max_cron_threads=0

polling run '2026-09-12-16:16:42' on ['bench0', 'bench1', 'bench2']...

[bench0] jobs:             5000 (5000 done, 0 failed)
[bench0] wall time:        92.3s
[bench0] throughput:       3252 jobs/minute
[bench0] avg queue time:   37.5091s
[bench0] p95 queue time:   90.5398s
[bench0] max queue time:   98.0871s
[bench0] avg exec time:    0.0064s
[bench0] p95 exec time:    0.0135s
[bench0] max connections:  12
[bench1] jobs:             5000 (5000 done, 0 failed)
[bench1] wall time:        92.3s
[bench1] throughput:       3251 jobs/minute
[bench1] avg queue time:   37.5152s
[bench1] p95 queue time:   90.5478s
[bench1] max queue time:   98.0970s
[bench1] avg exec time:    0.0065s
[bench1] p95 exec time:    0.0135s
[bench1] max connections:  12
[bench2] jobs:             5000 (5000 done, 0 failed)
[bench2] wall time:        92.3s
[bench2] throughput:       3251 jobs/minute
[bench2] avg queue time:   37.5214s
[bench2] p95 queue time:   90.5573s
[bench2] max queue time:   98.1061s
[bench2] avg exec time:    0.0065s
[bench2] p95 exec time:    0.0137s
[bench2] max connections:  12

Databases Aggregation
jobs:             15000 (15000 done, 0 failed)
wall time:        92.3s
throughput:       9753 jobs/minute
max connections (peak per db): {'bench0': 12, 'bench1': 12, 'bench2': 12}
stopping jobrunner...

----

dropdb --if-exists -p 6432 -hlocalhost bench0
dropdb --if-exists -p 6432 -hlocalhost bench1
dropdb --if-exists -p 6432 -hlocalhost bench2
createdb -p 6432 -hlocalhost -T benchmark_template bench0
createdb -p 6432 -hlocalhost -T benchmark_template bench1
createdb -p 6432 -hlocalhost -T benchmark_template bench2

ODOO_QUEUE_JOB_MAX_CAPACITY=8 \
BENCHMARK_DATABASES=bench0,bench1,bench2 \
BENCHMARK_JOBS=5000 \
odoo shell -c odoorc --log-level=warn < queue/queue_job/scripts/benchmark_jobs.py

enqueueing 5000 jobs in each of ['bench0', 'bench1', 'bench2'] (run '2026-09-12-16:19:23')...
  500 jobs enqueued in each of ['bench0', 'bench1', 'bench2']
  1000 jobs enqueued in each of ['bench0', 'bench1', 'bench2']
  1500 jobs enqueued in each of ['bench0', 'bench1', 'bench2']
  2000 jobs enqueued in each of ['bench0', 'bench1', 'bench2']
  2500 jobs enqueued in each of ['bench0', 'bench1', 'bench2']
  3000 jobs enqueued in each of ['bench0', 'bench1', 'bench2']
  3500 jobs enqueued in each of ['bench0', 'bench1', 'bench2']
  4000 jobs enqueued in each of ['bench0', 'bench1', 'bench2']
  4500 jobs enqueued in each of ['bench0', 'bench1', 'bench2']
  5000 jobs enqueued in each of ['bench0', 'bench1', 'bench2']
jobs enqueued
starting jobrunner with command: odoo -c odoorc -d bench0,bench1,bench2 --load=web,queue_job --log-level=warn --max-cron-threads=0

----configuration----
jobs per database: 5000 (duration 0.0s, channel 'root')
 workers=10 db_maxconn=64 max_cron_threads=0

polling run '2026-09-12-16:19:23' on ['bench0', 'bench1', 'bench2']...

[bench0] jobs:             5000 (5000 done, 0 failed)
[bench0] wall time:        95.0s
[bench0] throughput:       3157 jobs/minute
[bench0] avg queue time:   38.7851s
[bench0] p95 queue time:   93.2830s
[bench0] max queue time:   100.8591s
[bench0] avg exec time:    0.0066s
[bench0] p95 exec time:    0.0139s
[bench0] max connections:  12
[bench1] jobs:             5000 (5000 done, 0 failed)
[bench1] wall time:        95.0s
[bench1] throughput:       3156 jobs/minute
[bench1] avg queue time:   38.7913s
[bench1] p95 queue time:   93.2924s
[bench1] max queue time:   100.8688s
[bench1] avg exec time:    0.0066s
[bench1] p95 exec time:    0.0139s
[bench1] max connections:  12
[bench2] jobs:             5000 (5000 done, 0 failed)
[bench2] wall time:        95.1s
[bench2] throughput:       3156 jobs/minute
[bench2] avg queue time:   38.7978s
[bench2] p95 queue time:   93.3027s
[bench2] max queue time:   100.8858s
[bench2] avg exec time:    0.0066s
[bench2] p95 exec time:    0.0138s
[bench2] max connections:  12

Databases Aggregation
jobs:             15000 (15000 done, 0 failed)
wall time:        95.1s
throughput:       9467 jobs/minute
max connections (peak per db): {'bench0': 12, 'bench1': 12, 'bench2': 12}
stopping jobrunner...

Beside the throughput, the round robin seems to show good results as the queue time is almost equal for each database (of course, under the conditions of this benchmark, yet to prove in real life).

@guewen
guewen marked this pull request as ready for review September 12, 2026 17:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants