sidekiq-dynamic-throttle 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: 9b7fbc2235c707b7b851a12b9e03bf1b3db1cbaa
4
+ data.tar.gz: 080520421f62a90964903da61665b75e5b72c3ac
5
+ SHA512:
6
+ metadata.gz: e7e73a1a531249a84ab300f9e88d75f27b2c2b4c11b64ee954274bc80ffec007c1c384805440ff77de5e0378de1c7773380424d1943a564739ca95ffc74bbd2e
7
+ data.tar.gz: 61216980ba1bb3de1e4c640f063e3252888da87c17dbb554cef53220f14498d871550861ebdb8e4b42a9de7d6da270f24bcee6fe28794cfe16d91f96ea29ca26
data/.gitignore ADDED
@@ -0,0 +1,7 @@
1
+ .bundle/
2
+ *.gem
3
+ Gemfile.lock
4
+ pkg/
5
+ coverage/
6
+ *.swp
7
+ spec/support/redis/
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color
data/.simplecov ADDED
@@ -0,0 +1,12 @@
1
+ unless ENV['COVERAGE'] == 'false'
2
+ require 'simplecov-rcov'
3
+
4
+ class SimpleCov::Formatter::MergedFormatter
5
+ def format(result)
6
+ SimpleCov::Formatter::HTMLFormatter.new.format(result)
7
+ SimpleCov::Formatter::RcovFormatter.new.format(result)
8
+ end
9
+ end
10
+
11
+ SimpleCov.formatter = SimpleCov::Formatter::MergedFormatter
12
+ end
data/.travis.yml ADDED
@@ -0,0 +1,4 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.0.0
4
+ - 1.9.3
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/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source "http://rubygems.org"
2
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2013 Enova
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
6
+ this software and associated documentation files (the "Software"), to deal in
7
+ the Software without restriction, including without limitation the rights to
8
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9
+ the Software, and to permit persons to whom the Software is furnished to do so,
10
+ subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17
+ FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18
+ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,62 @@
1
+ Sidekiq Dynamic Throttle
2
+ ====================
3
+
4
+ Throttle fetching from queues dynamically.
5
+
6
+ ## Compatibility
7
+
8
+ sidekiq-rate-limiter works by using a custom fetch class, the class responsible
9
+ for pulling work from the queue stored in redis. Consequently you'll want to be
10
+ careful about using other gems that use a same strategy, [sidekiq-priority](https://github.com/socialpandas/sidekiq-priority)
11
+ being one example.
12
+
13
+
14
+ ## Installation
15
+
16
+ Add this line to your application's Gemfile:
17
+
18
+ gem 'sidekiq-dynamic-throttle'
19
+
20
+ And then execute:
21
+
22
+ $ bundle
23
+
24
+ Or install it yourself as:
25
+
26
+ $ gem install sidekiq-dynamic-throttle
27
+
28
+ ## Configuration
29
+
30
+ See [server.rb](lib/sidekiq-dynamic-throttle/server.rb) for an example of how to
31
+ configure sidekiq-rate-limiter. Alternatively you can add the following to your
32
+ initializer or what-have-you:
33
+
34
+ ```ruby
35
+ require 'sidekiq-dynamic-throttle/server'
36
+ ```
37
+
38
+ Or, if you prefer, amend your Gemfile like so:
39
+
40
+ gem 'sidekiq-dynamic-throttle', :require => 'sidekiq-dynamic-throttle/server'
41
+
42
+ ## Motivation
43
+
44
+ Sidekiq::Throttler is great for smaller quantities of jobs, but falls down a bit
45
+ for larger queues (see [issue #8](https://github.com/gevans/sidekiq-throttler/issues/8)). In addition, jobs that are
46
+ limited multiple times are counted as 'processed' each time, so the stats balloon quickly.
47
+
48
+ [Sidekiq::RateLimiter](https://github.com/enova/sidekiq-rate-limiter) works well when you've got consistent job processing limits, but can be a bit restrictive when throttling needs to be based on something that is more dynamic (i.e. API limits).
49
+
50
+ ## Contributing
51
+
52
+ 1. Fork
53
+ 2. Commit
54
+ 5. Pull Request
55
+
56
+ ## License
57
+
58
+ MIT. See [LICENSE](LICENSE) for details.
59
+
60
+ ## Credits
61
+
62
+ This was originally forked from [sidekiq-rate-limiter](https://github.com/enova/sidekiq-rate-limiter).
data/Rakefile ADDED
@@ -0,0 +1,11 @@
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
3
+
4
+ require 'rspec/core/rake_task'
5
+ RSpec::Core::RakeTask.new
6
+ task :default => :spec
7
+
8
+ desc 'Start IRB with preloaded environment'
9
+ task :console do
10
+ exec 'irb', "-I#{File.join(File.dirname(__FILE__), 'lib')}", '-rsidekiq-rate-limiter'
11
+ end
@@ -0,0 +1,19 @@
1
+ module Sidekiq::DynamicThrottle
2
+ class Fetch < Sidekiq::BasicFetch
3
+ def retrieve_work
4
+ work = Sidekiq.redis { |conn| conn.brpop(*unthrottled_queues) }
5
+ UnitOfWork.new(*work) if work
6
+ end
7
+
8
+ private
9
+
10
+ def unthrottled_queues
11
+ queues_cmd.reject do |queue|
12
+ throttles = Sidekiq::DynamicThrottle.throttles[queue]
13
+ next if throttles.blank?
14
+
15
+ throttles.find { |throttle| throttle.throttled? }
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,6 @@
1
+ require 'sidekiq-rate-limiter/version'
2
+ require 'sidekiq-rate-limiter/fetch'
3
+
4
+ Sidekiq.configure_server do |config|
5
+ Sidekiq.options[:fetch] = Sidekiq::DynamicThrottle::Fetch
6
+ end
@@ -0,0 +1,5 @@
1
+ module Sidekiq
2
+ module DynamicThrottle
3
+ VERSION = '0.1.0'
4
+ end
5
+ end
@@ -0,0 +1,15 @@
1
+ require 'celluloid'
2
+ require 'sidekiq/fetch'
3
+
4
+ require 'sidekiq-dynamic-throttle/version'
5
+ require 'sidekiq-dynamic-throttle/fetch'
6
+
7
+ module Sidekiq::DynamicThrottle
8
+ def self.throttles
9
+ @throttles ||= Hash.new { |hash, key| hash[key] = [] }
10
+ end
11
+
12
+ def self.register_throttle(queue, throttle)
13
+ throttles['queue:' + queue.to_s] << throttle
14
+ end
15
+ end
@@ -0,0 +1,30 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ $:.push File.expand_path '../lib', __FILE__
4
+ require 'sidekiq-dynamic-throttle/version'
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = 'sidekiq-dynamic-throttle'
8
+ s.license = 'MIT'
9
+ s.version = Sidekiq::DynamicThrottle::VERSION
10
+ s.platform = Gem::Platform::RUBY
11
+ s.authors = ['Bob Breznak', 'Blake Thomas', 'Enova']
12
+ s.email = ['bob.breznak@gmail.com', 'bwthomas@gmail.com']
13
+ s.homepage = 'https://github.com/bobbrez/sidekiq-dynamic-throttle'
14
+ s.summary = 'Worker backed throttling for job processing'
15
+ s.description = 'Worker backed throttling for job processing'
16
+
17
+ s.files = `git ls-files`.split("\n")
18
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
19
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
20
+ s.require_paths = ['lib']
21
+
22
+ s.add_development_dependency 'pry'
23
+ s.add_development_dependency 'rake'
24
+ s.add_development_dependency 'rspec'
25
+ s.add_development_dependency 'simplecov'
26
+ s.add_development_dependency 'simplecov-rcov'
27
+
28
+ s.add_dependency 'redis'
29
+ s.add_dependency 'sidekiq', '>= 2.0', '< 4.0'
30
+ end
@@ -0,0 +1,86 @@
1
+ require 'spec_helper'
2
+ require 'sidekiq'
3
+ require 'sidekiq/api'
4
+
5
+ describe Sidekiq::RateLimiter::Fetch do
6
+ before(:all) do
7
+ class Job
8
+ include Sidekiq::Worker
9
+ sidekiq_options 'queue' => 'basic',
10
+ 'retry' => false,
11
+ 'rate' => {
12
+ 'limit' => 1,
13
+ 'period' => 1
14
+ }
15
+ def perform(*args); end
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
28
+ end
29
+
30
+ let(:options) { { queues: [queue, another_queue, another_queue] } }
31
+ let(:queue) { 'basic' }
32
+ let(:another_queue) { 'some_other_queue' }
33
+ let(:args) { ['I am some args'] }
34
+ let(:worker) { Job }
35
+ let(:proc_worker) { ProcJob }
36
+ let(:redis_class) { Sidekiq.redis { |conn| conn.class } }
37
+
38
+ it 'should inherit from Sidekiq::BasicFetch' do
39
+ described_class.should < Sidekiq::BasicFetch
40
+ end
41
+
42
+ it 'should retrieve work with strict setting' do
43
+ fetch = described_class.new options.merge(:strict => true)
44
+ fetch.queues_cmd.should eql(["queue:#{queue}", "queue:#{another_queue}", 1])
45
+ end
46
+
47
+ it 'should retrieve work', queuing: true do
48
+ worker.perform_async(*args)
49
+ fetch = described_class.new(options)
50
+ work = fetch.retrieve_work
51
+ parsed = JSON.parse(work.message)
52
+
53
+ work.should_not be_nil
54
+ work.queue_name.should eql(queue)
55
+ work.acknowledge.should be_nil
56
+
57
+ parsed.should include(worker.get_sidekiq_options)
58
+ parsed.should include("class" => worker.to_s, "args" => args)
59
+ parsed.should include("jid", "enqueued_at")
60
+
61
+ q = Sidekiq::Queue.new(queue)
62
+ q.size.should == 0
63
+ end
64
+
65
+ it 'should place rate-limited work at the back of the queue', queuing: true do
66
+ worker.perform_async(*args)
67
+ Sidekiq::RateLimiter::Limit.any_instance.should_receive(:exceeded?).and_return(true)
68
+ redis_class.any_instance.should_receive(:lpush).exactly(:once).and_call_original
69
+
70
+ fetch = described_class.new(options)
71
+ fetch.retrieve_work.should be_nil
72
+
73
+ q = Sidekiq::Queue.new(queue)
74
+ q.size.should == 1
75
+ end
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
+
86
+ end
@@ -0,0 +1,20 @@
1
+ require 'spec_helper'
2
+
3
+ describe Sidekiq::RateLimiter, 'server configuration' do
4
+ before do
5
+ Sidekiq.stub(:server? => true)
6
+ require 'sidekiq-rate-limiter/server'
7
+ end
8
+
9
+ it 'should set Sidekiq.options[:fetch] as desired' do
10
+ Sidekiq.configure_server do |config|
11
+ Sidekiq.options[:fetch].should eql(Sidekiq::RateLimiter::Fetch)
12
+ end
13
+ end
14
+
15
+ it 'should inherit from Sidekiq::BasicFetch' do
16
+ Sidekiq.configure_server do |config|
17
+ Sidekiq.options[:fetch].should < Sidekiq::BasicFetch
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,96 @@
1
+ require 'sidekiq'
2
+ require 'sidekiq/testing'
3
+
4
+ ## Confirming presence of redis server executable
5
+ abort "## `redis-server` not in path" if %x(which redis-server).empty?
6
+ redis_dir = "#{File.dirname(__FILE__)}/support/redis"
7
+
8
+ ## Redis configuration
9
+ REDIS_CONFIG = <<-CONF
10
+ daemonize yes
11
+ pidfile #{redis_dir}/test.pid
12
+ port 6380
13
+ timeout 300
14
+ save 900 1
15
+ save 300 10
16
+ save 60 10000
17
+ dbfilename test.rdb
18
+ dir #{redis_dir}
19
+ loglevel warning
20
+ logfile stdout
21
+ databases 1
22
+ CONF
23
+
24
+ %x(echo '#{REDIS_CONFIG}' > #{redis_dir}/test.conf)
25
+ redis_command = "redis-server #{redis_dir}/test.conf"
26
+ %x[ #{redis_command} ]
27
+ ##
28
+
29
+ ## Configuring sidekiq
30
+ options = {
31
+ logger: nil,
32
+ redis: { :url => "redis://localhost:6380/0" }
33
+ }
34
+
35
+ Sidekiq.configure_client do |config|
36
+ options.each do |option, value|
37
+ config.send("#{option}=", value)
38
+ end
39
+ end
40
+
41
+ Sidekiq.configure_server do |config|
42
+ options.each do |option, value|
43
+ config.send("#{option}=", value)
44
+ end
45
+ end
46
+ ##
47
+
48
+ ## Configuring simplecov
49
+ require 'simplecov'
50
+
51
+ SimpleCov.start do
52
+ add_filter "vendor"
53
+ add_filter "spec"
54
+ end
55
+
56
+ require File.expand_path("../../lib/sidekiq-rate-limiter", __FILE__)
57
+ ##
58
+
59
+ ## Hook to set Sidekiq::Testing mode using rspec tags
60
+ RSpec.configure do |config|
61
+ config.before(:each) do
62
+ ## Use metadata to determine testing behavior
63
+ ## for queuing.
64
+
65
+ case example.metadata[:queuing].to_s
66
+ when 'enable', 'enabled', 'on', 'true'
67
+ Sidekiq::Testing.disable!
68
+ when 'fake', 'mock'
69
+ Sidekiq::Testing.fake!
70
+ when 'inline'
71
+ Sidekiq::Testing.inline!
72
+ else
73
+ defined?(Redis::Connection::Memory) ?
74
+ Sidekiq::Testing.disable! : Sidekiq::Testing.inline!
75
+ end
76
+
77
+ if Sidekiq::Testing.disabled?
78
+ Sidekiq.redis { |conn| conn.flushdb }
79
+ elsif Sidekiq::Testing.fake?
80
+ Sidekiq::Worker.clear_all
81
+ end
82
+
83
+ end
84
+
85
+ config.after(:all) do
86
+ ## Stopping Redis
87
+ ps = %x(ps -A -o pid,command | grep '#{redis_command}' | grep -v grep).split($/)
88
+ pids = ps.map { |p| p.split(/\s+/).reject(&:empty?).first.to_i }
89
+ pids.each { |pid| Process.kill("TERM", pid) }
90
+
91
+ ## Cleaning up
92
+ sleep 0.1
93
+ %x(rm -rf #{redis_dir}/*)
94
+ end
95
+ end
96
+ ##
File without changes
metadata ADDED
@@ -0,0 +1,174 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: sidekiq-dynamic-throttle
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Bob Breznak
8
+ - Blake Thomas
9
+ - Enova
10
+ autorequire:
11
+ bindir: bin
12
+ cert_chain: []
13
+ date: 2015-01-09 00:00:00.000000000 Z
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: pry
17
+ requirement: !ruby/object:Gem::Requirement
18
+ requirements:
19
+ - - ">="
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ requirements:
26
+ - - ">="
27
+ - !ruby/object:Gem::Version
28
+ version: '0'
29
+ - !ruby/object:Gem::Dependency
30
+ name: rake
31
+ requirement: !ruby/object:Gem::Requirement
32
+ requirements:
33
+ - - ">="
34
+ - !ruby/object:Gem::Version
35
+ version: '0'
36
+ type: :development
37
+ prerelease: false
38
+ version_requirements: !ruby/object:Gem::Requirement
39
+ requirements:
40
+ - - ">="
41
+ - !ruby/object:Gem::Version
42
+ version: '0'
43
+ - !ruby/object:Gem::Dependency
44
+ name: rspec
45
+ requirement: !ruby/object:Gem::Requirement
46
+ requirements:
47
+ - - ">="
48
+ - !ruby/object:Gem::Version
49
+ version: '0'
50
+ type: :development
51
+ prerelease: false
52
+ version_requirements: !ruby/object:Gem::Requirement
53
+ requirements:
54
+ - - ">="
55
+ - !ruby/object:Gem::Version
56
+ version: '0'
57
+ - !ruby/object:Gem::Dependency
58
+ name: simplecov
59
+ requirement: !ruby/object:Gem::Requirement
60
+ requirements:
61
+ - - ">="
62
+ - !ruby/object:Gem::Version
63
+ version: '0'
64
+ type: :development
65
+ prerelease: false
66
+ version_requirements: !ruby/object:Gem::Requirement
67
+ requirements:
68
+ - - ">="
69
+ - !ruby/object:Gem::Version
70
+ version: '0'
71
+ - !ruby/object:Gem::Dependency
72
+ name: simplecov-rcov
73
+ requirement: !ruby/object:Gem::Requirement
74
+ requirements:
75
+ - - ">="
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
78
+ type: :development
79
+ prerelease: false
80
+ version_requirements: !ruby/object:Gem::Requirement
81
+ requirements:
82
+ - - ">="
83
+ - !ruby/object:Gem::Version
84
+ version: '0'
85
+ - !ruby/object:Gem::Dependency
86
+ name: redis
87
+ requirement: !ruby/object:Gem::Requirement
88
+ requirements:
89
+ - - ">="
90
+ - !ruby/object:Gem::Version
91
+ version: '0'
92
+ type: :runtime
93
+ prerelease: false
94
+ version_requirements: !ruby/object:Gem::Requirement
95
+ requirements:
96
+ - - ">="
97
+ - !ruby/object:Gem::Version
98
+ version: '0'
99
+ - !ruby/object:Gem::Dependency
100
+ name: sidekiq
101
+ requirement: !ruby/object:Gem::Requirement
102
+ requirements:
103
+ - - ">="
104
+ - !ruby/object:Gem::Version
105
+ version: '2.0'
106
+ - - "<"
107
+ - !ruby/object:Gem::Version
108
+ version: '4.0'
109
+ type: :runtime
110
+ prerelease: false
111
+ version_requirements: !ruby/object:Gem::Requirement
112
+ requirements:
113
+ - - ">="
114
+ - !ruby/object:Gem::Version
115
+ version: '2.0'
116
+ - - "<"
117
+ - !ruby/object:Gem::Version
118
+ version: '4.0'
119
+ description: Worker backed throttling for job processing
120
+ email:
121
+ - bob.breznak@gmail.com
122
+ - bwthomas@gmail.com
123
+ executables: []
124
+ extensions: []
125
+ extra_rdoc_files: []
126
+ files:
127
+ - ".gitignore"
128
+ - ".rspec"
129
+ - ".simplecov"
130
+ - ".travis.yml"
131
+ - CHANGELOG.md
132
+ - Gemfile
133
+ - LICENSE
134
+ - README.md
135
+ - Rakefile
136
+ - lib/sidekiq-dynamic-throttle.rb
137
+ - lib/sidekiq-dynamic-throttle/fetch.rb
138
+ - lib/sidekiq-dynamic-throttle/server.rb
139
+ - lib/sidekiq-dynamic-throttle/version.rb
140
+ - sidekiq-dynamic-throttle.gemspec
141
+ - spec/sidekiq-rate-limiter/fetch_spec.rb
142
+ - spec/sidekiq-rate-limiter/server_spec.rb
143
+ - spec/spec_helper.rb
144
+ - spec/support/redis/.keep
145
+ homepage: https://github.com/bobbrez/sidekiq-dynamic-throttle
146
+ licenses:
147
+ - MIT
148
+ metadata: {}
149
+ post_install_message:
150
+ rdoc_options: []
151
+ require_paths:
152
+ - lib
153
+ required_ruby_version: !ruby/object:Gem::Requirement
154
+ requirements:
155
+ - - ">="
156
+ - !ruby/object:Gem::Version
157
+ version: '0'
158
+ required_rubygems_version: !ruby/object:Gem::Requirement
159
+ requirements:
160
+ - - ">="
161
+ - !ruby/object:Gem::Version
162
+ version: '0'
163
+ requirements: []
164
+ rubyforge_project:
165
+ rubygems_version: 2.2.2
166
+ signing_key:
167
+ specification_version: 4
168
+ summary: Worker backed throttling for job processing
169
+ test_files:
170
+ - spec/sidekiq-rate-limiter/fetch_spec.rb
171
+ - spec/sidekiq-rate-limiter/server_spec.rb
172
+ - spec/spec_helper.rb
173
+ - spec/support/redis/.keep
174
+ has_rdoc: