Skip to content

Commit bed97f5

Browse files
committed
[ADD] queue_job: implement per-database channels
1 parent e237643 commit bed97f5

7 files changed

Lines changed: 1140 additions & 67 deletions

File tree

queue_job/README.rst

Lines changed: 114 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -123,22 +123,100 @@ Be sure to have the ``requests`` library.
123123
Configuration
124124
=============
125125

126-
- Using environment variables and command line:
126+
There are two ways to configure the job runner:
127+
128+
Set ``channels`` (or ``ODOO_QUEUE_JOB_CHANNELS``) and every database
129+
shares the same channel tree (we will call it server-side channels):
130+
131+
.. code:: ini
132+
133+
[queue_job]
134+
channels = root:10,root.priority:3,root.slow:1
135+
136+
Leave ``channels`` unset and set ``max_capacity`` instead. The job
137+
runner then builds a separate channel tree for each database, based on
138+
the *Job Channels* configured on each database (we will call it
139+
per-database channels).
140+
141+
.. code:: ini
142+
143+
[queue_job]
144+
max_capacity = 10
145+
146+
``channels`` always has precedence over ``max_capacity``. If
147+
``channels`` is set, the per-database configuration is not used. If
148+
neither ``channels`` nor ``max_capacity`` are set, the default execution
149+
mode is per-database channels with a ``max_capacity`` of 1.
150+
151+
In the per-database mode, channels are configured from the *Job
152+
Channels* menu (or by XML data, see the Usage) instead of a global
153+
configuration string.
154+
155+
Alongside ``max_capacity``, a global configuration ``db_max_capacity``
156+
can be set. It represents the max number of jobs executed at the same
157+
time for a single database (capped by the ``max_capacity`` anyway):
158+
159+
.. code:: ini
160+
161+
[queue_job]
162+
max_capacity = 10
163+
db_max_capacity = 3 # no more than 3 simultaneous jobs per database
164+
165+
``db_max_capacity`` may be an integer or a pattern such as
166+
``prod_*:20,staging:2,*:5``, where the first match wins. When using a
167+
pattern, unmatched databases can be configured by a global pattern at
168+
the end (``*:n``), otherwise they will use the ``max_capacity``.
169+
170+
The root channel capacity of a database can still be set independently,
171+
however, will in any case be capped by the global ``max_capacity`` and
172+
``db_max_capacity`` parameters.
173+
174+
When set to 0, ``max_capacity`` or ``db_max_capacity`` means there is no
175+
jobs executed.
176+
177+
Editing a channel's capacity, sequential flag, throttle or set it to
178+
pause from the *Job Channels* menu **is applied live on the job
179+
runner**.
180+
181+
.. note::
182+
183+
A new database still needs the jobrunner to be restarted.
184+
185+
When using the server-side channels, the configuration is static and
186+
loaded at startup of the jobrunner.
187+
188+
The execution of channels by the job runner is defined by:
189+
190+
- ``capacity``: max number of jobs running at once in the channel (``0``
191+
means no limit of its own, the parent channel and ``max_capacity`` or
192+
``db_max_capacity`` still apply)
193+
- ``sequential``: jobs run one after the other, and a failed job blocks
194+
the channel (requires a capacity of 1)
195+
- ``throttle``: minimum delay, in seconds, between the start of two jobs
196+
- ``paused``: stop running jobs in this channel and its sub-channels
197+
198+
**Job Runner Configuration Parameters**
199+
200+
- Using environment variables:
127201

128202
- Adjust environment variables (optional):
129203

130-
- ``ODOO_QUEUE_JOB_CHANNELS=root:4`` or any other channels
131-
configuration. The default is ``root:1``
204+
- ``ODOO_QUEUE_JOB_CHANNELS=root:4`` or any other channels for
205+
server-side channels
206+
- ``ODOO_QUEUE_JOB_MAX_CAPACITY=4``, max number of concurrent jobs
207+
(not used if ``ODOO_QUEUE_JOB_CHANNELS`` is set) for per-database
208+
channels
209+
- ``ODOO_QUEUE_JOB_DB_MAX_CAPACITY=2``, max number of concurrent
210+
jobs per DB (not used if ``ODOO_QUEUE_JOB_CHANNELS`` is set)
132211
- ``ODOO_QUEUE_JOB_PORT=8069``, default ``--http-port``
133212
- ``ODOO_QUEUE_JOB_SCHEME=https``, default ``http``
134213
- ``ODOO_QUEUE_JOB_HOST=load-balancer``, default
135214
``--http-interface`` or ``localhost`` if unset
136215
- ``ODOO_QUEUE_JOB_HTTP_AUTH_USER=jobrunner``, default empty
137216
- ``ODOO_QUEUE_JOB_HTTP_AUTH_PASSWORD=s3cr3t``, default empty
138-
- Start Odoo with ``--load=web,queue_job`` and ``--workers`` greater
139-
than 1. [1]_
140217

141-
- Using the Odoo configuration file:
218+
- Using the Odoo configuration file (set either ``channels``, either
219+
``max_capacity`` and/or ``db_max_capacity``)
142220

