Class: Stoplight::Infrastructure::Redis::Storage::UnboundedMetrics
- Inherits:
-
Metrics
- Object
- Metrics
- Stoplight::Infrastructure::Redis::Storage::UnboundedMetrics
- Defined in:
- lib/stoplight/infrastructure/redis/storage/unbounded_metrics.rb,
sig/_private/stoplight/infrastructure/redis/storage/unbounded_metrics.rbs
Overview
The errors and successes fields in the returned Stoplight::Domain::Metrics
are always nil since this class doesn't track windowed totals.
The metrics hash TTL is refreshed on every write operation. Circuits with no activity will have their metrics expire, which is generally desirable for ephemeral or decommissioned lights.
Distributed metrics storage for consecutive-error light strategies.
This class provides a lightweight alternative to WindowMetrics for circuit
breakers that don't need time-windowed rate calculations. It tracks only:
- Consecutive success/failure counters (reset on opposite outcome)
- Most recent error with timestamp and serialized details
- Most recent success timestamp
Storage Structure
All data is stored in a single Redis hash:
stoplight:{version}:{system}:{light}:metrics
Hash fields:
last_success_at: Unix timestamp (float) of most recent successlast_error_at: Unix timestamp (float) of most recent failurelast_error_json: Serialized Domain::Failure for error detailsconsecutive_successes: Integer counter, reset to 0 on failureconsecutive_errors: Integer counter, reset to 0 on success
Performance Characteristics
This implementation is optimized for minimal Redis overhead:
- Single hash key per circuit (vs. multiple ZSETs for
WindowMetrics) - O(1) reads and writes
- No time-range queries or bucket management
- Low memory footprint
Atomicity
Record operations use Lua scripts to ensure atomic read-modify-write:
- Consecutive counters are incremented and reset in one round-trip
- "Last" timestamps only update if the new event is more recent, preventing out-of-order writes from corrupting state
When to Use
Choose UnboundedMetrics when your circuit breaker strategy is based on consecutive failures (e.g., "open after 5 failures in a row"). Choose WindowMetrics when you need error rate calculations (e.g., "open when error rate exceeds 50% over 5 minutes").
Direct Known Subclasses
Instance Attribute Summary collapse
-
#clock ⇒ Domain::_Clock
readonly
Returns the value of attribute clock.
-
#metrics_key ⇒ String
readonly
Returns the value of attribute metrics_key.
-
#redis ⇒ redis
readonly
Returns the value of attribute redis.
-
#scripting ⇒ Scripting
readonly
Returns the value of attribute scripting.
Instance Method Summary collapse
- #clear ⇒ Object
-
#initialize(redis:, scripting:, key_space:, clock:) ⇒ UnboundedMetrics
constructor
A new instance of UnboundedMetrics.
-
#metrics_snapshot ⇒ Object
Get metrics for the current light.
-
#record_failure(exception) ⇒ Object
Records failed circuit breaker execution.
-
#record_success ⇒ Object
Records successful circuit breaker execution.
Methods inherited from Metrics
#deserialize_failure, #serialize_exception
Constructor Details
#initialize(redis:, scripting:, key_space:, clock:) ⇒ UnboundedMetrics
Returns a new instance of UnboundedMetrics.
57 58 59 60 61 62 |
# File 'lib/stoplight/infrastructure/redis/storage/unbounded_metrics.rb', line 57 def initialize(redis:, scripting:, key_space:, clock:) @clock = clock @scripting = scripting @redis = redis @metrics_key = key_space.key(:metrics) end |
Instance Attribute Details
#clock ⇒ Domain::_Clock (readonly)
Returns the value of attribute clock.
118 119 120 |
# File 'lib/stoplight/infrastructure/redis/storage/unbounded_metrics.rb', line 118 def clock @clock end |
#metrics_key ⇒ String (readonly)
Returns the value of attribute metrics_key.
117 118 119 |
# File 'lib/stoplight/infrastructure/redis/storage/unbounded_metrics.rb', line 117 def metrics_key @metrics_key end |
#redis ⇒ redis (readonly)
Returns the value of attribute redis.
115 116 117 |
# File 'lib/stoplight/infrastructure/redis/storage/unbounded_metrics.rb', line 115 def redis @redis end |
#scripting ⇒ Scripting (readonly)
Returns the value of attribute scripting.
116 117 118 |
# File 'lib/stoplight/infrastructure/redis/storage/unbounded_metrics.rb', line 116 def scripting @scripting end |
Instance Method Details
#clear ⇒ Object
107 108 109 110 111 |
# File 'lib/stoplight/infrastructure/redis/storage/unbounded_metrics.rb', line 107 def clear redis.with do |client| client.hdel(metrics_key, "last_success_at", "last_error_json", "consecutive_errors", "consecutive_successes") end end |
#metrics_snapshot ⇒ Object
Get metrics for the current light
66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 |
# File 'lib/stoplight/infrastructure/redis/storage/unbounded_metrics.rb', line 66 def metrics_snapshot last_success_at, last_error_json, consecutive_errors, consecutive_successes = redis.with do |client| client.hmget( metrics_key, "last_success_at", "last_error_json", "consecutive_errors", "consecutive_successes" ) end Domain::MetricsSnapshot.new( successes: nil, errors: nil, consecutive_errors: consecutive_errors.to_i, consecutive_successes: consecutive_successes.to_i, last_error: deserialize_failure(last_error_json), last_success_at: (clock.at(last_success_at.to_f) if last_success_at) ) end |
#record_failure(exception) ⇒ Object
Records failed circuit breaker execution
97 98 99 100 101 102 103 104 105 |
# File 'lib/stoplight/infrastructure/redis/storage/unbounded_metrics.rb', line 97 def record_failure(exception) = clock.current_time.to_f scripting.call( :"unbounded_metrics/record_failure", args: [, serialize_exception(exception, timestamp:), metrics_ttl], keys: [metrics_key] ) end |
#record_success ⇒ Object
Records successful circuit breaker execution
85 86 87 88 89 90 91 92 93 |
# File 'lib/stoplight/infrastructure/redis/storage/unbounded_metrics.rb', line 85 def record_success = clock.current_time.to_f scripting.call( :"unbounded_metrics/record_success", args: [, metrics_ttl], keys: [metrics_key] ) end |