Skip to Content
Reliability Engineering, Fault Tolerance & Disaster Recovery

Reliability Engineering, Fault Tolerance & Disaster Recovery

  • system-design
  • reliability
  • fault-tolerance
  • disaster-recovery
  • chaos-engineering
3 min readSystem Design · Part 18 of 18Ritik Tiwari
Part 17System Design · Part 18 of 18

The Story: The City That Cannot Fall

A great city plans for earthquakes, floods, power grid failures, and cyberattacks — not because they’re common, but because when they happen, the cost of being unprepared is catastrophic.

Your system will face: hardware failures, software bugs, traffic spikes, DDoS attacks, operator errors, network partitions, and datacenter fires. Reliability is not an accident. It’s designed.

This chapter brings everything together into a complete HLD framework.


Reliability Engineering Fundamentals

SLI, SLO, SLA — The Three Layers of Reliability

SLI (Service Level Indicator)
  A specific metric: "99.3% of requests in the past hour returned 2xx in under 200ms"
  → It's measured. It's a fact.

SLO (Service Level Objective)
  The target: "99.9% of requests must return 2xx in under 200ms"
  → It's a goal. Internal. You hold yourself to this.

SLA (Service Level Agreement)
  The contract with customers: "We guarantee 99.9% uptime. Breach = credit."
  → Legal/business. SLA is typically less strict than SLO.

SLO should be stricter than SLA:
  SLA: 99.9%  ← if you breach this, customers get credits
  SLO: 99.95% ← internal alarm fires at 99.95%, giving you buffer

Error Budget

SLO: 99.9% = 0.1% allowed failures
Monthly: 0.1% × 2,592,000 seconds = 2,592 seconds ≈ 43 minutes of errors/month

Error budget = the "budget" you have for imperfect reliability.
Spend it on: deployments, experiments, maintenance.

If budget is exhausted → freeze new deployments, focus on reliability.

This is Google SRE’s framework. Error budgets create alignment between dev (move fast) and ops (keep stable).


Fault Tolerance Patterns (Complete Reference)

Redundancy at Every Layer

Network Layer:
  Multiple network paths
  BGP multipath routing
  Multiple ISPs

Load Balancer Layer:
  Active-passive pair (VRRP)
  Or active-active with DNS

Application Layer:
  Multiple app servers across AZs
  Auto-scaling groups

Cache Layer:
  Redis Sentinel (1 master + 2 replicas, auto-failover)
  Or Redis Cluster (16,384 hash slots, N shards × M replicas)

Database Layer:
  Primary + read replicas
  Multi-AZ deployment (synchronous replica in another AZ)
  Point-in-time recovery backups

Storage Layer:
  S3: 11 nines durability (replication across ≥3 AZs within region)
  Multi-region replication for disaster recovery

Datacenter Layer:
  Multi-AZ (same region, different power/cooling/network)
  Multi-Region (different geography, different disaster zones)

Chaos Engineering

Definition: Deliberately injecting failures into production (or staging) to verify your fault tolerance actually works.

Netflix Chaos Monkey: Randomly terminates EC2 instances in production during business hours.

Why production? Staging doesn’t have the same traffic patterns, data volumes, and dependency topology. You need to validate in the environment that matters.

Chaos experiment steps:
1. Define steady state: "p99 latency < 200ms, error rate < 0.1%"
2. Hypothesize: "Terminating one API server won't affect steady state"
3. Inject failure: terminate one API server
4. Observe: does steady state hold?
5. Fix if it doesn't. Document if it does.

Common chaos experiments:

  • Terminate random instances
  • Introduce network latency (50ms, 500ms) between services
  • Drop packets between services (network partition)
  • Fill disk
  • Kill the database primary (test failover time)
  • Kill the cache (test cold-cache fallback)

Disaster Recovery

RPO and RTO

Disaster occurs at T=0

    T=0                    T=RTO
     │                       │
     ▼                       ▼
[Disaster] ──────────────── [System restored]

     │← RPO →│
[Last backup]
     T=-RPO

