sidekiq-rate-limiter 0.0.1 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: eed802823e01b40545973fe5e2ddd21c5fa85002
4
+ data.tar.gz: 33ffa14b502ca8872d1573752c0e3eab5fc9799e
5
+ SHA512:
6
+ metadata.gz: ae5a1738821693e8a9bde19b9495cdbf4e87fd9ec7d20457460ec42a76c327cac522db118032178e833b2c83ec7328b89d0f75d19bac2f0fd6c34c0d4bbd30a3
7
+ data.tar.gz: 79e48fc5d5b222bdb3e1bc0cedb0b4a9b32372508ef284b4f246fd8c235c5cbaedf1d80f3d9e9048241bbcf2e3f3ade04756284a8169e6d4f49f508d30706e7a
data/CHANGELOG.md ADDED
@@ -0,0 +1,11 @@
1
+ ## 0.1.0 (May 30, 2014)
2
+
3
+ * Support callables for all configuration options
4
+
5
+ *Kevin Yank*
6
+
7
+ ## 0.0.1 (December 3, 2013)
8
+
9
+ * Initial release.
10
+
11
+ *Blake Thomas*
data/README.md CHANGED
@@ -3,8 +3,7 @@ sidekiq-rate-limiter
3
3
 
4
4
  [![Build Status](https://secure.travis-ci.org/enova/sidekiq-rate-limiter.png)](http://travis-ci.org/enova/sidekiq-rate-limiter)
5
5
 
6
- Adds to sidekiq the ability to rate limit job execution on a per-worker basis in
7
- a redis-backed fashion.
6
+ Redis-backed, per-worker rate limits for job processing.
8
7
 
9
8
  ## Compatibility
10
9
 
@@ -12,10 +11,10 @@ sidekiq-rate-limiter is actively tested against MRI versions 2.0.0 and 1.9.3.
12
11
 
13
12
  sidekiq-rate-limiter works by using a custom fetch class, the class responsible
14
13
  for pulling work from the queue stored in redis. Consequently you'll want to be
15
- careful about using other gems that use a same strategy ([sidekiq-priority](https://github.com/socialpandas/sidekiq-priority)
14
+ careful about using other gems that use a same strategy, [sidekiq-priority](https://github.com/socialpandas/sidekiq-priority)
16
15
  being one example.
17
16
 
18
- I've attempted to support the same options as used by [sidekiq-throttler](/gevans/sidekiq-throttler). So, if
17
+ I've attempted to support the same options as used by [sidekiq-throttler](https://github.com/gevans/sidekiq-throttler). So, if
19
18
  your worker already looks like this example I lifted from the sidekiq-throttler wiki:
20
19
 
21
20
  ```ruby
@@ -87,15 +86,37 @@ The configuration above would result in any jobs beyond the first 50 in a one
87
86
  hour period being delayed. The server will continue to fetch items from redis, &
88
87
  will place any items that are beyond the threshold at the back of their queue.
89
88
 
89
+ ### Dynamic Configuration
90
+
91
+ The simplest way to set the rate-limiting options (`:name`, `:limit`, and `:period`) is to assign them each a static value (as above). In some cases, you may wish to calculate values for these options for each specific job. You can do this by supplying a `Proc` for any or all of these options.
92
+
93
+ The `Proc` may receive as its arguments the same values that will be passed to `perform` when the job is finally performed.
94
+
95
+ ```ruby
96
+ class Job
97
+ include Sidekiq::Worker
98
+
99
+ sidekiq_options :queue => "my_queue",
100
+ :rate => {
101
+ :name => ->(user_id, rate_limit) { user_id },
102
+ :limit => ->(user_id, rate_limit) { rate_limit },
103
+ :period => ->{ Date.today.monday? ? 2.hours : 4.hours }, # can ignore arguments
104
+ }
105
+
106
+ def perform(user_id, rate_limit)
107
+ ## do something
108
+ ```
109
+
110
+ **Caveat**: Normally, Sidekiq stores the `sidekiq_options` with the job on your Redis server at the time the job is enqueued, and it is these stored values that are used for rate-limiting. This means that if you deploy a new version of your code with different `sidekiq_options`, the already-queued jobs will continue to behave according to the options that were in place when they were created. When you supply a `Proc` for one or more of your configuration options, your rate-limiting options can no longer be stored in Redis, but must instead be calculated when the job is fetched by your Sidekiq server for potential execution. If your application code changes while a job is in the queue, it may run with different `sidekiq_options` than existed when it was first enqueued.
111
+
90
112
  ## Motivation
91
113
 
92
114
  Sidekiq::Throttler is great for smaller quantities of jobs, but falls down a bit
93
- for larger queues (see [issue #8](/gevans/sidekiq-throttler/issues/8). In addition, jobs that are
94
- limited multiple times are counted as 'processed' each time, so the stats baloon quickly.
115
+ for larger queues (see [issue #8](https://github.com/gevans/sidekiq-throttler/issues/8)). In addition, jobs that are
116
+ limited multiple times are counted as 'processed' each time, so the stats balloon quickly.
95
117
 
96
118
  ## TODO
97
119
 
98
- * Most or all of the configuration options should support procs
99
120
  * While it subclasses instead of monkey patching, setting Sidekiq.options[:fetch]
100
121
  is still asking for interaction issues. It would be better for this to be directly
101
122
  in sidekiq or to use some other means to accomplish this goal.
@@ -14,18 +14,20 @@ module Sidekiq::RateLimiter
14
14
  def limit(work)
15
15
  message = JSON.parse(work.message) rescue {}
16
16
 
17
+ args = message['args']
17
18
  klass = message['class']
18
- rate = message['rate'] || message['throttle'] || {}
19
- limit = rate['limit'] || rate['threshold']
20
- interval = rate['period'] || rate['interval']
21
- name = rate['name'] || DEFAULT_LIMIT_NAME
19
+ rate = Rate.new(message)
22
20
 
23
- return work unless !!(klass && limit && interval)
21
+ return work unless !!(klass && rate.valid?)
22
+
23
+ limit = rate.limit
24
+ interval = rate.interval
25
+ name = rate.name
24
26
 
25
27
  options = {
26
- :limit => limit,
27
- :interval => interval,
28
- :name => name,
28
+ :limit => (limit.respond_to?(:call) ? limit.call(*args) : limit).to_i,
29
+ :interval => (interval.respond_to?(:call) ? interval.call(*args) : interval).to_f,
30
+ :name => (name.respond_to?(:call) ? name.call(*args) : name).to_s,
29
31
  }
30
32
 
31
33
  Sidekiq.redis do |conn|
@@ -39,6 +41,56 @@ module Sidekiq::RateLimiter
39
41
  end
40
42
  end
41
43
  end
44
+
45
+ end
46
+
47
+ class Rate
48
+ def initialize(message)
49
+ @message = message
50
+ end
51
+
52
+ def limit
53
+ rate['limit'] || rate['threshold']
54
+ end
55
+
56
+ def interval
57
+ rate['interval'] || rate['period']
58
+ end
59
+
60
+ def name
61
+ rate['name'] || DEFAULT_LIMIT_NAME
62
+ end
63
+
64
+ def valid?
65
+ !!(limit && interval)
66
+ end
67
+
68
+ private
69
+
70
+ def rate
71
+ use_server_rate? ? server_rate : client_rate
72
+ end
73
+
74
+ def use_server_rate?
75
+ server_rate['limit'] && server_rate['limit'].respond_to?(:call) ||
76
+ server_rate['threshold'] && server_rate['threshold'].respond_to?(:call) ||
77
+ server_rate['period'] && server_rate['period'].respond_to?(:call) ||
78
+ server_rate['interval'] && server_rate['interval'].respond_to?(:call) ||
79
+ server_rate['name'] && server_rate['name'].respond_to?(:call)
80
+ end
81
+
82
+ def client_rate
83
+ @client_rate ||= @message['rate'] || @message['throttle'] || {}
84
+ end
85
+
86
+ def server_rate
87
+ return @server_rate if @server_rate
88
+
89
+ worker_class = @message['class']
90
+ options = Object.const_get(worker_class).get_sidekiq_options rescue {}
91
+ server_rate = options['rate'] || options['throttle'] || {}
92
+ @server_rate = server_rate.stringify_keys
93
+ end
42
94
  end
43
95
 
44
96
  class Limit < RedisRateLimiter
@@ -1,5 +1,5 @@
1
1
  module Sidekiq
2
2
  module RateLimiter
3
- VERSION = "0.0.1"
3
+ VERSION = "0.1.0"
4
4
  end
5
5
  end
@@ -11,8 +11,8 @@ Gem::Specification.new do |s|
11
11
  s.authors = ["Blake Thomas", "Enova"]
12
12
  s.email = ["bwthomas@gmail.com"]
13
13
  s.homepage = "https://github.com/enova/sidekiq-rate-limiter"
14
- s.summary = %q{Rate-limit Sidekiq fetches by worker class}
15
- s.description = %q{Rate-limit Sidekiq fetches by worker class}
14
+ s.summary = %q{Redis-backed, per-worker rate limits for job processing}
15
+ s.description = %q{Redis-backed, per-worker rate limits for job processing}
16
16
  s.rubyforge_project = "nowarning"
17
17
 
18
18
  s.files = `git ls-files`.split("\n")
@@ -27,6 +27,6 @@ Gem::Specification.new do |s|
27
27
  s.add_development_dependency "simplecov-rcov"
28
28
 
29
29
  s.add_dependency "redis"
30
- s.add_dependency "sidekiq"
30
+ s.add_dependency "sidekiq", ">= 2.0", "< 4.0"
31
31
  s.add_dependency "redis_rate_limiter"
32
32
  end
@@ -1,5 +1,6 @@
1
1
  require 'spec_helper'
2
2
  require 'sidekiq'
3
+ require 'sidekiq/api'
3
4
 
4
5
  describe Sidekiq::RateLimiter::Fetch do
5
6
  before(:all) do
@@ -13,6 +14,17 @@ describe Sidekiq::RateLimiter::Fetch do
13
14
  }
14
15
  def perform(*args); end
15
16
  end
17
+ class ProcJob
18
+ include Sidekiq::Worker
19
+ sidekiq_options 'queue' => 'basic',
20
+ 'retry' => false,
21
+ 'rate' => {
22
+ 'limit' => ->(arg1, arg2) { arg2 },
23
+ 'name' => ->(arg1, arg2) { arg2 },
24
+ 'period' => ->(arg1, arg2) { arg2 }
25
+ }
26
+ def perform(arg1, arg2); end
27
+ end
16
28
  end
17
29
 
18
30
  let(:options) { { queues: [queue, another_queue, another_queue] } }
@@ -20,6 +32,7 @@ describe Sidekiq::RateLimiter::Fetch do
20
32
  let(:another_queue) { 'some_other_queue' }
21
33
  let(:args) { ['I am some args'] }
22
34
  let(:worker) { Job }
35
+ let(:proc_worker) { ProcJob }
23
36
  let(:redis_class) { Sidekiq.redis { |conn| conn.class } }
24
37
 
25
38
  it 'should inherit from Sidekiq::BasicFetch' do
@@ -61,4 +74,13 @@ describe Sidekiq::RateLimiter::Fetch do
61
74
  q.size.should == 1
62
75
  end
63
76
 
77
+ it 'should accept procs for limit, name, and period config keys', queuing: true do
78
+ proc_worker.perform_async(1,2)
79
+
80
+ Sidekiq::RateLimiter::Limit.should_receive(:new).with(anything(), {:limit => 2, :interval => 2, :name => "2"}).and_call_original
81
+
82
+ fetch = described_class.new(options)
83
+ work = fetch.retrieve_work
84
+ end
85
+
64
86
  end
metadata CHANGED
@@ -1,8 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sidekiq-rate-limiter
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
5
- prerelease:
4
+ version: 0.1.0
6
5
  platform: ruby
7
6
  authors:
8
7
  - Blake Thomas
@@ -10,137 +9,127 @@ authors:
10
9
  autorequire:
11
10
  bindir: bin
12
11
  cert_chain: []
13
- date: 2013-12-03 00:00:00.000000000 Z
12
+ date: 2014-05-30 00:00:00.000000000 Z
14
13
  dependencies:
15
14
  - !ruby/object:Gem::Dependency
16
15
  name: pry
17
16
  requirement: !ruby/object:Gem::Requirement
18
- none: false
19
17
  requirements:
20
- - - ! '>='
18
+ - - '>='
21
19
  - !ruby/object:Gem::Version
22
20
  version: '0'
23
21
  type: :development
24
22
  prerelease: false
25
23
  version_requirements: !ruby/object:Gem::Requirement
26
- none: false
27
24
  requirements:
28
- - - ! '>='
25
+ - - '>='
29
26
  - !ruby/object:Gem::Version
30
27
  version: '0'
31
28
  - !ruby/object:Gem::Dependency
32
29
  name: rake
33
30
  requirement: !ruby/object:Gem::Requirement
34
- none: false
35
31
  requirements:
36
- - - ! '>='
32
+ - - '>='
37
33
  - !ruby/object:Gem::Version
38
34
  version: '0'
39
35
  type: :development
40
36
  prerelease: false
41
37
  version_requirements: !ruby/object:Gem::Requirement
42
- none: false
43
38
  requirements:
44
- - - ! '>='
39
+ - - '>='
45
40
  - !ruby/object:Gem::Version
46
41
  version: '0'
47
42
  - !ruby/object:Gem::Dependency
48
43
  name: rspec
49
44
  requirement: !ruby/object:Gem::Requirement
50
- none: false
51
45
  requirements:
52
- - - ! '>='
46
+ - - '>='
53
47
  - !ruby/object:Gem::Version
54
48
  version: '0'
55
49
  type: :development
56
50
  prerelease: false
57
51
  version_requirements: !ruby/object:Gem::Requirement
58
- none: false
59
52
  requirements:
60
- - - ! '>='
53
+ - - '>='
61
54
  - !ruby/object:Gem::Version
62
55
  version: '0'
63
56
  - !ruby/object:Gem::Dependency
64
57
  name: simplecov
65
58
  requirement: !ruby/object:Gem::Requirement
66
- none: false
67
59
  requirements:
68
- - - ! '>='
60
+ - - '>='
69
61
  - !ruby/object:Gem::Version
70
62
  version: '0'
71
63
  type: :development
72
64
  prerelease: false
73
65
  version_requirements: !ruby/object:Gem::Requirement
74
- none: false
75
66
  requirements:
76
- - - ! '>='
67
+ - - '>='
77
68
  - !ruby/object:Gem::Version
78
69
  version: '0'
79
70
  - !ruby/object:Gem::Dependency
80
71
  name: simplecov-rcov
81
72
  requirement: !ruby/object:Gem::Requirement
82
- none: false
83
73
  requirements:
84
- - - ! '>='
74
+ - - '>='
85
75
  - !ruby/object:Gem::Version
86
76
  version: '0'
87
77
  type: :development
88
78
  prerelease: false
89
79
  version_requirements: !ruby/object:Gem::Requirement
90
- none: false
91
80
  requirements:
92
- - - ! '>='
81
+ - - '>='
93
82
  - !ruby/object:Gem::Version
94
83
  version: '0'
95
84
  - !ruby/object:Gem::Dependency
96
85
  name: redis
97
86
  requirement: !ruby/object:Gem::Requirement
98
- none: false
99
87
  requirements:
100
- - - ! '>='
88
+ - - '>='
101
89
  - !ruby/object:Gem::Version
102
90
  version: '0'
103
91
  type: :runtime
104
92
  prerelease: false
105
93
  version_requirements: !ruby/object:Gem::Requirement
106
- none: false
107
94
  requirements:
108
- - - ! '>='
95
+ - - '>='
109
96
  - !ruby/object:Gem::Version
110
97
  version: '0'
111
98
  - !ruby/object:Gem::Dependency
112
99
  name: sidekiq
113
100
  requirement: !ruby/object:Gem::Requirement
114
- none: false
115
101
  requirements:
116
- - - ! '>='
102
+ - - '>='
117
103
  - !ruby/object:Gem::Version
118
- version: '0'
104
+ version: '2.0'
105
+ - - <
106
+ - !ruby/object:Gem::Version
107
+ version: '4.0'
119
108
  type: :runtime
120
109
  prerelease: false
121
110
  version_requirements: !ruby/object:Gem::Requirement
122
- none: false
123
111
  requirements:
124
- - - ! '>='
112
+ - - '>='
125
113
  - !ruby/object:Gem::Version
126
- version: '0'
114
+ version: '2.0'
115
+ - - <
116
+ - !ruby/object:Gem::Version
117
+ version: '4.0'
127
118
  - !ruby/object:Gem::Dependency
128
119
  name: redis_rate_limiter
129
120
  requirement: !ruby/object:Gem::Requirement
130
- none: false
131
121
  requirements:
132
- - - ! '>='
122
+ - - '>='
133
123
  - !ruby/object:Gem::Version
134
124
  version: '0'
135
125
  type: :runtime
136
126
  prerelease: false
137
127
  version_requirements: !ruby/object:Gem::Requirement
138
- none: false
139
128
  requirements:
140
- - - ! '>='
129
+ - - '>='
141
130
  - !ruby/object:Gem::Version
142
131
  version: '0'
143
- description: Rate-limit Sidekiq fetches by worker class
132
+ description: Redis-backed, per-worker rate limits for job processing
144
133
  email:
145
134
  - bwthomas@gmail.com
146
135
  executables: []
@@ -151,6 +140,7 @@ files:
151
140
  - .rspec
152
141
  - .simplecov
153
142
  - .travis.yml
143
+ - CHANGELOG.md
154
144
  - Gemfile
155
145
  - LICENSE
156
146
  - README.md
@@ -167,31 +157,29 @@ files:
167
157
  homepage: https://github.com/enova/sidekiq-rate-limiter
168
158
  licenses:
169
159
  - MIT
160
+ metadata: {}
170
161
  post_install_message:
171
162
  rdoc_options: []
172
163
  require_paths:
173
164
  - lib
174
165
  required_ruby_version: !ruby/object:Gem::Requirement
175
- none: false
176
166
  requirements:
177
- - - ! '>='
167
+ - - '>='
178
168
  - !ruby/object:Gem::Version
179
169
  version: '0'
180
170
  required_rubygems_version: !ruby/object:Gem::Requirement
181
- none: false
182
171
  requirements:
183
- - - ! '>='
172
+ - - '>='
184
173
  - !ruby/object:Gem::Version
185
174
  version: '0'
186
175
  requirements: []
187
176
  rubyforge_project: nowarning
188
- rubygems_version: 1.8.23
177
+ rubygems_version: 2.0.14
189
178
  signing_key:
190
- specification_version: 3
191
- summary: Rate-limit Sidekiq fetches by worker class
179
+ specification_version: 4
180
+ summary: Redis-backed, per-worker rate limits for job processing
192
181
  test_files:
193
182
  - spec/sidekiq-rate-limiter/fetch_spec.rb
194
183
  - spec/sidekiq-rate-limiter/server_spec.rb
195
184
  - spec/spec_helper.rb
196
185
  - spec/support/redis/.keep
197
- has_rdoc: