resque-director 2.1.1 → 2.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +37 -18
- data/VERSION +1 -1
- data/lib/resque-director.rb +4 -0
- data/lib/resque/plugins/director.rb +10 -4
- data/lib/resque/plugins/director/push_pop.rb +0 -4
- data/lib/resque/plugins/director/scaler.rb +18 -14
- data/lib/resque/plugins/director/worker_tracker.rb +1 -1
- data/resque-director.gemspec +2 -2
- data/spec/resque/plugins/director/scaler_spec.rb +19 -0
- data/spec/resque/plugins/director/worker_tracker_spec.rb +13 -0
- data/spec/resque/plugins/director_spec.rb +45 -0
- metadata +5 -5
data/README.rdoc
CHANGED
@@ -4,9 +4,7 @@ Resque Director is a plugin for the Resque queueing system (http://github.com/de
|
|
4
4
|
|
5
5
|
==About
|
6
6
|
|
7
|
-
resque-director is mainly useful for when you are managing a large number of workers and don't want to waste resources keeping all of them waiting when they are not being used. Also useful in queues where the influx of jobs can change dramatically from time to time: enabling more workers during the times when the queue is filling up more quickly, and less in the opposite scenario. Different queues can be given different directions as well.
|
8
|
-
|
9
|
-
resque-director does not currently operate on workers that serve multiple queues, and does not include those workers in any scaling calculations.
|
7
|
+
resque-director is mainly useful for when you are managing a large number of workers and don't want to waste resources keeping all of them waiting when they are not being used. Also for jobs that are high priority or time sensitive you can have it autoscale workers when the time it took for the job to go through the queue is too long. For this reason resque director is useful in queues where the influx of jobs can change dramatically from time to time: enabling more workers during the times when the queue is filling up more quickly, and less in the opposite scenario. Different queues can be given different directions as well.
|
10
8
|
|
11
9
|
== Usage
|
12
10
|
|
@@ -35,15 +33,28 @@ For Example:
|
|
35
33
|
|
36
34
|
<b>wait_time</b>:: the time in seconds that it will wait after adding or removing a worker before being allowed to add or remove workers again. The default is 60 seconds.
|
37
35
|
|
38
|
-
===
|
36
|
+
=== Conditions For Starting Workers
|
39
37
|
|
40
|
-
<b>
|
38
|
+
A worker will be started if the queue length is greater than <b>max_queue</b> or if the time it takes a job to go through the queue is greater than <b>max_time</b>. Also a worker will only be started if the time since the last scaling is greater than <b>wait_time</b>. Workers will not be started if there are already the <b>max_workers</b> number of workers. By default resque-director allows you to have zero jobs running, when a job is enqueued then workers will be scaled within the max/min requirements you set. If there is not a single worker running, then the minimum number of workers will be scaled up (one worker will be scaled up if the minimum is zero).
|
39
|
+
|
40
|
+
=== Conditions For Removing Workers
|
41
|
+
|
42
|
+
A worker will be removed if the jobs in the queue fall below half of the <b>max_queue</b>, and if the time it takes for a job to be pulled off of a queue falls below half of the <b>max_time</b>. Workers will be scaled down to the minimum if there are no jobs on the queue. Workers will not be stopped if there are only <b>min_workers</b> number of workers. Also a worker will only be stopped if the time since the last scaling is greater than <b>wait_time</b>.
|
43
|
+
|
44
|
+
=== Special Cases
|
45
|
+
|
46
|
+
* If a max_worker is less than min_worker then the default for max_worker will be used (there will be no maximum).
|
47
|
+
* If a min_workers is set to anything less than 1 then it will be treated as 0.
|
48
|
+
|
49
|
+
== Advanced Options
|
50
|
+
|
51
|
+
The above options are plenty to handle the basic scaling of workers. The options below allow the user much more control over the way in which workers are scaled, the information tracked, and the times when a worker is scaled.
|
41
52
|
|
42
53
|
=== Start/Stop Options
|
43
54
|
|
44
|
-
<b>start_override</b>:: This option takes a lambda closure that accepts the queue as an argument, the block you pass in will be responsible for starting a SINGLE worker allowing you to fully customize the starting of a worker. If your block returns false then that signifies that a worker was not started and the last scaled time will not be set.
|
55
|
+
<b>start_override</b>:: You can set this option if you want to override the way to start a worker. This option takes a lambda closure that accepts the queue as an argument, the block you pass in will be responsible for starting a <em>SINGLE</em> worker allowing you to fully customize the starting of a worker. If your block returns false then that signifies that a worker was not started and the last scaled time will not be set. The default way a worker is started is with the system command: <tt>QUEUE=queue_name rake resque:work &</tt> where "queue_name" is the queue on which the job is running.
|
45
56
|
|
46
|
-
<b>stop_override</b>:: This option takes a lambda closure that accepts the queue as an argument, the block you pass in will be responsible for stopping a SINGLE worker allowing you to fully customize the stopping of a worker. If your block returns false then that signifies that a worker was not stopped and the last scaled time will not be set.
|
57
|
+
<b>stop_override</b>:: You can set this option if you want to override the way to stop a worker. This option takes a lambda closure that accepts the queue as an argument, the block you pass in will be responsible for stopping a <em>SINGLE</em> worker allowing you to fully customize the stopping of a worker. If your block returns false then that signifies that a worker was not stopped and the last scaled time will not be set. The default way a worker is stopped is that a QUIT signal is sent to a worker process.
|
47
58
|
|
48
59
|
=== Customized Starting/Stopping Workers Example
|
49
60
|
|
@@ -58,14 +69,6 @@ For Example:
|
|
58
69
|
#rest of your Job class here
|
59
70
|
end
|
60
71
|
|
61
|
-
=== Conditions For Starting Workers
|
62
|
-
|
63
|
-
A worker will be started if the queue length is greater than <b>max_queue</b> or if the time it takes a job to go through the queue is greater than <b>max_time</b>. Also a worker will only be started if the time since the last scaling is greater than <b>wait_time</b>. Workers will not be started if there are already the <b>max_workers</b> number of workers. When a job is enqueued and if <b>no_enqueue_scale</b> is not set or false, then workers will be scaled within the max/min requirements you set, if there is not a single worker running then the minimum number of workers will be scaled up (one worker will be scaled up if the minimum is zero).
|
64
|
-
|
65
|
-
=== Conditions For Removing Workers
|
66
|
-
|
67
|
-
A worker will be removed if the jobs in the queue fall below half of the <b>max_queue</b>, and if the time it takes for a job to be pulled off of a queue falls below half of the <b>max_time</b>. Workers will be scaled down to the minimum if there are no jobs on the queue.
|
68
|
-
|
69
72
|
=== Logging
|
70
73
|
|
71
74
|
To add director logging you can pass a logger as an option. It will prepend all log messages with "DIRECTORS LOG:" and will log when scaling up or down a worker. It will also log when a worker wants to scale up or down but is unable to do so due to the fact that the maximum or minimum number of workers has been reached for that queue.
|
@@ -88,10 +91,26 @@ To add director logging you can pass a logger as an option. It will prepend all
|
|
88
91
|
|
89
92
|
<b>log_level</b>:: This sets the level to log at as a symbol. The default level is :debug.
|
90
93
|
|
91
|
-
===
|
94
|
+
=== Scale Options
|
92
95
|
|
93
|
-
|
94
|
-
|
96
|
+
<b>no_enqueue_scale</b>:: When a job is enqueued workers will be scaled within the max/min requirements you set, if there is not a single worker running then the minimum number of workers will be scaled up (one worker will be scaled up if the minimum is zero). The worker itself handles the rest of the scaling. If you do not want scaling on enqueue and want to have the workers perform all the scaling then you can set this option to true, however if you do this you must be responsible for making sure that at least one worker is running on the queue. The default is false.
|
97
|
+
|
98
|
+
=== Multiqueue Options
|
99
|
+
|
100
|
+
<b>queue</b>:: The queue name(s) that you will be scaling and using to calculate the number of workers working. The default is the queue name of the Job.
|
101
|
+
|
102
|
+
=== Multiqueue Example
|
103
|
+
|
104
|
+
class Job
|
105
|
+
direct :queue = [:test, :test2]
|
106
|
+
@queue = :test
|
107
|
+
|
108
|
+
#rest of your Job class here
|
109
|
+
end
|
110
|
+
|
111
|
+
=== Multiqueue
|
112
|
+
|
113
|
+
resque-director will still look at the job's queue to determine when to scale. However if you set the <b>queue</b> option it will scale a worker working on that queue, or a worker working on multiple queues if you pass it an array of queues.
|
95
114
|
|
96
115
|
== Requirements
|
97
116
|
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
2.
|
1
|
+
2.2.0
|
data/lib/resque-director.rb
CHANGED
@@ -3,21 +3,23 @@ module Resque
|
|
3
3
|
module Director
|
4
4
|
def direct(options={})
|
5
5
|
Config.setup(options)
|
6
|
+
Config.queue = options[:queue]
|
6
7
|
end
|
7
8
|
|
8
9
|
def after_enqueue_scale_workers(*args)
|
9
|
-
|
10
|
+
set_queue
|
10
11
|
return if Config.no_enqueue_scale
|
11
12
|
Scaler.scale_within_requirements
|
12
13
|
end
|
13
14
|
|
14
15
|
def before_perform_direct_workers(*args)
|
16
|
+
set_queue
|
15
17
|
Scaler.scale_within_requirements if Config.no_enqueue_scale
|
16
18
|
end
|
17
19
|
|
18
20
|
def after_pop_direct_workers(start_time=Time.now.utc)
|
19
21
|
return unless scaling_config_set?
|
20
|
-
|
22
|
+
set_queue
|
21
23
|
|
22
24
|
time_through_queue = Time.now.utc - start_time
|
23
25
|
jobs_in_queue = Resque.size(@queue.to_s)
|
@@ -30,18 +32,22 @@ module Resque
|
|
30
32
|
end
|
31
33
|
|
32
34
|
def after_perform_direct_workers(*args)
|
33
|
-
|
35
|
+
set_queue
|
34
36
|
jobs_in_queue = Resque.size(@queue.to_s)
|
35
37
|
Scaler.scale_down_to_minimum if jobs_in_queue == 0
|
36
38
|
end
|
37
39
|
|
38
40
|
def on_failure_direct_workers(*args)
|
39
|
-
|
41
|
+
set_queue
|
40
42
|
jobs_in_queue = Resque.size(@queue.to_s)
|
41
43
|
Scaler.scale_down_to_minimum if jobs_in_queue == 0
|
42
44
|
end
|
43
45
|
|
44
46
|
private
|
47
|
+
|
48
|
+
def set_queue
|
49
|
+
Config.queue ||= @queue.to_s
|
50
|
+
end
|
45
51
|
|
46
52
|
def scaling_config_set?
|
47
53
|
Config.max_time > 0 || Config.max_queue > 0
|
@@ -14,7 +14,6 @@ module Resque
|
|
14
14
|
def scale_down(number_of_workers=1)
|
15
15
|
tracker = WorkerTracker.new
|
16
16
|
number_of_workers = tracker.total_to_remove(number_of_workers)
|
17
|
-
|
18
17
|
scaling(number_of_workers) do
|
19
18
|
stop(tracker, number_of_workers)
|
20
19
|
end
|
@@ -28,7 +27,6 @@ module Resque
|
|
28
27
|
|
29
28
|
def scale_within_requirements
|
30
29
|
number_of_workers = WorkerTracker.new.total_for_requirements
|
31
|
-
|
32
30
|
if number_of_workers > 0
|
33
31
|
scale_up(number_of_workers)
|
34
32
|
elsif number_of_workers < 0
|
@@ -54,22 +52,28 @@ module Resque
|
|
54
52
|
|
55
53
|
def start(number_of_workers)
|
56
54
|
Config.log("starting #{number_of_workers} workers on queue:#{Config.queue}") if number_of_workers > 0
|
57
|
-
if Config.start_override
|
58
|
-
|
59
|
-
else
|
60
|
-
number_of_workers.times { system("QUEUE=#{Config.queue} rake resque:work &") }
|
61
|
-
end
|
55
|
+
return override(number_of_workers, Config.start_override) if Config.start_override
|
56
|
+
start_default(number_of_workers)
|
62
57
|
end
|
63
58
|
|
64
59
|
def stop(tracker, number_of_workers)
|
65
60
|
Config.log("stopping #{number_of_workers} workers on queue:#{Config.queue}") if number_of_workers > 0
|
66
|
-
if Config.stop_override
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
61
|
+
return override(number_of_workers, Config.stop_override) if Config.stop_override
|
62
|
+
stop_default(tracker, number_of_workers)
|
63
|
+
end
|
64
|
+
|
65
|
+
def override(number_of_workers, override_block)
|
66
|
+
number_of_workers.times {override_block.call(Config.queue) }
|
67
|
+
end
|
68
|
+
|
69
|
+
def start_default(number_of_workers)
|
70
|
+
number_of_workers.times { system("QUEUE=#{[Config.queue].flatten.join(",")} rake resque:work &") }
|
71
|
+
end
|
72
|
+
|
73
|
+
def stop_default(tracker, number_of_workers)
|
74
|
+
worker_pids = tracker.valid_worker_pids[0...number_of_workers]
|
75
|
+
worker_pids.each do |pid|
|
76
|
+
Process.kill("QUIT", pid) rescue nil
|
73
77
|
end
|
74
78
|
end
|
75
79
|
end
|
data/resque-director.gemspec
CHANGED
@@ -5,11 +5,11 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = %q{resque-director}
|
8
|
-
s.version = "2.
|
8
|
+
s.version = "2.2.0"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = [%q{Nolan Frausto}]
|
12
|
-
s.date = %q{2011-09-
|
12
|
+
s.date = %q{2011-09-07}
|
13
13
|
s.description = %q{resque plugin for automatically scaling workers based on the amount of time it takes a job to go through the queue and/or the length of the queue }
|
14
14
|
s.email = %q{nrfrausto@gmail.com}
|
15
15
|
s.extra_rdoc_files = [
|
@@ -30,6 +30,12 @@ describe Resque::Plugins::Director::Scaler do
|
|
30
30
|
test_block.should_receive(:call).with("test")
|
31
31
|
subject.scale_up
|
32
32
|
end
|
33
|
+
|
34
|
+
it "scales up a worker to work on multiple queues" do
|
35
|
+
Resque::Plugins::Director::Config.queue = [:test, :test2]
|
36
|
+
subject.should_receive(:system).with("QUEUE=test,test2 rake resque:work &")
|
37
|
+
subject.scale_up
|
38
|
+
end
|
33
39
|
end
|
34
40
|
|
35
41
|
describe "#scaling" do
|
@@ -43,6 +49,13 @@ describe Resque::Plugins::Director::Scaler do
|
|
43
49
|
@times_scaled.should == 1
|
44
50
|
end
|
45
51
|
|
52
|
+
it "should not scale workers if last time scaled is too soon for watching multiple queues" do
|
53
|
+
Resque::Plugins::Director::Config.setup(:wait_time => 60)
|
54
|
+
Resque::Plugins::Director::Config.queue = [:test,:test2]
|
55
|
+
2.times { subject.scaling { @times_scaled += 1 } }
|
56
|
+
@times_scaled.should == 1
|
57
|
+
end
|
58
|
+
|
46
59
|
it "should scale workers if wait time has passed" do
|
47
60
|
Resque::Plugins::Director::Config.setup(:wait_time => 0)
|
48
61
|
2.times { subject.scaling { @times_scaled += 1 } }
|
@@ -55,6 +68,12 @@ describe Resque::Plugins::Director::Scaler do
|
|
55
68
|
@now = Time.now
|
56
69
|
Time.stub(:now => @now)
|
57
70
|
end
|
71
|
+
|
72
|
+
it "should the last scaled for scaling multiple queue workers" do
|
73
|
+
Resque::Plugins::Director::Config.queue = [:test,:test2]
|
74
|
+
subject.scaling { true }
|
75
|
+
Resque.redis.get("last_scaled_testtest2").to_i.should == @now.utc.to_i
|
76
|
+
end
|
58
77
|
|
59
78
|
it "should set the last scaled time" do
|
60
79
|
subject.scaling { true }
|
@@ -145,5 +145,18 @@ describe Resque::Plugins::Director::WorkerTracker do
|
|
145
145
|
Resque.should_receive(:workers).and_return [shutdown_worker, @worker, @worker]
|
146
146
|
subject.new.workers.should == [@worker, @worker]
|
147
147
|
end
|
148
|
+
|
149
|
+
it "does not set workers that have the queue included with others" do
|
150
|
+
other_worker = Resque::Worker.new(:other, :test)
|
151
|
+
Resque.should_receive(:workers).and_return [@worker, other_worker, @worker]
|
152
|
+
subject.new.workers.should == [@worker, @worker]
|
153
|
+
end
|
154
|
+
|
155
|
+
it "finds workers working on multiple queues if specified" do
|
156
|
+
other_worker = Resque::Worker.new(:other, :test)
|
157
|
+
Resque::Plugins::Director::Config.queue = [:other,:test]
|
158
|
+
Resque.should_receive(:workers).and_return [@worker, other_worker]
|
159
|
+
subject.new.workers.should == [other_worker]
|
160
|
+
end
|
148
161
|
end
|
149
162
|
end
|
@@ -21,6 +21,12 @@ describe Resque::Plugins::Director do
|
|
21
21
|
Resque::Plugins::Director::Config.queue.should == "test"
|
22
22
|
end
|
23
23
|
|
24
|
+
it "should not set the queue if it was manually set" do
|
25
|
+
TestJob.direct :queue => "real_queue"
|
26
|
+
Resque.enqueue(TestJob)
|
27
|
+
Resque::Plugins::Director::Config.queue.should == "real_queue"
|
28
|
+
end
|
29
|
+
|
24
30
|
it "should not scale if the no_enqueue_scale option is set" do
|
25
31
|
Resque::Worker.new(:test).register_worker
|
26
32
|
Resque::Plugins::Director::Scaler.should_not_receive(:scale_within_requirements)
|
@@ -30,6 +36,17 @@ describe Resque::Plugins::Director do
|
|
30
36
|
end
|
31
37
|
|
32
38
|
describe "#after_perform_direct_workers" do
|
39
|
+
it "should set the queue" do
|
40
|
+
TestJob.after_perform_direct_workers
|
41
|
+
Resque::Plugins::Director::Config.queue.should == "test"
|
42
|
+
end
|
43
|
+
|
44
|
+
it "should not set the queue if it is set" do
|
45
|
+
TestJob.direct :queue => "real_queue"
|
46
|
+
TestJob.after_perform_direct_workers
|
47
|
+
Resque::Plugins::Director::Config.queue.should == "real_queue"
|
48
|
+
end
|
49
|
+
|
33
50
|
it "should scale down to the minumum workers if there are no jobs in the queue" do
|
34
51
|
Resque::Plugins::Director::Scaler.should_receive(:scale_down_to_minimum)
|
35
52
|
TestJob.after_perform_direct_workers
|
@@ -43,6 +60,17 @@ describe Resque::Plugins::Director do
|
|
43
60
|
end
|
44
61
|
|
45
62
|
describe "#on_failure_direct_workers" do
|
63
|
+
it "should set the queue" do
|
64
|
+
TestJob.on_failure_direct_workers
|
65
|
+
Resque::Plugins::Director::Config.queue.should == "test"
|
66
|
+
end
|
67
|
+
|
68
|
+
it "should not set the queue if it is set" do
|
69
|
+
TestJob.direct :queue => "real_queue"
|
70
|
+
TestJob.on_failure_direct_workers
|
71
|
+
Resque::Plugins::Director::Config.queue.should == "real_queue"
|
72
|
+
end
|
73
|
+
|
46
74
|
it "should scale down to the minumum workers if there are no jobs in the queue" do
|
47
75
|
Resque::Plugins::Director::Scaler.should_receive(:scale_down_to_minimum)
|
48
76
|
TestJob.on_failure_direct_workers
|
@@ -67,6 +95,12 @@ describe Resque::Plugins::Director do
|
|
67
95
|
TestJob.after_pop_direct_workers(@start_time)
|
68
96
|
Resque::Plugins::Director::Config.queue.should == "test"
|
69
97
|
end
|
98
|
+
|
99
|
+
it "should not set the queue if it is set" do
|
100
|
+
TestJob.direct :max_time => 20, :queue => "real_queue"
|
101
|
+
TestJob.after_pop_direct_workers(@start_time)
|
102
|
+
Resque::Plugins::Director::Config.queue.should == "real_queue"
|
103
|
+
end
|
70
104
|
|
71
105
|
it "should not start workers if max_time is not set" do
|
72
106
|
Resque::Plugins::Director::Scaler.should_not_receive(:scale_up)
|
@@ -183,6 +217,17 @@ describe Resque::Plugins::Director do
|
|
183
217
|
end
|
184
218
|
|
185
219
|
describe "#before_perform_direct_workers" do
|
220
|
+
it "should set the queue" do
|
221
|
+
TestJob.before_perform_direct_workers
|
222
|
+
Resque::Plugins::Director::Config.queue.should == "test"
|
223
|
+
end
|
224
|
+
|
225
|
+
it "should not set the queue if it is set" do
|
226
|
+
TestJob.direct :queue => "real_queue"
|
227
|
+
TestJob.before_perform_direct_workers
|
228
|
+
Resque::Plugins::Director::Config.queue.should == "real_queue"
|
229
|
+
end
|
230
|
+
|
186
231
|
it "should scale within requirements if no_enqueue_scale is set" do
|
187
232
|
TestJob.direct :no_enqueue_scale => true
|
188
233
|
Resque::Plugins::Director::Scaler.should_receive(:scale_within_requirements)
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: resque-director
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 7
|
5
5
|
prerelease:
|
6
6
|
segments:
|
7
7
|
- 2
|
8
|
-
-
|
9
|
-
-
|
10
|
-
version: 2.
|
8
|
+
- 2
|
9
|
+
- 0
|
10
|
+
version: 2.2.0
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Nolan Frausto
|
@@ -15,7 +15,7 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date: 2011-09-
|
18
|
+
date: 2011-09-07 00:00:00 Z
|
19
19
|
dependencies:
|
20
20
|
- !ruby/object:Gem::Dependency
|
21
21
|
requirement: &id001 !ruby/object:Gem::Requirement
|