RPO (Recovery Point Objective):
  Maximum acceptable data loss.
  RPO = 1 hour → you may lose up to 1 hour of data
  RPO = 0 → zero data loss (synchronous replication required)

RTO (Recovery Time Objective):
  Maximum acceptable downtime after a disaster.
  RTO = 1 hour → system must be restored within 1 hour
  RTO = 0 → zero downtime (hot standby required)

DR Strategies (cost vs recovery speed)

Strategy Description RTO RPO Cost
Backup & restore Periodic snapshots. Restore from backup on failure. Hours–days Hours $
Pilot light Core infrastructure ready, minimal services running ~1 hour Minutes $$
Warm standby Scaled-down prod environment running, ready to scale Minutes Seconds $$$
Active-active Full prod in multiple regions, both serving traffic Near zero Near zero $$$$

Active-active is expensive but required for: payment gateways, stock exchanges, critical healthcare, telco infrastructure.


A Complete HLD: Design a Notification System

Let’s put everything together by designing a notification system (used by Uber, LinkedIn, WhatsApp for push notifications, emails, SMS).

Step 1: Requirements

Functional:

  • Send notifications via: push (iOS/Android), email, SMS
  • Support different notification types: transactional (immediate), marketing (scheduled/batched)
  • Users can configure preferences (opt-out of marketing, choose channels)
  • Delivery confirmation tracking
  • Rate limiting per user (don’t spam)

Non-functional:

  • Scale: 10M notifications/day initially, 1B/day target
  • Latency: transactional < 1 second (OTP, order confirmation)
  • Marketing: can be delayed (batch processing acceptable)
  • Availability: 99.99% (notification failure is a bad user experience)

Step 2: Estimation

10M notifications/day ÷ 86,400 = ~115 notifications/second average
Peak: 3× = ~350/second
1B/day = 11,574/second

Push notifications: 60% of total
Email: 30%
SMS: 10%

Storage:
  Each notification event: ~500 bytes
  10M/day × 500B = 5GB/day
  1 year: ~1.8TB

Step 3: API Design

# Internal API (called by other services)
POST /notifications/send
{
  "user_id": "42",
  "type": "transactional",   // or "marketing"
  "template_id": "order_confirmed",
  "channels": ["push", "email"],  // or ["auto"] — system picks based on preferences
  "data": { "order_id": "ORD-123", "amount": 599 },
  "priority": "high"  // or "normal", "low"
}

# User preference API
GET  /users/{id}/notification-preferences
PUT  /users/{id}/notification-preferences
{
  "marketing_email": false,
  "marketing_push": true,
  "transactional_sms": true
}

# Tracking API
GET /notifications/{id}/status
→ { "status": "delivered", "channel": "push", "delivered_at": "..." }

Step 4: High-Level Architecture

[Other Services] → [Notification API Service]

                  [User Preference DB]   (check opt-in/opt-out)

                  [Rate Limiter]         (Redis: max N per user per hour)

                  [Priority Queue]       (Kafka topics by priority)
                    /         \
             [High Priority]  [Low Priority]
                    ↓               ↓
              [Workers]         [Workers]
              (immediate)       (batched)

          ┌─────────────────────────┐
          ↓          ↓              ↓
   [Push Worker] [Email Worker] [SMS Worker]
        ↓              ↓              ↓
  [FCM/APNs]    [SendGrid/SES]  [Twilio/SNS]
  (Firebase)                          ↓
       ↓                        [Delivery DB]  (tracking)
  [Delivery DB]

Step 5: Deep Dives

Handling delivery failures

# Worker pseudocode
async def process_notification(notification):
    try:
        result = await send_push(notification)
        await db.update_status(notification.id, "delivered")
    except TemporaryError as e:
        # Retry with exponential backoff
        retry_count = notification.retry_count + 1
        if retry_count < 5:
            delay = 2 ** retry_count  # 2, 4, 8, 16, 32 seconds
            await queue.requeue(notification, delay=delay, retry_count=retry_count)
        else:
            await db.update_status(notification.id, "failed")
            await fallback_channel(notification)  # try email if push failed
    except PermanentError:
        # Invalid device token — remove from registry
        await db.update_status(notification.id, "failed_permanent")
        await device_registry.remove(notification.device_token)

