sidekiq-dynamic-throttle 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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: