The Network at Scale: Distributed Systems for People Who Use Them Without Knowing It
SummaryReveals that every modern web application is already...
Reveals that every modern web application is already...
Reveals that every modern web application is already a distributed system, then systematically dismantles the abstractions hiding CAP theorem tradeoffs, eventual consistency realities, distributed computing fallacies, and the organizational-vs-technical tension of microservices.
The Network at Scale: Distributed Systems for People Who Use Them Without Knowing It
You’re Already Running One
You have a web application. It talks to a PostgreSQL database. You’ve got Redis in front for caching. There’s a background job queue — maybe Celery, maybe Sidekiq. A third-party payment processor. An email service. An S3 bucket for file uploads.
Congratulations. You’re running a distributed system.
Not the kind they teach in PhD seminars with Lamport clocks and vector timestamps. The kind that fails at 2 AM on a Saturday because your cache returned stale data, your database connection pool was exhausted, and your payment processor timed out in the middle of charging someone’s credit card. Twice.
The moment your application spans more than one process — and it does — you’ve entered distributed systems territory. Every abstraction you’ve stacked on top exists to hide that fact from you. Your ORM pretends the database is local memory. Your HTTP client pretends the network is a function call. Your cache library pretends stale data doesn’t exist. These are useful lies, right up until they aren’t.
This chapter is about what happens when the lies break down.
The CAP Theorem: Choose Your Pain
In 2000, Eric Brewer conjectured — and in 2002, Seth Gilbert and Nancy Lynch proved — that a distributed data store can provide at most two of three guarantees simultaneously: Consistency, Availability, and Partition Tolerance.
That sounds academic. Here’s what it means in practice.
Consistency means every read gets the most recent write. If you update a user’s email address, the next read from any node returns the new email. No surprises.
Availability means every request gets a response. Not necessarily the most current data, but a response. The system doesn’t hang, doesn’t error, doesn’t leave you staring at a spinner.
Partition Tolerance means the system keeps operating even when network messages between nodes are dropped or delayed. Some nodes can’t talk to other nodes, but the system doesn’t collapse.
Here’s the thing about partition tolerance: you don’t get to opt out. Networks partition. Cables get cut. Switches fail. Cloud availability zones lose connectivity to each other. This isn’t a theoretical concern — AWS has had multiple events where one AZ couldn’t reach another for minutes or hours. Partition tolerance isn’t a feature you choose. It’s reality you accept.
So the real choice is between consistency and availability. When a network partition happens — and it will — do you want your system to refuse to respond until it can guarantee correctness? Or do you want it to respond, knowing the response might be based on stale data?
The Shopping Cart During a Partition
Picture an e-commerce platform with two database replicas, East and West. A customer in New York adds an item to their cart. That write goes to the East replica. Another customer in Seattle views the same shared wishlist. That read goes to the West replica.
Now the network link between East and West drops.
If you chose consistency (CP system): The West replica knows it might have stale data. It refuses to serve the read. The Seattle customer sees an error or a loading spinner. The system is correct but unavailable.
If you chose availability (AP system): The West replica serves what it has. The Seattle customer sees the wishlist without the newly added item. The system is available but inconsistent.
Neither option is wrong. Both are painful. The actual decision depends on your domain. For a shopping cart? Availability usually wins — a stale view is better than an error. For a bank account balance? Consistency wins — showing the wrong number is unacceptable.
The problem isn’t the tradeoff itself. The problem is that most engineers don’t know they’re making it. They pick a database, deploy it in a cluster, and assume everything is fine. Then a partition happens, and they discover — live, in production, with real money on the line — which tradeoff they accidentally chose.
Eventual Consistency: What “Eventually” Really Means
“Eventual consistency” is the term distributed systems use when they chose availability over consistency. It means: all replicas will converge to the same state… eventually. The word “eventually” is doing an enormous amount of heavy lifting in that sentence.
Here’s what eventual consistency actually looks like with timestamps:
T=0ms User updates profile name to "Alice" on Node A
T=0ms Node A acknowledges the write. Response: success.
T=2ms User reads profile from Node B (load balancer's choice)
Node B returns: "A1ice_old_name" ← STALE
T=15ms Node A begins replicating to Node B
T=45ms Replication message arrives at Node B
T=46ms Node B applies the update
T=50ms User reads profile from Node B
Node B returns: "Alice" ← CURRENT
For 46 milliseconds, Nodes A and B disagreed about reality. If you’re thinking “46 milliseconds is nothing,” you’re right — under normal conditions. Now imagine the replication link is congested. Or the receiving node is under heavy load. Or there’s a partial network issue. That 46ms becomes 500ms. Or 5 seconds. Or 30 seconds.
During that window, different users — or the same user hitting different nodes — get different answers to the same question. Your user updates their display name and immediately sees the old one. They refresh. Still old. They refresh again. New name. They refresh again. Old name (different node). This isn’t a bug in your code. It’s the system working exactly as designed.
The question isn’t whether eventual consistency is acceptable. It’s whether your application can tolerate the inconsistency window, and whether your users will understand what’s happening when they see stale data. For a social media “likes” counter, nobody cares if it’s 3 seconds behind. For an inventory count that determines whether a customer can place an order? Those 3 seconds generate oversells and angry emails.
The Fallacies of Distributed Computing
In 1994, Peter Deutsch compiled a list of eight assumptions that developers new to distributed systems invariably make. All eight are false. All eight will burn you. Three of them are particularly lethal.
Fallacy 1: The Network Is Reliable
Every requests.get() call you make assumes the network will deliver your message and return a response. It won’t — not always. Packets drop. Connections reset. Servers crash mid-response. DNS fails.
The really insidious version: your request arrives, the remote server processes it, and the response gets lost on the way back. You don’t know if the operation succeeded or failed. You retry. If the operation wasn’t idempotent — say, charging a credit card — you just charged the customer twice.
Without explicit timeout, retry, and idempotency handling, every network call is a gamble. Your SDK hides this from you. The HTTP client defaults to “wait forever.” The database driver reconnects silently. The message queue acknowledges delivery but not processing. Every abstraction layer whispers “the network is reliable” and hopes you don’t look too closely.
Fallacy 2: Latency Is Zero
A function call within your process takes nanoseconds. A network call to a service in the same data center takes 1-5 milliseconds. That’s a million-fold difference. When you decompose a monolith into 20 microservices and a single user request touches 6 of them sequentially, you’ve added 6-30ms of pure network latency. Add serialization, deserialization, load balancer hops, TLS handshakes, and you’re at 50-100ms before your code even starts doing useful work.
This compounds. Twenty services, each calling two others, each adding 5ms. Your request waterfall looks like a staircase descending into misery.
Fallacy 3: The Network Is Homogeneous
Your service in us-east-1 calls a service in eu-west-1. Latency jumps from 2ms to 80ms. Your load balancer distributes traffic equally, but one availability zone has a degraded network switch, so 33% of requests take 3x longer than the others. Your DNS TTL is 60 seconds, but the client library caches it for 300 seconds, so after a failover, one-third of your fleet is still sending traffic to a dead endpoint.
The network is not a uniform, symmetric, predictable medium. It’s a patchwork of different hardware, different software, different configurations, and different failure modes. Treating it as homogeneous means your system works perfectly in your local Docker Compose setup and fails in production in ways you never anticipated.
These fallacies don’t go away with better abstractions. They’re properties of physics and infrastructure. You can mitigate them — retries, timeouts, circuit breakers, idempotency keys — but you cannot abstract them away.
Microservices: Solving the Org Chart, Breaking the Architecture
Microservices solve a real problem, and it’s not a technical one.
Conway’s Law, formulated in 1967, states that organizations design systems that mirror their communication structures. If you have four teams, you’ll get four services. This isn’t a suggestion — it’s an observation with the predictive power of gravity.
Large organizations need independent teams that can deploy independently. A monolith with 200 developers and a single deployment pipeline means every team is blocked by every other team’s bugs. Microservices let Team A deploy their service without waiting for Team B to fix their failing tests. Team A owns their service, their database, their deployment schedule.
This is a legitimate organizational benefit. The cost is that you’ve replaced in-process function calls with network calls. And network calls, as we’ve just discussed, are unreliable, have non-zero latency, and traverse a heterogeneous medium.
Here’s the monolith debugging experience: an order fails. You open one log file. You set one breakpoint. You step through the code. Orders → Inventory → Payment → Notification. One process. One stack trace. Twenty minutes to root cause.
Here’s the microservices debugging experience: an order fails. You open the distributed trace. The Order Service called the Inventory Service, which called the Pricing Service, which timed out calling the Discount Service, which was waiting on the User Service, which had a connection pool exhaustion issue caused by a slow query in the Profile Database. Five services, five log streams, five different deployment versions, five different teams who all say “it’s not our service.” Three hours to root cause — if you have distributed tracing set up. Three days if you don’t.
The irony is thick: microservices solved the organizational coordination problem by creating a technical coordination problem.
The SDK Trap: One Line, Seven Hops
Consider this line of code:
result = stripe.Charge.create(amount=2000, currency="usd", source=token)
One line. One function call. Here’s what actually happens:
- Your code serializes the request parameters to JSON
- The Stripe SDK constructs an HTTP request with authentication headers
- Your application’s HTTP client resolves
api.stripe.comvia DNS (cache miss? that’s another network call) - A TLS handshake establishes a secure connection (1-2 round trips)
- The request traverses your VPC, through a NAT gateway, through the internet
- Stripe’s load balancer routes the request to an application server
- Stripe’s server validates your API key, checks rate limits, processes the charge across their internal distributed system (itself involving multiple services, databases, and third-party connections to payment networks)
- The response traverses the same path in reverse
- Your SDK deserializes the response and either returns an object or raises an exception
Seven network hops minimum. Any one of them can fail, timeout, or return an unexpected result. The SDK gave you a function call interface. Under that interface is a distributed system interaction spanning multiple organizations, networks, and failure domains.
When that one line fails — and it will — you need to know which of those seven hops broke. Was it DNS? TLS? A Stripe outage? A rate limit? An invalid token? A network partition between your VPC and the internet? The abstraction can’t help you diagnose any of these. It can only throw an exception and hope you handle it correctly.
The SDK is a gift and a trap simultaneously. It makes distributed systems calls ergonomic. It also makes them look safe. They are not safe. They are the most dangerous lines of code in your application, and they look identical to the safest ones.
What You’re Not Allowed to Not Know
You don’t need a PhD in distributed systems. You don’t need to implement Paxos or understand vector clocks. But you do need to know:
- Your system is distributed. The moment you have a database on a different machine, you’re in distributed territory.
- CAP is a constraint, not a choice. You’re already on one side of the tradeoff. Figure out which side, before production figures it out for you.
- Eventual doesn’t mean instant. Know your consistency window. Know what happens in it Stale reads, duplicate writes, lost updates — which ones can your application tolerate?
- The network is hostile. Every network call needs a timeout, a retry strategy, and a plan for when retries fail.
- Microservices are an organizational pattern. If you don’t have the organizational problem, you’re paying technical costs for zero benefit.
- SDKs hide complexity, they don’t remove it. That one-line API call is the most dangerous line in your codebase. Treat it accordingly.
The invisible layer between your code and the network is the thickest one in your entire stack. Every other abstraction in this book hides complexity that’s annoying but manageable. This one hides complexity that’s fundamental and irreducible. The network doesn’t care about your abstractions. It will partition, lag, and fail on its own schedule. Your only defense is understanding what’s actually happening beneath the API call, the SDK, and the managed service dashboard.
That understanding starts with the fallacies, continues through the tradeoffs, and ends with the discipline to treat every network boundary as the fault line it actually is.