hero-image

Circuit Breaking in AdTech: Enhancing System Reliability and Performance

2024-06-18

Introduction

In the ever-evolving world of digital advertising, ensuring system reliability and performance is paramount. circuit breaking is a crucial technique that has gained prominence in recent years, particularly within AdTech ecosystems. This blog post delves into the concept of circuit breaking, its necessity, the benefits it offers, and how it solves common problems in digital advertising infrastructure.

What is circuit breaking?

Circuit breaking is a design pattern used to detect failures and encapsulate the logic of preventing a failure from constantly recurring, which helps maintain the stability of the system. The pattern is typically implemented to handle and mitigate the impact of transient faults in distributed systems.

Why is circuit breaking Nnecessary?

In the AdTech industry, where numerous microservices and third-party integrations are involved, the risk of failures is significant. Services may become unresponsive due to various reasons, such as network issues, increased load, or internal errors. Without a circuit breaker, repeated failed requests can lead to cascading failures, causing the entire system to slow down or crash. Circuit breaking helps with the following:

  • Preventing system overload: Limiting the number of failed requests allowed prevents the system from being overwhelmed.

  • Enhancing resilience: Circuit breaking allows systems to quickly recover from temporary failures by isolating and addressing issues without affecting the entire system.

  • Improving user experience: Reducing downtime and maintaining the system's responsiveness ensures a smooth and consistent user experience. 

Benefits of circuit breaking in AdTech

  • Increased reliability
    Circuit breakers enhance the reliability of the advertising platform by preventing cascading failures. This ensures that a single point of failure in one service does not bring down the entire system.

  • Improved performance
    By isolating failures and allowing the system to handle errors gracefully, circuit breakers maintain optimal performance levels. This is particularly important in the high-traffic environments that are typical of ad-serving platforms.

  • Better resource management
    Circuit breakers help in managing resources efficiently by avoiding unnecessary retries and reducing the load on failing services, thereby conserving computational power and bandwidth.

  • Enhanced fault tolerance
    Implementing circuit breakers provides the system with better fault tolerance, allowing it to continue operating smoothly even when some components fail.

Problems solved by circuit breaking

  • Transient faults management
    Circuit breakers effectively manage transient faults by temporarily halting requests to failing services, thus preventing further errors and allowing time for recovery.

  • Load shedding
    During high-load periods, circuit breakers prevent the system from being overloaded by shedding load and rejecting requests that are likely to fail.

  • Graceful degradation
    In case of failures, circuit breakers enable the system to degrade gracefully rather than crashing entirely, ensuring that critical services remain available.

Implementation of a circuit breaker

Let’s explore how Java implements circuit breaking in AdTech systems. Below is a simplified implementation of a circuit breaker with different states: Open, Closed, Half-Open, and No-Op.

Circuit Breaker States

First, we define the different states our circuit breaker can be in. For example, the OpenState prevents all requests:

 public class OpenState implements State {

    private static final String OPEN = "open";

 

    @Override

    public boolean tryAcquire() {

        return false; // Prevents all requests

    }

 

    @Override

    public String getStateName() {

        return OPEN;

    }

 

    @Override

    public int getStateNumber() {

        return 1;

    }

}

In the ClosedState, the circuit breaker allows all requests but tracks failures:

 public class ClosedState implements State {

    private static final String CLOSED = "closed";

    private final CircuitBreakerMetrics metrics;

 

    public ClosedState() {

        metrics = new CircuitBreakerMetrics();

    }

 

    @Override

    public boolean tryAcquire() {

        metrics.incCallCounter();

        return true; // Allows all requests

    }

 

    @Override

    public void onFailure() {

        metrics.incFailureCounter();

    }

 

    @Override

    public double getFailureRate(int lowerMeasurementCountBoundary) {

        return metrics.getMeanFailureRate(lowerMeasurementCountBoundary);

    }

 

    @Override

    public String getStateName() {

        return CLOSED;

    }

 

    @Override

    public int getStateNumber() {

        return 2;

    }

}

 

 In the HalfOpenState, the circuit breaker allows a limited number of requests to test system health:

public class HalfOpenState implements State {

    private static final String HALF_OPEN = "half_open";

    private final CountDownLatch probeAttemptLatch;

    private final CircuitBreakerMetrics metrics;

    private final int attemptsInHalfOpenState;

 

