00:00

Availability in System Design

In system design, availability means how much of the time a system is up and working when users want to use it. If a system is available, users can open it, send requests and get correct answers without errors. In simple words, availability answers one question: "Is the system working right now?"

This tutorial explains how availability is measured, what the famous "nines" mean, how to calculate the availability of a whole system, what causes downtime, and the proven techniques that keep systems running, with simple Java examples.

1. What Is Availability?

Availability is usually written as a percentage of time. A system with 99.9% availability is working 99.9% of the time and is unavailable for the remaining 0.1%.

Simple analogy: Think of a bank ATM. If it works whenever you walk up to it, it is highly available. If it often shows "Out of service", it has poor availability, even if it never loses your money.

A system is highly available when it keeps working even if some of its parts fail. This is usually achieved by removing single points of failure: parts whose failure would bring the whole system down.

2. Why Availability Is Important

Users expect applications to work at any time, from anywhere. When a system is down:

  • Revenue is lost: an online store that is down cannot take orders, especially painful during a sale.
  • Trust is damaged: users who find a service broken may move to a competitor.
  • Productivity drops: employees who rely on internal tools cannot work.
  • Contracts may be broken: many services promise a minimum uptime and must pay penalties if they miss it.
  • Safety can be at risk: in healthcare, transport or banking, downtime can have serious consequences.

This is why availability is one of the most important non-functional requirements in system design.

3. How Availability Is Measured

The Basic Formula

Formula
Availability = Uptime / (Uptime + Downtime)

Example: a 30-day month has 720 hours. If the system is down for 1 hour:

Example calculation
Uptime       = 720 - 1 = 719 hours
Availability = 719 / 720 = 0.99861  ->  about 99.86%

Using MTBF and MTTR

Engineers also describe availability with two averages:

  • MTBF (Mean Time Between Failures): how long the system runs, on average, before it fails.
  • MTTR (Mean Time To Repair/Recover): how long it takes, on average, to get it working again.
MTBF and MTTR
Availability = MTBF / (MTBF + MTTR)

Example: MTBF = 500 hours, MTTR = 1 hour
Availability = 500 / 501 ≈ 0.998  ->  about 99.8%

This formula shows the two ways to improve availability: fail less often (higher MTBF) or recover faster (lower MTTR). In practice, automatic recovery that cuts MTTR from hours to seconds is often the biggest win.

4. The "Nines" of Availability

Availability targets are often described by their number of nines. Each extra nine cuts the allowed downtime by ten times:

AvailabilityDowntime per YearDowntime per MonthDowntime per Day
99% (two nines)About 3.65 daysAbout 7.3 hoursAbout 14.4 minutes
99.9% (three nines)About 8.77 hoursAbout 43.8 minutesAbout 1.4 minutes
99.99% (four nines)About 52.6 minutesAbout 4.4 minutesAbout 8.6 seconds
99.999% (five nines)About 5.3 minutesAbout 26 secondsUnder 1 second

Each extra nine is much harder and more expensive to reach. Most web applications aim for 99.9% or 99.99%; five nines is usually reserved for critical systems such as telecom networks and payment infrastructure.

5. Availability of a Whole System

A real system is made of many parts. How they are connected changes the overall availability.

Components in Series

When a request must pass through every component (load balancer → app server → database), all of them must work. Multiply their availabilities:

Series
A(total) = A1 x A2 x A3

Load balancer 99.99%  x  App server 99.9%  x  Database 99.9%
= 0.9999 x 0.999 x 0.999 ≈ 0.9979  ->  about 99.79%

The whole system is less available than its weakest part. Every extra dependency in the chain lowers the total.

Components in Parallel

When there are several identical copies and only one needs to work, the system fails only if all copies fail at the same time:

Parallel
A(total) = 1 - (1 - A) ^ n

Two app servers, each 99.9%:
= 1 - (0.001 x 0.001) = 0.999999  ->  99.9999%

