28.7. High Availability
Table of Contents
Understanding High Availability
High availability means designing your backend so that it is up and serving users almost all the time, even when parts of the system fail or need maintenance. It is about reducing downtime and making failures boring and recoverable, instead of catastrophic.
High availability is closely related to fault tolerance, monitoring, and deployment, but here we focus on what makes a backend highly available in production, and how you can design for it.
Key Concepts of High Availability
High availability usually talks about three main ideas: availability, reliability, and resilience.
- Availability
The fraction of time your system is up and able to serve requests.
Example: 99.9% availability per month means at most about 43 minutes of downtime in that month. - Reliability
The probability that the system will work correctly for a given period. Reliability is about not failing. Availability is about staying up even when parts do fail. - Resilience
The ability to recover from failures. A resilient system might fail partially, but it remains usable and recovers gracefully.
A simple relation for availability is:
$$
\text{Availability} = \frac{\text{MTBF}}{\text{MTBF} + \text{MTTR}}
$$
Where:
- MTBF = Mean Time Between Failures
- MTTR = Mean Time To Repair
Important rule: To improve availability, you can either fail less often (increase MTBF) or recover faster (reduce MTTR). High availability focuses heavily on reducing MTTR through redundancy and automation.
Common Availability Levels
| Availability | Nickname | Allowed downtime per month |
|---|---|---|
| 99% | 2 nines | ~7 hours 18 minutes |
| 99.9% | 3 nines | ~43 minutes |
| 99.99% | 4 nines | ~4 minutes 19 seconds |
| 99.999% | 5 nines | ~26 seconds |
Even moving from 99.9% to 99.99% can be very expensive in terms of architecture and operations. The right target depends on your product and users.
Single Points of Failure
A single point of failure (SPOF) is any component that, if it fails, brings the whole system down.
Examples of SPOFs in a simple backend:
- One application server instance.
- One database server.
- One Redis instance for sessions or caching.
- One load balancer VM.
- One disk volume for file uploads.
- One region or availability zone in a cloud provider.
To achieve high availability, your main job is to find SPOFs and remove or reduce them.
Identifying SPOFs: Concrete Examples
Imagine a basic FastAPI + PostgreSQL deployment:
- 1 FastAPI container.
- 1 PostgreSQL container.
- 1 Redis container for caching.
- Nginx as a reverse proxy in front.
SPOFs here:
| Component | SPOF? | Why |
|---|---|---|
| FastAPI app | Yes | If that container dies, no app. |
| PostgreSQL | Yes | If it dies, app cannot use data. |
| Redis | Yes | If used for sessions, users break |
| Nginx | Yes | If it fails, no entry point |
| Network link | Yes | If network to server fails |
Awareness is the first step. Later we will see how to add redundancy and failover around these.
Redundancy and Replication
Redundancy means having multiple instances of the same component so that if one fails, others can take over. Replication means having the same data stored in multiple places.
Both are core tools to reach high availability.
Redundant Application Servers
Instead of one application server, you run multiple instances behind a load balancer.
- Example:
- 3 FastAPI instances running the same code.
- A load balancer spreads incoming HTTP requests across them.
- If one instance crashes, the others continue to handle traffic.
This does not help if:
- They all run on a single physical machine and the entire machine dies.
- Your database is still a SPOF.
So redundancy should go beyond just application processes.
Database Replication
For relational databases such as PostgreSQL, a common pattern is:
- Primary (leader) node
Handles all writes, and usually reads. - Replica (follower) nodes
Receive data changes from primary and can serve read-only queries.
If the primary fails, a failover process promotes a replica to primary.
Simplified example:
- You have:
db-primaryin zone A.db-replica-1in zone B.db-replica-2in zone C.- Your application:
- Sends all writes to
db-primary. - Sends some reads to replicas (optional but common).
- When
db-primaryfails: - A failover tool (or managed service) automatically chooses a replica and promotes it to primary.
- App either reconnects automatically or gets reconfigured to point to the new primary.
Types of Replication
| Type | Description | Example use case |
|---|---|---|
| Synchronous | Primary waits until replica writes data | Strong consistency, smaller setups |
| Asynchronous | Primary does not wait for replica | Higher performance, possible lag |
| Multi-primary | Multiple writable nodes | Advanced setups, conflict resolution |
For many backends, async replication with automatic failover is a reasonable balance.
Important rule: Do not confuse replication with backups. Replication copies current data to other nodes, including accidental deletions. You still need backups.
Health Checks and Failover
High availability requires the system to detect failures and move traffic away from unhealthy components quickly.
Health Checks
A health check is an automated test that runs repeatedly to see if a component is working.
At the application level, you might expose:
/healthzor/healthendpoint:- Returns HTTP 200 if the app is healthy.
- Might check:
- Basic process status.
- Ability to connect to the database.
- Ability to connect to Redis or external services.
Simple FastAPI example:
from fastapi import FastAPI
import asyncpg
app = FastAPI()
db_pool = None
@app.on_event("startup")
async def startup():
global db_pool
db_pool = await asyncpg.create_pool(dsn="postgresql://...")
@app.get("/healthz")
async def healthz():
async with db_pool.acquire() as conn:
await conn.execute("SELECT 1")
return {"status": "ok"}Your load balancer can then:
- Call
/healthzon each instance every few seconds. - If an instance fails health checks, remove it from rotation.
Types of Health Checks
| Type | What it checks | Cost | Examples |
|---|---|---|---|
| Liveness | Is the process alive? | Very low | Used by orchestrators like Kubernetes |
| Readiness | Can this instance serve traffic now? | Low / medium | DB connection, config loaded |
| Deep health | Deeper checks of dependencies | Higher | Queue connectivity, external APIs, etc. |
For high availability, readiness checks are crucial, because an app might be running but temporarily unable to serve.
Failover
Failover is the automatic switch from a failed component to a healthy one.
Examples:
- App layer failover
Load balancer removes unhealthy app instance, routes to others. - Database failover
A cluster manager promotes a replica to primary when the old primary is down. - DNS failover
Your DNS provider changes A/AAAA records to a different IP when the main one fails.
Key points:
- Failover should be automatic to keep MTTR low.
- There should be clear rules for when to failover, to avoid flapping between nodes.
Typical configuration at a load balancer:
- Check
/healthzevery 5 seconds. - Consider instance unhealthy after 3 consecutive failures.
- Consider instance healthy again after 3 consecutive successes.
Load Balancing for High Availability
A load balancer sits in front of your application instances and splits traffic between them. It is also a central piece for health checks and failover.
What Load Balancers Do
- Distribute requests across instances.
- Remove unhealthy instances from rotation based on health checks.
- Support rolling updates, by gradually draining old instances and adding new ones.
- Often terminate TLS (HTTPS) and forward plain HTTP to backend services.
Common Load Balancing Algorithms
| Algorithm | Description | Example use case |
|---|---|---|
| Round robin | Each request goes to the next instance | Good default |
| Least connections | Request goes to instance with fewest connections | Long-lived connections (WebSockets) |
| IP hash | Same client IP goes to same instance | Simple session stickiness |
| Weighted round robin | Some instances get more traffic | Mixed instance sizes |
For most basic HTTP APIs, round robin is enough.
Typical High-Availability Topology
Imagine a simple high availability setup:
- 1 public load balancer:
- Talks to 3 FastAPI instances.
- 3 FastAPI instances:
- In 3 different availability zones.
- 1 primary PostgreSQL + 2 replicas:
- One in each zone.
A request flow:
- User hits
https://api.example.com. - DNS resolves
api.example.comto the load balancer IP. - Load balancer chooses a healthy FastAPI instance.
- FastAPI instance connects to the primary database.
- Response is returned to user.
If one FastAPI instance fails, the load balancer stops sending traffic to it. If one availability zone has issues, the remaining zones still serve users.
Statelessness and Session Management
Stateless applications are easier to scale and keep highly available.
Stateless vs Stateful
- Stateless service
Does not depend on local memory or disk for user-specific state between requests. Any instance can handle any request. - Stateful service
Keeps important state in memory or on local disk. Losing an instance may lose data or break sessions.
For high availability, aim to make your application layer stateless.
Sessions and High Availability
A classic problem is user sessions. If you store sessions in process memory:
- User logs in and gets a session on instance A.
- Next request is routed to instance B.
- B knows nothing about the user session, so the user appears logged out.
Solutions:
- Sticky sessions at the load balancer:
- Same user always goes to the same instance.
- Simple but creates coupling between user and instance.
- If the instance fails, the user loses the session.
- Shared session storage:
- Store sessions in Redis, database, or another central store.
- Any instance can read any user's session.
- If an instance fails, user can continue on another instance.
- Stateless authentication:
- Use JWTs or similar tokens.
- The token contains all needed data, signed by the server.
- Server does not store any session data, just validates tokens.
For high availability, 2 or 3 are preferred.
Example of storing sessions in Redis:
- On login:
- Generate a session ID.
- Store it in Redis with user ID and expiration.
- Set session ID in secure cookie.
- On each request:
- Read session ID from cookie.
- Verify session in Redis.
- If Redis is replicated and highly available, losing one app instance does not affect sessions.
Important rule: To keep application instances stateless, do not store important user state only in memory of a single instance. Use shared or stateless mechanisms.
Multi-Zone and Multi-Region Setups
High availability is not only about processes, but also about infrastructure failures, such as power outages or network problems in a data center.
Cloud providers offer:
- Availability zones (AZs)
Separate data centers within a region. They are isolated but connected with low latency. - Regions
Geographically separate clusters of zones, such as "us-east-1" vs "eu-west-1".
Multi-AZ (Multi-Zone) Architecture
A common step towards high availability:
- Run multiple app instances in different zones within a region.
- Run database primary in one zone, replicas in other zones.
- Run load balancer in front and use subnets in multiple zones.
Benefits:
- Zone outage does not take down the entire service.
- Latency stays low, since all zones are in the same region.
Basic pattern:
| Component | Placement |
|---|---|
| Load balancer | Public, spans all zones |
| App instances | At least 2 zones |
| DB primary | Zone A |
| DB replicas | Zone B and C |
Multi-Region Architecture
Multi-region is more complex, but can:
- Protect against entire region outage.
- Improve latency for global users.
Common patterns:
- Primary region + warm secondary:
- All writes go to the primary region.
- Secondary region has replicated data.
- Traffic normally goes to primary region.
- If primary fails, failover DNS or load balancer to secondary.
- Active-active regions:
- Both regions handle traffic.
- Database is replicated in both directions.
- Requires careful conflict resolution and design.
Multi-region often involves:
- Global load balancers or DNS routing based on geolocation or health.
- Special database replication mechanisms (for example, logical replication).
This is advanced and often not needed for early stages, but it is central for very high availability (e.g. 4 or 5 nines).
Maintenance Without Downtime
High availability is not only about random failures. It is also about planned changes without visible downtime.
Rolling Deployments
Instead of stopping all instances and deploying new code, you:
- Have multiple instances running version A.
- Start new instances with version B.
- Load balancer:
- Marks an old instance as "draining".
- Stops sending new requests there, but lets existing requests finish.
- Once old instance is idle, shut it down.
- Repeat until all instances run version B.
During this process, users always have some healthy instances available.
Database Maintenance
Even databases need maintenance:
- Schema migrations.
- Engine version upgrades.
- Index rebuilds.
Approaches:
- Use migrations that are backward compatible with both old and new app versions.
- Run migrations while database is still online.
- For major DB upgrades:
- Set up a new replica.
- Sync data.
- Perform a controlled failover to the upgraded node.
Blue-Green Deployments
Another pattern:
- Blue environment:
- Current live environment.
- Green environment:
- New version, fully provisioned and tested, but not receiving production traffic.
Deployment steps:
- Deploy new version to green.
- Test green environment thoroughly.
- Switch traffic from blue to green (via DNS, load balancer, etc.).
- Keep blue as a fallback for quick rollback for some time.
Blue-green allows quick rollback and minimal downtime during large changes.
Designing for Degradation, Not Perfection
High availability does not mean "everything always works". It means:
- The core of your system works most of the time.
- When parts fail, the system degrades gracefully.
Examples of graceful degradation:
- If recommendation service is down:
- Show a static "popular items" list instead of recommendations.
- If email service is down:
- Queue emails for later delivery, but do not block user signups.
- If one region is unhealthy:
- Route most traffic to another region, even if latency is higher.
You should decide which features are critical and which are nice to have, and design degradation paths.
Practical Checklist for High Availability
Here is a simple checklist you can apply to a backend:
| Area | Question |
|---|---|
| Application | Do we run multiple instances in different zones? |
| Application | Do we use health checks and automatic removal of bad instances? |
| Sessions | Are sessions or auth tokens shared or stateless? |
| Database | Do we have replicas and automatic or manual failover? |
| Database | Are backups tested and restorable? |
| Cache / Redis | Is there a replication or persistence strategy? |
| Network | Is there a single critical IP or region? |
| Deployment | Can we deploy without full downtime? |
| Monitoring | Can we detect failures quickly and alert humans? |
You will not have everything perfect from day one. Start from the biggest risks and single points of failure, and improve step by step.
Summary
High availability is about staying up in the face of failures and maintenance. You:
- Identify and remove single points of failure.
- Use redundancy and replication across instances and zones.
- Add health checks and automatic failover.
- Put a load balancer in front of multiple stateless app instances.
- Store state in shared systems or make authentication stateless.
- Consider multi-zone and, for advanced setups, multi-region deployments.
- Perform rolling or blue-green deployments to avoid downtime during changes.
- Design for graceful degradation when noncritical components fail.
These ideas combine with monitoring, fault tolerance, and incident response to form a production-grade backend that real users can depend on.
Views: 5
KAHIBARO