    public HalfOpenState() {

        attemptsInHalfOpenState = TechnicalConfig.getInstance().getDspCircuitBreakerPermittedCallsInHalfOpenState();

        probeAttemptLatch = new CountDownLatch(attemptsInHalfOpenState);

        metrics = new CircuitBreakerMetrics();

    }

 

    @Override

    public boolean tryAcquire() {

        long attemptsSoFar = metrics.getCallAttemptsCount();

        if (attemptsSoFar > attemptsInHalfOpenState) {

            return false; // Prevents further requests after limit

        }

        metrics.incCallCounter();

        return true; // Allows limited requests

    }

 

    @Override

    public void onSuccess() {

        if (probeAttemptLatch.getCount() > 0) {

            probeAttemptLatch.countDown();

        }

    }

 

    @Override

    public void onFailure() {

        if (probeAttemptLatch.getCount() > 0) {

            metrics.incFailureCounter();

            probeAttemptLatch.countDown();

        }

    }

 

    @Override

    public double getFailureRate() {

        return metrics.getMeanFailureRate();

    }

 

    @Override

    public String getStateName() {

        return HALF_OPEN;

    }

 

    @Override

    public int getStateNumber() {

        return 3;

    }

 

    @SneakyThrows

    public boolean awaitProbeAttempts() {

        return probeAttemptLatch.await(1, TimeUnit.MINUTES);

    }

Circuit Breaker Management

The CircuitBreaker class manages state transitions and circuit-breaking logic:

public class CircuitBreaker implements State {

    private final String dspId;

    private volatile State state = new ClosedState();

 

    public CircuitBreaker(String dspId) {

        this.dspId = dspId;

    }

 

    @Override

    public boolean tryAcquire() {

        if (state.tryAcquire()) {

            return true;

        } else {

            return false;

        }

    }

 

    @Override

    public void onSuccess() {

        state.onSuccess();

    }

 

    @Override

    public void onFailure() {

        state.onFailure();

    }

 

    @Override

    public double getFailureRate(int lowerMeasurementCountBoundary) {

        return state.getFailureRate(lowerMeasurementCountBoundary);

    }

 

    public void transitionTo(State newState) {

        this.state = newState;

    }

}

  

The CircuitBreakerManager class handles the lifecycle and state transitions of circuit breakers:

 public class CircuitBreakerManager {

    private Map<String, CircuitBreaker> circuitBreakerDspMap = new ConcurrentHashMap<>();

    private final ScheduledExecutorService stateManagerExecutor = Executors.newScheduledThreadPool(2);

 

    public CircuitBreaker getCircuitBreaker(String dspId) {

        return circuitBreakerDspMap.computeIfAbsent(dspId, id -> {

            CircuitBreaker circuitBreaker = new CircuitBreaker(id);

            transitionToNoOpState(circuitBreaker);

            return circuitBreaker;

        });

    }

 

    private void transitionToNoOpState(CircuitBreaker cb) {

        cb.transitionTo(new NoOpState());

    }

 

    private void transitionToOpenState(CircuitBreaker cb) {

        cb.transitionTo(new OpenState());

        stateManagerExecutor.schedule(() -> transitionToHalfOpenState(cb), 60, TimeUnit.SECONDS);

    }

 

    private void transitionToHalfOpenState(CircuitBreaker cb) {

        HalfOpenState state = new HalfOpenState();

        cb.transitionTo(state);

        if (!state.awaitProbeAttempts()) {

            transitionToClosedState(cb);

            return;

        }

        double failureRate = cb.getFailureRate(10);

        if (failureRate > 0.5) {

            transitionToOpenState(cb);

        } else {

            transitionToClosedState(cb);

        }

    }

 

    private void transitionToClosedState(CircuitBreaker cb) {

        cb.transitionTo(new ClosedState());

    }

}

Conclusion

By integrating circuit breaking into an AdTech platform, companies can enhance system reliability, performance, and user experience. Circuit breakers help manage transient faults, shed load during high-traffic periods, and enable graceful degradation of services. Implementing this design pattern ensures that ad-serving systems remain robust and responsive, even in the face of failures.

Circuit breaking is a powerful tool in the AdTech tool kit, addressing critical challenges and providing a smoother, more reliable experience for both advertisers and users.

Share:

Recent Posts