Marcio Cunha

Unstable microservices? How to use circuit breaker and fallbacks

Learn how to protect your distributed architecture against cascading failures using the circuit breaker pattern and fallback responses under heavy load.

Marcio Cunha•3 min
Also available in:PortuguêsEspañol
Summary
  • The circuit breaker pattern stops calls to unstable services to protect the ecosystem from cascading failures.
  • Fallback responses ensure the system remains operational even when critical dependencies go down.
  • Monitoring error rates and latency allows tuning the breaker sensitivity based on real traffic behavior.
  • Consistent distributed transaction strategies prevent inconsistent states during network failures, similar to transactional consistency with Saga .
  • Testing simulated failures in a controlled environment ensures recovery mechanisms respond correctly under pressure.

Why do microservices crash together under heavy load?

When we split a large system into smaller blocks called microservices, we gain the freedom to scale isolated parts. However, we create a complex network of dependencies where a single failure can spread rapidly. If the payment service freezes due to database slowness, requests start piling up in waiting queues. In practice, this means threads and network connections exhaust in a chain, bringing down the entire application in minutes due to a localized bottleneck.

In modern architectures, the greatest danger is not just a service failing, but how client systems react to that unavailability. Without protective mechanisms, each retry consumes precious CPU and memory resources while waiting for a response that never arrives. Software engineering applies defensive patterns precisely to solve this resource exhaustion problem.

What is a circuit breaker and how does it work?

Inspired by electrical circuit breakers in our homes that shut off power during overloads, a software circuit breaker is a component that monitors calls between services. It operates mainly in three states: closed, open, and half-open. In the closed state, requests flow normally to the external service. When errors exceed a tolerable threshold, the breaker opens, immediately blocking new calls and preventing wasted time and resources.

While the circuit remains open, any call to the faulty service fails instantly, returning a pre-programmed alternative response. After a predetermined period, the breaker shifts to a half-open state, allowing a single request to test the waters. If successful, the circuit closes again; if it fails, it returns to the open state. This dynamic protects both the client and the overloaded server, giving the system time to recover.

Implementing smart fallbacks to keep the system alive

Blocking a request with a circuit breaker solves resource exhaustion, but leaves the end user without an answer if there is no alternative plan. This is where fallbacks come in, acting as an operational Plan B when the primary tool fails. Instead of displaying a generic error screen or crashing the entire page, the system delivers cached data, partial information, or a friendly message reporting temporary instability.

To ensure this flow works without corrupting complex transactional data, many teams combine breakers with resilience approaches, aligning with robust strategies like transactional consistency management in microservices. The secret to a good fallback is balancing utility and simplicity, ensuring the alternative route is fast, lightweight, and independent of the failed component.

Practical example of a circuit breaker in code

To understand how this works in practice, we can look at a basic implementation using modern resilience libraries in backend languages. The code below demonstrates how to configure a breaker that monitors call failures and triggers an automatic fallback response if the external service takes too long or returns consecutive errors.

const { CircuitBreaker } = require('opossum');

async function callPaymentService(data) {
  // Simulates an external API call that might fail
  if (Math.random() > 0.5) throw new Error('Service unavailable');
  return 'Payment approved';
}

const options = {
  timeout: 3000,
  errorThresholdPercentage: 50,
  resetTimeout: 10000
};

const breaker = new CircuitBreaker(callPaymentService, options);

breaker.fallback(() => 'System unstable. Please try again later.');

breaker.fire({ id: 123 })
  .then(console.log)
  .catch(console.error);

In the example above, we configured a three-second timeout and set fifty percent error rates to open the breaker for ten seconds. During this interval, the fallback function takes over immediately. This prevents external slowness spikes from paralyzing the entire main microservice, preserving the overall user experience. To complement this implementation in production environments, see our related technical analyses on Electromagnetic noise on the board? How to eliminate EMI routing and Slow server? How to find disk and IOPS bottlenecks in Linux.

Final considerations on distributed resilience

Developing resilient applications requires a mindset shift where we assume network failures and server crashes are normal events, not unlikely exceptions. The combined use of circuit breakers and fallbacks transforms fragile systems into structures capable of absorbing shocks without collapsing. When planning your architecture, remember that distributed system stability depends as much on core service robustness as on the intelligence with which it handles others' unavailability.