kubra-sidekiq-throttled 1.5.3 → 2.0.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 +4 -4
- data/README.adoc +4 -9
- data/lib/sidekiq/throttled/config.rb +1 -1
- data/lib/sidekiq/throttled/job.rb +2 -2
- data/lib/sidekiq/throttled/registry.rb +2 -2
- data/lib/sidekiq/throttled/strategy/base.rb +5 -1
- data/lib/sidekiq/throttled/strategy/concurrency.lua +2 -2
- data/lib/sidekiq/throttled/strategy/concurrency.rb +10 -8
- data/lib/sidekiq/throttled/version.rb +1 -1
- data/lib/sidekiq/throttled/web/stats.rb +7 -6
- data/lib/sidekiq/throttled/web.rb +17 -21
- data/web/views/index.erb +41 -0
- metadata +14 -16
- data/lib/sidekiq/throttled/web/throttled.html.erb +0 -35
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 98dc070784c42ddf555bbf84a5df49cf460efe2e5ccde93887498ca6295a5bc7
|
|
4
|
+
data.tar.gz: f2241d460e0879cb2153eac52ea4d06fd109ffd92a960d74f04fc75193d429db
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: bdacff8274dcbda5d30fcc798935e25ea5cdf4e1533f5895dd29346d42aacdfdb14bf5d99ea1edd95907dfcd43a7c492f8bca14393b8044d9f0b45f78ad8c5bf
|
|
7
|
+
data.tar.gz: 69de366c927ffdc47d18cc74753b5c325821ad8ffafa9786b4ff2344502b75ea343f630914ac22908de7240499cf07d8e349c8c6a8e1f83ba0db88052b713803
|
data/README.adoc
CHANGED
|
@@ -351,11 +351,9 @@ sidekiq_throttle(
|
|
|
351
351
|
|
|
352
352
|
This library aims to support and is tested against the following Ruby versions:
|
|
353
353
|
|
|
354
|
-
* Ruby 2.7.x
|
|
355
|
-
* Ruby 3.0.x
|
|
356
|
-
* Ruby 3.1.x
|
|
357
354
|
* Ruby 3.2.x
|
|
358
355
|
* Ruby 3.3.x
|
|
356
|
+
* Ruby 3.4.x
|
|
359
357
|
|
|
360
358
|
If something doesn't work on one of these versions, it's a bug.
|
|
361
359
|
|
|
@@ -375,15 +373,11 @@ dropped.
|
|
|
375
373
|
|
|
376
374
|
This library aims to support and work with following Sidekiq versions:
|
|
377
375
|
|
|
378
|
-
* Sidekiq
|
|
379
|
-
* Sidekiq 7.1.x
|
|
380
|
-
* Sidekiq 7.2.x
|
|
376
|
+
* Sidekiq 8.0.x
|
|
381
377
|
|
|
382
378
|
And the following Sidekiq Pro versions:
|
|
383
379
|
|
|
384
|
-
* Sidekiq Pro
|
|
385
|
-
* Sidekiq Pro 7.1.x
|
|
386
|
-
* Sidekiq Pro 7.2.x
|
|
380
|
+
* Sidekiq Pro 8.0.x
|
|
387
381
|
|
|
388
382
|
== Development
|
|
389
383
|
|
|
@@ -398,6 +392,7 @@ If you're working on Sidekiq-Pro support make sure that you have Sidekiq-Pro
|
|
|
398
392
|
license set either in the global config, or in `BUNDLE_GEMS\__CONTRIBSYS__COM`
|
|
399
393
|
environment variable.
|
|
400
394
|
|
|
395
|
+
|
|
401
396
|
== Contributing
|
|
402
397
|
|
|
403
398
|
* Fork sidekiq-throttled on GitHub
|
|
@@ -51,7 +51,7 @@ module Sidekiq
|
|
|
51
51
|
|
|
52
52
|
# @!attribute [w] default_requeue_options
|
|
53
53
|
def default_requeue_options=(options)
|
|
54
|
-
requeue_with = options.delete(:with)
|
|
54
|
+
requeue_with = options.delete(:with)&.to_sym || :enqueue
|
|
55
55
|
|
|
56
56
|
@default_requeue_options = options.merge({ with: requeue_with })
|
|
57
57
|
end
|
|
@@ -88,8 +88,8 @@ module Sidekiq
|
|
|
88
88
|
# @param [Hash] requeue What to do with jobs that are throttled
|
|
89
89
|
# @see Registry.add
|
|
90
90
|
# @return [void]
|
|
91
|
-
def sidekiq_throttle(**
|
|
92
|
-
Registry.add(self, **
|
|
91
|
+
def sidekiq_throttle(**)
|
|
92
|
+
Registry.add(self, **)
|
|
93
93
|
end
|
|
94
94
|
|
|
95
95
|
# Adds current worker to preconfigured throttling strategy. Allows
|
|
@@ -17,10 +17,10 @@ module Sidekiq
|
|
|
17
17
|
#
|
|
18
18
|
# @param (see Strategy#initialize)
|
|
19
19
|
# @return [Strategy]
|
|
20
|
-
def add(name, **
|
|
20
|
+
def add(name, **)
|
|
21
21
|
name = name.to_s
|
|
22
22
|
|
|
23
|
-
@strategies[name] = Strategy.new(name, **
|
|
23
|
+
@strategies[name] = Strategy.new(name, **)
|
|
24
24
|
end
|
|
25
25
|
|
|
26
26
|
# Adds alias for existing strategy.
|
|
@@ -14,7 +14,11 @@ module Sidekiq
|
|
|
14
14
|
key = @base_key.dup
|
|
15
15
|
return key unless @key_suffix
|
|
16
16
|
|
|
17
|
-
key << ":#{
|
|
17
|
+
key << ":#{key_suffix(job_args)}"
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def key_suffix(job_args)
|
|
21
|
+
@key_suffix.respond_to?(:call) ? @key_suffix.call(*job_args) : @key_suffix
|
|
18
22
|
rescue StandardError => e
|
|
19
23
|
Sidekiq.logger.error "Failed to get key suffix: #{e}"
|
|
20
24
|
raise e
|
|
@@ -19,8 +19,8 @@ end
|
|
|
19
19
|
-- can cause unnecessary delays in job processing. Underestimates are much
|
|
20
20
|
-- safer as they only increase workload of sidekiq processors.
|
|
21
21
|
local function est_current_backlog_size()
|
|
22
|
-
local old_size = tonumber(redis.call("HGET", backlog_info_key, "size") or 0
|
|
23
|
-
local old_timestamp = tonumber(redis.call("HGET", backlog_info_key, "timestamp") or now
|
|
22
|
+
local old_size = tonumber(redis.call("HGET", backlog_info_key, "size")) or 0
|
|
23
|
+
local old_timestamp = tonumber(redis.call("HGET", backlog_info_key, "timestamp")) or now
|
|
24
24
|
|
|
25
25
|
local jobs_lost_since_old_timestamp = (now - old_timestamp) / lost_job_threshold * lmt
|
|
26
26
|
|
|
@@ -31,13 +31,18 @@ module Sidekiq
|
|
|
31
31
|
# @param [#to_i] lost_job_threshold Seconds to wait before considering
|
|
32
32
|
# a job lost or dead. Default: 900 or 3 * avg_job_duration
|
|
33
33
|
# @param [Proc] key_suffix Dynamic key suffix generator.
|
|
34
|
+
# @param [#to_i] max_delay Maximum number of seconds to delay a job when it
|
|
35
|
+
# throttled. This prevents jobs from being schedule very far in the future
|
|
36
|
+
# when the backlog is large. Default: the smaller of 30 minutes or 10 * avg_job_duration
|
|
34
37
|
# @deprecated @param [#to_i] ttl Obsolete alias for `lost_job_threshold`.
|
|
35
38
|
# Default: 900 or 3 * avg_job_duration
|
|
36
|
-
def initialize(strategy_key, limit:, avg_job_duration: nil, ttl: nil,
|
|
39
|
+
def initialize(strategy_key, limit:, avg_job_duration: nil, ttl: nil, # rubocop:disable Metrics/ParameterLists
|
|
40
|
+
lost_job_threshold: ttl, key_suffix: nil, max_delay: nil)
|
|
37
41
|
@base_key = "#{strategy_key}:concurrency.v2"
|
|
38
42
|
@limit = limit
|
|
39
43
|
@avg_job_duration, @lost_job_threshold = interp_duration_args(avg_job_duration, lost_job_threshold)
|
|
40
44
|
@key_suffix = key_suffix
|
|
45
|
+
@max_delay = max_delay || [(10 * @avg_job_duration), 1_800].min
|
|
41
46
|
|
|
42
47
|
raise(ArgumentError, "lost_job_threshold must be greater than avg_job_duration") if
|
|
43
48
|
@lost_job_threshold <= @avg_job_duration
|
|
@@ -57,7 +62,7 @@ module Sidekiq
|
|
|
57
62
|
keys = [key(job_args), backlog_info_key(job_args)]
|
|
58
63
|
argv = [jid.to_s, job_limit, @lost_job_threshold, Time.now.to_f]
|
|
59
64
|
|
|
60
|
-
Sidekiq.redis { |redis|
|
|
65
|
+
Sidekiq.redis { |redis| 1 == SCRIPT.call(redis, keys: keys, argv: argv) }
|
|
61
66
|
end
|
|
62
67
|
|
|
63
68
|
# @return [Float] How long, in seconds, before we'll next be able to take on jobs
|
|
@@ -65,7 +70,8 @@ module Sidekiq
|
|
|
65
70
|
job_limit = limit(job_args)
|
|
66
71
|
return 0.0 if !job_limit || count(*job_args) < job_limit
|
|
67
72
|
|
|
68
|
-
estimated_backlog_size(job_args) * @avg_job_duration / limit(job_args)
|
|
73
|
+
(estimated_backlog_size(job_args) * @avg_job_duration / limit(job_args))
|
|
74
|
+
.then { |delay_sec| @max_delay * (1 - Math.exp(-delay_sec / @max_delay)) } # limit to max_delay
|
|
69
75
|
end
|
|
70
76
|
|
|
71
77
|
# @return [Integer] Current count of jobs
|
|
@@ -99,17 +105,13 @@ module Sidekiq
|
|
|
99
105
|
old_size = (old_size_str || 0).to_f
|
|
100
106
|
old_timestamp = (old_timestamp_str || Time.now).to_f
|
|
101
107
|
|
|
102
|
-
|
|
108
|
+
(old_size - jobs_lost_since(old_timestamp, job_args)).clamp(0, Float::INFINITY)
|
|
103
109
|
end
|
|
104
110
|
|
|
105
111
|
def jobs_lost_since(timestamp, job_args)
|
|
106
112
|
(Time.now.to_f - timestamp) / @lost_job_threshold * limit(job_args)
|
|
107
113
|
end
|
|
108
114
|
|
|
109
|
-
def nonneg(number)
|
|
110
|
-
[number, 0].max
|
|
111
|
-
end
|
|
112
|
-
|
|
113
115
|
def interp_duration_args(avg_job_duration, lost_job_threshold)
|
|
114
116
|
if avg_job_duration && lost_job_threshold
|
|
115
117
|
[avg_job_duration.to_i, lost_job_threshold.to_i]
|
|
@@ -62,12 +62,13 @@ module Sidekiq
|
|
|
62
62
|
|
|
63
63
|
# @return [String]
|
|
64
64
|
def humanize_integer(int)
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
65
|
+
int.to_s.chars
|
|
66
|
+
.reverse
|
|
67
|
+
.each_slice(3)
|
|
68
|
+
.map(&:reverse)
|
|
69
|
+
.reverse
|
|
70
|
+
.map(&:join)
|
|
71
|
+
.join(",")
|
|
71
72
|
end
|
|
72
73
|
end
|
|
73
74
|
end
|
|
@@ -1,43 +1,39 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
# stdlib
|
|
4
3
|
require "pathname"
|
|
5
4
|
|
|
6
|
-
# 3rd party
|
|
7
5
|
require "sidekiq"
|
|
8
6
|
require "sidekiq/web"
|
|
9
7
|
|
|
10
|
-
# internal
|
|
11
8
|
require_relative "./registry"
|
|
12
9
|
require_relative "./web/stats"
|
|
13
10
|
|
|
14
11
|
module Sidekiq
|
|
15
12
|
module Throttled
|
|
16
|
-
# Provides Sidekiq tab to monitor and reset throttled stats.
|
|
17
13
|
module Web
|
|
18
|
-
|
|
19
|
-
|
|
14
|
+
ROOT = Pathname.new(__dir__).join("../../../web").expand_path.realpath.freeze
|
|
15
|
+
VIEWS = ROOT.join("views").freeze
|
|
20
16
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
register_throttled_tab app
|
|
17
|
+
def self.registered(app)
|
|
18
|
+
app.get("/throttled") do
|
|
19
|
+
erb :index, views: VIEWS
|
|
25
20
|
end
|
|
26
21
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
app.get("/throttled") { erb THROTTLED_TPL.dup }
|
|
31
|
-
|
|
32
|
-
app.post("/throttled/:id/reset") do
|
|
33
|
-
Registry.get(params[:id], &:reset!)
|
|
34
|
-
redirect "#{root_path}throttled"
|
|
35
|
-
end
|
|
22
|
+
app.post("/throttled/:id/reset") do
|
|
23
|
+
Registry.get(route_params(:id), &:reset!)
|
|
24
|
+
redirect "#{root_path}throttled"
|
|
36
25
|
end
|
|
37
26
|
end
|
|
38
27
|
end
|
|
39
28
|
end
|
|
40
29
|
end
|
|
41
30
|
|
|
42
|
-
Sidekiq::Web.
|
|
43
|
-
|
|
31
|
+
Sidekiq::Web.configure do |config|
|
|
32
|
+
config.register_extension(
|
|
33
|
+
Sidekiq::Throttled::Web,
|
|
34
|
+
name: "throttled",
|
|
35
|
+
tab: %w[Throttled],
|
|
36
|
+
index: %w[throttled],
|
|
37
|
+
root_dir: Sidekiq::Throttled::Web::ROOT.to_s
|
|
38
|
+
)
|
|
39
|
+
end
|
data/web/views/index.erb
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
<section>
|
|
2
|
+
<header>
|
|
3
|
+
<h1>Throttled</h1>
|
|
4
|
+
</header>
|
|
5
|
+
|
|
6
|
+
<div class="table_container">
|
|
7
|
+
<table class="throttled">
|
|
8
|
+
<thead>
|
|
9
|
+
<tr>
|
|
10
|
+
<th>Name</th>
|
|
11
|
+
<th>Concurrency</th>
|
|
12
|
+
<th>Threshold</th>
|
|
13
|
+
<th>Actions</th>
|
|
14
|
+
</tr>
|
|
15
|
+
</thead>
|
|
16
|
+
<tbody>
|
|
17
|
+
<% Sidekiq::Throttled::Registry.each_with_static_keys do |name, strategy| %>
|
|
18
|
+
<tr>
|
|
19
|
+
<td style="vertical-align:middle;"><%= name %></td>
|
|
20
|
+
<td style="vertical-align:middle;text-align:center;">
|
|
21
|
+
<% strategy.concurrency.each do |concurrency| %>
|
|
22
|
+
<%= Sidekiq::Throttled::Web::Stats.new(concurrency).to_html %>
|
|
23
|
+
<% end %>
|
|
24
|
+
</td>
|
|
25
|
+
<td style="vertical-align:middle;text-align:center;">
|
|
26
|
+
<% strategy.threshold.each do |threshold| %>
|
|
27
|
+
<%= Sidekiq::Throttled::Web::Stats.new(threshold).to_html %>
|
|
28
|
+
<% end %>
|
|
29
|
+
</td>
|
|
30
|
+
<td style="vertical-align:middle;text-align:center;">
|
|
31
|
+
<form action="<%= root_path %>throttled/<%= CGI.escape name %>/reset" method="post">
|
|
32
|
+
<%= csrf_tag %>
|
|
33
|
+
<button class="btn btn-danger" type="submit">Reset</button>
|
|
34
|
+
</form>
|
|
35
|
+
</td>
|
|
36
|
+
</tr>
|
|
37
|
+
<% end %>
|
|
38
|
+
</tbody>
|
|
39
|
+
</table>
|
|
40
|
+
</div>
|
|
41
|
+
</section>
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: kubra-sidekiq-throttled
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version:
|
|
4
|
+
version: 2.0.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Peter Williams
|
|
@@ -9,7 +9,7 @@ authors:
|
|
|
9
9
|
autorequire:
|
|
10
10
|
bindir: bin
|
|
11
11
|
cert_chain: []
|
|
12
|
-
date: 2025-
|
|
12
|
+
date: 2025-10-01 00:00:00.000000000 Z
|
|
13
13
|
dependencies:
|
|
14
14
|
- !ruby/object:Gem::Dependency
|
|
15
15
|
name: concurrent-ruby
|
|
@@ -45,18 +45,17 @@ dependencies:
|
|
|
45
45
|
requirements:
|
|
46
46
|
- - ">="
|
|
47
47
|
- !ruby/object:Gem::Version
|
|
48
|
-
version: '
|
|
48
|
+
version: '8.0'
|
|
49
49
|
type: :runtime
|
|
50
50
|
prerelease: false
|
|
51
51
|
version_requirements: !ruby/object:Gem::Requirement
|
|
52
52
|
requirements:
|
|
53
53
|
- - ">="
|
|
54
54
|
- !ruby/object:Gem::Version
|
|
55
|
-
version: '
|
|
55
|
+
version: '8.0'
|
|
56
56
|
description:
|
|
57
57
|
email:
|
|
58
|
-
-
|
|
59
|
-
- alexey@zapparov.com
|
|
58
|
+
- pezra@barelyenough.org
|
|
60
59
|
executables: []
|
|
61
60
|
extensions: []
|
|
62
61
|
extra_rdoc_files: []
|
|
@@ -85,16 +84,16 @@ files:
|
|
|
85
84
|
- lib/sidekiq/throttled/version.rb
|
|
86
85
|
- lib/sidekiq/throttled/web.rb
|
|
87
86
|
- lib/sidekiq/throttled/web/stats.rb
|
|
88
|
-
- lib/sidekiq/throttled/web/throttled.html.erb
|
|
89
87
|
- lib/sidekiq/throttled/worker.rb
|
|
90
|
-
|
|
88
|
+
- web/views/index.erb
|
|
89
|
+
homepage: https://github.com/ixti/sidekiq-throttled
|
|
91
90
|
licenses:
|
|
92
91
|
- MIT
|
|
93
92
|
metadata:
|
|
94
|
-
homepage_uri: https://github.com/
|
|
95
|
-
source_code_uri: https://github.com/
|
|
96
|
-
bug_tracker_uri: https://github.com/
|
|
97
|
-
changelog_uri: https://github.com/
|
|
93
|
+
homepage_uri: https://github.com/ixti/sidekiq-throttled
|
|
94
|
+
source_code_uri: https://github.com/ixti/sidekiq-throttled/tree/v2.0.0
|
|
95
|
+
bug_tracker_uri: https://github.com/ixti/sidekiq-throttled/issues
|
|
96
|
+
changelog_uri: https://github.com/ixti/sidekiq-throttled/blob/v2.0.0/CHANGES.md
|
|
98
97
|
rubygems_mfa_required: 'true'
|
|
99
98
|
post_install_message:
|
|
100
99
|
rdoc_options: []
|
|
@@ -104,16 +103,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
104
103
|
requirements:
|
|
105
104
|
- - ">="
|
|
106
105
|
- !ruby/object:Gem::Version
|
|
107
|
-
version: '2
|
|
106
|
+
version: '3.2'
|
|
108
107
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
109
108
|
requirements:
|
|
110
109
|
- - ">="
|
|
111
110
|
- !ruby/object:Gem::Version
|
|
112
111
|
version: '0'
|
|
113
112
|
requirements: []
|
|
114
|
-
rubygems_version: 3.
|
|
113
|
+
rubygems_version: 3.4.19
|
|
115
114
|
signing_key:
|
|
116
115
|
specification_version: 4
|
|
117
|
-
summary: Concurrency and rate-limit throttling for Sidekiq (
|
|
118
|
-
improvements) )
|
|
116
|
+
summary: Concurrency and rate-limit throttling for Sidekiq (with improvements)
|
|
119
117
|
test_files: []
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
<h3>Throttled</h3>
|
|
2
|
-
|
|
3
|
-
<div class="table_container">
|
|
4
|
-
<table class="table table-hover table-bordered table-striped table-white">
|
|
5
|
-
<thead>
|
|
6
|
-
<tr>
|
|
7
|
-
<th>Name</th>
|
|
8
|
-
<th style="text-align:center;">Concurrency</th>
|
|
9
|
-
<th style="text-align:center;">Threshold</th>
|
|
10
|
-
<th style="text-align:center;">Actions</th>
|
|
11
|
-
</tr>
|
|
12
|
-
</thead>
|
|
13
|
-
<% Sidekiq::Throttled::Registry.each_with_static_keys do |name, strategy| %>
|
|
14
|
-
<tr>
|
|
15
|
-
<td style="vertical-align:middle;"><%= name %></td>
|
|
16
|
-
<td style="vertical-align:middle;text-align:center;">
|
|
17
|
-
<% strategy.concurrency.each do |concurrency| %>
|
|
18
|
-
<%= Sidekiq::Throttled::Web::Stats.new(concurrency).to_html %>
|
|
19
|
-
<% end %>
|
|
20
|
-
</td>
|
|
21
|
-
<td style="vertical-align:middle;text-align:center;">
|
|
22
|
-
<% strategy.threshold.each do |threshold| %>
|
|
23
|
-
<%= Sidekiq::Throttled::Web::Stats.new(threshold).to_html %>
|
|
24
|
-
<% end %>
|
|
25
|
-
</td>
|
|
26
|
-
<td style="vertical-align:middle;text-align:center;">
|
|
27
|
-
<form action="<%= root_path %>throttled/<%= CGI.escape name %>/reset" method="post">
|
|
28
|
-
<%= csrf_tag %>
|
|
29
|
-
<button class="btn btn-danger" type="submit">Reset</button>
|
|
30
|
-
</form>
|
|
31
|
-
</td>
|
|
32
|
-
</tr>
|
|
33
|
-
<% end %>
|
|
34
|
-
</table>
|
|
35
|
-
</div>
|