solid_queue_autoscaler 1.0.19 → 1.0.20
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
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 0f94990d679958250735f0fd7fa473c1d7e232d0f817479cfbd5f13c68d4f75b
|
|
4
|
+
data.tar.gz: 89178dfc3eed39b2553c99e778412158b6fe0582f6e3b59628892fd3a5e0251b
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 9a7aaffab4800907fdf86f2037e64ce7aaff36999f1fe9bd4eae77e768ac4633261cc2ebb0e411977314ae9b50b110f5dc2350bc5c2b4ce059f41dd02bd3b91e
|
|
7
|
+
data.tar.gz: 3787b68c1338cd3df627487a145419c82a687930b050999237943008b1cb2a5521dac9bf74e0e82febf4571a789dc28fcaffc7a721676b402954320210734c57
|
data/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [1.0.20] - 2025-02-02
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- **Scale-from-zero optimization** - New configuration options for faster cold starts when `min_workers = 0`:
|
|
14
|
+
- `scale_from_zero_queue_depth` (default: 1) - Scale up immediately when at 0 workers if queue has at least this many jobs
|
|
15
|
+
- `scale_from_zero_latency_seconds` (default: 1.0) - Job must be at least this old before scaling up (gives other workers a chance to pick it up first)
|
|
16
|
+
- When at 0 workers, uses these lower thresholds instead of the normal `scale_up_queue_depth` and `scale_up_latency_seconds`
|
|
17
|
+
- Cooldowns are bypassed when scaling from 0 workers for fast cold start
|
|
18
|
+
- Comprehensive tests in `scale_to_zero_workflow_spec.rb`
|
|
19
|
+
|
|
10
20
|
## [1.0.19] - 2025-02-02
|
|
11
21
|
|
|
12
22
|
### Added
|
|
@@ -69,6 +69,9 @@ module SolidQueueAutoscaler
|
|
|
69
69
|
# AutoscaleJob settings
|
|
70
70
|
attr_accessor :job_queue, :job_priority
|
|
71
71
|
|
|
72
|
+
# Scale-from-zero settings (for faster cold start when min_workers=0)
|
|
73
|
+
attr_accessor :scale_from_zero_queue_depth, :scale_from_zero_latency_seconds
|
|
74
|
+
|
|
72
75
|
def initialize
|
|
73
76
|
# Configuration name (auto-set when using named configurations)
|
|
74
77
|
@name = :default
|
|
@@ -141,6 +144,11 @@ module SolidQueueAutoscaler
|
|
|
141
144
|
# AutoscaleJob settings
|
|
142
145
|
@job_queue = :autoscaler # Queue name for the autoscaler job
|
|
143
146
|
@job_priority = nil # Job priority (lower = higher priority, nil = default)
|
|
147
|
+
|
|
148
|
+
# Scale-from-zero settings (for faster cold start when min_workers=0)
|
|
149
|
+
# When at 0 workers, use these lower thresholds instead of normal scale_up thresholds
|
|
150
|
+
@scale_from_zero_queue_depth = 1 # Scale up if at least 1 job in queue
|
|
151
|
+
@scale_from_zero_latency_seconds = 1.0 # Job must be at least 1 second old (gives other workers a chance)
|
|
144
152
|
end
|
|
145
153
|
|
|
146
154
|
# Returns the lock key, auto-generating based on name if not explicitly set
|
|
@@ -196,6 +204,10 @@ module SolidQueueAutoscaler
|
|
|
196
204
|
errors << "scaling_strategy must be one of: #{VALID_SCALING_STRATEGIES.join(', ')}"
|
|
197
205
|
end
|
|
198
206
|
|
|
207
|
+
# Validate scale-from-zero settings
|
|
208
|
+
errors << 'scale_from_zero_queue_depth must be > 0' if scale_from_zero_queue_depth <= 0
|
|
209
|
+
errors << 'scale_from_zero_latency_seconds must be >= 0' if scale_from_zero_latency_seconds.negative?
|
|
210
|
+
|
|
199
211
|
raise ConfigurationError, errors.join(', ') if errors.any?
|
|
200
212
|
|
|
201
213
|
true
|
|
@@ -41,12 +41,30 @@ module SolidQueueAutoscaler
|
|
|
41
41
|
def should_scale_up?(metrics, current_workers)
|
|
42
42
|
return false if current_workers >= @config.max_workers
|
|
43
43
|
|
|
44
|
+
# Special case: scale-from-zero uses lower thresholds for faster cold start
|
|
45
|
+
# This allows immediate scaling when at 0 workers with any work in queue
|
|
46
|
+
if current_workers.zero? && @config.min_workers.zero?
|
|
47
|
+
return should_scale_from_zero?(metrics)
|
|
48
|
+
end
|
|
49
|
+
|
|
44
50
|
queue_depth_high = metrics.queue_depth >= @config.scale_up_queue_depth
|
|
45
51
|
latency_high = metrics.oldest_job_age_seconds >= @config.scale_up_latency_seconds
|
|
46
52
|
|
|
47
53
|
queue_depth_high || latency_high
|
|
48
54
|
end
|
|
49
55
|
|
|
56
|
+
# Scale-from-zero check: uses lower thresholds for faster cold start
|
|
57
|
+
# Requires:
|
|
58
|
+
# 1. Queue depth >= scale_from_zero_queue_depth (default: 1)
|
|
59
|
+
# 2. Oldest job age >= scale_from_zero_latency_seconds (default: 1s)
|
|
60
|
+
# This gives other workers/queues a chance to pick up the job first
|
|
61
|
+
def should_scale_from_zero?(metrics)
|
|
62
|
+
has_work = metrics.queue_depth >= @config.scale_from_zero_queue_depth
|
|
63
|
+
job_old_enough = metrics.oldest_job_age_seconds >= @config.scale_from_zero_latency_seconds
|
|
64
|
+
|
|
65
|
+
has_work && job_old_enough
|
|
66
|
+
end
|
|
67
|
+
|
|
50
68
|
def should_scale_down?(metrics, current_workers)
|
|
51
69
|
return false if current_workers <= @config.min_workers
|
|
52
70
|
|
|
@@ -161,12 +179,22 @@ module SolidQueueAutoscaler
|
|
|
161
179
|
def build_scale_up_reason(metrics, current_workers = nil, target = nil)
|
|
162
180
|
reasons = []
|
|
163
181
|
|
|
164
|
-
if
|
|
165
|
-
|
|
166
|
-
|
|
182
|
+
# Check if this is a scale-from-zero scenario
|
|
183
|
+
is_scale_from_zero = current_workers&.zero? && @config.min_workers.zero? &&
|
|
184
|
+
metrics.queue_depth >= @config.scale_from_zero_queue_depth &&
|
|
185
|
+
metrics.oldest_job_age_seconds >= @config.scale_from_zero_latency_seconds
|
|
167
186
|
|
|
168
|
-
if
|
|
169
|
-
reasons << "
|
|
187
|
+
if is_scale_from_zero
|
|
188
|
+
reasons << "scale_from_zero: queue_depth=#{metrics.queue_depth} >= #{@config.scale_from_zero_queue_depth}"
|
|
189
|
+
reasons << "job_age=#{metrics.oldest_job_age_seconds.round(1)}s >= #{@config.scale_from_zero_latency_seconds}s"
|
|
190
|
+
else
|
|
191
|
+
if metrics.queue_depth >= @config.scale_up_queue_depth
|
|
192
|
+
reasons << "queue_depth=#{metrics.queue_depth} >= #{@config.scale_up_queue_depth}"
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
if metrics.oldest_job_age_seconds >= @config.scale_up_latency_seconds
|
|
196
|
+
reasons << "latency=#{metrics.oldest_job_age_seconds.round}s >= #{@config.scale_up_latency_seconds}s"
|
|
197
|
+
end
|
|
170
198
|
end
|
|
171
199
|
|
|
172
200
|
base_reason = reasons.join(', ')
|
|
@@ -185,6 +185,12 @@ module SolidQueueAutoscaler
|
|
|
185
185
|
end
|
|
186
186
|
|
|
187
187
|
def cooldown_active?(decision)
|
|
188
|
+
# Bypass cooldowns when scaling from zero - we want fast cold start
|
|
189
|
+
# This is safe because there are no workers to destabilize
|
|
190
|
+
if decision.scale_up? && decision.from.zero? && @config.min_workers.zero?
|
|
191
|
+
return false
|
|
192
|
+
end
|
|
193
|
+
|
|
188
194
|
if @config.persist_cooldowns && cooldown_tracker.table_exists?
|
|
189
195
|
# Use database-persisted cooldowns (survives process restarts)
|
|
190
196
|
if decision.scale_up?
|