semian 0.27.0 → 0.28.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.md +75 -0
- data/lib/semian/adapter.rb +1 -1
- data/lib/semian/adaptive_circuit_breaker.rb +136 -0
- data/lib/semian/circuit_breaker.rb +44 -25
- data/lib/semian/circuit_breaker_behaviour.rb +64 -0
- data/lib/semian/configuration_validator.rb +52 -0
- data/lib/semian/dual_circuit_breaker.rb +165 -0
- data/lib/semian/mysql2.rb +2 -2
- data/lib/semian/net_http.rb +3 -3
- data/lib/semian/pid_controller.rb +217 -0
- data/lib/semian/pid_controller_thread.rb +72 -0
- data/lib/semian/protected_resource.rb +1 -1
- data/lib/semian/simple_exponential_smoother.rb +137 -0
- data/lib/semian/unprotected_resource.rb +3 -3
- data/lib/semian/version.rb +1 -1
- data/lib/semian.rb +78 -3
- metadata +8 -2
data/lib/semian.rb
CHANGED
|
@@ -11,6 +11,8 @@ require "semian/instrumentable"
|
|
|
11
11
|
require "semian/platform"
|
|
12
12
|
require "semian/resource"
|
|
13
13
|
require "semian/circuit_breaker"
|
|
14
|
+
require "semian/adaptive_circuit_breaker"
|
|
15
|
+
require "semian/dual_circuit_breaker"
|
|
14
16
|
require "semian/protected_resource"
|
|
15
17
|
require "semian/unprotected_resource"
|
|
16
18
|
require "semian/simple_sliding_window"
|
|
@@ -197,6 +199,32 @@ module Semian
|
|
|
197
199
|
# +exceptions+: An array of exception classes that should be accounted as resource errors. Default [].
|
|
198
200
|
# (circuit breaker)
|
|
199
201
|
#
|
|
202
|
+
# # +exponential_backoff_error_timeout+: When set to true, instead of opening the circuit for the full
|
|
203
|
+
# error_timeout duration, it starts with a smaller timeout and increases exponentially on each subsequent
|
|
204
|
+
# opening up to error_timeout. This helps avoid over-opening the circuit for temporary issues.
|
|
205
|
+
# Default false. (circuit breaker)
|
|
206
|
+
#
|
|
207
|
+
# +exponential_backoff_initial_timeout+: The initial timeout in seconds when exponential backoff is enabled.
|
|
208
|
+
# Only valid when exponential_backoff_error_timeout is true. Default 1. (circuit breaker)
|
|
209
|
+
#
|
|
210
|
+
# +exponential_backoff_multiplier+: The factor by which to multiply the timeout on each subsequent opening
|
|
211
|
+
# when exponential backoff is enabled. Only valid when exponential_backoff_error_timeout is true.
|
|
212
|
+
# Default 2. (circuit breaker)
|
|
213
|
+
#
|
|
214
|
+
# +adaptive_circuit_breaker+: Enable adaptive circuit breaker using PID controller. Default false.
|
|
215
|
+
# When enabled, this replaces the traditional circuit breaker with an adaptive version
|
|
216
|
+
# that dynamically adjusts rejection rates based on service health. (adaptive circuit breaker)
|
|
217
|
+
#
|
|
218
|
+
# +dual_circuit_breaker+: Enable dual circuit breaker mode where both legacy and adaptive
|
|
219
|
+
# circuit breakers are initialized. Default false. When enabled, both circuit breakers track
|
|
220
|
+
# requests, but only one is used for decision-making based on use_adaptive.
|
|
221
|
+
# (dual circuit breaker)
|
|
222
|
+
#
|
|
223
|
+
# +use_adaptive+: A callable (Proc/lambda) that returns true to use adaptive circuit breaker
|
|
224
|
+
# or false to use legacy. Only used when dual_circuit_breaker is enabled. Default: ->() { false }.
|
|
225
|
+
# Example: ->() { MyFeatureFlag.enabled?(:adaptive_circuit_breaker) }
|
|
226
|
+
# (dual circuit breaker)
|
|
227
|
+
#
|
|
200
228
|
# Returns the registered resource.
|
|
201
229
|
def register(name, **options)
|
|
202
230
|
return UnprotectedResource.new(name) if ENV.key?("SEMIAN_DISABLED")
|
|
@@ -204,7 +232,14 @@ module Semian
|
|
|
204
232
|
# Validate configuration before proceeding
|
|
205
233
|
ConfigurationValidator.new(name, options).validate!
|
|
206
234
|
|
|
207
|
-
circuit_breaker =
|
|
235
|
+
circuit_breaker = if options[:dual_circuit_breaker]
|
|
236
|
+
create_dual_circuit_breaker(name, **options)
|
|
237
|
+
elsif options[:adaptive_circuit_breaker]
|
|
238
|
+
create_adaptive_circuit_breaker(name, **options)
|
|
239
|
+
else
|
|
240
|
+
create_circuit_breaker(name, **options)
|
|
241
|
+
end
|
|
242
|
+
|
|
208
243
|
bulkhead = create_bulkhead(name, **options)
|
|
209
244
|
|
|
210
245
|
resources[name] = ProtectedResource.new(name, bulkhead, circuit_breaker)
|
|
@@ -300,12 +335,49 @@ module Semian
|
|
|
300
335
|
|
|
301
336
|
private
|
|
302
337
|
|
|
303
|
-
def
|
|
338
|
+
def create_dual_circuit_breaker(name, **options)
|
|
339
|
+
return if ENV.key?("SEMIAN_CIRCUIT_BREAKER_DISABLED")
|
|
340
|
+
|
|
341
|
+
classic_cb = create_circuit_breaker(name, is_child: true, **options)
|
|
342
|
+
adaptive_cb = create_adaptive_circuit_breaker(name, is_child: true, **options)
|
|
343
|
+
|
|
344
|
+
DualCircuitBreaker.new(
|
|
345
|
+
name: name,
|
|
346
|
+
classic_circuit_breaker: classic_cb,
|
|
347
|
+
adaptive_circuit_breaker: adaptive_cb,
|
|
348
|
+
)
|
|
349
|
+
end
|
|
350
|
+
|
|
351
|
+
def create_adaptive_circuit_breaker(name, is_child: false, **options)
|
|
352
|
+
return if ENV.key?("SEMIAN_CIRCUIT_BREAKER_DISABLED")
|
|
353
|
+
|
|
354
|
+
exceptions = options[:exceptions] || []
|
|
355
|
+
cls = is_child ? DualCircuitBreaker::ChildAdaptiveCircuitBreaker : AdaptiveCircuitBreaker
|
|
356
|
+
cls.new(
|
|
357
|
+
name: name,
|
|
358
|
+
exceptions: Array(exceptions) + [::Semian::BaseError],
|
|
359
|
+
kp: options[:kp] || 1.0,
|
|
360
|
+
ki: options[:ki] || 0.2,
|
|
361
|
+
kd: options[:kd] || 0.0,
|
|
362
|
+
window_size: options[:window_size] || 10,
|
|
363
|
+
initial_error_rate: options[:initial_error_rate] || 0.05,
|
|
364
|
+
dead_zone_ratio: options[:dead_zone_ratio] || 0.25,
|
|
365
|
+
# We use an environment vraiable for the sliding interval because it is shared among all circuit breakers
|
|
366
|
+
sliding_interval: ENV.fetch("SEMIAN_ADAPTIVE_CIRCUIT_BREAKER_SLIDING_INTERVAL", 1).to_i,
|
|
367
|
+
ideal_error_rate_estimator_cap_value: options[:ideal_error_rate_estimator_cap_value] || 0.1,
|
|
368
|
+
integral_upper_cap: options[:integral_upper_cap] || 10.0,
|
|
369
|
+
integral_lower_cap: options[:integral_lower_cap] || -10.0,
|
|
370
|
+
implementation: implementation(**options),
|
|
371
|
+
)
|
|
372
|
+
end
|
|
373
|
+
|
|
374
|
+
def create_circuit_breaker(name, is_child: false, **options)
|
|
304
375
|
return if ENV.key?("SEMIAN_CIRCUIT_BREAKER_DISABLED")
|
|
305
376
|
return unless options.fetch(:circuit_breaker, true)
|
|
306
377
|
|
|
307
378
|
exceptions = options[:exceptions] || []
|
|
308
|
-
CircuitBreaker
|
|
379
|
+
cls = is_child ? DualCircuitBreaker::ChildClassicCircuitBreaker : CircuitBreaker
|
|
380
|
+
cls.new(
|
|
309
381
|
name,
|
|
310
382
|
success_threshold: options[:success_threshold],
|
|
311
383
|
error_threshold: options[:error_threshold],
|
|
@@ -323,6 +395,9 @@ module Semian
|
|
|
323
395
|
end,
|
|
324
396
|
exceptions: Array(exceptions) + [::Semian::BaseError],
|
|
325
397
|
half_open_resource_timeout: options[:half_open_resource_timeout],
|
|
398
|
+
exponential_backoff_error_timeout: options[:exponential_backoff_error_timeout] || false,
|
|
399
|
+
exponential_backoff_initial_timeout: options[:exponential_backoff_initial_timeout] || 1,
|
|
400
|
+
exponential_backoff_multiplier: options[:exponential_backoff_multiplier] || 2,
|
|
326
401
|
implementation: implementation(**options),
|
|
327
402
|
)
|
|
328
403
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: semian
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.28.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Scott Francis
|
|
@@ -51,13 +51,18 @@ files:
|
|
|
51
51
|
- lib/semian/activerecord_postgresql_adapter.rb
|
|
52
52
|
- lib/semian/activerecord_trilogy_adapter.rb
|
|
53
53
|
- lib/semian/adapter.rb
|
|
54
|
+
- lib/semian/adaptive_circuit_breaker.rb
|
|
54
55
|
- lib/semian/circuit_breaker.rb
|
|
56
|
+
- lib/semian/circuit_breaker_behaviour.rb
|
|
55
57
|
- lib/semian/configuration_validator.rb
|
|
58
|
+
- lib/semian/dual_circuit_breaker.rb
|
|
56
59
|
- lib/semian/grpc.rb
|
|
57
60
|
- lib/semian/instrumentable.rb
|
|
58
61
|
- lib/semian/lru_hash.rb
|
|
59
62
|
- lib/semian/mysql2.rb
|
|
60
63
|
- lib/semian/net_http.rb
|
|
64
|
+
- lib/semian/pid_controller.rb
|
|
65
|
+
- lib/semian/pid_controller_thread.rb
|
|
61
66
|
- lib/semian/platform.rb
|
|
62
67
|
- lib/semian/protected_resource.rb
|
|
63
68
|
- lib/semian/rails.rb
|
|
@@ -65,6 +70,7 @@ files:
|
|
|
65
70
|
- lib/semian/redis/v5.rb
|
|
66
71
|
- lib/semian/redis_client.rb
|
|
67
72
|
- lib/semian/resource.rb
|
|
73
|
+
- lib/semian/simple_exponential_smoother.rb
|
|
68
74
|
- lib/semian/simple_integer.rb
|
|
69
75
|
- lib/semian/simple_sliding_window.rb
|
|
70
76
|
- lib/semian/simple_state.rb
|
|
@@ -94,7 +100,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
94
100
|
- !ruby/object:Gem::Version
|
|
95
101
|
version: '0'
|
|
96
102
|
requirements: []
|
|
97
|
-
rubygems_version: 4.0.
|
|
103
|
+
rubygems_version: 4.0.8
|
|
98
104
|
specification_version: 4
|
|
99
105
|
summary: Bulkheading for Ruby with SysV semaphores
|
|
100
106
|
test_files: []
|