143221
.. code:: ini
144222
@@ -150,12 +228,26 @@ Configuration
150228
(...)
151229
[queue_job]
152230
channels = root:2
231+
max_capacity = 8
232+
db_max_capacity = 3
153233
scheme = https
154234
host = load-balancer
155235
port = 443
156236
http_auth_user = jobrunner
157237
http_auth_password = s3cr3t
158238
239+
- Odoo has to be started with ``queue_job`` as server-wide module,
240+
either using the command line option ``--load=web,queue_job``, either
241+
by setting it in the Odoo configuration file, and ``--workers``
242+
greater than 1. [1]_
243+
244+
.. code:: ini
245+
246+
[options]
247+
(...)
248+
workers = 6
249+
server_wide_modules = web,queue_job
250+
159251
- Confirm the runner is starting correctly by checking the odoo log
160252
file:
161253

@@ -166,14 +258,25 @@ Configuration
166258
...INFO...queue_job.jobrunner.runner: queue job runner ready for db <dbname>
167259
...INFO...queue_job.jobrunner.runner: database connections ready
168260

169-
- Create jobs (eg using ``base_import_async``) and observe they start
170-
immediately and in parallel.
261+
- Create jobs (you can create test jobs by opening
262+
``https://yourodoourl/queue_job/create_test_job``) and observe they
263+
start immediately and in parallel.
171264
- Tip: to enable debug logging for the queue job, use
172265
``--log-handler=odoo.addons.queue_job:DEBUG``
173266

174-
- Jobs that remain in ``enqueued`` or ``started`` state (because, for
175-
instance, their worker has been killed) will be automatically
176-
re-queued.
267+
**Migrating from server-side channels to per-database channels**
268+
269+
As long as ``channels`` (or ``ODOO_QUEUE_JOB_CHANNELS``) is set, the job
270+
runner keeps using the server-side channels.
271+
272+
To move to channels per database:
273+
274+
1. Configure the channels you need on each database, from the *Job
275+
Channels* menu: capacity, sequential, throttle, pause
276+
2. Once this configuration is done, remove the ``channels`` options and
277+
set ``max_capacity`` (and optionally ``db_max_capacity``) instead (or
278+
their corresponding environment variables)
279+
3. Restart the job runner
177280

178281
.. [1]
179282
It works with the threaded Odoo server too, although this way of

queue_job/jobrunner/__init__.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
queue_job_config = config.misc.get("queue_job", {})
2121

2222

23-
from .runner import QueueJobRunner, _channels
23+
from .runner import QueueJobRunner, _channels, _max_capacity
2424

2525
_logger = logging.getLogger(__name__)
2626

@@ -87,7 +87,12 @@ def signal_time_expired_handler(self, n, stack):
8787

8888

8989
def _is_runner_enabled():
90-
return not _channels().strip().startswith("root:0")
90+
channel_config = _channels()
91+
if channel_config and channel_config.strip().startswith("root:0"):
92+
return False
93+
elif channel_config:
94+
return True
95+
return _max_capacity() != 0
9196

9297

9398
def _start_runner_thread(server_type):
@@ -100,7 +105,8 @@ def _start_runner_thread(server_type):
100105
else:
101106
_logger.info(
102107
"jobrunner thread (in %s) NOT started, "
103-
"because the root channel's capacity is set to 0",
108+
"because the root channel's capacity or the max capacity "
109+
"is set to 0",
104110
server_type,
105111
)
106112

queue_job/jobrunner/channels.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html)
44
import logging
55
from collections import namedtuple
6+
from dataclasses import asdict, dataclass
67
from functools import total_ordering
78
from heapq import heappop, heappush
89
from weakref import WeakValueDictionary
@@ -17,6 +18,19 @@
1718
_logger = logging.getLogger(__name__)
1819

1920

21+
@dataclass
22+
class ChannelConfig:
23+
"""Configuration of a channel"""
24+
25+
name: str
26+
capacity: int = 0
27+
sequential: bool = False
28+
throttle: int = 0
29+
paused: bool = False
30+
capacity_default: int = 0
31+
sequential_default: bool = False
32+
33+
2034
class PriorityQueue:
2135
"""A priority queue that supports removing arbitrary objects.
2236
@@ -1024,6 +1038,11 @@ def simple_configure(self, config_string):
10241038
for config in ChannelManager.parse_simple_config(config_string):
10251039
self.get_channel_from_config(config)
10261040

1041+
def configure(self, configs):
1042+
"""Configure the channel manager from list of :class:`ChannelConfig`"""
1043+
for config in configs:
1044+
self.get_channel_from_config(asdict(config))
1045+
10271046
def get_channel_from_config(self, config):
10281047
"""Return a Channel object from a parsed configuration.
10291048
@@ -1174,3 +1193,8 @@ def get_jobs_to_run(self, now):
11741193

11751194
def get_wakeup_time(self):
11761195
return self._root_channel.get_wakeup_time()
1196+
1197+
@property
1198+
def running_count(self) -> int:
1199+
"""Number of jobs currently running"""
1200+
return len(self._root_channel._running)

0 commit comments

Comments
 (0)