sidekiq-amigo 1.5.0 → 1.6.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 70d47770af144a84aedec89e8b74e4ebd13851119a5c329a0f4c65d56ec94a60
4
- data.tar.gz: 69305abac1bb5eeb00eaf81613e591476bf1acb35cdac50dcc97a094d255aab8
3
+ metadata.gz: c51def3cb58812e0889c0768708747aefd3956495692ffcbb6521a1d24c6d0b0
4
+ data.tar.gz: 5081debc483ce040e8c6f3a26cfacdd85094c5e70895b390f23f58f795663da3
5
5
  SHA512:
6
- metadata.gz: adf9aa76c352c9ddb9832f142d415b4efded423324dd78a87cfc3140a755eb266dd1a1b80e15a2f911ccafaaf8ea89f416d66dfe25a17955709fb1e4828554ef
7
- data.tar.gz: f69c16bfd06a7ea5cc3ac15db4a962f3fb02345db60ac6f581123bf5425627775bb1958e9f99ff0be8b5956db57d4c6fec64d5422ff7903c0157bf0a0f8ba4d1
6
+ metadata.gz: b126004a168ea18f4842bd34268fc3edb263d21de40731d178e9bd4a5db64bf403da0953b6083ea0e5c5e8d32bc9aa4faf0e66e54f0f166a077780d6573565f6
7
+ data.tar.gz: 4a710ac1029e607ed60b1f448e4c443d287bcb599252801a70a17a9478e0f5c80b373f610a14034be1d2d3c98541512e64ab2f8b2bc85869b70fbc009308007d
@@ -0,0 +1,146 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "platform-api"
4
+
5
+ require "amigo/autoscaler"
6
+
7
+ module Amigo
8
+ class Autoscaler
9
+ # Autoscaler to use on Heroku, that starts additional worker processes when there is a high latency event
10
+ # and scales them down after the event is finished.
11
+ #
12
+ # When the first call of a high latency event happens (depth: 1), this class
13
+ # will ask Heroku how many dynos are in the formation. This is known as +active_event_initial_workers+.
14
+ #
15
+ # If +active_event_initial_workers+ is 0, no autoscaling will be done.
16
+ # This avoids a situation where a high latency event is triggered
17
+ # due to workers being deprovisioned intentionally, perhaps for maintenance.
18
+ #
19
+ # Each time the alert fires (see +Amigo::Autoscaler#alert_interval+),
20
+ # an additional worker will be added to the formation, up to +max_additional_workers+.
21
+ # So with +active_event_initial_workers+ of 1 and +max_additional_workers+ of 2,
22
+ # the first time the alert times, the formation will be set to 2 workers.
23
+ # The next time, it'll be set to 3 workers.
24
+ # After that, no additional workers will be provisioned.
25
+ #
26
+ # After the high latency event resolves,
27
+ # the dyno formation is restored to +active_event_initial_workers+.
28
+ #
29
+ # To use:
30
+ #
31
+ # heroku = PlatformAPI.connect_oauth(heroku_oauth_token)
32
+ # heroku_scaler = Amigo::Autoscaler::Heroku.new(heroku:, default_workers: 1)
33
+ # Amigo::Autoscaler.new(
34
+ # handlers: [heroku_scaler.alert_callback],
35
+ # latency_restored_handlers: [heroku_scaler.restored_callback],
36
+ # )
37
+ #
38
+ # See instance attributes for additional options.
39
+ #
40
+ # Note that this class is provided as an example, and potentially a base or implementation class.
41
+ # Your actual implementation may also want to alert when a max depth or duration is reached,
42
+ # since it can indicate a bigger problem. Autoscaling, especially of workers, is a tough problem
43
+ # without a one-size-fits-all approach.
44
+ class Heroku
45
+ # Heroku client, usually created via PlatformAPI.oauth_connect.
46
+ # @return [PlatformAPI::Client]
47
+ attr_reader :heroku
48
+
49
+ # Captured at the start of a high latency event.
50
+ # Nil otherwise.
51
+ # @return [Integer]
52
+ attr_reader :active_event_initial_workers
53
+
54
+ # Maximum number of workers to add.
55
+ #
56
+ # As the 'depth' of the alert is increased,
57
+ # workers are added to the recorded worker count until the max is reached.
58
+ # By default, this is 2 (so the max workers will be the recorded number, plus 2).
59
+ # Do not set this too high, since it can for example exhaust database connections or just end up
60
+ # increasing load.
61
+ #
62
+ # See class docs for more information.
63
+ # @return [Integer]
64
+ attr_reader :max_additional_workers
65
+
66
+ # Defaults to HEROKU_APP_NAME, which should already be set if you use Heroku dyna metadata,
67
+ # as per https://devcenter.heroku.com/articles/dyno-metadata.
68
+ # This must be provided if the env var is missing.
69
+ # @return [String]
70
+ attr_reader :app_id_or_app_name
71
+
72
+ # Defaults to 'worker', which is what you'll probably use if you have a simple system.
73
+ # If you use multiple worker processes for different queues, this class probably isn't sufficient.
74
+ # You will probably need to look at the slow queue names and determine the formation name to scale up.
75
+ # @return [String]
76
+ attr_reader :formation_id_or_formation_type
77
+
78
+ def initialize(
79
+ heroku:,
80
+ max_additional_workers: 2,
81
+ app_id_or_app_name: ENV.fetch("HEROKU_APP_NAME"),
82
+ formation_id_or_formation_type: "worker"
83
+ )
84
+
85
+ @heroku = heroku
86
+ @max_additional_workers = max_additional_workers
87
+ @app_id_or_app_name = app_id_or_app_name
88
+ @formation_id_or_formation_type = formation_id_or_formation_type
89
+ # Is nil outside of a latency event, set during a latency event. So if this is initialized to non-nil,
90
+ # we're already in a latency event.
91
+ @active_event_initial_workers = Sidekiq.redis do |r|
92
+ v = r.get("#{namespace}/active_event_initial_workers")
93
+ v&.to_i
94
+ end
95
+ end
96
+
97
+ def alert_callback
98
+ self.method(:scale_up)
99
+ end
100
+
101
+ def restored_callback
102
+ self.method(:scale_down)
103
+ end
104
+
105
+ protected def namespace
106
+ return "amigo/autoscaler/heroku"
107
+ end
108
+
109
+ # Potentially add another worker to the formation.
110
+ # @return [:noscale, :maxscale, :scaled] One of :noscale (no +active_event_initial_workers+),
111
+ # :maxscale (+max_additional_workers+ reached), or :scaled.
112
+ def scale_up(_queues_and_latencies, depth:, **)
113
+ # When the scaling event starts (or if this is the first time we've seen it
114
+ # but the event is already in progress), store how many workers we have.
115
+ # It needs to be stored in redis so it persists if
116
+ # the latency event continues through restarts.
117
+ if @active_event_initial_workers.nil?
118
+ @active_event_initial_workers = @heroku.formation.info(@app_id_or_app_name, @formation_id_or_formation_type).
119
+ fetch("quantity")
120
+ Sidekiq.redis do |r|
121
+ r.set("#{namespace}/active_event_initial_workers", @active_event_initial_workers.to_s)
122
+ end
123
+ end
124
+ return :noscale if @active_event_initial_workers.zero?
125
+ new_quantity = @active_event_initial_workers + depth
126
+ max_quantity = @active_event_initial_workers + @max_additional_workers
127
+ return :maxscale if new_quantity > max_quantity
128
+ @heroku.formation.update(@app_id_or_app_name, @formation_id_or_formation_type, {quantity: new_quantity})
129
+ return :scaled
130
+ end
131
+
132
+ # Reset the formation to +active_event_initial_workers+.
133
+ # @return [:noscale, :scaled] :noscale if +active_event_initial_workers+ is 0, otherwise :scaled.
134
+ def scale_down(**)
135
+ initial_workers = @active_event_initial_workers
136
+ Sidekiq.redis do |r|
137
+ r.del("#{namespace}/active_event_initial_workers")
138
+ end
139
+ @active_event_initial_workers = nil
140
+ return :noscale if initial_workers.zero?
141
+ @heroku.formation.update(@app_id_or_app_name, @formation_id_or_formation_type, {quantity: initial_workers})
142
+ return :scaled
143
+ end
144
+ end
145
+ end
146
+ end
@@ -6,20 +6,31 @@ require "amigo"
6
6
 