This is why redundancy is so powerful: two ordinary servers together are far more available than one excellent server. (This assumes the copies fail independently; if both run in the same data centre and it loses power, they fail together.)

6. SLA, SLO and SLI

TermMeaningExample
SLI (Service Level Indicator)What you actually measurePercentage of successful requests in the last 30 days
SLO (Service Level Objective)The internal target for that measurement99.95% of requests succeed each month
SLA (Service Level Agreement)A promise to customers, often with penalties99.9% uptime, or customers get a 10% credit

Teams usually set the SLO stricter than the SLA, so they get a warning before a customer promise is broken. The gap between 100% and the SLO is called the error budget: the amount of failure the team can "spend" on risky changes such as new releases.

7. Common Causes of Downtime

  • Hardware failures: disks, servers, power supplies or network cards stop working.
  • Software bugs: crashes, memory leaks or infinite loops.
  • Bad deployments: a new release or configuration change breaks something; this is one of the most common causes.
  • Overload: sudden traffic spikes exhaust CPU, memory, threads or database connections.
  • Network problems: lost connectivity, DNS errors or expired certificates.
  • Dependency failures: a database, payment provider or other service is down, and the failure spreads.
  • Human error: a wrong command, a deleted table or a misconfigured firewall.
  • Disasters: a power outage, fire or flood takes out a whole data centre.
  • Planned maintenance: upgrades that need the system to be stopped.

8. How to Improve Availability

  • Redundancy: Run at least two copies of every important component (servers, databases, load balancers) so one can fail without stopping the system.
  • Load Balancing with Health Checks: Spread traffic across servers and stop sending requests to any server that fails its health check. See Load Balancing.
  • Failover: Automatically switch to a backup when the main component fails.
  • Data Replication: Keep copies of the database on other servers so data is still reachable if one server goes down.
  • Multiple Zones and Regions: Spread servers across separate data centres (availability zones) and, for critical systems, across geographic regions.
  • Graceful Degradation: When a non-critical feature fails, hide it or show a simple default instead of failing the whole page.
  • Timeouts, Retries and Circuit Breakers: Stop one slow dependency from blocking everything else.
  • Rate Limiting and Auto-Scaling: Protect the system from overload and add capacity automatically when traffic grows.
  • Zero-Downtime Deployments: Use rolling, blue-green or canary releases so updates never require taking the system offline.
  • Monitoring and Alerting: Detect problems quickly, often before users notice, and alert the on-call engineer.
  • Backups and Disaster Recovery: Regularly back up data and practise restoring it.

Active-Passive vs Active-Active Failover

AspectActive-PassiveActive-Active
How it worksOne node serves traffic; a standby takes over if it failsAll nodes serve traffic at the same time
Failover timeSeconds to minutes while the standby takes overAlmost instant; others keep serving
Resource useStandby is mostly idleAll capacity is used
ComplexitySimpler, especially for databasesHarder; data must stay in sync across nodes
Typical usePrimary database with a replicaStateless web and application servers

Disaster Recovery: RTO and RPO

  • RTO (Recovery Time Objective): the maximum acceptable time to restore service after a disaster, for example 1 hour.
  • RPO (Recovery Point Objective): the maximum acceptable amount of data loss, measured in time, for example the last 5 minutes of data.

9. Example: A Highly Available Online Store

Imagine an online shopping website where customers browse products, place orders and pay. If the site goes down during a big sale, customers cannot buy anything and the business loses money. Here is how the system can be designed to stay up:

Highly available architecture
                    Users
                      |
                      v
            [ DNS with health checks ]
                      |
          +-----------+-----------+
          |                       |
   Availability Zone A     Availability Zone B
          |                       |
  [ Load Balancer A ]     [ Load Balancer B ]
          |                       |
  App Server 1, 2         App Server 3, 4
          |                       |
  [ Primary DB ] -- replication --> [ Replica DB ]
          (automatic failover if the primary fails)
  • Multiple servers instead of one: four app servers across two zones; any of them can handle any request.
  • Automatic traffic redirection: load balancers remove unhealthy servers, and DNS can send users away from a failed zone.
  • Replicated database: the replica is promoted to primary if the primary fails, so data stays reachable.
  • Graceful degradation: if the recommendation service is down, the product page shows best sellers instead of an error.
  • Safe releases: new versions roll out to one server at a time and are rolled back automatically if errors rise.

