This is Chapter 1 of the System Design series.
Architecture reviews and design discussions keep coming back to the same vocabulary regardless of what’s being built. I keep this list so I’m reasoning from consistent definitions each time, not re-deriving the same trade-offs from scratch.
Core concepts
Systems Always Lose Consistency - CAP Is Fundamental
- Scalability
- Availability
- Latency
- Consistency
- CAP
- Idempotency
- Fault tolerance
1. Scalability
- Vertical scaling (bigger machine) buys time but hits a ceiling fast and creates a single point of failure.
- Horizontal scaling (more machines) is the default for anything expected to grow past a single box, at the cost of needing to handle state, coordination, and partial failure across nodes.
2. Availability
Usually expressed in nines:
| Availability | Downtime per year |
|---|---|
| 99% | ~3.65 days |
| 99.9% | ~8.76 hours |
| 99.99% | ~52.6 minutes |
| 99.999% | ~5.26 minutes |
Each additional nine costs disproportionately more in redundancy, automation, and operational discipline. Match the target to what the business actually needs - five nines on an internal reporting tool is wasted spend.
3. Latency vs throughput
- Latency is how long one request takes.
- Throughput is how many requests the system handles per unit of time.
They trade off: batching improves throughput by adding latency to individual requests. A system tuned for one is rarely optimal for the other.
4. Consistency models
- Strong consistency means every read sees the latest write, which requires coordination and costs latency.
- Eventual consistency allows reads to lag behind for a window, in exchange for availability and lower latency.
Most systems don’t need strong consistency everywhere - decide it per data type, not per system.
5. CAP theorem and PACELC
- CAP: under a network partition (P), a system chooses between consistency (C) and availability (A); it cannot have both.
- PACELC extends this to the normal case: even without a partition, there’s a trade-off between latency (L) and consistency (C).
CAP gets cited more than it’s useful day to day - PACELC’s latency/consistency trade-off is the one that shows up in almost every real design decision.
6. Idempotency
- An operation that produces the same result no matter how many times it’s applied.
Required for safe retries - a payment API without idempotency keys will double-charge on a network timeout where the client retries a request the server actually processed.
7. Fault tolerance
- The system keeps serving correct results when a component fails.
Built from redundancy (no single point of failure), health checks, timeouts, and graceful degradation - not from hoping the failure doesn’t happen.
Building blocks
Layers Compose Distributed Messaging And Caches Over Vectors
- Load balancers
- Caches
- Databases
- Message queues
- API gateways
- CDNs
- Object storage
- Vector databases
1. Load balancers
Distribute requests across instances:
| Algorithm | How it works | Good fit |
|---|---|---|
| Round robin | Cycles through instances in order | Uniform request cost, similar instance capacity |
| Least connections | Sends to the instance with fewest active connections | Variable request duration |
| Consistent hashing | Maps requests to instances by hash, minimising remapping when instances change | Sticky sessions, cache-friendly routing |
2. Caches
Sits between the application and a slower data source. The two decisions that matter:
- Eviction policy: which entry gets removed when the cache is full.
- Write strategy: how writes flow between the cache and the backing store.
| Strategy | How it works | Trade-off |
|---|---|---|
| LRU eviction | Evicts least recently used entry | Simple; misses stable-but-infrequent hot data |
| LFU eviction | Evicts least frequently used entry | Better for stable hot data; higher bookkeeping cost |
| Write-through | Writes to cache and store together | Safe; adds write latency |
| Write-behind | Writes to cache first, flushes to store async | Fast; risks data loss on cache failure |
Cache invalidation is the hard part in practice, not lookup speed - stale data from a forgotten invalidation path causes more incidents than the cache ever saves in latency.
3. Databases
- SQL stores structured, relational data with schemas, joins, and ACID transactions.
- NoSQL covers a family of non-relational stores, each optimised for a different access pattern.
| Type | Examples | Good fit |
|---|---|---|
| SQL (relational) | PostgreSQL, MySQL | Structured data, joins, transactions |
| Key-value | Redis, DynamoDB | Simple lookups by key, high throughput |
| Document | MongoDB | Semi-structured data, flexible schema |
| Wide-column | Cassandra | Write-heavy, time-series, large scale |
| Graph | Neo4j | Highly connected data - social graphs, recommendations |
4. Message queues
Kafka, RabbitMQ, SQS, and Pub/Sub decouple producers from consumers, absorb load spikes, and let consumers fail and recover without losing work.
- At-least-once delivery is simpler and pairs with idempotent consumers.
- Exactly-once delivery adds real coordination complexity for a guarantee most systems don’t strictly need.
5. API gateways and reverse proxies
In front of backend services, so individual services don’t each re-implement them:
- Centralise routing
- Authentication
- Rate limiting
- TLS termination
6. CDNs
The right tool for anything that doesn’t change per-request:
- Cache static assets at edge locations
- Cut latency and offload origin traffic
7. Object storage
Large, immutable blobs that don’t belong in a relational database:
- Images and video
- Backups
- Logs
8. Vector databases and LLM gateways
- Vector databases store embeddings for similarity search, the backbone of retrieval-augmented generation.
- LLM gateways centralise model routing, rate limiting, and cost tracking across providers the same way an API gateway does for internal services.
Architecture patterns
Strong Replicas Scale Easily - Microservices Matter
- Sharding
- Replication
- Scaling reads/writes
- Event-driven
- Microservices
- Multi-region
1. Sharding and partitioning
Splits data across nodes so no single node holds the whole dataset:
- Consistent hashing on a key is the standard distribution mechanism
- The shard key is the one decision in a sharded system that’s expensive to change later - get it wrong and every cross-shard query becomes a fan-out. Spend the most time here.
2. Replication
Copies data across nodes for redundancy and read scaling:
- Leader-follower replication has one node accept writes and others replicate from it - simple, but the leader is a bottleneck and a single point of failure for writes.
- Multi-leader replication accepts writes on multiple nodes, trading that bottleneck for conflict resolution complexity.
3. Scaling reads vs writes
Read and write scaling are different problems:
- Read-heavy systems scale with replicas and caching.
- Write-heavy systems scale with sharding, since replication doesn’t help when every node still has to process every write.
4. Event-driven architecture and pub-sub
Decouples producers from consumers through events rather than direct calls:
- A producer doesn’t need to know who’s listening
- New consumers can be added without touching the producer
5. Microservices
Trades a simpler deployment and debugging story for independent scaling and deployment per service:
- Network calls replace function calls
- Distributed-systems failure modes replace in-process ones
- The trade-off only pays off once team size and deployment cadence justify the operational overhead
6. Multi-region architecture
Addresses latency and disaster recovery:
- Latency - serve users from the nearest region
- Disaster recovery - survive a regional outage
Also reintroduces the CAP trade-off at a larger scale - cross-region writes either wait for consensus or accept eventual consistency.
Capacity estimation anchor points - the numbers worth memorising so back-of-envelope math doesn’t stall a design discussion:
- 1 day ≈ 86,400 seconds
- DAU x average requests per user / 86,400 ≈ requests per second
- Peak traffic is typically 2-5x average - size for peak, not average
- Storage = record size x record count x replication factor - then add headroom for growth over the system’s expected lifetime
Trade-offs
| Choice | Pick the first when… | Pick the second when… |
|---|---|---|
| SQL vs NoSQL | Data is structured, transactions matter | Schema is fluid, scale matters more than joins |
| Strong vs eventual consistency | Correctness can’t tolerate a stale read (balances, inventory) | Availability and latency matter more (social feeds, view counts) |
| Monolith vs microservices | Team is small, product is early | Independent scaling and deployment outweigh the operational cost |
| Normalization vs denormalization | Write consistency and storage efficiency matter | Read performance matters more than write complexity |
| REST vs RPC vs GraphQL | Public API, caching, simplicity matter | Internal service-to-service calls (RPC) or flexible client queries (GraphQL) matter more |
| Batch vs stream processing | Latency doesn’t matter, throughput and cost do | Results are needed in near real time |
| ACID vs BASE | Transactional correctness is non-negotiable | Availability and scale outweigh strict consistency |
What’s different in 2026
- AI components (embeddings, vector search, RAG) show up in mainstream designs the same way caching and queues do - no longer a separate track.
- Cost is a first-class constraint, not an afterthought - a design that burns budget unnecessarily is a weaker answer than one that’s cost-aware from the start.
- Real-time is the default expectation rather than a special requirement to call out.
- Multi-region reliability, once reserved for a handful of global platforms, is now assumed for anything with a meaningful user base.
How I work through a design problem
Careful Engineers Don’t Skip Deep Analyses
- Clarify requirements
- Estimate scale
- Define the API
- Sketch the high-level design
- Deep-dive
- Address bottlenecks
- Clarify requirements - functional scope, scale, and the constraints that actually shape the design (latency targets, consistency needs, budget)
- Estimate scale - rough numbers for traffic, storage, and bandwidth using the anchor points above
- Define the API - the contract the rest of the design has to support
- Sketch the high-level design - the major components and how data flows between them
- Deep-dive - pick the one or two components doing the most interesting work and go deep
- Address bottlenecks and failure modes - what breaks first under load, and what happens when it does
The order matters more than the content of any single step - skipping straight to a deep-dive before the scale is estimated produces a design that’s solving the wrong problem.
Notes
Next: Chapter 2 - URL Shortener (coming soon)