7
7
  # When queues achieve a latency that is too high,
8
8
  # take some action.
9
- # You should start this up at Sidekiq application startup:
9
+ # You should start this up at Web application startup:
10
10
  #
11
- # # sidekiq.rb
12
- # Amigo::Autoscaler.new.start
11
+ # # puma.rb or similar
12
+ # Amigo::Autoscaler.new.start
13
13
  #
14
- # Right now, this is pretty simple- we alert any time
15
- # there is a latency over a threshold.
14
+ # When latency grows beyond +latency_threshold+,
15
+ # a "high latency event" is started.
16
+ # Some action is taken, which is defined by the +handlers+ argument.
17
+ # This includes logging, alerting, and/or autoscaling.
16
18
  #
17
- # In the future, we can:
19
+ # When latency returns to normal (defined by +latency_restored_threshold+),
20
+ # the high latency event finishes.
21
+ # Some additional action is taken, which is defined by the +latency_restored_handlers+ argument.
22
+ # Usually this is logging, and/or returning autoscaling to its original status.
18
23
  #
19
- # 1) actually autoscale rather than just alert
20
- # (this may take the form of a POST to a configurable endpoint),
21
- # 2) become more sophisticated with how we detect latency growth.
24
+ # There are several parameters to control behavior, such as how often polling is done,
25
+ # how often alerting/scaling is done, and more.
22
26
  #
