Availability in Software
Availability is the ability of a software system to remain accessible and usable when users need it.
A system may be fast, scalable, and feature-rich, but if users regularly receive errors or cannot connect to it, availability is poor.
In simple terms:
Availability answers: "Can the system serve requests right now?"
High availability does not mean that nothing ever fails. Servers crash. Databases restart. Networks break. Deployments go wrong.
A highly available system is designed so that individual failures do not make the entire service unavailable.
Table of Contents
- What Is Availability?
- Single Points of Failure
- Redundancy
- Load Balancing
- Health Checks and Automatic Failover
- Database Availability
- Multi-Zone Deployment
- Multi-Region Availability
- Graceful Degradation
- Handling Dependency Failures
- Availability During Deployments
- Capacity and Traffic Spikes
- Monitoring Availability
- Example: Making an Online Store Highly Available
- Common Availability Mistakes
- Production Checklist
- Conclusion
What Is Availability?
Consider a simple API:
User │ ▼ Application │ ▼ Database
If both the application and database are working, requests succeed.
If the application crashes:
User │ ▼ Application ✗
the service becomes unavailable.
If the database crashes:
Application
│
▼
Database ✗
the application may still be running, but users cannot complete requests.
Availability therefore depends on the entire request path, not only one component.
User │ ▼ DNS │ ▼ Load Balancer │ ▼ Application │ ▼ Cache │ ▼ Database │ ▼ External Services
If a critical component in that chain is unavailable and there is no fallback, the user-facing service may also become unavailable.
Availability Percentage
Availability is often expressed as a percentage.
A simplified formula is:
Availability = successful operating time ------------------------- total measured time
Common targets are:
| Availability | Approximate Downtime per Year |
|---|---|
| 99% | 3.65 days |
| 99.9% | 8.76 hours |
| 99.99% | 52.6 minutes |
| 99.999% | 5.26 minutes |
The difference between 99.9% and 99.99% may look small, but operationally it is significant.
Achieving another "nine" usually requires more redundancy, better failover, safer deployments, stronger monitoring, and more operational discipline.
Availability vs Reliability
Availability and reliability are related but not identical.
Availability asks:
Can the system serve the request?
Reliability asks:
Does the system continue behaving correctly over time?
Consider a payment API that is reachable 100% of the time but occasionally charges customers twice.
Availability: high Reliability: poor
Now consider another service that experiences a short planned outage once a year but otherwise behaves correctly.
Its availability is slightly lower, but its overall reliability may be better.
Availability is therefore one important dimension of a reliable system.
Single Points of Failure
A single point of failure is a component whose failure can make the entire system unavailable.
Consider:
Users │ ▼ One Application Server │ ▼ One Database
This architecture has at least two obvious single points of failure.
If the application server fails:
Application ✗
↓
Entire service unavailable
If the database fails:
Database ✗
↓
Entire service unavailable
Single points of failure can be less obvious too:
One Redis instance One message broker One network gateway One DNS provider One authentication service One configuration service One storage volume
High-availability design begins by identifying these dependencies and deciding which ones need redundancy.
Redundancy
Redundancy means having more than one instance of a critical component.
Instead of one application server:
Users │ ▼ App Server
run several:
┌──► App 1
Users ──► Load Balancer
├──► App 2
└──► App 3
If App 2 fails:
App 1 ✓ App 2 ✗ App 3 ✓
traffic can continue through App 1 and App 3.
This is one of the simplest ways to improve availability.
Redundancy can exist at many layers:
Multiple app instances Multiple cache nodes Multiple database replicas Multiple queue brokers Multiple availability zones Multiple regions
But redundancy alone is not enough.
The system must also detect failures and route traffic around them.
Load Balancing
A load balancer distributes requests across multiple application instances.
┌──────────┐
┌───►│ App 1 │
│ └──────────┘
│
Users ──► Load Balancer ──► App 2
│
│ ┌──────────┐
└───►│ App 3 │
└──────────┘
Suppose App 2 crashes.
A healthy architecture should stop sending requests to it:
Before: App 1 ✓ App 2 ✓ App 3 ✓ After failure: App 1 ✓ App 2 ✗ App 3 ✓
The user may never notice the failure.
Load balancing therefore improves both scalability and availability.
For availability, the important behavior is not merely distributing traffic. It is removing unhealthy instances quickly and safely.
Health Checks and Automatic Failover
Systems need a way to determine whether an instance can receive traffic.
A load balancer might periodically call:
GET /health
A healthy server returns:
{
"status": "ok"
}
An unhealthy server may fail the check.
The load balancer then removes it:
App 1 → healthy App 2 → unhealthy App 3 → healthy Traffic: App 1 ✓ App 2 skipped App 3 ✓
Health checks should answer the right question.
A process can be running but unable to serve requests.
Process alive ✓ Database pool exhausted ✗ Ready for traffic? No
This is why systems often distinguish between:
Liveness "Should this process be restarted?" Readiness "Should this instance receive traffic?"
For a deeper explanation, see Health Checks, Readiness, and Liveness Probes.
Database Availability
Application servers are relatively easy to duplicate because they can often remain stateless.
Databases are harder because they contain persistent state.
A single database creates a major availability risk:
Applications
│
▼
Database
✗
If it fails, every application instance may become useless.
Replication
Replication maintains additional copies of data.
Primary Database
│
├──► Replica 1
└──► Replica 2
The primary handles writes while replicas receive replicated changes.
If one replica fails, another copy still exists.
Some architectures also use replicas for read traffic:
Writes ─────────► Primary
│
replication
┌─────┴─────┐
▼ ▼
Replica 1 Replica 2
▲ ▲
│ │
Reads Reads
Replication improves availability, but it introduces consistency and failover questions.
For example, a replica may be slightly behind the primary.
Primary: order #101 exists Replica: order #101 not replicated yet
The architecture must decide whether this temporary inconsistency is acceptable for a particular request.
Failover
When the primary database fails, a replica may be promoted.
Before: Primary ✓ Replica A ✓ Replica B ✓
Then:
Primary ✗ Replica A ✓ Replica B ✓
The system promotes Replica A:
Replica A ↓ New Primary
Applications then reconnect to the new primary.
This process is called failover.
Availability depends heavily on how quickly and safely failover happens.
Failure detection: 5 seconds Leader promotion: 10 seconds Client reconnect: 5 seconds Approximate impact: 20 seconds
A poorly configured failover system can take minutes or require manual intervention.
Multi-Zone Deployment
Running multiple servers does not help much if every server is in the same failure domain.
Consider three application servers in one data center:
Data Center A ├── App 1 ├── App 2 └── App 3
If the entire data center loses power, all three disappear together.
A better design distributes instances across independent zones:
Zone A ├── App 1 └── DB Replica Zone B ├── App 2 └── DB Replica Zone C ├── App 3 └── DB Replica
If Zone B fails:
Zone A ✓ Zone B ✗ Zone C ✓
traffic continues through the remaining zones.
This is an important principle:
Redundant components should not share the same failure domain.
Examples of shared failure domains include:
same physical server same rack same power supply same network switch same availability zone same cloud region
Multi-Region Availability
For systems with very high availability requirements, one cloud region may not be enough.
A multi-region architecture may look like:
Global Traffic Router
/ \
/ \
▼ ▼
US Region EU Region
├── Apps ├── Apps
├── Cache ├── Cache
└── Database └── Database
If the US region becomes unavailable:
US Region ✗ EU Region ✓
traffic may be redirected to Europe.
This sounds simple, but multi-region systems are significantly more complicated than multi-zone systems.
Questions immediately appear:
Where do writes go? How is data replicated? Can both regions accept writes? What happens during network partition? How quickly does DNS or routing change? How much replication lag is acceptable?
There are two common high-level approaches.
Active-passive:
Region A → active Region B → standby
If Region A fails, Region B takes over.
Active-active:
Region A → serving traffic Region B → serving traffic
Both regions are active simultaneously.
Active-active can provide excellent availability and lower geographic latency, but conflict resolution and data consistency become much harder.
For a deeper discussion, see Multi-Region Architecture and Disaster Recovery.
Graceful Degradation
A highly available system does not always require every feature to be available.
Consider a product page:
Product details Price Inventory Reviews Recommendations Recently viewed
The recommendation service fails.
One design returns:
500 Internal Server Error
The entire page is unavailable.
A better design returns:
Product details ✓ Price ✓ Inventory ✓ Reviews ✓ Recommendations unavailable Recently viewed ✓
The important functionality remains available.
This is graceful degradation.
More examples:
Personalization fails → show popular content Search reranker fails → return basic search ranking Analytics fails → continue checkout and buffer events Image optimization fails → serve original image Reviews fail → show product without reviews
The key is deciding which features are critical and which are optional.
For more detail, see Designing Graceful Degradation Strategies.
Handling Dependency Failures
A system may be healthy while one dependency is unhealthy.
Consider:
Checkout Service
│
├──► Inventory
├──► Payment
├──► Tax Service
└──► Analytics
If Analytics becomes slow and every checkout request waits 30 seconds for it, Analytics can indirectly make Checkout unavailable.
This is why remote calls need protection.
Common techniques include:
Timeouts Retries Exponential backoff Circuit breakers Bulkheads Fallbacks ```
For example:
Analytics call ↓ timeout after 300 ms ↓ store event locally or queue it ↓ checkout continues
A non-critical dependency should not normally be allowed to take down a critical path.
For more detail, see Timeouts, Retries, and Exponential Backoff.
Availability During Deployments
Deployments are a common source of downtime.
Consider one server:
Old Version
│
stop
│
▼
deploy
│
▼
New Version
During the replacement, the application may be unavailable.
With multiple instances, deployments can happen gradually.
Before: App 1 → v1 App 2 → v1 App 3 → v1
Update one instance:
App 1 → v2 App 2 → v1 App 3 → v1
If App 1 passes health checks:
App 1 → v2 App 2 → v2 App 3 → v1
Eventually:
App 1 → v2 App 2 → v2 App 3 → v2
This is the basic idea behind a rolling deployment.
Another approach is blue-green deployment:
Blue Environment Version 1 Serving traffic Green Environment Version 2 Ready but idle
After validation:
Traffic │ ▼ Green Environment Version 2
If Version 2 is broken, traffic can often be moved back quickly.
Availability must therefore include deployment strategy, not only runtime infrastructure.
Capacity and Traffic Spikes
A system can become unavailable even when nothing technically fails.
It may simply receive more traffic than it can handle.
Suppose the application supports:
Maximum: 10,000 requests/sec
Then a promotion creates:
Incoming: 25,000 requests/sec
Possible result:
CPU → 100% Latency → increases Timeouts → increase Retries → increase Load → increases further Service → unavailable
This is why high availability requires capacity planning.
One strategy is keeping headroom:
Maximum capacity: 20,000 req/sec Normal traffic: 10,000 req/sec Headroom: 10,000 req/sec
Another is autoscaling:
Low traffic: 3 app instances Traffic rises: 6 app instances Large spike: 12 app instances
But autoscaling takes time, so it should not be the only protection.
Other mechanisms may include:
Rate limiting Load shedding Queues Caching Backpressure Admission control
If a system cannot serve every request, intentionally rejecting some low-priority work may protect critical functionality.
Monitoring Availability
Availability needs to be measured from the user's perspective.
CPU utilization alone does not tell whether users can successfully use the system.
Useful availability indicators include:
Successful request percentage 5xx error rate Timeout rate Dependency failure rate Health-check failures Failover events Request latency Synthetic probe success ```
For example:
Requests: 1,000,000 Successful: 999,500 Failed: 500
The successful-request availability is:
999,500 / 1,000,000 = 99.95%
But the exact definition matters.
Should a 400 validation error count as downtime? Usually not.
Should a request that returns HTTP 200 but contains unusable data count as successful? Maybe not.
A meaningful availability SLI should reflect whether users received a valid service result.
For example:
SLI: Percentage of valid checkout requests completed successfully within 2 seconds
An SLO might then be:
99.95% over 30 days
This is more useful than simply measuring whether application processes are alive.
Example: Making an Online Store Highly Available
Consider an online store starting with:
Users │ ▼ Application │ ▼ Database
Stage 1: Remove the application single point of failure.
┌──► App 1
Users ──► Load Balancer
├──► App 2
└──► App 3
If one application instance fails, traffic continues.
Stage 2: Distribute instances across zones.
Zone A → App 1 Zone B → App 2 Zone C → App 3
A single-zone outage no longer removes the whole application tier.
Stage 3: Replicate the database.
Primary DB │ ├──► Replica A └──► Replica B
If the primary fails, a replica can take over.
Stage 4: Protect dependency calls.
Product Service
│
├──► Inventory
├──► Reviews
└──► Recommendations
Each dependency gets timeouts and failure handling.
Recommendations become optional:
Recommendations fail
↓
Product page still available
Stage 5: Move non-critical work off the request path.
Checkout
│
├──► Save Order
│
└──► Queue
│
├──► Email
├──► Analytics
└──► CRM
An analytics outage no longer blocks checkout.
Stage 6: Make deployments zero-downtime.
App 1 → v2 App 2 → v1 App 3 → v1 then gradually replace remaining instances
Stage 7: Add capacity protection.
Load Balancer
│
├──► App instances
│ +
│ autoscaling
│
├──► Cache
│
└──► Rate limiting
Stage 8: Add regional disaster recovery if required.
Global Router
/ \
▼ ▼
Region A Region B
```
The final design may look like:
Global DNS / Router
│
▼
Load Balancer
│
┌───────────────┼───────────────┐
▼ ▼ ▼
Zone A Zone B Zone C
App 1 App 2 App 3
\ │ /
\ │ /
└─────────────┼─────────────┘
│
┌────────┼────────┐
▼ ▼ ▼
Cache Database Queue
│ │
┌─────┴─────┐ ├──► Email
▼ ▼ ├──► Analytics
Replica Replica └──► CRM
```
The important idea is not that every system needs this architecture.
The important idea is that every important failure has a planned response.
App fails → another app serves traffic Zone fails → other zones continue Database primary fails → replica takes over Optional dependency fails → feature degrades Traffic spikes → scale or shed load Bad deployment → stop rollout or roll back
Common Availability Mistakes
Assuming Multiple Instances Automatically Mean High Availability
Three application instances in the same failure domain can still fail together.
One zone ├── App 1 ├── App 2 └── App 3 ```
A zone failure removes all three.
Availability depends on independent failure domains, not simply instance count.
Ignoring the Database
This architecture:
20 Application Servers
│
▼
One Database
```
still contains an obvious single point of failure.
The full dependency chain must be evaluated.
Using Weak Health Checks
A process returning HTTP 200 from /health does not prove that it can serve real traffic.
Process running ✓ Connection pool exhausted ✗ ```
Readiness checks should represent actual serving capability without becoming so complex that the check itself becomes unstable.
Using Aggressive Retries During Outages
Retries can reduce availability when a dependency is overloaded.
10,000 requests/sec × 3 retries potentially 40,000 attempts/sec ```
Retries should be bounded and combined with backoff, jitter, timeouts, and circuit breaking.
Running Without Headroom
If normal traffic already consumes 95% of system capacity, one failed instance can overload the remaining instances.
Normal: 3 servers × 80% load One server fails: 2 servers must absorb everything Result: 120% required capacity ```
Redundancy without spare capacity may not survive actual failures.
Measuring Process Uptime Instead of Service Availability
A server may be running while users receive errors.
Process uptime: 100% Successful checkouts: 92% ```
The second number is far more important for the user.
Production Checklist
- Define availability SLIs and SLOs based on user-visible success.
- Identify critical single points of failure.
- Run critical application components redundantly.
- Distribute replicas across independent failure domains.
- Use health checks and automatic traffic removal.
- Design database replication and failover.
- Use timeouts for remote dependencies.
- Apply retries carefully with backoff and jitter.
- Degrade optional functionality instead of failing entire requests.
- Use queues for non-critical asynchronous work.
- Deploy gradually with health validation and rollback capability.
- Keep capacity headroom for traffic spikes and instance failures.
- Test failover instead of assuming it works.
- Monitor availability from the user's perspective.
Conclusion
Availability means keeping a software service usable when users need it.
High availability does not require eliminating every failure. That is unrealistic.
Instead, the architecture prevents individual failures from becoming complete outages.
Component fails
↓
Detect failure
↓
Route around it
↓
Degrade if necessary
↓
Recover
↓
Return to full capacity
Redundancy, load balancing, health checks, replication, failover, timeouts, graceful degradation, safe deployments, and capacity planning all contribute to availability.
Key Takeaway: high availability is achieved when the system can lose components without losing the service.
Comments (0)