autoscaler 0.11.0 → 0.12.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/CHANGELOG.md +5 -1
- data/README.md +1 -1
- data/lib/autoscaler/sidekiq/thread_server.rb +74 -48
- data/lib/autoscaler/version.rb +1 -1
- data/spec/autoscaler/sidekiq/thread_server_spec.rb +29 -8
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f7f8b0bdbb93db7c32f3b0c3f4fe3f55902eed07
|
4
|
+
data.tar.gz: 379df2b6120350848c660040c705c22e11c1d1c3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1f7c387a19bf6908ab7264aaff7566ca8b905c6957e7ffde3d55e6b77e27fa982434e872c66127cf980b618785dc1119ed49577f02574d937bd6e03f62e4b841
|
7
|
+
data.tar.gz: 6353aeb2213fc015299017b73c82b6a53b1f328b90dbe221593390c6ede3e3880c9b1884fefbd0c7aec118f937ea16cda48696ef80f2b2415b9c1ecdce984ad1
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,9 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
+
## 0.12.0
|
4
|
+
|
5
|
+
- Rewrite ThreadServer use a singleton monitor thread; it was incorrectly creating one per middleware instance, which was per-job
|
6
|
+
|
3
7
|
## 0.11.0
|
4
8
|
|
5
9
|
- Replace celluloid monitor with thread based middleware for Sidekiq 4.0
|
@@ -8,7 +12,7 @@
|
|
8
12
|
## 0.10.0
|
9
13
|
|
10
14
|
- Require Sidekiq 3.5
|
11
|
-
- You may use `HerokuPlatformScaler` and `HEROKU_ACCESS_TOKEN` in place of `
|
15
|
+
- You may use `HerokuPlatformScaler` and `HEROKU_ACCESS_TOKEN` in place of `HerokuScaler` and `HEROKU_API_KEY`
|
12
16
|
- QueueSystem#workers returns the number of engaged SK processes.
|
13
17
|
- Linear Scaling Strategy will not scale down past number of active workers. Assumes 1-1 SK process/dyno mapping.
|
14
18
|
- Calls the SideKiq quiet api when shutting down
|
data/README.md
CHANGED
@@ -17,7 +17,7 @@ This gem uses the [Heroku Platform-Api](https://github.com/heroku/platform-api)
|
|
17
17
|
HEROKU_ACCESS_TOKEN=.....
|
18
18
|
HEROKU_APP=....
|
19
19
|
|
20
|
-
Support is still present for [Heroku-Api](https://github.com/heroku/heroku.rb) via `
|
20
|
+
Support is still present for [Heroku-Api](https://github.com/heroku/heroku.rb) via `HerokuScaler` and `HEROKU_API_KEY`, but may be removed in a future major version.
|
21
21
|
|
22
22
|
Install the middleware in your `Sidekiq.configure_` blocks
|
23
23
|
|
@@ -6,7 +6,8 @@ require 'thread'
|
|
6
6
|
module Autoscaler
|
7
7
|
module Sidekiq
|
8
8
|
# Sidekiq server middleware
|
9
|
-
#
|
9
|
+
# Sidekiq creates a new instance of the middleware for each worker
|
10
|
+
# spawns a Monitor thread to monitor the sidekiq server for scale-down
|
10
11
|
class ThreadServer
|
11
12
|
# @param [scaler] scaler object that actually performs scaling operations (e.g. {HerokuPlatformScaler})
|
12
13
|
# @param [Strategy,Numeric] timeout strategy object that determines target workers, or a timeout in seconds to be passed to {DelayedShutdown}+{BinaryScalingStrategy}
|
@@ -15,76 +16,101 @@ module Autoscaler
|
|
15
16
|
@scaler = scaler
|
16
17
|
@strategy = strategy(timeout)
|
17
18
|
@system = QueueSystem.new(specified_queues)
|
18
|
-
@mutex = Mutex.new
|
19
|
-
@done = false
|
20
19
|
end
|
21
20
|
|
22
21
|
# Sidekiq middleware api entry point
|
23
22
|
def call(worker, msg, queue, _ = nil)
|
24
23
|
yield
|
25
24
|
ensure
|
26
|
-
active_now!
|
27
|
-
wait_for_downscale
|
25
|
+
monitor.active_now!
|
26
|
+
monitor.wait_for_downscale(@scaler, @strategy, @system)
|
28
27
|
end
|
29
28
|
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
end
|
29
|
+
private
|
30
|
+
|
31
|
+
def strategy(timeout)
|
32
|
+
if timeout.respond_to?(:call)
|
33
|
+
timeout
|
34
|
+
else
|
35
|
+
DelayedShutdown.new(BinaryScalingStrategy.new, timeout)
|
38
36
|
end
|
39
37
|
end
|
40
38
|
|
41
|
-
#
|
42
|
-
#
|
43
|
-
|
44
|
-
|
45
|
-
|
39
|
+
# Manages the single thread that watches for scaledown
|
40
|
+
# Singleton (see bottom) but instanceable for testing
|
41
|
+
class Monitor
|
42
|
+
def initialize
|
43
|
+
@mutex = Mutex.new
|
44
|
+
@done = false
|
45
|
+
end
|
46
|
+
|
47
|
+
# Start the monitoring thread if it's not running
|
48
|
+
# @param [scaler] scaler object that actually performs scaling operations (e.g. {HerokuPlatformScaler})
|
49
|
+
# @param [Strategy] strategy object that determines target workers
|
50
|
+
# @param [QueueSystem] system queue system that determines which sidekiq queues are watched
|
51
|
+
def wait_for_downscale(scaler, strategy, system)
|
52
|
+
@thread ||= Thread.new do
|
53
|
+
begin
|
54
|
+
run(scaler, strategy, system)
|
55
|
+
rescue
|
56
|
+
@thread = nil
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
46
60
|
|
47
|
-
|
61
|
+
# Thread core loop
|
62
|
+
# Periodically update the desired number of workers
|
63
|
+
# @param [scaler] scaler object that actually performs scaling operations (e.g. {HerokuPlatformScaler})
|
64
|
+
# @param [Strategy] strategy object that determines target workers
|
65
|
+
# @param [QueueSystem] system queue system that determines which sidekiq queues are watched
|
66
|
+
# @param [Numeric] interval polling interval, mostly for testing
|
67
|
+
def run(scaler, strategy, system, interval = 15)
|
68
|
+
active_now!
|
48
69
|
|
49
|
-
|
50
|
-
sleep(interval)
|
51
|
-
target_workers = @strategy.call(@system, idle_time)
|
52
|
-
workers = @scaler.workers = target_workers unless workers == target_workers
|
53
|
-
end while !@done && workers > 0
|
54
|
-
::Sidekiq::ProcessSet.new.each(&:quiet!)
|
55
|
-
end
|
70
|
+
workers = :unknown
|
56
71
|
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
t.value
|
72
|
+
begin
|
73
|
+
sleep(interval)
|
74
|
+
target_workers = strategy.call(system, idle_time)
|
75
|
+
workers = scaler.workers = target_workers unless workers == target_workers
|
76
|
+
end while !@done && workers > 0
|
77
|
+
::Sidekiq::ProcessSet.new.each(&:quiet!)
|
64
78
|
end
|
65
|
-
end
|
66
79
|
|
67
|
-
|
80
|
+
# Shut down the thread, pause until complete
|
81
|
+
def terminate
|
82
|
+
@done = true
|
83
|
+
if @thread
|
84
|
+
t = @thread
|
85
|
+
@thread = nil
|
86
|
+
t.value
|
87
|
+
end
|
88
|
+
end
|
68
89
|
|
69
|
-
|
70
|
-
|
71
|
-
@
|
90
|
+
# Record last active time
|
91
|
+
def active_now!
|
92
|
+
@mutex.synchronize do
|
93
|
+
@activity = Time.now
|
94
|
+
end
|
72
95
|
end
|
73
|
-
end
|
74
96
|
|
75
|
-
|
76
|
-
|
77
|
-
|
97
|
+
private
|
98
|
+
|
99
|
+
def idle_time
|
100
|
+
@mutex.synchronize do
|
101
|
+
Time.now - @activity
|
102
|
+
end
|
78
103
|
end
|
79
104
|
end
|
80
105
|
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
DelayedShutdown.new(BinaryScalingStrategy.new, timeout)
|
86
|
-
end
|
106
|
+
Singleton = Monitor.new
|
107
|
+
|
108
|
+
def monitor
|
109
|
+
Singleton
|
87
110
|
end
|
111
|
+
|
112
|
+
private_constant :Monitor
|
113
|
+
private_constant :Singleton
|
88
114
|
end
|
89
115
|
end
|
90
116
|
end
|
data/lib/autoscaler/version.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'spec_helper'
|
2
|
+
require 'autoscaler/sidekiq/queue_system'
|
2
3
|
require 'autoscaler/sidekiq/thread_server'
|
3
4
|
require 'timeout'
|
4
5
|
|
@@ -8,35 +9,55 @@ describe Autoscaler::Sidekiq::ThreadServer do
|
|
8
9
|
Sidekiq.redis {|c| c.flushdb }
|
9
10
|
end
|
10
11
|
|
11
|
-
let(:
|
12
|
+
let(:middleware) {Autoscaler::Sidekiq::ThreadServer}
|
13
|
+
let(:monitor) {Autoscaler::Sidekiq::ThreadServer.const_get(:Monitor)}
|
12
14
|
let(:scaler) {TestScaler.new(1)}
|
15
|
+
let(:strategy0) {lambda{|s,t| 0}}
|
16
|
+
let(:strategy1) {lambda{|s,t| 1}}
|
17
|
+
let(:system) {Autoscaler::Sidekiq::QueueSystem.new}
|
13
18
|
|
14
19
|
describe "thread core" do
|
15
20
|
it "scales with no work" do
|
16
|
-
server =
|
17
|
-
Timeout.timeout(1) { server.run(0.5) }
|
21
|
+
server = monitor.new
|
22
|
+
Timeout.timeout(1) { server.run(scaler, strategy0, system, 0.5) }
|
18
23
|
expect(scaler.workers).to eq 0
|
19
24
|
server.terminate
|
20
25
|
end
|
21
26
|
|
22
27
|
it "does not scale with pending work" do
|
23
|
-
server =
|
24
|
-
expect {Timeout.timeout(1) { server.run(0.5) }}.to raise_error Timeout::Error
|
28
|
+
server = monitor.new
|
29
|
+
expect {Timeout.timeout(1) { server.run(scaler, strategy1, system, 0.5) }}.to raise_error Timeout::Error
|
25
30
|
expect(scaler.workers).to eq 1
|
26
31
|
server.terminate
|
27
32
|
end
|
28
33
|
|
29
34
|
it "will downscale with initial workers zero" do
|
30
35
|
scaler = TestScaler.new(0)
|
31
|
-
server =
|
32
|
-
Timeout.timeout(1) { server.run(0.5) }
|
36
|
+
server = monitor.new
|
37
|
+
Timeout.timeout(1) { server.run(scaler, strategy0, system, 0.5) }
|
33
38
|
expect(scaler.workers).to eq 0
|
34
39
|
server.terminate
|
35
40
|
end
|
41
|
+
|
42
|
+
it "only starts a single thread " do
|
43
|
+
scaler = TestScaler.new(0)
|
44
|
+
mon = monitor.new
|
45
|
+
$counter = 0
|
46
|
+
def mon.run(a, b, c, i = 15)
|
47
|
+
$counter += 1
|
48
|
+
end
|
49
|
+
t1 = Thread.new { mon.wait_for_downscale(scaler, strategy0, system) }
|
50
|
+
t2 = Thread.new { mon.wait_for_downscale(scaler, strategy0, system) }
|
51
|
+
sleep(0.1)
|
52
|
+
t1.value
|
53
|
+
t2.value
|
54
|
+
expect($counter).to eq 1
|
55
|
+
mon.terminate
|
56
|
+
end
|
36
57
|
end
|
37
58
|
|
38
59
|
describe "Middleware interface" do
|
39
|
-
let(:server) {
|
60
|
+
let(:server) {middleware.new(scaler, 0, ['queue'])}
|
40
61
|
|
41
62
|
it('yields') {expect(server.call(Object.new, {}, 'queue') {:foo}).to eq :foo}
|
42
63
|
it('yields with a redis pool') {expect(server.call(Object.new, {}, 'queue', Sidekiq.method(:redis)) {:foo}).to eq :foo}
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: autoscaler
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.12.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Justin Love
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date:
|
12
|
+
date: 2016-11-06 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: sidekiq
|