sidekiq-throttled 1.5.2 → 2.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 +4 -4
- data/README.adoc +75 -12
- 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 +51 -6
- data/lib/sidekiq/throttled/strategy/concurrency.rb +56 -11
- 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
- data/web/views/index.html.erb +1 -0
- metadata +10 -13
- 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: dcec267e317bfaeabe891a21b776cbddc083cba27a163c82a5e5e0839390fb8d
|
|
4
|
+
data.tar.gz: '09706d9f91fdc770f01a5fb7835d8c35706c2852dfe798ec3e60dcd76bdeb8e9'
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 4c90a776bb731f2a140469a03092e8307997602d7723f73517ed804f0f3973cbfed9692baf8f0f98e47320e0b5c4316bbaa15ffc36c70637d68d553e9a537d25
|
|
7
|
+
data.tar.gz: e52c211802a6e740dcd8b1b77ef3d7cad8b39d6a5aa714b7f1619c9ec2391d893140a636562cb6bafdc326ce4bcbd42904bfe053e6410ceec215720d72100b4a
|
data/README.adoc
CHANGED
|
@@ -79,6 +79,30 @@ class MyWorker
|
|
|
79
79
|
end
|
|
80
80
|
----
|
|
81
81
|
|
|
82
|
+
=== Requeue Strategy
|
|
83
|
+
The default requeue strategy `:enqueue` puts jobs immiediately back on the queue if they are being limited, with a potential `cooldown_period`. This is often an appropriate strategy but in some situations may cause the system to repeatedly dequeue and reenque the same job causing excessive redis CPU usage.
|
|
84
|
+
|
|
85
|
+
An alternative requeue strategy `:schedule` is available that schedules the work for the future. This can be less appropriate for highly concurrent jobs, but may be a good way to reduce redis load when the number of jobs to be processed is modest.
|
|
86
|
+
|
|
87
|
+
The alternative `:schedule` strategy may be configured globally with `config.default_requeue_options = { with: :schedule }`
|
|
88
|
+
|
|
89
|
+
It may be configured on a per job basis with:
|
|
90
|
+
|
|
91
|
+
```ruby
|
|
92
|
+
sidekiq_throttle(
|
|
93
|
+
# Allow 5 jobs to processed within 1 minute, using the
|
|
94
|
+
threshold: { limit: 5, period: 1.minute, requeue: {with: :schedule} }
|
|
95
|
+
)
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
It is also possible to requeue jobs to another queue:
|
|
99
|
+
|
|
100
|
+
```ruby
|
|
101
|
+
sidekiq_throttle(
|
|
102
|
+
# Allow 5 jobs to processed within 1 minute, using the
|
|
103
|
+
threshold: { limit: 5, period: 1.minute, requeue: {to: :other_queue, with: :schedule} }
|
|
104
|
+
)
|
|
105
|
+
```
|
|
82
106
|
|
|
83
107
|
=== Web UI
|
|
84
108
|
|
|
@@ -246,8 +270,8 @@ class MyJob
|
|
|
246
270
|
sidekiq_throttle(
|
|
247
271
|
# Allow maximum 10 concurrent jobs per project at a time and maximum 2 jobs per user
|
|
248
272
|
concurrency: [
|
|
249
|
-
{ limit: 10, key_suffix: -> (project_id, user_id) { project_id } },
|
|
250
|
-
{ limit: 2, key_suffix: -> (project_id, user_id) { user_id } }
|
|
273
|
+
{ limit: 10, key_suffix: -> (project_id, user_id) { "project_id:#{project_id}" } },
|
|
274
|
+
{ limit: 2, key_suffix: -> (project_id, user_id) { "user_id:#{user_id}" } }
|
|
251
275
|
]
|
|
252
276
|
# For :threshold it works the same
|
|
253
277
|
)
|
|
@@ -309,16 +333,56 @@ lock TTL to fit your needs:
|
|
|
309
333
|
sidekiq_throttle(concurrency: { limit: 20, ttl: 1.hour.to_i })
|
|
310
334
|
----
|
|
311
335
|
|
|
336
|
+
=== Scheduling based concurrency tuning
|
|
337
|
+
|
|
338
|
+
The default concurrency throttling algorithm immediately requeues throttled
|
|
339
|
+
jobs. This can lead to a lot of wasted work picking up the same set of still
|
|
340
|
+
throttled jobs repeatedly. This churn also often starves lower priority
|
|
341
|
+
jobs/queues. The `:schedule` requeue strategy delays checking the runability of
|
|
342
|
+
throttled jobs until likely to be runnable. This future time is estimated based
|
|
343
|
+
on the expected runtime of the job and current number of throttled jobs. This
|
|
344
|
+
eliminates -- or greatly reduces -- the negative impacts to non-throttled job
|
|
345
|
+
types and queues and reduces wasted work constantly rechecking the same still
|
|
346
|
+
throttled jobs.
|
|
347
|
+
|
|
348
|
+
**Config items**
|
|
349
|
+
|
|
350
|
+
- `limit` — Maximum number of this job allowed to run simultaneously.
|
|
351
|
+
- `avg_job_duration` — Expected runtime (in seconds) for this job type.
|
|
352
|
+
Choose a value on the high end of what’s plausible; if you set this too low under heavy load, job scheduling will become sub-optimal.
|
|
353
|
+
- `lost_job_threshold` — Duration (in seconds) representing how long a job “owns” its concurrency slot before being considered lost.
|
|
354
|
+
- `ttl` — Deprecated alias for `lost_job_threshold`.
|
|
355
|
+
- `max_delay` — The maximum number of seconds to delay a job when it is throttled.
|
|
356
|
+
Prevents excessively long scheduling delays as the backlog grows.
|
|
357
|
+
_Default: the smaller of 30 minutes or `10 × avg_job_duration`._
|
|
358
|
+
|
|
359
|
+
[source,ruby]
|
|
360
|
+
----
|
|
361
|
+
sidekiq_throttle(
|
|
362
|
+
concurrency: {
|
|
363
|
+
# only run 10 of this job at a time
|
|
364
|
+
limit: 10,
|
|
365
|
+
# these jobs finish in less than 30 seconds
|
|
366
|
+
avg_job_duration: 30,
|
|
367
|
+
# if it doesn't release its lease in 2 minutes it's considered lost
|
|
368
|
+
lost_job_threshold: 120,
|
|
369
|
+
# maximum delay allowed when throttled
|
|
370
|
+
max_delay: 300
|
|
371
|
+
},
|
|
372
|
+
# requeue using Sidekiq's scheduler
|
|
373
|
+
requeue: { with: :schedule }
|
|
374
|
+
)
|
|
375
|
+
----
|
|
376
|
+
|
|
312
377
|
|
|
313
378
|
== Supported Ruby Versions
|
|
314
379
|
|
|
315
380
|
This library aims to support and is tested against the following Ruby versions:
|
|
316
381
|
|
|
317
|
-
* Ruby 2.7.x
|
|
318
|
-
* Ruby 3.0.x
|
|
319
|
-
* Ruby 3.1.x
|
|
320
382
|
* Ruby 3.2.x
|
|
321
383
|
* Ruby 3.3.x
|
|
384
|
+
* Ruby 3.4.x
|
|
385
|
+
* Ruby 4.0.x
|
|
322
386
|
|
|
323
387
|
If something doesn't work on one of these versions, it's a bug.
|
|
324
388
|
|
|
@@ -338,15 +402,13 @@ dropped.
|
|
|
338
402
|
|
|
339
403
|
This library aims to support and work with following Sidekiq versions:
|
|
340
404
|
|
|
341
|
-
* Sidekiq
|
|
342
|
-
* Sidekiq
|
|
343
|
-
* Sidekiq 7.2.x
|
|
405
|
+
* Sidekiq 8.0.x
|
|
406
|
+
* Sidekiq 8.1.x
|
|
344
407
|
|
|
345
|
-
And the following Sidekiq Pro versions:
|
|
408
|
+
And (might work with) the following Sidekiq Pro versions:
|
|
346
409
|
|
|
347
|
-
* Sidekiq Pro
|
|
348
|
-
* Sidekiq Pro
|
|
349
|
-
* Sidekiq Pro 7.2.x
|
|
410
|
+
* Sidekiq Pro 8.0.x
|
|
411
|
+
* Sidekiq Pro 8.1.x
|
|
350
412
|
|
|
351
413
|
== Development
|
|
352
414
|
|
|
@@ -361,6 +423,7 @@ If you're working on Sidekiq-Pro support make sure that you have Sidekiq-Pro
|
|
|
361
423
|
license set either in the global config, or in `BUNDLE_GEMS\__CONTRIBSYS__COM`
|
|
362
424
|
environment variable.
|
|
363
425
|
|
|
426
|
+
|
|
364
427
|
== Contributing
|
|
365
428
|
|
|
366
429
|
* 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
|
|
@@ -1,16 +1,61 @@
|
|
|
1
|
-
local
|
|
1
|
+
local in_progress_jobs_key = KEYS[1]
|
|
2
|
+
local backlog_info_key = KEYS[2]
|
|
2
3
|
local jid = ARGV[1]
|
|
3
4
|
local lmt = tonumber(ARGV[2])
|
|
4
|
-
local
|
|
5
|
+
local lost_job_threshold = tonumber(ARGV[3])
|
|
5
6
|
local now = tonumber(ARGV[4])
|
|
6
7
|
|
|
7
|
-
|
|
8
|
+
-- supporting functions
|
|
9
|
+
local function over_limit()
|
|
10
|
+
return lmt <= redis.call("ZCARD", in_progress_jobs_key)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
local function job_already_in_progress()
|
|
14
|
+
return redis.call("ZSCORE", in_progress_jobs_key, jid)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
-- Estimates current backlog size. This function tends to underestimate
|
|
18
|
+
-- the actual backlog. This is intentional. Overestimates are bad as it
|
|
19
|
+
-- can cause unnecessary delays in job processing. Underestimates are much
|
|
20
|
+
-- safer as they only increase workload of sidekiq processors.
|
|
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
|
|
24
|
+
|
|
25
|
+
local jobs_lost_since_old_timestamp = (now - old_timestamp) / lost_job_threshold * lmt
|
|
26
|
+
|
|
27
|
+
return math.max(old_size - jobs_lost_since_old_timestamp, 0)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
local function change_backlog_size(delta)
|
|
32
|
+
local curr_backlog_size = est_current_backlog_size()
|
|
33
|
+
|
|
34
|
+
redis.call("HSET", backlog_info_key, "size", curr_backlog_size + delta)
|
|
35
|
+
redis.call("HSET", backlog_info_key, "timestamp", now)
|
|
36
|
+
redis.call("EXPIRE", backlog_info_key, math.ceil((lost_job_threshold * curr_backlog_size) + 1 / lmt))
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
local function register_job_in_progress()
|
|
40
|
+
redis.call("ZADD", in_progress_jobs_key, now + lost_job_threshold , jid)
|
|
41
|
+
redis.call("EXPIRE", in_progress_jobs_key, lost_job_threshold)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
local function clear_stale_in_progress_jobs()
|
|
45
|
+
local cleared_count = redis.call("ZREMRANGEBYSCORE", in_progress_jobs_key, "-inf", "(" .. now)
|
|
46
|
+
change_backlog_size(-cleared_count)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
-- END supporting functions
|
|
50
|
+
|
|
51
|
+
clear_stale_in_progress_jobs()
|
|
8
52
|
|
|
9
|
-
if
|
|
53
|
+
if over_limit() and not job_already_in_progress() then
|
|
54
|
+
change_backlog_size(1)
|
|
10
55
|
return 1
|
|
11
56
|
end
|
|
12
57
|
|
|
13
|
-
|
|
14
|
-
|
|
58
|
+
register_job_in_progress()
|
|
59
|
+
change_backlog_size(-1)
|
|
15
60
|
|
|
16
61
|
return 0
|
|
@@ -26,13 +26,26 @@ module Sidekiq
|
|
|
26
26
|
# @param [#to_s] strategy_key
|
|
27
27
|
# @param [#to_i, #call] limit Amount of allowed concurrent jobs
|
|
28
28
|
# per processors running for given key.
|
|
29
|
-
# @param [#to_i]
|
|
29
|
+
# @param [#to_i] avg_job_duration Average number of seconds needed
|
|
30
|
+
# to complete a job of this type. Default: 300 or 1/3 of lost_job_threshold
|
|
31
|
+
# @param [#to_i] lost_job_threshold Seconds to wait before considering
|
|
32
|
+
# a job lost or dead. Default: 900 or 3 * avg_job_duration
|
|
30
33
|
# @param [Proc] key_suffix Dynamic key suffix generator.
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
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
|
|
37
|
+
# @deprecated @param [#to_i] ttl Obsolete alias for `lost_job_threshold`.
|
|
38
|
+
# Default: 900 or 3 * avg_job_duration
|
|
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)
|
|
41
|
+
@base_key = "#{strategy_key}:concurrency.v2"
|
|
42
|
+
@limit = limit
|
|
43
|
+
@avg_job_duration, @lost_job_threshold = interp_duration_args(avg_job_duration, lost_job_threshold)
|
|
35
44
|
@key_suffix = key_suffix
|
|
45
|
+
@max_delay = max_delay || [(10 * @avg_job_duration), 1_800].min
|
|
46
|
+
|
|
47
|
+
raise(ArgumentError, "lost_job_threshold must be greater than avg_job_duration") if
|
|
48
|
+
@lost_job_threshold <= @avg_job_duration
|
|
36
49
|
end
|
|
37
50
|
|
|
38
51
|
# @return [Boolean] Whenever strategy has dynamic config
|
|
@@ -46,8 +59,8 @@ module Sidekiq
|
|
|
46
59
|
return false unless job_limit
|
|
47
60
|
return true if job_limit <= 0
|
|
48
61
|
|
|
49
|
-
keys = [key(job_args)]
|
|
50
|
-
argv = [jid.to_s, job_limit, @
|
|
62
|
+
keys = [key(job_args), backlog_info_key(job_args)]
|
|
63
|
+
argv = [jid.to_s, job_limit, @lost_job_threshold, Time.now.to_f]
|
|
51
64
|
|
|
52
65
|
Sidekiq.redis { |redis| 1 == SCRIPT.call(redis, keys: keys, argv: argv) }
|
|
53
66
|
end
|
|
@@ -57,9 +70,8 @@ module Sidekiq
|
|
|
57
70
|
job_limit = limit(job_args)
|
|
58
71
|
return 0.0 if !job_limit || count(*job_args) < job_limit
|
|
59
72
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
expiry_time - Time.now.to_f
|
|
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
|
|
63
75
|
end
|
|
64
76
|
|
|
65
77
|
# @return [Integer] Current count of jobs
|
|
@@ -76,7 +88,40 @@ module Sidekiq
|
|
|
76
88
|
# Remove jid from the pool of jobs in progress
|
|
77
89
|
# @return [void]
|
|
78
90
|
def finalize!(jid, *job_args)
|
|
79
|
-
Sidekiq.redis
|
|
91
|
+
Sidekiq.redis do |conn|
|
|
92
|
+
conn.zrem(key(job_args), jid.to_s)
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
private
|
|
97
|
+
|
|
98
|
+
def backlog_info_key(job_args)
|
|
99
|
+
"#{key(job_args)}.backlog_info"
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def estimated_backlog_size(job_args)
|
|
103
|
+
old_size_str, old_timestamp_str =
|
|
104
|
+
Sidekiq.redis { |conn| conn.hmget(backlog_info_key(job_args), "size", "timestamp") }
|
|
105
|
+
old_size = (old_size_str || 0).to_f
|
|
106
|
+
old_timestamp = (old_timestamp_str || Time.now).to_f
|
|
107
|
+
|
|
108
|
+
(old_size - jobs_lost_since(old_timestamp, job_args)).clamp(0, Float::INFINITY)
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def jobs_lost_since(timestamp, job_args)
|
|
112
|
+
(Time.now.to_f - timestamp) / @lost_job_threshold * limit(job_args)
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def interp_duration_args(avg_job_duration, lost_job_threshold)
|
|
116
|
+
if avg_job_duration && lost_job_threshold
|
|
117
|
+
[avg_job_duration.to_i, lost_job_threshold.to_i]
|
|
118
|
+
elsif avg_job_duration && lost_job_threshold.nil?
|
|
119
|
+
[avg_job_duration.to_i, avg_job_duration.to_i * 3]
|
|
120
|
+
elsif avg_job_duration.nil? && lost_job_threshold
|
|
121
|
+
[lost_job_threshold.to_i / 3, lost_job_threshold.to_i]
|
|
122
|
+
else
|
|
123
|
+
[300, 900]
|
|
124
|
+
end
|
|
80
125
|
end
|
|
81
126
|
end
|
|
82
127
|
end
|
|
@@ -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>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
index.erb
|
metadata
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: sidekiq-throttled
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 2.1.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Alexey Zapparov
|
|
8
|
-
autorequire:
|
|
9
8
|
bindir: bin
|
|
10
9
|
cert_chain: []
|
|
11
|
-
date:
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
12
11
|
dependencies:
|
|
13
12
|
- !ruby/object:Gem::Dependency
|
|
14
13
|
name: concurrent-ruby
|
|
@@ -44,15 +43,14 @@ dependencies:
|
|
|
44
43
|
requirements:
|
|
45
44
|
- - ">="
|
|
46
45
|
- !ruby/object:Gem::Version
|
|
47
|
-
version: '
|
|
46
|
+
version: '8.0'
|
|
48
47
|
type: :runtime
|
|
49
48
|
prerelease: false
|
|
50
49
|
version_requirements: !ruby/object:Gem::Requirement
|
|
51
50
|
requirements:
|
|
52
51
|
- - ">="
|
|
53
52
|
- !ruby/object:Gem::Version
|
|
54
|
-
version: '
|
|
55
|
-
description:
|
|
53
|
+
version: '8.0'
|
|
56
54
|
email:
|
|
57
55
|
- alexey@zapparov.com
|
|
58
56
|
executables: []
|
|
@@ -83,18 +81,18 @@ files:
|
|
|
83
81
|
- lib/sidekiq/throttled/version.rb
|
|
84
82
|
- lib/sidekiq/throttled/web.rb
|
|
85
83
|
- lib/sidekiq/throttled/web/stats.rb
|
|
86
|
-
- lib/sidekiq/throttled/web/throttled.html.erb
|
|
87
84
|
- lib/sidekiq/throttled/worker.rb
|
|
85
|
+
- web/views/index.erb
|
|
86
|
+
- web/views/index.html.erb
|
|
88
87
|
homepage: https://github.com/ixti/sidekiq-throttled
|
|
89
88
|
licenses:
|
|
90
89
|
- MIT
|
|
91
90
|
metadata:
|
|
92
91
|
homepage_uri: https://github.com/ixti/sidekiq-throttled
|
|
93
|
-
source_code_uri: https://github.com/ixti/sidekiq-throttled/tree/
|
|
92
|
+
source_code_uri: https://github.com/ixti/sidekiq-throttled/tree/v2.1.0
|
|
94
93
|
bug_tracker_uri: https://github.com/ixti/sidekiq-throttled/issues
|
|
95
|
-
changelog_uri: https://github.com/ixti/sidekiq-throttled/blob/
|
|
94
|
+
changelog_uri: https://github.com/ixti/sidekiq-throttled/blob/v2.1.0/CHANGES.md
|
|
96
95
|
rubygems_mfa_required: 'true'
|
|
97
|
-
post_install_message:
|
|
98
96
|
rdoc_options: []
|
|
99
97
|
require_paths:
|
|
100
98
|
- lib
|
|
@@ -102,15 +100,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
102
100
|
requirements:
|
|
103
101
|
- - ">="
|
|
104
102
|
- !ruby/object:Gem::Version
|
|
105
|
-
version: '2
|
|
103
|
+
version: '3.2'
|
|
106
104
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
107
105
|
requirements:
|
|
108
106
|
- - ">="
|
|
109
107
|
- !ruby/object:Gem::Version
|
|
110
108
|
version: '0'
|
|
111
109
|
requirements: []
|
|
112
|
-
rubygems_version: 3.
|
|
113
|
-
signing_key:
|
|
110
|
+
rubygems_version: 3.6.9
|
|
114
111
|
specification_version: 4
|
|
115
112
|
summary: Concurrency and rate-limit throttling for Sidekiq
|
|
116
113
|
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>
|