forked from earl/beanstalkc
-
Notifications
You must be signed in to change notification settings - Fork 1
/
TUTORIAL
460 lines (327 loc) · 13.2 KB
/
TUTORIAL
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
Welcome, dear stranger, to a tour de force through beanstalkd's capabilities.
Say hello to your fellow travel companion, the beanstalkc client library for
Python. You'll get to know each other fairly well during this trip, so better
start off on a friendly note. And now, let's go!
Getting Started
---------------
You'll need beanstalkd listening at port 14711 to follow along. So simply start
it using: `beanstalkd -l 127.0.0.1 -p 14711`
Besides having beanstalkc installed, you'll typically also need PyYAML. If you
insist, you can also use beanstalkc without PyYAML. For more details see
Appendix A of this tutorial.
To use beanstalkc we have to import the library and set up a connection to an
(already running) beanstalkd server:
>>> import beanstalkc
>>> beanstalk = beanstalkc.Connection(host='localhost', port=14711)
If we leave out the `host` and/or `port` parameters, `'localhost'` and `11300`
would be used as defaults, respectively.
Basic Operation
---------------
Now that we have a connection set up, we can enqueue jobs:
>>> beanstalk.put('hey!')
1
Or we can request jobs:
>>> job = beanstalk.reserve()
>>> job.body
'hey!'
Once we are done with processing a job, we have to mark it as done, otherwise
jobs are re-queued by beanstalkd after a "time to run" (120 seconds, per
default) is surpassed. A job is marked as done, by calling `delete`:
>>> job.delete()
`reserve` blocks until a job is ready, possibly forever. If that is not desired,
we can invoke `reserve` with a timeout (in seconds) how long we want to wait to
receive a job. If such a `reserve` times out, it will return `None`:
>>> beanstalk.reserve(timeout=0) is None
True
If you use a timeout of 0, `reserve` will immediately return either a job or
`None`.
Note that beanstalkc requires job bodies to be strings, conversion to/from
strings is left up to you:
>>> beanstalk.put(42)
Traceback (most recent call last):
...
AssertionError: Job body must be a str instance
Tube Management
---------------
A single beanstalkd server can provide many different queues, called "tubes" in
beanstalkd. To see all available tubes:
>>> beanstalk.tubes()
['default']
A beanstalkd client can choose one tube into which its job are put. This is the
tube "used" by the client. To see what tube you are currently using:
>>> beanstalk.using()
'default'
Unless told otherwise, a client uses the 'default' tube. If you want to use a
different tube:
>>> beanstalk.use('foo')
'foo'
>>> beanstalk.using()
'foo'
If you decide to use a tube, that does not yet exist, the tube is automatically
created by beanstalkd:
>>> beanstalk.tubes()
['default', 'foo']
Of course, you can always switch back to the default tube. Tubes that don't have
any client using or watching, vanish automatically:
>>> beanstalk.use('default')
'default'
>>> beanstalk.using()
'default'
>>> beanstalk.tubes()
['default']
Further, a beanstalkd client can choose many tubes to reserve jobs from. These
tubes are "watched" by the client. To see what tubes you are currently watching:
>>> beanstalk.watching()
['default']
To watch an additional tube:
>>> beanstalk.watch('bar')
2
>>> beanstalk.watching()
['default', 'bar']
As before, tubes that do not yet exist are created automatically once you start
watching them:
>>> beanstalk.tubes()
['default', 'bar']
To stop watching a tube:
>>> beanstalk.ignore('bar')
1
>>> beanstalk.watching()
['default']
You can't watch zero tubes. So if you try to ignore the last tube you are
watching, this is silently ignored:
>>> beanstalk.ignore('default')
1
>>> beanstalk.watching()
['default']
To recap: each beanstalkd client manages two separate concerns: which tube
newly created jobs are put into, and which tube(s) jobs are reserved from.
Accordingly, there are two separate sets of functions for these concerns:
- `use` and `using` affect where jobs are `put`;
- `watch` and `watching` control where jobs are `reserve`d from.
Note that these concerns are fully orthogonal: for example, when you `use` a
tube, it is not automatically `watch`ed. Neither does `watch`ing a tube affect
the tube you are `using`.
Statistics
----------
Beanstalkd accumulates various statistics at the server, tube and job level.
Statistical details for a job can only be retrieved during the job's lifecycle.
So let's create another job:
>>> beanstalk.put('ho?')
2
>>> job = beanstalk.reserve()
Now we retrieve job-level statistics:
>>> from pprint import pprint
>>> pprint(job.stats())
{'age': 0,
...
'id': 2,
...
'state': 'reserved',
...
'tube': 'default'}
If you try to access job stats after the job was deleted, you'll get a
`CommandFailed` exception:
>>> job.delete()
>>> job.stats()
Traceback (most recent call last):
...
CommandFailed: ('stats-job', 'NOT_FOUND', [])
Let's have a look at some numbers for the `'default'` tube:
>>> pprint(beanstalk.stats_tube('default'))
{...
'current-jobs-ready': 0,
'current-jobs-reserved': 0,
'current-jobs-urgent': 0,
...
'name': 'default',
...}
Finally, there's an abundant amount of server-level statistics accessible via
the `Connection`'s `stats` method. We won't go into details here, but:
>>> pprint(beanstalk.stats())
{...
'current-connections': 1,
'current-jobs-buried': 0,
'current-jobs-delayed': 0,
'current-jobs-ready': 0,
'current-jobs-reserved': 0,
'current-jobs-urgent': 0,
...}
Advanced Operation
------------------
In "Basic Operation" above, we discussed the typical lifecycle of a job:
put reserve delete
-----> [READY] ---------> [RESERVED] --------> *poof*
(This picture was taken from beanstalkd's protocol documentation. It is
originally contained in `protocol.txt`, part of the beanstalkd
distribution.) #doctest:+SKIP
But besides `ready` and `reserved`, a job can also be `delayed` or `buried`.
Along with those states come a few transitions, so the full picture looks like
the following:
put with delay release with delay
----------------> [DELAYED] <------------.
| |
| (time passes) |
| |
put v reserve | delete
-----------------> [READY] ---------> [RESERVED] --------> *poof*
^ ^ | |
| \ release | |
| `-------------' |
| |
| kick |
| |
| bury |
[BURIED] <---------------'
|
| delete
`--------> *poof*
(This picture was taken from beanstalkd's protocol documentation. It is
originally contained in `protocol.txt`, part of the beanstalkd
distribution.) #doctest:+SKIP
Now let's have a practical look at those new possibilities. For a start, we can
create a job with a delay. Such a job will only be available for reservation
once this delay passes:
>>> beanstalk.put('yes!', delay=1)
3
>>> beanstalk.reserve(timeout=0) is None
True
>>> job = beanstalk.reserve(timeout=1)
>>> job.body
'yes!'
If we are not interested in a job anymore (e.g. after we failed to
process it), we can simply release the job back to the tube it came from:
>>> job.release()
>>> job.stats()['state']
'ready'
Want to get rid of a job? Well, just "bury" it! A buried job is put aside and is
therefore not available for reservation anymore:
>>> job = beanstalk.reserve()
>>> job.bury()
>>> job.stats()['state']
'buried'
>>> beanstalk.reserve(timeout=0) is None
True
Buried jobs are maintained in a special FIFO-queue outside of the normal job
processing lifecycle until they are kicked alive again:
>>> beanstalk.kick()
1
You can request many jobs to be kicked alive at once, `kick`'s return value will
tell you how many jobs were actually kicked alive again:
>>> beanstalk.kick(42)
0
Inspecting Jobs
---------------
Besides reserving jobs, a client can also "peek" at jobs. This allows to inspect
jobs without modifying their state. If you know the `id` of a job you're
interested, you can directly peek at the job. We still have job #3 hanging
around from our previous examples, so:
>>> job = beanstalk.peek(3)
>>> job.body
'yes!'
Note that this peek did *not* reserve the job:
>>> job.stats()['state']
'ready'
If you try to peek at a non-existing job, you'll simply see nothing:
>>> beanstalk.peek(42) is None
True
If you are not interested in a particular job, but want to see jobs according to
their state, beanstalkd also provides a few commands. To peek at the same job
that would be returned by `reserve` -- the next ready job -- use `peek-ready`:
>>> job = beanstalk.peek_ready()
>>> job.body
'yes!'
Note that you can't release, or bury a job that was not reserved by you. Those
requests on unreserved jobs are silently ignored:
>>> job.release()
>>> job.bury()
>>> job.stats()['state']
'ready'
You can, though, delete a job that was not reserved by you:
>>> job.delete()
>>> job.stats()
Traceback (most recent call last):
...
CommandFailed: ('stats-job', 'NOT_FOUND', [])
Finally, you can also peek into the special queues for jobs that are delayed:
>>> beanstalk.put('o tempores', delay=120)
4
>>> job = beanstalk.peek_delayed()
>>> job.stats()['state']
'delayed'
... or buried:
>>> beanstalk.put('o mores!')
5
>>> job = beanstalk.reserve()
>>> job.bury()
>>> job = beanstalk.peek_buried()
>>> job.stats()['state']
'buried'
Job Priorities
--------------
Without job priorities, beanstalkd operates as a FIFO queue:
>>> _ = beanstalk.put('1')
>>> _ = beanstalk.put('2')
>>> job = beanstalk.reserve() ; print job.body ; job.delete()
1
>>> job = beanstalk.reserve() ; print job.body ; job.delete()
2
If need arises, you can override this behaviour by giving different jobs
different priorities. There are three hard facts to know about job priorities:
1. Jobs with lower priority numbers are reserved before jobs with higher
priority numbers.
2. beanstalkd priorities are 32-bit unsigned integers (they range from 0 to
2**32 - 1).
3. beanstalkc uses 2**31 as default job priority
(`beanstalkc.DEFAULT_PRIORITY`).
To create a job with a custom priority, use the keyword-argument `priority`:
>>> _ = beanstalk.put('foo', priority=42)
>>> _ = beanstalk.put('bar', priority=21)
>>> _ = beanstalk.put('qux', priority=21)
>>> job = beanstalk.reserve() ; print job.body ; job.delete()
bar
>>> job = beanstalk.reserve() ; print job.body ; job.delete()
qux
>>> job = beanstalk.reserve() ; print job.body ; job.delete()
foo
Note how `'bar'` and `'qux'` left the queue before `'foo'`, even though they
were enqueued well after `'foo'`. Note also that within the same priority jobs
are still handled in a FIFO manner.
Fin!
----
>>> beanstalk.close()
That's it, for now. We've left a few capabilities untouched (touch and
time-to-run). But if you've really read through all of the above, send me a
message and tell me what you think of it. And then go get yourself a treat. You
certainly deserve it.
Appendix A: beanstalkc and YAML
-------------------------------
As beanstalkd uses YAML for diagnostic information (like the results of
`stats()` or `tubes()`), you'll typically need [PyYAML](). Depending on your
performance needs, you may want to supplement that with the [libyaml]() C
extension.
[PyYAML]: http://pyyaml.org/
[libyaml]: http://pyyaml.org/wiki/LibYAML
If, for whatever reason, you cannot use PyYAML, you can still use beanstalkc
and just leave the YAML responses unparsed. To do that, pass `parse_yaml=False`
when creating the `Connection`:
>>> beanstalk = beanstalkc.Connection(host='localhost',
... port=14711,
... parse_yaml=False)
>>> beanstalk.tubes()
'---\n- default\n'
>>> beanstalk.stats_tube('default')
'---\nname: default\ncurrent-jobs-urgent: 0\ncurrent-jobs-ready: 0\n...'
>>> beanstalk.close()
This possibility is mostly useful if you don't use the introspective
capabilities of beanstalkd (`Connection#tubes`, `Connection#watching`,
`Connection#stats`, `Connection#stats_tube`, and `Job#stats`).
Alternatively, you can also pass a function to be used as YAML parser:
>>> beanstalk = beanstalkc.Connection(host='localhost',
... port=14711,
... parse_yaml=lambda x: x.split('\n'))
>>> beanstalk.tubes()
['---', '- default', '']
>>> beanstalk.stats_tube('default')
['---', 'name: default', 'current-jobs-urgent: 0', ...]
>>> beanstalk.close()
This should come in handy if PyYAML simply does not fit your needs.