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: 8a4488b919923025829a72e3c9a23220a528f76499adfe1575b3f7484adca8ec
4
- data.tar.gz: 19b2bf974f03231b8f060ebaaa12ff0ce214ee3bb89620a1b90981fee3e0410e
3
+ metadata.gz: 0f94990d679958250735f0fd7fa473c1d7e232d0f817479cfbd5f13c68d4f75b
4
+ data.tar.gz: 89178dfc3eed39b2553c99e778412158b6fe0582f6e3b59628892fd3a5e0251b
5
5
  SHA512:
6
- metadata.gz: 4dac7f9c83dab082137824c6a730fb6bb68e042758f155aac8480067810904bc234b9f152c7ee023264b6fcb4eb09c2fbfebc43b668520b0fdb446cc66eb1dad
7
- data.tar.gz: 56c9cc4d51523f1752fc36e70f8c3cc4cb83177c4e7c345c8972116d27b0e12812aedbe2839f8bcb958f4ea50dd20432f3a686d2e9217e2fbf2588bf0cbfe872
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 metrics.queue_depth >= @config.scale_up_queue_depth
165
- reasons << "queue_depth=#{metrics.queue_depth} >= #{@config.scale_up_queue_depth}"
166
- end
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 metrics.oldest_job_age_seconds >= @config.scale_up_latency_seconds
169
- reasons << "latency=#{metrics.oldest_job_age_seconds.round}s >= #{@config.scale_up_latency_seconds}s"
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?
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SolidQueueAutoscaler
4
- VERSION = '1.0.19'
4
+ VERSION = '1.0.20'
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: solid_queue_autoscaler
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.19
4
+ version: 1.0.20
5
5
  platform: ruby
6
6
  authors:
7
7
  - reillyse