Availability Reliability Durability and Trade-Offs
Purpose: separate three words that are often mixed together
Availability, reliability, and durability are related, but they protect different promises. Availability asks whether the service can answer a request at the moment a user needs it. Reliability asks whether the service continues to behave correctly over time, including under expected faults. Durability asks whether accepted data remains stored and recoverable after failures. In system design interviews and design reviews, confusing these terms leads to weak requirements: a cache can make reads highly available while being completely non-durable, and a backup archive can be durable while being unavailable for live traffic.
The outcome of this lesson is a practical way to reason about those promises. You will calculate downtime budgets, inspect how redundancy changes data survival probability, identify retry behavior that improves or harms reliability, and choose trade-offs deliberately instead of saying that every system should simply have more replicas.
Mechanism: what each property measures internally
Availability is usually measured as the fraction of time or requests for which a system is able to provide an acceptable response. The phrase “four nines” means 99.99% availability, but the useful design object is the error budget implied by that target. A 99.99% annual target leaves about 52.6 minutes of unavailability per year if measured by time. If measured by requests, it allows one failed request in ten thousand. These two measurement styles can disagree: a short outage during peak traffic may be small by minutes and large by failed requests.
Internally, availability improves when there is no single component whose failure prevents the whole service from responding. Load balancers, multiple application instances, health checks, failover databases, queue buffering, and graceful degradation all change the shape of the dependency graph. The critical path matters most. If a checkout request synchronously needs the API, inventory service, payment gateway, and primary database, each dependency’s unavailability compounds with the others. Optional work, such as sending a receipt email, should be moved out of the synchronous path so it cannot make checkout unavailable.
Reliability is the probability that the system performs the correct function for a duration or number of operations. Availability tolerates an answer that is merely present; reliability requires the answer to be right enough for the contract. A service that returns HTTP 200 with stale or corrupt account balances is available but not reliable. Mechanisms that improve reliability include idempotent operations, bounded retries, consensus for leader election, checksums, transactional writes, backpressure, circuit breakers, and testable recovery procedures.
Durability begins when the system acknowledges that data has been accepted. A durable write should survive process crashes, disk failure, node replacement, and other expected faults in the threat model. Internally, durable systems use write-ahead logs, fsync or equivalent flush policies, replication, erasure coding, snapshots, backups, and restore validation. Durability is not the same as consistency. A database may replicate writes asynchronously, making a recent acknowledged write vulnerable if the primary dies before followers receive it. Another database may commit only after a quorum stores the write, increasing durability but adding latency.
Configuration and API anatomy
Reliability requirements become useful when written as measurable service level objectives. A compact requirement names the operation, target, window, and exclusions. For example: “The read profile endpoint will return a non-5xx response within 300 ms for 99.95% of valid requests over a rolling 30-day window, excluding client network failure.” For durability, the requirement should state the recovery point objective, recovery time objective, and storage fault assumptions. For example: “Confirmed invoices must be restorable with at most five minutes of data loss after the loss of one availability zone, and recovery must complete within two hours.” These statements drive architecture: synchronous replication may be needed for a low recovery point objective, while warm standby capacity may be needed for a low recovery time objective.
Common configuration knobs map directly to the three properties. Replica count and placement affect availability and durability. Timeout values affect reliability because a too-short timeout creates false failures, while a too-long timeout ties up callers and can amplify incidents. Retry count and backoff affect availability during transient faults but can overload a weak dependency. Quorum settings decide how many replicas must participate before a read or write is accepted. Backup interval, retention, encryption, and restore testing define whether durable storage is actually recoverable.
Worked example 1: downtime budget from an availability target
The first example turns percentages into an engineering budget. This makes the difference between 99.9%, 99.99%, and 99.999% concrete.
from fractions import Fraction
def annual_downtime_minutes(availability_percent):
unavailable = Fraction(10000 - int(round(availability_percent * 100)), 10000)
return float(unavailable * 365 * 24 * 60)
for label, availability in [("one instance", 99.9), ("regional service", 99.99), ("critical path", 99.999)]:
print(f"{label}: {annual_downtime_minutes(availability):.1f} minutes/year")
Expected output:
one instance: 525.6 minutes/year
regional service: 52.6 minutes/year
critical path: 5.3 minutes/year
A single weekly maintenance window can consume a 99.99% annual budget. A service with a five-minute annual budget cannot rely on manual failover that takes ten minutes to diagnose. The lesson for system design is that the availability target should be chosen before the architecture. High availability requires automatic detection, fast removal of unhealthy nodes, and dependencies that can fail independently.
Worked example 2: durability from independent copies
Durability often comes from storing data in more than one failure domain. This simplified calculation assumes each copy has an independent 1% chance of being lost during the event being modeled.
def probability_at_least_one_copy_survives(copy_loss_probability, copies):
all_copies_lost = copy_loss_probability ** copies
return 1 - all_copies_lost
for copies in [1, 2, 3]:
survival = probability_at_least_one_copy_survives(0.01, copies)
print(f"{copies} copy/copies: {survival:.6f}")
Expected output:
1 copy/copies: 0.990000
2 copy/copies: 0.999900
3 copy/copies: 0.999999
The numbers improve quickly only because the example assumes independent failures. If all three copies are on the same rack, deleted by the same buggy job, or encrypted by the same compromised credential, the real probability is much worse. Durability design therefore combines replication with isolation: separate zones or regions, deletion protection, immutable backup windows, checksums, and tested restores.
Worked example 3: retries improve availability only when bounded
Retries can hide transient faults from users, but they are not free. This example shows a fixed sequence of dependency outcomes and compares a three-attempt policy with a two-attempt policy.
def call_with_retry(outcomes, max_attempts):
attempts = 0
for outcome in outcomes[:max_attempts]:
attempts += 1
if outcome == "ok":
return f"success after {attempts} attempt(s)"
return f"failed after {attempts} attempt(s)"
print(call_with_retry(["timeout", "timeout", "ok"], 3))
print(call_with_retry(["timeout", "timeout", "ok"], 2))
Expected output:
success after 3 attempt(s)
failed after 2 attempt(s)
The three-attempt policy improves availability for this request. In a real service, however, every retry consumes caller threads, network capacity, and dependency capacity. A retry storm can turn a small downstream slowdown into a full outage. Production retry policies usually need exponential backoff, jitter, per-request deadlines, idempotency keys, and circuit breakers that stop calls when a dependency is already failing.
Design choices and trade-offs
There is no universal best point. Active-active multi-region service can improve availability when one region fails, but it raises complexity around data conflicts, routing, cost, and operational testing. Active-passive service is simpler and cheaper, but failover may be slower and less frequently exercised. Synchronous replication lowers data loss risk, but it adds latency and may reduce availability if a quorum cannot be reached. Asynchronous replication keeps the primary path fast and available, but it allows a nonzero recovery point objective.
Graceful degradation is another trade-off. If product recommendations fail, an ecommerce site can show popular items and keep checkout available. If payment authorization fails, the same site probably should not pretend success. Reliability is tied to correctness, so degraded modes must be selected per operation. Read-only mode after database failover may preserve availability for browsing while blocking writes that cannot be made durable.
Cost is part of the design, not an afterthought. More replicas, more regions, longer backup retention, and lower failover time all cost money and operational attention. A course forum may accept lower availability during maintenance and rely on nightly backups. A payment ledger needs stronger durability, audit trails, and tested recovery even if that makes writes slower.
Failure modes and troubleshooting
Symptom: users see intermittent 503 responses during one instance’s deploy. The likely cause is that the load balancer routes to an instance before it is actually ready, or keeps routing after the process has begun shutdown. Diagnose by comparing deployment timestamps, health check logs, instance lifecycle events, and load balancer target status. Correct it with readiness checks that verify dependencies needed for serving, graceful shutdown that drains connections, and a deployment policy that keeps enough healthy capacity during rollout.
Symptom: data acknowledged just before failover is missing after recovery. The likely cause is asynchronous replication lag or a failover to a replica that did not receive the latest write. Diagnose by checking commit timestamps, replication lag metrics, database promotion logs, and application acknowledgement timing. Correct it by changing the write concern or quorum for critical data, lowering the stated recovery point objective only when the storage path can meet it, and documenting which operations can tolerate loss.
Symptom: error rate increases after a dependency slows down, even though the dependency is not completely down. The likely cause is a retry storm or resource exhaustion in callers waiting on long timeouts. Diagnose by graphing latency percentiles, request concurrency, retry counts, timeout rates, and dependency saturation together. Correct it with shorter end-to-end deadlines, bounded retries with jitter, concurrency limits, and fallback paths for noncritical work.
Security, performance, and reliability implications
Security controls can strengthen or weaken these properties depending on how they are designed. Immutable backups and separate credentials improve durability against accidental deletion and malicious compromise. On the other hand, a central authentication provider on every request can become an availability dependency unless tokens are cached safely and failure behavior is explicit. Encryption protects durable data, but key loss can make perfectly replicated data unrecoverable, so key backup and rotation procedures belong in durability planning.
Performance decisions also change reliability. Aggressive caching improves read availability and latency, but stale cache entries can make answers unreliable for domains that require freshness. Batching writes improves throughput, but if the system acknowledges before the batch reaches durable storage, it has weakened durability. The precise question is always: what has the system promised at the moment it responds?
Hands-on lab: evaluate a small service design
Prerequisites: Python 3, a terminal, and permission to create a temporary working directory. No external services are required.
- Create a temporary directory and save the three Python examples from this lesson as separate files:
availability.py,durability.py, andretries.py. - Run each file with Python. Verify that the output exactly matches the expected output shown under each example.
- Change the availability target in the first file from 99.99 to 99.95. Re-run it and note that the annual downtime budget becomes much larger.
- Change the copy loss probability in the second file from 0.01 to 0.10. Re-run it and compare how much three copies still help, while remembering that correlated loss would break the model.
- Change the retry example to allow four attempts and add another timeout before the ok result. Verify that success still occurs, but only after more load has been placed on the dependency.
Verification: You have completed the lab when you can explain which modification affects availability, which affects durability, and why the retry modification has both a benefit and a cost.
Cleanup: Delete the temporary directory. There is no external state to roll back.
Assessment exercises
- A profile page depends on a user database and a recommendation service. The database is required, but recommendations are optional. Describe a design that improves availability without lying to users about correctness.
- A team advertises zero data loss after failover, but the primary database acknowledges writes before replicas confirm them. Explain the mismatch and propose a correction.
- A service has 99.99% uptime by minutes, but most outages happen during peak traffic. Which additional measurement would you ask for, and why?
- A retry policy uses five immediate retries for every failed payment request. Identify the reliability risk and redesign the policy for a payment operation that must not double charge customers.
- Choose active-active or active-passive deployment for a learning management discussion board. State the availability benefit, data trade-off, and operational cost of your choice.
Summary
Availability is the ability to answer now, reliability is the ability to answer correctly over time, and durability is the ability to keep accepted data recoverable. The mechanisms overlap, but the promises differ. Strong system designs state measurable targets, identify the critical path, choose replication and failover behavior intentionally, and test recovery rather than assuming that extra copies or retries automatically solve every failure.