Because of this setup, even if a server, a database or a whole data centre fails, the store keeps working. This makes the system highly available.

10. Availability Techniques in Java

Calculating System Availability

This small utility applies the series and parallel formulas from section 5 to compare a design without and with redundancy:

AvailabilityCalculator.java
public final class AvailabilityCalculator {

    private static final double HOURS_PER_YEAR = 365 * 24; // 8,760 hours

    private AvailabilityCalculator() {
    }

    /** Every component must work, so availabilities are multiplied. */
    public static double inSeries(double... availabilities) {
        double result = 1.0;
        for (double availability : availabilities) {
            result *= availability;
        }
        return result;
    }

    /** At least one of the identical copies must work. */
    public static double inParallel(double availability, int copies) {
        return 1.0 - Math.pow(1.0 - availability, copies);
    }

    public static double downtimeHoursPerYear(double availability) {
        return (1.0 - availability) * HOURS_PER_YEAR;
    }

    public static void main(String[] args) {
        // Load balancer 99.99%, one app server 99.9%, one database 99.9%
        double single = inSeries(0.9999, 0.999, 0.999);

        // Same design, but with two app servers and two databases
        double redundant = inSeries(0.9999, inParallel(0.999, 2), inParallel(0.999, 2));

        System.out.printf("Single copies  : %.4f%% (%.1f hours down per year)%n",
                single * 100, downtimeHoursPerYear(single));
        System.out.printf("With redundancy: %.4f%% (%.1f hours down per year)%n",
                redundant * 100, downtimeHoursPerYear(redundant));
    }
}
Output
Single copies  : 99.7901% (18.4 hours down per year)
With redundancy: 99.9898% (0.9 hours down per year)

Adding a second app server and a database replica cuts the expected downtime from about 18 hours to less than one hour per year. The load balancer is now the weakest link, so it should be made redundant too.

Health Checks with Spring Boot Actuator

Load balancers and Kubernetes decide whether to send traffic to an instance by calling its health endpoint. Spring Boot Actuator provides one, and you can add your own checks:

application.yml
management:
  endpoints:
    web:
      exposure:
        include: health
  endpoint:
    health:
      probes:
        enabled: true        # adds /actuator/health/liveness and /actuator/health/readiness
      show-details: always   # show component details (restrict this in production)
PaymentGatewayHealthIndicator.java
package com.javabytechie.store.health;

import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.stereotype.Component;

@Component
public class PaymentGatewayHealthIndicator implements HealthIndicator {

    private final PaymentGatewayClient paymentGatewayClient;

    public PaymentGatewayHealthIndicator(PaymentGatewayClient paymentGatewayClient) {
        this.paymentGatewayClient = paymentGatewayClient;
    }

    @Override
    public Health health() {
        long start = System.currentTimeMillis();
        try {
            paymentGatewayClient.ping();
            return Health.up()
                    .withDetail("responseTimeMs", System.currentTimeMillis() - start)
                    .build();
        } catch (Exception ex) {
            return Health.down(ex).build();
        }
    }
}

Calling GET /actuator/health now includes a paymentGateway entry. Keep external dependencies out of the liveness check; otherwise a payment provider outage could make Kubernetes restart healthy servers.

Failover: Try the Primary, Then the Backup

This simple client tries each endpoint in order and returns the first successful answer:

FailoverClient.java
import java.util.List;
import java.util.function.Supplier;

public class FailoverClient<T> {

    private final List<Supplier<T>> endpoints; // in priority order: primary first

    public FailoverClient(List<Supplier<T>> endpoints) {
        if (endpoints.isEmpty()) {
            throw new IllegalArgumentException("At least one endpoint is required");
        }
        this.endpoints = List.copyOf(endpoints);
    }

