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 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
- === Scale Options
36
+ === Conditions For Starting Workers
39
37
 
40
- <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.
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. By default to start a worker the system command "QUEUE=queue_name rake resque:work &" is run, where queue_name is whatever queue the job is running.
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. By default a to stop a worker the worker process on that host is sent a QUIT signal.
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
- === Special Cases
94
+ === Scale Options
92
95
 
93
- * If a max_worker is less than min_worker then the default for max_worker will be used (there will be no maximum).
94
- * If a min_workers is set to anything less than 1 then it will be treated as 0.
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.1
1
+ 2.2.0
@@ -5,3 +5,7 @@ require 'resque/plugins/director/worker_tracker'
5
5
  require 'resque/plugins/director/config'
6
6
  require 'resque/plugins/director/scaler'
7
7
  require 'resque/plugins/director/push_pop'
8
+
9
+ module Resque
10
+ include Resque::Plugins::Director::PushPop
11
+ end
@@ -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
- Config.queue = @queue.to_s
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
- Config.queue = @queue.to_s
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
- Config.queue = @queue.to_s
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
- Config.queue = @queue.to_s
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
@@ -34,8 +34,4 @@ module Resque
34
34
  end
35
35
  end
36
36
  end
37
- end
38
-
39
- module Resque
40
- include Resque::Plugins::Director::PushPop
41
37
  end
@@ -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
- number_of_workers.times { Config.start_override.call(Config.queue) }
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
- number_of_workers.times {Config.stop_override.call(Config.queue) }
68
- else
69
- worker_pids = tracker.valid_worker_pids[0...number_of_workers]
70
- worker_pids.each do |pid|
71
- Process.kill("QUIT", pid) rescue nil
72
- end
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
@@ -57,7 +57,7 @@ module Resque
57
57
 
58
58
  def current_workers
59
59
  Resque.workers.select do |w|
60
- w.queues.map(&:to_s) == [Config.queue.to_s] && !w.shutdown?
60
+ w.queues.map(&:to_s) == Config.queue.map(&:to_s) && !w.shutdown?
61
61
  end
62
62
  end
63
63
  end
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{resque-director}
8
- s.version = "2.1.1"
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-02}
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: 9
4
+ hash: 7
5
5
  prerelease:
6
6
  segments:
7
7
  - 2
8
- - 1
9
- - 1
10
- version: 2.1.1
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-02 00:00:00 Z
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