What Is a Distributed System?
Benefits: Scalability and Fault Tolerance
Read aloud is unavailable in this browser.
The Twin Pillars of Distributed Systems
If distributed systems are so difficult—demanding expertise in consensus algorithms, failure detection, network protocols, and consistency models—why do organizations invest billions in building and operating them? The answer lies in two fundamental capabilities that only distributed architectures can provide at scale: scalability and fault tolerance.
Scalability allows Netflix to stream content to 260 million subscribers simultaneously during peak evening hours. Fault tolerance allows Amazon to process orders even when entire data centers go dark. Together, these capabilities transform what would be fragile, limited systems into robust platforms serving billions.
This page dissects these benefits in depth—not as abstract concepts but as concrete engineering achievements with specific patterns, trade-offs, and implementation strategies.
By the end of this page, you will understand the mechanics of horizontal and vertical scaling, the patterns that enable linear scalability, the theory of fault tolerance and redundancy, and the architectural approaches that let systems survive partial failures while continuing to serve users.
Understanding Scalability
Scalability is the system's ability to handle increased load by adding resources. This seemingly simple definition hides substantial nuance.
Formal Definition:
A system is scalable if its performance improves proportionally as resources are added, for a defined workload and performance metric.
Key Elements of This Definition:
1. "Performance improves proportionally"
- Adding 2x resources should yield ~2x capacity (linear scaling)
- In practice, overhead causes sub-linear gains (1.8x with 2x resources)
- Super-linear gains (>2x with 2x resources) are rare but possible (cache effects)
2. "As resources are added"
- Resources can be compute (CPU cores), memory, storage, network, or entire machines
- The type of resource matters: network-bound apps don't benefit from more CPU
3. "For a defined workload"
- A system might scale linearly for reads but not writes
- Different query patterns may scale differently
- Always specify the workload when discussing scalability
4. "And performance metric"
- Throughput (requests/second)
- Latency (response time at a given percentile)
- Capacity (concurrent users, stored data)
- Each may scale differently
| Metric | Description | How It Scales | Common Bottleneck |
|---|---|---|---|
| Throughput | Requests processed per unit time | Should increase linearly with resources | CPU, worker threads, I/O |
| Latency (p50) | Median response time | Should remain constant as load increases | Contention, queue depth |
| Latency (p99) | 99th percentile response time | Often degrades before p50; key indicator | Tail latency sources |
| Concurrent Users | Simultaneous active sessions | Limited by memory, connection limits | Connection pools, session state |
| Data Volume | Total storable data | Should increase linearly with storage nodes | Rebalancing, consistency overhead |
Systems are not simply 'scalable' or 'not scalable.' Scalability is a spectrum measured in specific dimensions. A system might scale writes to 100K/sec but not 1M/sec. Always quantify: 'This system scales to X under workload Y with acceptable P99 latency Z.'
Horizontal vs Vertical Scaling
Two fundamental approaches to scaling exist, each with distinct trade-offs.
- •Definition: Add more resources (CPU, RAM, disk) to existing machine
- •Implementation: Upgrade hardware, resize VM/container
- •Limit: Physical maximum of largest available machine
- •Complexity: Simple—application unchanged
- •Availability: Downtime often required for upgrades
- •Cost Curve: Exponential (2x resources ≠ 2x cost)
- •Definition: Add more machines to share the load
- •Implementation: Add nodes, configure load balancing
- •Limit: Theoretically unlimited (practical limits exist)
- •Complexity: Significant—application must be distributed
- •Availability: No downtime; add nodes live
- •Cost Curve: Near-linear (2x machines ≈ 2x cost, 2x capacity)
When to Use Each Approach:
Vertical Scaling Is Appropriate When:
- Current machine is not near resource limits
- Application cannot be easily parallelized
- Immediate, simple scaling is needed
- Budget allows for premium hardware
- Strong consistency requires single-node transactions
Horizontal Scaling Is Required When:
- Resource needs exceed the largest available machine
- High availability requires redundancy
- Cost optimization necessitates commodity hardware
- Geographic distribution is needed
- Elastic scaling for variable load is desired
The Reality: Hybrid Approaches
Most production systems use both:
- Right-size vertically first: Ensure each node is optimally configured
- Then scale horizontally: Add more optimally-sized nodes
- Different tiers, different strategies: Cache servers might scale differently than database nodes
Example: A Typical Web Application
- Load balancers: Horizontal (multiple for redundancy)
- Application servers: Horizontal (stateless, easy to add)
- Cache layer: Horizontal (partitioned by key)
- Database: Vertical initially, then horizontal (read replicas, then sharding)
- Object storage: Horizontal (inherently distributed)
Before horizontal scaling, optimize: algorithms, database queries, caching strategies, and connection pooling. An inefficient application that scales horizontally just wastes resources at scale. Make each unit efficient, then multiply units.
Patterns for Linear Scalability
Achieving linear scalability—where doubling resources doubles capacity—requires specific architectural patterns. Without these patterns, systems hit coordination bottlenecks that prevent scaling.
Pattern 1: Shared-Nothing Architecture
Each node operates independently with its own private resources:
- No shared disk (each node has its own storage)
- No shared memory (except through explicit message passing)
- No single-node bottleneck (no master coordinator for normal operations)
Why It Scales: Nodes don't contend for resources. Adding a node adds independent capacity.
Examples:
- Cassandra (each node stores/serves its partition independently)
- Kafka partitions (each partition processed independently)
- Stateless application servers (no shared state between requests)
Pattern 2: Data Partitioning (Sharding)
Divide data across nodes so each node handles a subset:
- Hash-based partitioning: Hash(key) → node (uniform distribution)
- Range-based partitioning: Key ranges map to nodes (locality preservation)
- Directory-based partitioning: Lookup table maps keys to nodes (flexibility)
Why It Scales: Each node handles 1/N of the data. Adding nodes reduces per-node load.
Challenge: Cross-partition operations require coordination, reducing scalability.
Pattern 3: Replication for Read Scaling
Replicate data across multiple nodes; any replica can serve reads:
- Primary-secondary (master-slave): Writes to primary, reads from any
- Multi-master: Writes to any, conflicts resolved
Why It Scales: Read capacity multiplies with each replica.
Challenge: Write scaling still limited; replication lag creates stale reads.
Pattern 4: Stateless Services
Services hold no local state between requests:
- All state externalized to databases, caches, or message queues
- Any instance can handle any request
- Load balancers distribute requests arbitrarily
Why It Scales: Adding a service instance adds proportional capacity.
Challenge: Externalizing state adds latency; state stores become bottlenecks.
| Pattern | Scales | Limitation | Use Case |
|---|---|---|---|
| Shared-Nothing | Compute and storage linearly | Complex coordination for global operations | Distributed databases, parallel processing |
| Partitioning | Data capacity and throughput | Cross-partition queries expensive | Large datasets, high-throughput writes |
| Replication | Read throughput | Writes don't scale; consistency lag | Read-heavy workloads, geographic distribution |
| Stateless Services | Request handling capacity | State stores become bottleneck | API servers, web applications |
| Async Processing | Throughput (decoupled from latency) | Increased latency, eventual consistency | Background jobs, event processing |
Pattern 5: Asynchronous Processing
Decouple request acceptance from processing:
- Accept requests quickly, queue for processing
- Workers process from queue at their pace
- Scale workers independently of API servers
Why It Scales: Workers can be scaled to match processing demand, not request rate.
Challenge: Adds latency; harder to provide synchronous responses.
Anti-Patterns That Prevent Scaling:
- Central coordinator: A single master that must approve all operations
- Shared mutable state: Locks and contention increase with nodes
- Synchronous cross-service calls: Each call waits; latency compounds
- Unbounded data structures: A list that every request appends to
- Global transactions: ACID across all nodes serializes operations
If 5% of your workload requires serialization (cannot run in parallel), your maximum speedup from parallelization is 20x—no matter how many nodes you add. Identify serialization bottlenecks; they cap your scalability ceiling.
Understanding Fault Tolerance
Fault tolerance is the system's ability to continue operating correctly despite component failures. In distributed systems, failures are not exceptions—they are the norm.
Formal Definition:
A system is fault-tolerant to failure type F if it continues providing its specified service despite occurrences of F.
Types of Faults:
1. Crash Faults (Fail-Stop)
- Component simply stops working
- Detectable: No response to requests
- Most algorithms assume this failure model
- Examples: Process crash, server shutdown, pod eviction
2. Omission Faults
- Component fails to send or receive messages
- May appear operational but drops communications
- Examples: Network packet loss, full buffer drops, message queue overflow
3. Timing Faults
- Component responds, but too slowly
- Violates timing assumptions but not correctness
- Examples: CPU contention delays, network congestion, garbage collection pauses
4. Byzantine Faults
- Component behaves arbitrarily, possibly maliciously
- May send conflicting information to different nodes
- Hardest to tolerate; requires 3f+1 nodes to tolerate f failures
- Examples: Compromised servers, software bugs sending wrong data
| Fault Type | Behavior | Detection | Tolerance Requirement |
|---|---|---|---|
| Crash | Stops completely | Heartbeat timeouts | f+1 replicas for f failures |
| Omission | Loses messages | Acknowledgment timeouts | Retries + f+1 replicas |
| Timing | Responds late | Deadline violations | Timeouts + fallbacks |
| Byzantine | Arbitrary/malicious | Cryptographic verification | 3f+1 replicas for f failures |
The Fundamental Insight:
Fault tolerance requires redundancy—having more resources than strictly necessary for normal operation so that failures can be absorbed.
Redundancy Approaches:
Active Redundancy (Hot Standby)
- Multiple components process every request
- If one fails, others already have the result
- Fastest failover; highest resource cost
- Example: RAID-1 mirroring, multi-region active-active
Passive Redundancy (Warm Standby)
- Backup components receive updates but don't process requests
- On failure, backup is promoted to primary
- Moderate failover time; moderate cost
- Example: Primary-secondary database replication
Spare Redundancy (Cold Standby)
- Backup components wait idle
- On failure, spare is started and data copied
- Slowest failover; lowest ongoing cost
- Example: Provisioning new VMs on failure
A fault is a defect in a component. A failure is when the system deviates from its specified behavior. Fault tolerance aims to prevent faults from causing failures. A hard drive fault (bad sector) shouldn't cause a storage system failure (data loss).
Redundancy and Replication Strategies
Replication is the primary mechanism for achieving fault tolerance in distributed systems. Understanding replication strategies is essential for system design.
Single-Leader (Primary-Secondary) Replication:
- •Writes: All writes go to a single leader (primary)
- •Reads: Can be served by leader or followers (secondaries)
- •Replication: Leader propagates writes to followers
- •Consistency: Strong consistency possible (read from leader) or eventual (read from followers)
- •Failover: On leader failure, a follower is promoted (manual or automatic)
- •Scaling: Read scaling via followers; writes limited to leader capacity
Multi-Leader Replication:
- Multiple nodes can accept writes
- Each leader replicates to other leaders
- Enables geographic distribution of writes
- Requires conflict resolution (last-write-wins, merge, custom logic)
- Examples: CouchDB, Active Directory, collaborative editing
Leaderless Replication:
- Any node can accept reads and writes
- Writes sent to multiple nodes; reads query multiple nodes
- Quorum-based: Write to W nodes, read from R nodes (W + R > N ensures overlap)
- No failover needed; nodes join and leave dynamically
- Examples: Cassandra, DynamoDB, Riak
Synchronous vs Asynchronous Replication:
Synchronous:
- Write acknowledged only after all replicas confirm
- Guarantees durability: No data loss on leader failure
- Trade-off: Latency equals slowest replica
- A single slow replica stalls all writes
Asynchronous:
- Write acknowledged after leader writes; replicas updated later
- Lower latency; higher throughput
- Trade-off: Data can be lost if leader fails before replication completes
- Followers may serve stale data
Semi-Synchronous:
- Wait for at least one replica to confirm (not all)
- Balance between durability and performance
- Common in databases (e.g., MySQL semi-sync)
| Strategy | Consistency | Write Latency | Availability | Complexity |
|---|---|---|---|---|
| Single-Leader Sync | Strong | High (wait for all) | Leader is SPOF until failover | Low |
| Single-Leader Async | Eventual | Low | Leader is SPOF; may lose recent writes | Low |
| Multi-Leader | Eventual (conflicts) | Low (local leader) | High (any leader available) | High (conflict resolution) |
| Leaderless Quorum | Tunable (by W, R) | Medium | High (no single leader) | Medium |
There is no universally best replication strategy. Pick based on: If you need strong consistency, use synchronous replication with tradeoffs in latency and availability. If you need low latency and high availability, use async or leaderless with eventual consistency. Multi-leader is for specific use cases like geographic distribution of writes.
Failure Detection and Recovery
Before a system can tolerate a failure, it must detect it. In distributed systems, detection is surprisingly difficult.
The Detection Problem:
In a distributed system, how do you distinguish between:
- A node that has crashed?
- A node that is slow?
- A network partition (node is fine but unreachable)?
Answer: You can't definitively. You can only observe that the node isn't responding within your timeout threshold. This fundamental uncertainty drives most distributed systems complexity.
Detection Mechanisms:
1. Heartbeats
- Nodes periodically send "I'm alive" messages
- Missing heartbeats trigger failure suspicion
- Parameters: Heartbeat interval (T), failure threshold (missed heartbeats)
- Trade-off: Shorter T detects faster but generates more traffic
2. Ping-Pong (Request-Response)
- Monitor actively queries node status
- Timeouts indicate potential failure
- More reliable than passive heartbeats
- Trade-off: Adds monitoring load
3. Swim/Gossip-Based Detection
- Nodes randomly ping peers
- Failed pings escalate to "probe" via intermediaries
- Distributed detection; no single monitor
- Trade-off: Detection time is probabilistic
Timeout Configuration:
Setting timeouts is a critical and difficult decision:
Too Short:
- False positives: Healthy but slow nodes marked failed
- Unnecessary failovers, which are expensive and disruptive
- "Flapping": Nodes repeatedly marked failed and recovered
- Under partition, may mark entire healthy partition as failed
Too Long:
- Slow detection: Users experience errors while waiting
- Longer recovery time: Downtime extended
- Resource waste: Traffic still sent to dead nodes
Typical Values:
- Heartbeat interval: 1-5 seconds
- Failure threshold: 3-5 missed heartbeats (3-25 seconds)
- Aggressive systems: 1-second detection (with false positive handling)
Recovery Mechanisms:
Automatic Failover:
- Detect failure (via heartbeats/timeouts)
- Elect new leader (consensus protocol or pre-assigned)
- Redirect traffic to new leader
- Sync data if necessary
- Resume operations
Graceful Degradation:
- Partial functionality instead of complete outage
- Example: Read-only mode if write replicas unavailable
- Example: Serve cached data if database unreachable
Self-Healing:
- Automatically restart failed processes
- Container orchestrators (Kubernetes) provide this
- Health checks determine when to restart
In a network partition, both sides may conclude the other has failed and elect their own leader. Now you have two leaders accepting conflicting writes—split brain. Prevention requires quorum-based decisions: Only the partition with >50% of nodes can elect a leader. The minority partition must refuse to operate.
Building Resilient Systems
Fault tolerance isn't just about replication—it's about building resilience into every layer of the system.
Resilience Patterns:
- •Timeouts: Every remote call must have a timeout. No infinite waits. Failing fast prevents cascade failures.
- •Retries with Backoff: Retry failed calls with exponential backoff (1s, 2s, 4s...). Add jitter to prevent thundering herd.
- •Circuit Breakers: After repeated failures to a service, stop attempting calls. Periodically retry to check if it recovered. Prevents wasting resources on dead services.
- •Bulkheads: Isolate failures by separating resources. Different pools for different clients. One slow client doesn't exhaust resources for others.
- •Rate Limiting: Protect services from overload. Reject excess requests rather than degrade for everyone.
- •Fallbacks: When primary fails, use degraded alternatives. Show cached data, default recommendations, or reduced functionality.
- •Health Checks: Expose endpoints reporting service health. Enable load balancers to route around unhealthy instances.
Defense in Depth:
Resilience should exist at multiple levels:
Application Level:
- Error handling for every external call
- Graceful degradation when dependencies fail
- Idempotent operations for safe retries
- Request validation to prevent malformed input
Service Level:
- Multiple instances behind load balancer
- Health checks for automatic removal of unhealthy instances
- Circuit breakers between service dependencies
- Bulkhead pools for different traffic sources
Infrastructure Level:
- Multiple availability zones
- Automated failover for databases
- Self-healing container orchestration
- Geographic distribution for disaster recovery
Process Level:
- Runbooks for incident response
- Regular disaster recovery testing
- Chaos engineering to find weaknesses
- Post-incident reviews to prevent recurrence
- •No timeouts on network calls
- •Retries without backoff
- •No circuit breakers
- •Shared resource pools
- •Synchronous chains of calls
- •No fallback behavior defined
- •Timeouts everywhere
- •Exponential backoff with jitter
- •Circuit breakers on dependencies
- •Bulkhead isolation
- •Async where possible
- •Defined degraded modes
Netflix operates under the assumption that any component can fail at any time. Their Chaos Monkey randomly terminates instances in production to ensure the system handles failures gracefully. This mindset—assuming failure rather than hoping for reliability—drives resilient design decisions from day one.
KEEP LEARNING
There’s more to understand.
You’re reading the introduction. Create a free account to read the complete lesson, save notes and keep track of your progress.