    public T call() {
        RuntimeException lastError = null;
        for (Supplier<T> endpoint : endpoints) {
            try {
                return endpoint.get();
            } catch (RuntimeException ex) {
                lastError = ex; // remember the error and try the next endpoint
            }
        }
        throw new IllegalStateException("All endpoints failed", lastError);
    }

    public static void main(String[] args) {
        FailoverClient<String> client = new FailoverClient<>(List.of(
                () -> { throw new RuntimeException("Primary is down"); },
                () -> "Response from backup server"));

        System.out.println(client.call()); // prints: Response from backup server
    }
}

Graceful Degradation with a Fallback

With Resilience4j, a failing recommendation service does not break the product page; customers simply see best sellers instead:

RecommendationService.java
package com.javabytechie.store.recommendation;

import io.github.resilience4j.circuitbreaker.annotation.CircuitBreaker;
import java.util.List;
import org.springframework.stereotype.Service;

@Service
public class RecommendationService {

    private final RecommendationClient recommendationClient;
    private final BestSellerCache bestSellerCache;

    public RecommendationService(RecommendationClient recommendationClient,
                                 BestSellerCache bestSellerCache) {
        this.recommendationClient = recommendationClient;
        this.bestSellerCache = bestSellerCache;
    }

    @CircuitBreaker(name = "recommendations", fallbackMethod = "bestSellers")
    public List<Product> recommendFor(String customerId) {
        return recommendationClient.fetchRecommendations(customerId);
    }

    // Used when the call fails or the circuit is open: a simpler, but still useful, answer.
    private List<Product> bestSellers(String customerId, Throwable ex) {
        return bestSellerCache.topTen();
    }
}

Learn more in the Circuit Breaker Pattern tutorial.

11. Availability vs Reliability vs Durability

These three terms are related but not the same:

TermQuestion It AnswersMeasured By
AvailabilityIs the system up and responding right now?Percentage of uptime, e.g. 99.9%
ReliabilityDoes it keep working correctly without failing?MTBF, failure rate, error rate
DurabilityIs stored data safe from being lost?Chance of losing data, e.g. 99.999999999%

Availability depends on both how rarely a system fails (reliability) and how quickly it recovers (MTTR). A system that fails now and then but recovers in seconds can still be highly available. A system can also be up but unreliable, for example when it responds quickly with wrong results. See Reliability in System Design.

12. Trade-offs of High Availability

  • Cost: extra servers, zones and regions cost money, and each additional nine costs more than the last.
  • Complexity: replication, failover and multi-region setups are harder to build, test and operate.
  • Consistency: during a network failure, a system that keeps answering may return slightly outdated data. This is the trade-off described by the CAP theorem; see Consistency in System Design.
  • Latency: keeping data in sync across distant regions can make writes slower.

Choose the availability target that the business actually needs, not the highest possible number.

13. Best Practices

  1. Set a clear availability target (SLO) based on business needs.
  2. Find and remove every single point of failure.
  3. Run at least two instances of every service, in different availability zones.
  4. Keep application servers stateless so any instance can handle any request.
  5. Use health checks and automatic failover instead of manual steps.
  6. Protect calls to dependencies with timeouts, retries and circuit breakers.
  7. Release changes gradually and make rollbacks fast and automatic.
  8. Monitor from the user's point of view and alert on symptoms, not just causes.
  9. Back up data regularly and actually test restoring it.
  10. Practise failures (chaos testing) so recovery works when it is really needed.

14. Summary

Availability in system design describes how consistently a system is accessible to its users. It is measured as a percentage of uptime and often expressed in "nines". Components connected in series lower overall availability, while redundant components in parallel raise it dramatically. Highly available systems combine redundancy, load balancing with health checks, automatic failover, replication across zones, graceful degradation, safe deployments and strong monitoring. Designing for availability is essential for modern applications, where users expect services to work anytime and anywhere.