que 0.8.2 → 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +4 -0
- data/README.md +8 -1
- data/docs/customizing_que.md +34 -10
- data/lib/que/job.rb +1 -1
- data/lib/que/version.rb +1 -1
- data/spec/unit/customization_spec.rb +49 -9
- data/spec/unit/work_spec.rb +16 -0
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6e9631191c30bd35b86706f09611faebc0f99e07
|
4
|
+
data.tar.gz: b53b3005e278471f4e4ee7e55a41637db002a2be
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7a8da4e60a2d3e9bd85be9891cf7d3c736621ed5e72d07d2f6145e2430ca7af15d678441c2d3bd9358aee1aab84fde5a73d14be0af1a87c2425a3ec51de652de
|
7
|
+
data.tar.gz: 1245796b0d471b8ab3ccc528f016cb244dad07fc80282fcae82e5bd1203281df01e40bd012e04cc1eade1a63a8424a3488043a81ddf68d02886ca6b8256c5067
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,7 @@
|
|
1
|
+
### 0.9.0 (2014-12-16)
|
2
|
+
|
3
|
+
* The error_handler callable is now be passed two objects, the error and the job that raised it. If your current error_handler is a proc, as recommended in the docs, you shouldn't need to make any code changes, unless you want to use the job in your error handling. If your error_handler is a lambda, or another callable with a strict arity requirement, you'll want to change it before upgrading. (#69) (statianzo)
|
4
|
+
|
1
5
|
### 0.8.2 (2014-10-12)
|
2
6
|
|
3
7
|
* Fix errors raised during rollbacks in the ActiveRecord adapter, which remained silent until Rails 4.2. (#64, #65) (Strech)
|
data/README.md
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
**TL;DR: Que is a high-performance alternative to DelayedJob or QueueClassic that improves the reliability of your application by protecting your jobs with the same [ACID guarantees](https://en.wikipedia.org/wiki/ACID) as the rest of your data.**
|
4
4
|
|
5
|
-
Que is a queue for Ruby and PostgreSQL that manages jobs using [advisory locks](http://www.postgresql.org/docs/current/static/explicit-locking.html#ADVISORY-LOCKS), which gives it several advantages over other RDBMS-backed queues:
|
5
|
+
Que ("keɪ", or "kay") is a queue for Ruby and PostgreSQL that manages jobs using [advisory locks](http://www.postgresql.org/docs/current/static/explicit-locking.html#ADVISORY-LOCKS), which gives it several advantages over other RDBMS-backed queues:
|
6
6
|
|
7
7
|
* **Concurrency** - Workers don't block each other when trying to lock jobs, as often occurs with "SELECT FOR UPDATE"-style locking. This allows for very high throughput with a large number of workers.
|
8
8
|
* **Efficiency** - Locks are held in memory, so locking a job doesn't incur a disk write. These first two points are what limit performance with other queues - all workers trying to lock jobs have to wait behind one that's persisting its UPDATE on a locked_at column to disk (and the disks of however many other servers your database is synchronously replicating to). Under heavy load, Que's bottleneck is CPU, not I/O.
|
@@ -104,6 +104,13 @@ To determine what happens when a job is queued, you can set Que's mode in your a
|
|
104
104
|
|
105
105
|
If you're using ActiveRecord to dump your database's schema, you'll probably want to [set schema_format to :sql](http://guides.rubyonrails.org/migrations.html#types-of-schema-dumps) so that Que's table structure is managed correctly.
|
106
106
|
|
107
|
+
## Related Gems
|
108
|
+
|
109
|
+
* [que-web](https://github.com/statianzo/que-web) is a Sinatra-based UI for inspecting your job queue.
|
110
|
+
* [que-testing](https://github.com/statianzo/que-testing) allows making assertions on enqueued jobs.
|
111
|
+
|
112
|
+
If you have a gem that uses or relates to Que, feel free to submit a PR adding it to the list!
|
113
|
+
|
107
114
|
## Contributing
|
108
115
|
|
109
116
|
1. Fork it
|
data/docs/customizing_que.md
CHANGED
@@ -6,24 +6,48 @@ One of Que's goals to be easily extensible and hackable (and if anyone has any s
|
|
6
6
|
|
7
7
|
Que's support for scheduling jobs makes it easy to implement reliable recurring jobs. For example, suppose you want to run a job every hour that processes the users created in that time:
|
8
8
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
9
|
+
class CronJob < Que::Job
|
10
|
+
# Default repetition interval in seconds. Can be overridden in
|
11
|
+
# subclasses. Can use 1.minute if using Rails.
|
12
|
+
INTERVAL = 60
|
13
|
+
|
14
|
+
attr_reader :start_at, :end_at, :run_again_at, :time_range
|
15
|
+
|
16
|
+
def _run
|
17
|
+
args = attrs[:args].first
|
18
|
+
@start_at, @end_at = Time.at(args.delete('start_at')), Time.at(args.delete('end_at'))
|
19
|
+
@run_again_at = @end_at + self.class::INTERVAL
|
20
|
+
@time_range = @start_at...@end_at
|
21
|
+
|
22
|
+
super
|
23
|
+
|
24
|
+
args['start_at'] = @end_at.to_f
|
25
|
+
args['end_at'] = @run_again_at.to_f
|
26
|
+
self.class.enqueue(args, run_at: @run_again_at)
|
17
27
|
end
|
18
28
|
end
|
19
|
-
end
|
20
29
|
|
21
|
-
|
30
|
+
class MyCronJob < CronJob
|
31
|
+
INTERVAL = 3600
|
32
|
+
|
33
|
+
def run(args)
|
34
|
+
User.where(created_at: time_range).each { ... }
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# To enqueue:
|
39
|
+
tf = Time.now
|
40
|
+
t0 = Time.now - 3600
|
41
|
+
MyCronJob.enqueue :start_at => t0.to_f, :end_at => tf.to_f
|
42
|
+
|
43
|
+
Note that instead of using Time.now in our database query, and requeueing the job at 1.hour.from_now, we use job arguments to track start and end times. This lets us correct for delays in running the job. Suppose that there's a backlog of priority jobs, or that the worker briefly goes down, and this job, which was supposed to run at 11:00 a.m. isn't run until 11:05 a.m. A lazier implementation would look for users created after 1.hour.ago, and miss those that signed up between 10:00 a.m. and 10:05 a.m.
|
22
44
|
|
23
45
|
This also compensates for clock drift. `Time.now` on one of your application servers may not match `Time.now` on another application server may not match `now()` on your database server. The best way to stay reliable is have a single authoritative source on what the current time is, and your best source for authoritative information is always your database (this is why Que uses Postgres' `now()` function when locking jobs, by the way).
|
24
46
|
|
25
47
|
Note also the use of the triple-dot range, which results in a query like `SELECT "users".* FROM "users" WHERE ("users"."created_at" >= '2014-01-08 10:00:00.000000' AND "users"."created_at" < '2014-01-08 11:00:00.000000')` instead of a BETWEEN condition. This ensures that a user created at 11:00 am exactly isn't processed twice, by the jobs starting at both 10 am and 11 am.
|
26
48
|
|
49
|
+
Finally, by passing both the start and end times for the period to be processed, and only using the interval to calculate the period for the following job, we make it easy to change the interval at which the job runs, without the risk of missing or double-processing any users.
|
50
|
+
|
27
51
|
### DelayedJob-style Jobs
|
28
52
|
|
29
53
|
DelayedJob offers a simple API for delaying methods to objects:
|
data/lib/que/job.rb
CHANGED
@@ -118,7 +118,7 @@ module Que
|
|
118
118
|
|
119
119
|
if Que.error_handler
|
120
120
|
# Similarly, protect the work loop from a failure of the error handler.
|
121
|
-
Que.error_handler.call(error) rescue nil
|
121
|
+
Que.error_handler.call(error, job) rescue nil
|
122
122
|
end
|
123
123
|
|
124
124
|
return {:event => :job_errored, :error => error, :job => job}
|
data/lib/que/version.rb
CHANGED
@@ -4,20 +4,60 @@ require 'spec_helper'
|
|
4
4
|
# stay functional.
|
5
5
|
describe "Customizing Que" do
|
6
6
|
it "Cron should allow for easy recurring jobs" do
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
7
|
+
begin
|
8
|
+
class CronJob < Que::Job
|
9
|
+
# Default repetition interval in seconds. Can be overridden in
|
10
|
+
# subclasses. Can use 1.minute if using Rails.
|
11
|
+
INTERVAL = 60
|
12
|
+
|
13
|
+
attr_reader :start_at, :end_at, :run_again_at, :time_range
|
14
|
+
|
15
|
+
def _run
|
16
|
+
args = attrs[:args].first
|
17
|
+
@start_at, @end_at = Time.at(args.delete('start_at')), Time.at(args.delete('end_at'))
|
18
|
+
@run_again_at = @end_at + self.class::INTERVAL
|
19
|
+
@time_range = @start_at...@end_at
|
20
|
+
|
21
|
+
super
|
22
|
+
|
23
|
+
args['start_at'] = @end_at.to_f
|
24
|
+
args['end_at'] = @run_again_at.to_f
|
25
|
+
self.class.enqueue(args, run_at: @run_again_at)
|
26
|
+
end
|
11
27
|
end
|
12
|
-
end
|
13
28
|
|
14
|
-
|
29
|
+
class MyCronJob < CronJob
|
30
|
+
INTERVAL = 1.5
|
15
31
|
|
16
|
-
|
32
|
+
def run(args)
|
33
|
+
$args = args.dup
|
34
|
+
$time_range = time_range
|
35
|
+
end
|
36
|
+
end
|
17
37
|
|
18
|
-
|
38
|
+
t = (Time.now - 1000).to_f.round(6)
|
39
|
+
MyCronJob.enqueue :start_at => t, :end_at => t + 1.5, :arg => 4
|
19
40
|
|
20
|
-
|
41
|
+
$args.should be nil
|
42
|
+
$time_range.should be nil
|
43
|
+
|
44
|
+
Que::Job.work
|
45
|
+
|
46
|
+
$args.should == {'arg' => 4}
|
47
|
+
$time_range.begin.to_f.round(6).should be_within(0.000001).of t
|
48
|
+
$time_range.end.to_f.round(6).should be_within(0.000001).of t + 1.5
|
49
|
+
$time_range.exclude_end?.should be true
|
50
|
+
|
51
|
+
DB[:que_jobs].get(:run_at).to_f.round(6).should be_within(0.000001).of(t + 3.0)
|
52
|
+
args = JSON.parse(DB[:que_jobs].get(:args)).first
|
53
|
+
args.keys.should == ['arg', 'start_at', 'end_at']
|
54
|
+
args['arg'].should == 4
|
55
|
+
args['start_at'].should be_within(0.000001).of(t + 1.5)
|
56
|
+
args['end_at'].should be_within(0.000001).of(t + 3.0)
|
57
|
+
ensure
|
58
|
+
$args = nil
|
59
|
+
$time_range = nil
|
60
|
+
end
|
21
61
|
end
|
22
62
|
|
23
63
|
it "Object#delay should allow for simpler job enqueueing" do
|
data/spec/unit/work_spec.rb
CHANGED
@@ -323,6 +323,22 @@ describe Que::Job, '.work' do
|
|
323
323
|
end
|
324
324
|
end
|
325
325
|
|
326
|
+
it "should pass job to an error handler, if one is defined" do
|
327
|
+
begin
|
328
|
+
jobs = []
|
329
|
+
Que.error_handler = proc { |error, job| jobs << job }
|
330
|
+
|
331
|
+
ErrorJob.enqueue
|
332
|
+
result = Que::Job.work
|
333
|
+
|
334
|
+
jobs.count.should be 1
|
335
|
+
job = jobs[0]
|
336
|
+
job.should be result[:job]
|
337
|
+
ensure
|
338
|
+
Que.error_handler = nil
|
339
|
+
end
|
340
|
+
end
|
341
|
+
|
326
342
|
it "should not do anything if the error handler itelf throws an error" do
|
327
343
|
begin
|
328
344
|
Que.error_handler = proc { |error| raise "Another error!" }
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: que
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.9.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Chris Hanks
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-
|
11
|
+
date: 2014-12-16 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|