27
+ # As an example autoscaler that includes actual resource scaling,
28
+ # check out +Amigo::Autoscaler::Heroku+.
29
+ # Its ideas can easily be expanded to other platforms.
30
+ #
31
+ # Note that +Autoscaler+ maintains its state over multiple processes;
32
+ # it needs to keep track of high latency events even if the process running the autoscaler
33
+ # (usually a web process) restarts.
23
34
  module Amigo
24
35
  class Autoscaler
25
36
  class InvalidHandler < StandardError; end
@@ -31,18 +42,26 @@ module Amigo
31
42
  # @return [Integer]
32
43
  attr_reader :latency_threshold
33
44
  # What hosts/processes should this run on?
34
- # Look at ENV['DYNO'] and Socket.gethostname.
45
+ # Looks at ENV['DYNO'] and Socket.gethostname for a match.
35
46
  # Default to only run on 'web.1', which is the first Heroku web dyno.
36
47
  # We run on the web, not worker, dyno, so we report backed up queues
37
48
  # in case we, say, turn off all workers (broken web processes
38
49
  # are generally easier to find).
39
50
  # @return [Regexp]
40
51
  attr_reader :hostname_regex
41
- # Methods to call when alerting.
42
- # Valid values are 'log' and 'sentry' (requires Sentry to be required already).
43
- # Anything that responds to +call+ will be invoked with a hash of
44
- # `{queue name => latency in seconds}`.
45
- # @return [Array<String,Proc>]
52
+ # Methods to call when alerting, as strings/symbols or procs.
53
+ # Valid string values are 'log' and 'sentry' (requires Sentry to be required already).
54
+ # Anything that responds to +call+ will be invoked with:
55
+ # - Positional argument which is a +Hash+ of `{queue name => latency in seconds}`
56
+ # - Keyword argument +:depth+: Number of alerts as part of this latency event.
57
+ # For example, the first alert has a depth of 1, and if latency stays high,
58
+ # it'll be 2 on the next call, etc. +depth+ can be used to incrementally provision
59
+ # additional processing capacity, and stop adding capacity at a certain depth
60
+ # to avoid problems with too many workers (like excessive DB load).
61
+ # - Keyword argument +:duration+: Number of seconds since this latency spike started.
62
+ # - Additional undefined keywords. Handlers should accept additional options,
63
+ # like via `**kw` or `opts={}`, for compatibility.
64
+ # @return [Array<String,Symbol,Proc,#call>]
46
65
  attr_reader :handlers
47
66
  # Only alert this often.
48
67
  # For example, with poll_interval of 10 seconds
@@ -50,20 +69,55 @@ module Amigo
50
69
  # we'd alert once and then 210 seconds later.
51
70
  # @return [Integer]
52
71
  attr_reader :alert_interval