Template rendering

# Template stored in DB:
# "Your order {order_id} has been confirmed! Total: ₹{amount}"

def render_template(template_id: str, data: dict, locale: str = "en") -> str:
    template = template_cache.get(f"{template_id}:{locale}")
    if not template:
        template = db.get_template(template_id, locale)
        template_cache.set(f"{template_id}:{locale}", template, ttl=3600)
    return template.format(**data)

Deduplication

# Problem: message queue may deliver twice (at-least-once)
# Solution: idempotency key

def process_notification(notification):
    # Check if already processed
    idempotency_key = f"notif:{notification.id}"
    if redis.set(idempotency_key, 1, nx=True, ex=86400):
        # nx=True means "only set if not exists"
        # Successfully acquired lock — process
        send_and_track(notification)
    else:
        # Already processed — skip
        logger.info(f"Duplicate notification {notification.id}, skipping")

Step 6: Bottlenecks and Solutions

Bottleneck At what scale Solution
Single Notification API server 10K RPS Horizontal scaling + LB
Single Kafka partition 100K msg/s Partition by user_id (parallelism)
Single worker pool 10K deliveries/s Auto-scaling worker pool (K8s HPA)
Delivery DB writes 10K writes/s Cassandra (designed for high write throughput)
Template cache misses At cold start Pre-warm cache on deploy
3rd party rate limits (FCM, Twilio) > their limits Multiple API keys, account rotation

Step 7: Trade-offs

This design prioritises throughput and availability over strict ordering. Marketing notifications may arrive slightly out of order — acceptable. Transactional notifications (OTP, order confirmation) go through a high-priority Kafka topic with fewer consumers to ensure fast delivery. We use eventual consistency for delivery tracking — there may be a few seconds’ lag before the status dashboard updates. This is acceptable; users don’t watch delivery logs in real time.


The Complete System Design Checklist

Use this for every design problem:

Requirements checklist

  • Functional requirements — 3–5 core features
  • Scale — DAU, peak RPS, data volume
  • Latency — p99 targets per endpoint
  • Availability — number of nines
  • Consistency — strong or eventual, per data type
  • Out of scope — explicitly stated

Architecture checklist

  • DNS → Load balancer → App servers (stateless)
  • Cache layer (Redis) for hot data
  • Database with replication
  • CDN for static assets
  • Message queue for async operations
  • No single points of failure identified

Reliability checklist

  • Circuit breakers on downstream calls
  • Retries with exponential backoff + jitter
  • Timeouts on all external calls
  • Graceful degradation for non-critical features
  • Health checks (liveness + readiness)
  • Monitoring — four golden signals

Data checklist

  • Primary key strategy (UUID or Snowflake)
  • Indexes on frequently queried columns
  • Soft deletes for important data
  • Backups and retention policy
  • Encryption at rest and in transit

Flashcards

Q: What is the difference between SLI, SLO, and SLA?

SLI = the measured metric. SLO = internal target. SLA = contractual commitment with customers. SLO stricter than SLA.

Q: What is an error budget?

The allowed failure budget = (1 - SLO) × time period. When exhausted, freeze deployments and focus on reliability.

Q: What is RPO vs RTO?

RPO = max acceptable data loss (time). RTO = max acceptable downtime after disaster. Both drive DR strategy and cost.

Q: What is chaos engineering?

Deliberately injecting failures into production to verify fault tolerance. Netflix Chaos Monkey is the famous example.

Q: What is the active-active DR strategy?

Full production runs in multiple regions simultaneously, both serving traffic. Zero RTO and RPO. Most expensive.

Q: What four reliability metrics does Google SRE define as “golden signals”?

Latency, Traffic, Errors, Saturation.


Series · System Design

Part 18 of 18 · Aug 2026