You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Mongo Redis Queue - A distributed worker task queue in Python
5
4
5
+
Full documentation is available on [readthedocs]()
6
+
6
7
/!\ MRQ is not yet ready for public use. Soon!
7
8
8
-
Why?
9
-
====
9
+
# Why?
10
10
11
11
MRQ is an opinionated task queue. It aims to be simple and beautiful like http://python-rq.org while having performance close to http://celeryproject.org
12
12
@@ -29,299 +29,15 @@ The main features of MRQ are:
29
29
***Integrated memory leak debugger:** Track down jobs leaking memory and find the leaks with objgraph.
30
30
31
31
32
-
33
-
Dashboard
34
-
=========
35
-
36
-
A strong focus was put on the tools and particularly the dashboard. After all it is what you will work with most of the time!
32
+
# Dashboard Screnshots
37
33
38
34

39
35
40
36

41
37
42
-
There are too much features on the dashboard to list, but the goal is to have complete visibility and control over what your workers are doing!
43
-
44
-
45
-
Design
46
-
======
47
-
48
-
A talk+slides about MRQ's design is upcoming.
49
-
50
-
A couple things to know:
51
-
- We use Redis as a main queue for task IDs
52
-
- We store metadata on the tasks in MongoDB so they can be browsable and managed more easily.
53
-
54
-
55
-
56
-
Performance
57
-
===========
58
-
59
-
On a MacbookPro, we see 1300 jobs/second in a single worker process with very simple jobs that store results, to measure the overhead of MRQ. However what we are really measuring there is MongoDB's write performance.
60
-
61
-
62
-
Install & dependencies
63
-
======================
64
-
65
-
MRQ can be installed via PIP:
66
-
67
-
```pip install mrq```
68
-
69
-
MRQ has only been tested with Python 2.7+. External service dependencies are MongoDB >= 2.4 and Redis >= 2.6 (we use LUA scripting to boost performance and provide extra safety).
70
-
71
-
You will need [Docker](http://docker.io) to run our unit tests. Our [Dockerfile](https://github.com/pricingassistant/mrq/blob/master/Dockerfile) is actually a good way to see a complete list of dependencies, including dev tools like graphviz for memleak images.
72
-
73
-
You may want to convert your logs db to a capped collection : ie. run db.runCommand({"convertToCapped": "mrq_jobs", "size": 10737418240})
74
-
75
-
76
-
Configuration
77
-
=============
78
-
79
-
Check all the [available config options](mrq/config.py)
80
-
81
-
For each of these values, configuration is loaded in this order by default:
- Environment variables prefixed by MRQ_ (`MRQ_REDIS=redis://127.0.0.1:6379 mrq-worker`)
84
-
- Python variables in a config file, by default `mrq-config.py` (`REDIS="redis://127.0.0.1:6379"` in this file)
85
-
86
-
Most of the time, you want to set all your configuration in a `mrq-config.py` file in the directory where you will launch your workers, and override some of it from the command line.
87
-
88
-
On Heroku, environment variables are very handy because they can be set like `heroku config:set MRQ_REDIS=redis://127.0.0.1:6379`
89
-
90
-
91
-
Command-line use
92
-
================
93
-
94
-
All the command-line tools support a set of common configuration flags, defined in [config.py](https://github.com/pricingassistant/mrq/blob/master/mrq/config.py). Use --help with any of them to see the full list.
95
-
96
-
-`mrq-worker` starts a worker
97
-
-`mrq-dashboard` starts the web dashboard on the default port
98
-
-`mrq-run` runs a task. If you add the `--async` option that will enqueue it to be later ran by a worker
# This will requeue jobs 'lost' between redis.blpop() and mongo.update(status=started).
133
-
# This can happen only when the worker is killed brutally in the middle of dequeue_jobs()
134
-
{
135
-
"path": "mrq.basetasks.cleaning.RequeueLostJobs",
136
-
"params": {},
137
-
"interval": 24 * 3600
138
-
}
139
-
]
140
-
```
141
-
142
-
Obviously this implies that all your jobs should be *idempotent*, meaning that they could be done multiple times, maybe partially, without breaking your app. This is a very good design to enforce for your whole task queue, though you can still manage locks yourself in your code that make sure a block of code will only run once.
143
-
144
-
145
-
Raw queues
146
-
==========
147
-
148
-
With regular queues, MRQ stores the task metadata in MongoDB and the task IDs in a Redis list. This design allows a good compromise between performance and visibility.
149
-
150
-
Raw queues give you more performance and some new features in exchange for a bit less visibility. In their case, only the parameters of a task are stored in serialized form in Redis when queued, and they are inserted in MongoDB only after being dequeued by a worker.
151
-
152
-
There are 4 types of raw queues. The type of a queue is determined by a suffix in its name:
153
-
154
-
*```_raw``` : The simpliest raw queue, stored in a Redis LIST.
155
-
*```_set``` : Stored in a Redis SET. Gives you the ability to have "unique" tasks: only one (task, parameters) couple can be queued at a time.
156
-
*```_sorted_set``` : The most powerful MRQ queue type, stored in a Redis ZSET. Allows you to order (and re-order) the tasks to be dequeued. Like ```_set```, task parameters will be unique.
157
-
*```_timed_set``` : A special case of ```_sorted_set```, where tasks are sorted with a UNIX timestamp. This means you can schedule tasks to be executed at a precise time in the future.
158
-
159
-
Raw queues need a special entry in the configuration to unserialize their "raw" parameters in a regular dict of parameters. They also need to be linked to a regular queue ("default" if none) for providing visibility and retries, after they are dequeued from the raw queue.
160
-
161
-
This is an example of raw queue configuration:
162
-
163
-
```python
164
-
RAW_QUEUES= {
165
-
"myqueue_raw": {
166
-
"job_factory": lambdarawparam: {
167
-
"path": "tests.tasks.general.Add",
168
-
"params": {
169
-
"a": int(rawparam.split("")[0]),
170
-
"b": int(rawparam.split("")[1])
171
-
}
172
-
},
173
-
"retry_queue": "high"
174
-
}
175
-
}
176
-
```
177
-
178
-
This task adds two integers. To queue tasks, you can do from the code:
179
-
180
-
```python
181
-
from mrq.queue import send_raw_tasks
182
-
183
-
send_raw_tasks("myqueue_raw", [
184
-
["1 1"],
185
-
["42 8"]
186
-
])
187
-
```
188
-
189
-
To run them, start a worker listening to both queues:
190
-
191
-
```
192
-
$ mrq-worker high myqueue_raw
193
-
```
194
-
195
-
Queueing on timed sets is a bit different, you can pass unix timestamps directly:
196
-
197
-
```python
198
-
from mrq.queue import send_raw_tasks
199
-
import time
200
-
201
-
send_raw_tasks("myqueue_timed_set", {
202
-
"rawparam_xxx": time.time(),
203
-
"rawparam_yyy": time.time() +3600# Do this in an hour
204
-
})
205
-
```
206
-
207
-
For more examples of raw queue configuration, check https://github.com/pricingassistant/mrq/blob/master/tests/fixtures/config-raw1.py
208
-
209
-
210
-
Hunting memory leaks
211
-
====================
212
-
213
-
Memory leaks can be a big issue with gevent workers because several tasks share the same python process.
214
-
215
-
Thankfully, MRQ provides tools to track down such issues. Memory usage of each worker is graphed in the dashboard and makes it easy to see if memory leaks are happening.
216
-
217
-
When a worker has a steadily growing memory usage, here are the steps to find the leak:
218
-
219
-
* Check which jobs are running on this worker and try to isolate which of them is leaking and on which queue
220
-
* Start a dedicated worker with ```--trace_memory --gevent 1``` on the same queue : This will start a worker doing one job at a time with memory profiling enabled. After each job you should see a report of leaked object types.
221
-
* Find the most unique type in the list (usually not 'list' or 'dict') and restart the worker with ```--trace_memory --gevent 1 --trace_memory_type=XXX --trace_memory_output_dir=memdbg``` (after creating the directory memdbg).
222
-
* There you will find a graph for each task generated by [objgraph](https://mg.pov.lt/objgraph/) which is incredibly helpful to track down the leak.
223
-
224
-
225
-
Simulating network latency
226
-
==========================
227
-
228
-
Sometimes it is helpful in local development to simulate an environment with higher network latency.
229
-
230
-
To do this we added a ```--add_network_latency=0.1``` config option that will add (in this case) a random delay between 0 and 0.1 seconds to every network call.
231
-
232
-
233
-
Worker concurrency
234
-
==================
235
-
236
-
The default is to run tasks one at a time. You should obviously change this behaviour to use Gevent's full capabilities with something like:
237
-
238
-
`mrq-worker --processes 3 --gevent 10`
239
-
240
-
This will start 30 greenlets over 3 UNIX processes. Each of them will run 10 jobs at the same time.
241
-
242
-
As soon as you use the `--processes` option (even with `--processes=1`) then supervisord will be used to control the processes. It is quite useful to manage long-running instances.
243
-
244
-
On Heroku's 512M dynos, we have found that for IO-bound jobs, `--processes 4 --gevent 30` may be a good setting.
245
-
246
-
247
-
Metrics & Graphite
248
-
==================
249
-
250
-
MRQ doesn't support sending metrics to Graphite out of the box but makes it extremely easy to do so.
251
-
252
-
All you have to do is add this hook in your mrq-config file:
if any([name.startswith(m) for m in whitelisted_metrics]):
269
-
_graphite_client.send(name, incr)
270
-
271
-
272
-
```
273
-
274
-
If you have another monitoring system you can plug anything in this hook to connect to it!
275
-
276
-
277
-
Tests
278
-
=====
279
-
280
-
Testing is done inside a Docker container for maximum repeatability.
281
-
We don't use Travis-CI or friends because we need to be able to kill our process dependencies (MongoDB, Redis, ...) on demand.
282
-
283
-
Therefore you need to ([install docker](https://www.docker.io/gettingstarted/#h_installation)) to run the tests.
284
-
If you're not on an os that supports natively docker, don't forget to start up your VM and ssh into it.
285
-
286
-
```
287
-
$ make test
288
-
```
289
-
290
-
You can also open a shell inside the docker (just like you would enter in a virtualenv) with:
291
-
292
-
```
293
-
$ make docker (if it wasn't build before)
294
-
$ make ssh
295
-
```
296
-
297
-
298
-
PyPy
299
-
====
300
-
301
-
Earlier in its development MRQ was tested successfully on PyPy but we are waiting for better PyPy+gevent support to continue working on it, as performance was worse than CPython.
302
-
303
-
304
-
Useful third-party utils
305
-
========================
306
-
307
-
*http://superlance.readthedocs.org/en/latest/
308
-
38
+
# Get Started
309
39
310
-
Credits
311
-
=======
312
40
313
-
Inspirations:
314
-
* RQ
315
-
* Celery
316
41
317
-
JS libraries used in the Dashboard:
318
-
*http://backbonejs.org
319
-
*http://underscorejs.org
320
-
*http://requirejs.org
321
-
*http://momentjs.com
322
-
*http://jquery.com
323
-
*http://datatables.net
324
-
*https://github.com/Jowin/Datatables-Bootstrap3/
325
-
*https://github.com/twbs/bootstrap
42
+
# More
326
43
327
-
... as well as all the Python modules in requirements.txt!
All the command-line tools support a set of common configuration flags, defined in [config.py](https://github.com/pricingassistant/mrq/blob/master/mrq/config.py). Use --help with any of them to see the full list.
5
+
6
+
-`mrq-worker` starts a worker
7
+
-`mrq-dashboard` starts the web dashboard on the default port
8
+
-`mrq-run` runs a task. If you add the `--async` option that will enqueue it to be later ran by a worker
The default is to run tasks one at a time. You should obviously change this behaviour to use Gevent's full capabilities with something like:
6
+
7
+
`mrq-worker --processes 3 --gevent 10`
8
+
9
+
This will start 30 greenlets over 3 UNIX processes. Each of them will run 10 jobs at the same time.
10
+
11
+
As soon as you use the `--processes` option (even with `--processes=1`) then supervisord will be used to control the processes. It is quite useful to manage long-running instances.
12
+
13
+
14
+
## Simulating network latency
15
+
16
+
Sometimes it is helpful in local development to simulate an environment with higher network latency.
17
+
18
+
To do this we added a ```--add_network_latency=0.1``` config option that will add (in this case) a random delay between 0 and 0.1 seconds to every network call.
- Environment variables prefixed by MRQ_ (`MRQ_REDIS=redis://127.0.0.1:6379 mrq-worker`)
8
+
- Python variables in a config file, by default `mrq-config.py` (`REDIS="redis://127.0.0.1:6379"` in this file)
9
+
10
+
Most of the time, you want to set all your configuration in a `mrq-config.py` file in the directory where you will launch your workers, and override some of it from the command line.
11
+
12
+
On Heroku, environment variables are very handy because they can be set like `heroku config:set MRQ_REDIS=redis://127.0.0.1:6379`
0 commit comments