72
+ # After an alert happens, what latency should be considered "back to normal" and
73
+ # +latency_restored_handlers+ will be called?
74
+ # In most cases this should be the same as (and defaults to) +latency_threshold+
75
+ # so that we're 'back to normal' once we're below the threshold.
76
+ # It may also commonly be 0, so that the callback is fired when the queue is entirely clear.
77
+ # Note that, if +latency_restored_threshold+ is less than +latency_threshold+,
78
+ # while the latency is between the two, no alerts will fire.
79
+ attr_reader :latency_restored_threshold
80
+ # Methods to call when a latency of +latency_restored_threshold+ is reached
81
+ # (ie, when we get back to normal latency after a high latency event).
82
+ # Valid string values are 'log'.
83
+ # Usually this handler will deprovision capacity procured as part of the alert +handlers+.
84
+ # Anything that responds to +call+ will be invoked with:
85
+ # - Keyword +:depth+, the number of times an alert happened before
86
+ # the latency spike was resolved.
87
+ # - Keyword +:duration+, the number of seconds for the latency spike has been going on.
88
+ # - Additional undefined keywords. Handlers should accept additional options,
89
+ # like via `**kw`, for compatibility.
90
+ # @return [Array<String,Symbol,Proc,#call>]
91
+ attr_reader :latency_restored_handlers
92
+ # Proc/callable called with (level, message, params={}).
93
+ # By default, use +Amigo.log+ (which logs to the Sidekiq logger).
94
+ attr_reader :log
53
95
 
54
96
  def initialize(
55
97
  poll_interval: 20,
56
98
  latency_threshold: 5,
57
99
  hostname_regex: /^web\.1$/,
58
- handlers: ["log"],
59
- alert_interval: 120
100
+ handlers: [:log],
101
+ alert_interval: 120,
102
+ latency_restored_threshold: latency_threshold,
103
+ latency_restored_handlers: [:log],
104
+ log: ->(level, message, params={}) { Amigo.log(nil, level, message, params) }
60
105
  )
61
106
 
107
+ raise ArgumentError, "latency_threshold must be > 0" if
108
+ latency_threshold <= 0
109
+ raise ArgumentError, "latency_restored_threshold must be >= 0" if
110
+ latency_restored_threshold.negative?
111
+ raise ArgumentError, "latency_restored_threshold must be <= latency_threshold" if
112
+ latency_restored_threshold > latency_threshold
62
113
  @poll_interval = poll_interval
63
114
  @latency_threshold = latency_threshold
64
115
  @hostname_regex = hostname_regex
65
- @handlers = handlers
116
+ @handlers = handlers.freeze
66
117
  @alert_interval = alert_interval
118
+ @latency_restored_threshold = latency_restored_threshold
119
+ @latency_restored_handlers = latency_restored_handlers.freeze
120
+ @log = log
67
121
  end
68
122
 
69
123
  def polling_thread
@@ -73,17 +127,44 @@ module Amigo
73
127
  def setup
74
128
  # Store these as strings OR procs, rather than grabbing self.method here.
75
129
  # It gets extremely hard ot test if we capture the method here.
76
- @alert_methods = self.handlers.map do |a|
77
- if a.respond_to?(:call)
78
- a
79
- else
80
- method_name = meth = "alert_#{a.strip}".to_sym
81
- raise InvalidHandler, a.inspect unless self.method(method_name)
82
- meth
83
- end
84
- end
85
- @last_alerted = Time.at(0)
130
+ @alert_methods = self.handlers.map { |a| _handler_to_method("alert_", a) }
131
+ @restored_methods = self.latency_restored_handlers.map { |a| _handler_to_method("alert_restored_", a) }
86
132
  @stop = false
133
+ Sidekiq.redis do |r|
134
+ @last_alerted = Time.at((r.get("#{namespace}/last_alerted") || 0).to_f)
135
+ @depth = (r.get("#{namespace}/depth") || 0).to_i
136
+ @latency_event_started = Time.at((r.get("#{namespace}/latency_event_started") || 0).to_f)
137
+ end
138
+ end
139
+
140
+ private def persist
141
+ Sidekiq.redis do |r|
142
+ r.set("#{namespace}/last_alerted", @last_alerted.to_f.to_s)
143
+ r.set("#{namespace}/depth", @depth.to_s)
144
+ r.set("#{namespace}/latency_event_started", @latency_event_started.to_f.to_s)
145
+ end
146
+ end
147
+
148
+ # Delete all the keys that Autoscaler stores.
149
+ # Can be used in extreme cases where things need to be cleaned up,
150
+ # but should not be normally used.
151
+ def unpersist
152
+ Sidekiq.redis do |r|
153
+ r.del("#{namespace}/last_alerted")
154
+ r.del("#{namespace}/depth")
155
+ r.del("#{namespace}/latency_event_started")
156
+ end
157
+ end
158
+
159
+ protected def namespace
160
+ return "amigo/autoscaler"
161
+ end
162
+
163
+ private def _handler_to_method(prefix, a)
164
+ return a if a.respond_to?(:call)
165
+ method_name = "#{prefix}#{a.to_s.strip}".to_sym
166
+ raise InvalidHandler, a.inspect unless (meth = self.method(method_name))
167
+ return meth
87
168
  end
