Skip to main content

Introduction

Many applications today are data-intensive rather than compute-intensive. The biggest challenges are usually the amount of data, the complexity of data, and the speed at which it’s changing—not the raw CPU power. A data-intensive application is typically built from standard building blocks:
  • Databases: Store data for later retrieval
  • Caches: Remember expensive operation results to speed up reads
  • Search indexes: Allow users to search/filter data by keywords
  • Stream processing: Send messages to other processes for asynchronous handling
  • Batch processing: Periodically crunch large amounts of accumulated data
This chapter focuses on three fundamental concerns in software systems:
  1. Reliability: The system continues working correctly even when things go wrong
  2. Scalability: As the system grows, there are reasonable ways of dealing with that growth
  3. Maintainability: Over time, different people will work on the system, and they should be able to work on it productively

1. Reliability

Reliability means the system continues to work correctly, even when things go wrong. The things that can go wrong are called faults, and systems that anticipate faults and can cope with them are called fault-tolerant or resilient.
A fault is not the same as a failure:
  • Fault: One component deviating from its spec
  • Failure: The whole system stops providing the required service to the user
The goal is to prevent faults from causing failures.

1.1 Hardware faults

Hardware faults are common in large datacenters:
  • Hard disks crash
  • RAM becomes faulty
  • Power grid has a blackout
  • Network cables are unplugged
Hardware redundancy examples:
  • Disks set up in RAID configuration
  • Dual power supplies
  • Hot-swappable CPUs
  • Backup power (batteries and diesel generators)
Modern trend: Moving toward systems that can tolerate the loss of entire machines by using software fault-tolerance techniques in addition to hardware redundancy. Example: AWS spot instances can be terminated at any moment, so applications must be designed to handle sudden loss of machines.

1.2 Software errors

Software errors are systematic and harder to anticipate: Real-world examples:
  • Linux leap second bug (2012): Many applications hung due to a kernel bug triggered by leap second
  • Memory leak: Process gradually uses more memory until system runs out
  • Cascading failure: One server’s slowdown causes others to overload
Mitigation approaches:

1.3 Human errors

Humans are known to be unreliable. Studies show that configuration errors by operators are a leading cause of outages. Best practices to minimize human errors:
  1. Design systems that minimize opportunities for error
    • Well-designed abstractions, APIs, and admin interfaces
    • Example: Admin UI that makes it easy to “do the right thing” and discourages “the wrong thing”
  2. Decouple the places where people make mistakes from the places where they can cause failures
    • Provide fully featured sandbox environments where people can explore safely
    • Example: Separate staging environment identical to production
  3. Test thoroughly at all levels
    • Unit tests, integration tests, and manual tests
    • Automated testing for corner cases
  4. Allow quick and easy recovery from human errors
    • Fast rollback of configuration changes
    • Roll out new code gradually
    • Provide tools to recompute data
  5. Set up detailed and clear monitoring
    • Telemetry: performance metrics and error rates
    • Early warning signals

2. Scalability

Scalability is the term we use to describe a system’s ability to cope with increased load. Even if a system is working reliably today, that doesn’t mean it will necessarily work reliably in the future with 10x or 100x the load.

2.1 Describing load

Load can be described with a few numbers called load parameters. The best choice depends on your system architecture: Example: Twitter’s scaling challenge (circa 2012) Twitter has two main operations:
  1. Post tweet: User publishes a new message (4.6k requests/sec on average, 12k at peak)
  2. Home timeline: User views tweets from people they follow (300k requests/sec)
Approach 1: Insert tweet and query at read time
Approach 2: Maintain a cache for each user’s home timeline
Challenge: Users with millions of followers create a fan-out load challenge. If a celebrity with 30 million followers posts a tweet, that’s 30 million writes to home timeline caches! Twitter’s solution: Hybrid approach
  • Most users: fan-out on write (fast reads)
  • Celebrities: fan-out on read (avoid massive writes)

2.2 Describing performance

Once you’ve described load, you can investigate what happens when load increases:
  • When load increases and system resources stay the same, how is performance affected?
  • When load increases, how much do you need to increase resources to keep performance unchanged?
Use percentiles not averages to measure response time.
Example: Monitoring response time percentiles
Queueing delays and head-of-line blocking: Even if only a small percentage of backend calls are slow, if a user request requires multiple backend calls, the probability of getting a slow call increases (tail latency amplification).

2.3 Approaches for coping with load

How do we maintain good performance when load parameters increase by an order of magnitude? Scaling strategies:
  1. Vertical scaling (scaling up): Moving to a more powerful machine
    • Simpler to implement
    • Limited by maximum machine size
    • Single point of failure
  2. Horizontal scaling (scaling out): Distributing load across multiple smaller machines
    • Can scale indefinitely
    • More complex (distributed systems challenges)
    • Better fault tolerance
Reality: Most large-scale systems use a pragmatic mixture of both approaches. Example: Designing for scale
There is no one-size-fits-all scalable architecture. The architecture of systems that operate at large scale is usually highly specific to the application.
Example volume calculations:

3. Maintainability

The majority of the cost of software is in ongoing maintenance:
  • Fixing bugs
  • Keeping systems operational
  • Investigating failures
  • Adapting to new platforms
  • Modifying for new use cases
  • Repaying technical debt
  • Adding new features

3.1 Operability: Making life easy for operations

Good operability means making routine tasks easy, allowing operations teams to focus on high-value activities. Good operability practices:

3.2 Simplicity: Managing complexity

As projects grow, they often become very complex and difficult to understand. This complexity slows down everyone who needs to work on the system. Solution: Abstraction - hiding implementation details behind clean, simple-to-understand interfaces. Example: Good abstraction

3.3 Evolvability: Making change easy

System requirements change constantly:
  • New facts are learned
  • Unexpected use cases emerge
  • Business priorities shift
  • Users request new features
  • New platforms emerge
  • Legal or regulatory requirements change
  • Growth in scale requires architectural changes
Example: Designing for evolvability with clean interfaces
Designing for change: Make it easy to add new payment providers without modifying existing code (Open/Closed Principle).

Summary

This chapter explored three fundamental concerns in software systems:

Reliability

Key takeaways:
  • Anticipate and handle faults to prevent failures
  • Use redundancy for hardware faults
  • Minimize opportunities for human error
  • Test thoroughly and monitor continuously

Scalability

Key takeaways:
  • Describe load with specific parameters relevant to your system
  • Measure performance with percentiles, not just averages
  • No universal scalability solution—design for your specific needs
  • Plan for growth: vertical scaling (up) or horizontal scaling (out)

Maintainability

Key takeaways:
  • Operability: Make it easy to keep the system running smoothly
  • Simplicity: Manage complexity with good abstractions
  • Evolvability: Design for change from the start
These three concerns (reliability, scalability, maintainability) are the foundation for building data-intensive applications that can grow and evolve successfully over time.