v5.0.1
Stoplight 5.0 Release Notes
We're excited to share Stoplight 5.0 with you - this has been our biggest release yet! We've completely rewritten major
parts of the library to deliver better performance, cleaner APIs, and a much more pleasant developer experience. While
this does come with some breaking changes that will require migration work, we think you'll find the new features and
improvements well worth the effort.
What's New and Exciting
The headline feature is definitely our new simplified initialization syntax. Instead of chaining a bunch of method calls,
you can now configure everything upfront with keyword arguments:
light = Stoplight("Payment Service",
threshold: 5, # 5 failures before turning red
cool_off_time: 60, # Wait 60 seconds before attempting recovery
window_size: 300, # Only count failures in the last 5 minutes
tracked_errors: [TimeoutError], # Only count these errors
skipped_errors: [ValidationError] # Ignore these errors
)Don't worry though - if you love the classic builder-style interface, it's sticking around. We have no plans to remove
it, so you can keep using the familiar pattern:
light = Stoplight.new("Payment Service")
.with_threshold(5)
.with_cool_off_time(60)
.with_tracked_errors([TimeoutError])Whichever style you prefer, it does not impart the Stoplight's execution overhead. However, if you prefer to instantiate
stoplight often, a new interface could offer up to 5x performance improvement over the classic one in some scenarios.
Admin UI Gets Some Love
The Stoplight Admin UI, which has been living as a separate gem for years, is now part of the main library. We've given
it a complete visual overhaul and fixed some long-standing issues. The biggest improvement is that it now actually
shows all your circuit breakers, including the green ones that were previously invisible. You'll also get much more
accurate real-time statistics thanks to our new data storage architecture.
Getting started with the Admin UI is super simple now. You can spin up a Docker container in seconds:
docker run --net=host -e REDIS_URL=redis://localhost:6378 bolshakov/stoplight-adminOr if you're using Rails, just mount it in your routes:
Rails.application.routes.draw do
mount Stoplight::Admin => '/stoplights'
end
Error Handling Made Simple
The error handling configuration was always cumbersome in previous versions. The new approach is much more
straightforward. Instead of wrestling with complex error handler configurations, you can now just specify which errors
to track and which to ignore:
# Only track specific errors
api_gateway = Stoplight("API Gateway", tracked_errors: [TimeoutError, InternalServerError])
# Ignore certain errors completely
action_light = Stoplight("Action", skipped_errors: [ValidationError, ActiveRecord::RecordNotFound])This makes it much clearer what your circuit breaker is actually monitoring and responding to. By default all StandardErrors are tracked.
Redis Connection Pooling
For those of you running high-traffic applications, we've added proper Redis connection pooling support. This lets you
manage your Redis connections much more efficiently:
require "connection_pool"
pool = ConnectionPool.new(size: 5, timeout: 3) { Redis.new }
data_store = Stoplight::DataStore::Redis.new(pool)
Stoplight.configure do |config|
config.data_store = data_store
endYou can still use a plain Redis client if connection pooling isn't needed for your use case.
Global Configuration
We've added a proper global configuration system that lets you set application-wide defaults. This is especially useful
if you have consistent patterns across all your circuit breakers:
Stoplight.configure do |config|
config.threshold = 5
config.cool_off_time = 30
config.window_size = 60
config.data_store = Stoplight::DataStore::Redis.new(redis)
config.notifiers = [Stoplight::Notifier::Logger.new(Rails.logger)]
config.tracked_errors = [StandardError]
config.skipped_errors = [ActiveRecord::RecordNotFound]
endBetter Fallback Handling
Fallbacks work differently now - instead of configuring them on the light instance, you pass them directly to the
run method. This gives you much more flexibility since different operations can have completely different
fallback strategies:
light = Stoplight("Payment Gateway")
# Different fallbacks for different operations
light.run(-> { [] }) { get_invoices } # Return empty array on failure
light.run(-> { 0 }) { get_credits } # Return zero on failureClock Skew Detection
We've also added automatic clock skew detection for Redis deployments. Clock skew between your application servers and Redis can cause some confusing circuit breaker behavior - different instances might see different error counts or transition states at different times. Stoplight now automatically detects this issue and provides clear guidance on how to resolve it, with a warning when it occurs. The detection runs probabilistically (~1% of the time) to avoid any performance impact, and you can disable the warnings if needed.
Example warning:
Detected clock skew between Redis and the application server. Redis time: 1748277980, Application time: 1748274380. See https://github.com/bolshakov/stoplight/wiki/Clock-Skew-and-Stoplight-Reliability
Usage:
# Default behavior - warnings enabled
data_store = Stoplight::DataStore::Redis.new(redis)
# Disable warnings if needed (e.g., testing environments)
data_store = Stoplight::DataStore::Redis.new(redis, warn_on_clock_skew: false)Under the Hood Improvements
We've completely rewritten the data storage layer using a metadata-driven approach. The old system was getting pretty
creaky, especially in distributed environments where you might have race conditions between different processes
updating circuit breaker state.
The new implementation uses Lua scripting for atomic operations, which eliminates the need for distributed locks while
actually improving performance. We're seeing fewer round trips to Redis and much better coordination when multiple
application instances are working with the same circuit breakers.
This foundation also sets us up for some exciting features we're planning for future releases, particularly around
more sophisticated error detection and recovery strategies.
Documentation and Developer Experience
We've completely rewritten our documentation to be more approachable for newcomers while still providing the depth
that advanced users need. The new README provides a visual walkthrough of concepts and includes numerous real-world examples.
We've also added a comprehensive BDD test suite that makes the library's behavior much clearer and gives us confidence when making changes. You can find an example of Cucumber report here
The Migration Story
Now for the part you're probably wondering about - yes, this release includes breaking changes that will require some
migration work. The global configuration API has changed significantly, some deprecated interfaces have been removed,
and the data storage format is incompatible with previous versions, which means existing circuit breaker states will be
lost during the upgrade.
We've put together [a comprehensive migration guide] that walks through each change step-by-step. While the migration
does require some effort, most of the changes are straightforward find-and-replace operations, and the new APIs are
much cleaner once you've made the switch.
The biggest impact is probably the Redis data incompatibility - when you upgrade, all your circuit breakers will reset
to green state. For most applications, this isn't a big deal since circuit breakers are designed to adapt quickly to
current conditions, but you'll want to plan your deployment accordingly.
What's Next
This release represents a major step forward for Stoplight and lays the groundwork for some exciting features we have
planned. The new data storage architecture opens up possibilities for much more sophisticated error detection strategies
and better observability features.
Full Changelog: v4.1.1...v5.0.1