88
169
 
89
170
  def start
@@ -92,7 +173,7 @@ module Amigo
92
173
  hostname = ENV.fetch("DYNO") { Socket.gethostname }
93
174
  return false unless self.hostname_regex.match?(hostname)
94
175
 
95
- self.log(:info, "async_autoscaler_starting")
176
+ self._log(:info, "async_autoscaler_starting")
96
177
  self.setup
97
178
  @polling_thread = Thread.new do
98
179
  until @stop
@@ -109,21 +190,53 @@ module Amigo
109
190
 
110
191
  def check
111
192
  now = Time.now
112
- skip_check = now < (@last_alerted + self.poll_interval)
193
+ skip_check = now < (@last_alerted + self.alert_interval)
113
194
  if skip_check
114
- self.log(:debug, "async_autoscaler_skip_check")
195
+ self._log(:debug, "async_autoscaler_skip_check")
115
196
  return
116
197
  end
117
- self.log(:info, "async_autoscaler_check")
198
+ self._log(:info, "async_autoscaler_check")
118
199
  high_latency_queues = Sidekiq::Queue.all.
119
200
  map { |q| [q.name, q.latency] }.
120
201
  select { |(_, latency)| latency > self.latency_threshold }.
121
202
  to_h
122
- return if high_latency_queues.empty?
203
+ if high_latency_queues.empty?
204
+ # Whenever we are in a latency event, we have a depth > 0. So a depth of 0 means
205
+ # we're not in a latency event, and still have no latency, so can noop.
206
+ return if @depth.zero?
207
+ # We WERE in a latency event, and now we're not, so report on it.
208
+ @restored_methods.each do |m|
209
+ m.call(depth: @depth, duration: (Time.now - @latency_event_started).to_f)
210
+ end
211
+ # Reset back to 0 depth so we know we're not in a latency event.
212
+ @depth = 0
213
+ @latency_event_started = Time.at(0)
214
+ @last_alerted = now
215
+ self.persist
216
+ return
217
+ end
218
+ if @depth.positive?
219
+ # We have already alerted, so increment the depth and when the latency started.
220
+ @depth += 1
221
+ duration = (Time.now - @latency_event_started).to_f
222
+ else
223
+ # Indicate we are starting a high latency event.
224
+ @depth = 1
225
+ @latency_event_started = Time.now
226
+ duration = 0.0
227
+ end
228
+ # Alert each handler. For legacy reasons, we support handlers that accept
229
+ # ({queues and latencies}) and ({queues and latencies}, {}keywords}).
230
+ kw = {depth: @depth, duration: duration}
123
231
  @alert_methods.each do |m|
124
- m.respond_to?(:call) ? m.call(high_latency_queues) : self.send(m, high_latency_queues)
232
+ if m.respond_to?(:arity) && m.arity == 1
233
+ m.call(high_latency_queues)
234
+ else
235
+ m.call(high_latency_queues, **kw)
236
+ end
125
237
  end
126
238
  @last_alerted = now
239
+ self.persist
127
240
  end
128
241
 
129
242
  def alert_sentry(names_and_latencies)
@@ -134,14 +247,18 @@ module Amigo
134
247
  end
135
248
  end
136
249
 
137
- def alert_log(names_and_latencies)
138
- self.log(:warn, "high_latency_queues", queues: names_and_latencies)
250
+ def alert_log(names_and_latencies, depth:, duration:)
251
+ self._log(:warn, "high_latency_queues", queues: names_and_latencies, depth: depth, duration: duration)
139
252
  end
140
253
 
141
- def alert_test(_names_and_latencies); end
254
+ def alert_test(_names_and_latencies, _opts={}); end
255
+
256
+ def alert_restored_log(depth:, duration:)
257
+ self._log(:info, "high_latency_queues_restored", depth: depth, duration: duration)
258
+ end
142
259
 
143
- protected def log(level, msg, **kw)
144
- Amigo.log(nil, level, msg, kw)
260
+ protected def _log(level, msg, **kw)
261
+ self.log[level, msg, kw]
145
262
  end
146
263
  end
147
264
  end
data/lib/amigo/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Amigo
4
- VERSION = "1.5.0"
4
+ VERSION = "1.6.0"
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sidekiq-amigo
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.5.0
4
+ version: 1.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Lithic Technology
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-03-04 00:00:00.000000000 Z
11
+ date: 2023-04-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: sidekiq
@@ -38,6 +38,20 @@ dependencies:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
40
  version: '1'
41
+ - !ruby/object:Gem::Dependency
42
+ name: platform-api
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">"
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">"
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
41
55
  - !ruby/object:Gem::Dependency
42
56
  name: rack
43
57
  requirement: !ruby/object:Gem::Requirement
@@ -86,28 +100,28 @@ dependencies:
86
100
  requirements:
87
101
  - - "~>"
88
102
  - !ruby/object:Gem::Version
89
- version: '1.11'
103
+ version: '1.48'
90
104
  type: :development
91
105
  prerelease: false
92
106
  version_requirements: !ruby/object:Gem::Requirement
93
107
  requirements:
94
108
  - - "~>"
95
109
  - !ruby/object:Gem::Version
96
- version: '1.11'
110
+ version: '1.48'
97
111
  - !ruby/object:Gem::Dependency
98
112
  name: rubocop-performance
99
113
  requirement: !ruby/object:Gem::Requirement
100
114
  requirements:
101
115
  - - "~>"
102
116
  - !ruby/object:Gem::Version
103
- version: '1.10'
117
+ version: '1.16'
104
118
  type: :development
105
119
  prerelease: false
106
120
  version_requirements: !ruby/object:Gem::Requirement
107
121
  requirements:
108
122
  - - "~>"
109
123
  - !ruby/object:Gem::Version
110
- version: '1.10'
124
+ version: '1.16'
111
125
  - !ruby/object:Gem::Dependency
112
126
  name: sentry-ruby
113
127
  requirement: !ruby/object:Gem::Requirement
@@ -136,10 +150,24 @@ dependencies:
136
150
  - - "~>"
137
151
  - !ruby/object:Gem::Version
138
152
  version: '0'
153
+ - !ruby/object:Gem::Dependency
154
+ name: webmock
155
+ requirement: !ruby/object:Gem::Requirement
156
+ requirements:
157
+ - - ">"
158
+ - !ruby/object:Gem::Version
159
+ version: '0'
160
+ type: :development
161
+ prerelease: false
162
+ version_requirements: !ruby/object:Gem::Requirement
163
+ requirements:
164
+ - - ">"
165
+ - !ruby/object:Gem::Version
166
+ version: '0'
139
167
  description: 'sidekiq-amigo provides a pubsub system and other enhancements around
140
168
  Sidekiq.
141
169
 
142
- '
170
+ '
143
171
  email: hello@lithic.tech
144
172
  executables: []
145
173
  extensions: []
@@ -148,6 +176,7 @@ files:
148
176
  - lib/amigo.rb
149
177
  - lib/amigo/audit_logger.rb
150
178
  - lib/amigo/autoscaler.rb
179
+ - lib/amigo/autoscaler/heroku.rb
151
180
  - lib/amigo/deprecated_jobs.rb
152
181
  - lib/amigo/job.rb
153
182
  - lib/amigo/queue_backoff_job.rb
@@ -163,7 +192,7 @@ licenses:
163
192
  - MIT
164
193
  metadata:
165
194
  rubygems_mfa_required: 'true'
166
- post_install_message:
195
+ post_install_message:
167
196
  rdoc_options: []
168
197
  require_paths:
169
198
  - lib
@@ -178,8 +207,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
178
207
  - !ruby/object:Gem::Version
179
208
  version: '0'
180
209
  requirements: []
181
- rubygems_version: 3.1.6
182
- signing_key:
210
+ rubygems_version: 3.3.7
211
+ signing_key:
183
212
  specification_version: 4
184
213
  summary: Pubsub system and other enhancements around Sidekiq.
185
214
  test_files: []