Is eight copies of one app microservices
No, it is one codebase deployed many times, which is a horizontally scaled monolith.
Copies of the same program are still a monolith, just a scaled one, and that is a fine architecture. Every technique in units 2 through 7 applies to it, so scaling and microservices are separate questions that people routinely conflate.
The distinguishing feature is deployability rather than machine count. Eight copies ship together from one build, and microservices means each piece ships on its own schedule.
Microservices means splitting the codebase itself into separately deployed programs, usually with their own databases. This lesson is about when that split is worth its considerable cost.
Two ways to organize the same code
A monolith is one program containing all your features: auth, payments, feed, search, one codebase, one deploy. Everything we built in units 1 through 7 works perfectly with a monolith.
Microservices split those features into separate programs, each with its own deploys and often its own database, talking to each other over the network.
What microservices genuinely buy:
- Independent deploys: the payments team ships without waiting for the feed team
- Independent scaling: run 40 copies of the hot service, 2 of the quiet one
- Failure isolation: a crash in search does not take down checkout
What they genuinely cost:
- Every in-process function call becomes a network call: slower, and it can fail (the How the Internet Works lesson again)
- Debugging spans machines, testing needs many services running
- Data is split, so no joins across services and no single transaction
The honest rule: microservices solve an organizational problem, too many engineers stepping on one codebase. Under about 20 engineers, a well-factored monolith is almost always the better system.
The latency cost of a network hop
A function call inside one process costs about a microsecond, and a call between services in the same data center costs about 2 ms.
in_process_call_ms = 0.001 network_hop_ms = 2 calls_in_chain = 5 print("monolith, 5 in-process calls:", round(in_process_call_ms * calls_in_chain, 3), "ms") print("microservices, 5 network hops:", network_hop_ms * calls_in_chain, "ms")
Output
monolith, 5 in-process calls: 0.005 ms microservices, 5 network hops: 10 ms
A 2000x difference per call chain, before any failures or retries are added. Ten milliseconds is a meaningful share of a request budget that used to be 25 ms in unit 1.
The latency is the smaller half of the cost, and reliability is the larger one. A function call cannot fail on its own, and a network call can time out, return an error, or succeed after the caller gave up, so each hop needs a timeout and a fallback from lesson 8-3.
Availability multiplies rather than averages across a chain. Five services at 99.9% each give 99.5% for the chain, which is roughly four hours of downtime a month created purely by the split.
The chain length is the thing to design against. A request touching two services is usually fine, and one touching twelve is a system where no single team can explain a slow request, which is why deep call chains are the classic microservices regret.
Note that these numbers assume the same data center. A cross-region hop is 50 to 150 ms, so a chain that accidentally crosses regions turns a 10 ms cost into half a second.
API contracts between services
Once features live in separate services, the request and response formats between them become contracts: agreements that other teams' code depends on.
The cardinal rule is backward compatibility. The feed service cannot rename a field in its response on Tuesday, because the three services reading that field would break instantly. Safe changes add optional fields. Breaking changes require a versioned API (/v1/feed, /v2/feed), running both versions while every caller migrates.
Teams write contracts down in machine-readable form, OpenAPI specs for HTTP APIs or protobuf schemas for gRPC, so both sides can generate code and catch mismatches before deploy. When an interviewer asks how services communicate, naming the contract and its versioning story is what separates a real answer from a hand wave.
The safe way to rename a field
Add total_cents alongside total, migrate the four readers, then retire total.
Add the new field, keep the old one, migrate readers at their own pace, then remove the old field once nothing reads it. This is an expand-and-contract migration, and it is the standard answer for any change to a shared contract.
The one-deploy rename guarantees breakage for every consumer at the same moment. There is no ordering of deploys that avoids it, since the readers cannot be updated before the field exists and cannot survive after the old one is gone.
The expand phase costs a little duplication and buys independence. Both fields carry the same value for a while, so each consuming team migrates on its own schedule, which is the property microservices were adopted for in the first place.
The contract phase needs evidence rather than optimism. Logging which callers still read total, or tracking it per client version, is what tells you the removal is safe, and skipping that step is how the final deploy breaks a forgotten consumer.
Note that the rename is worth questioning at all. total_cents is clearer than total, and the improvement has to justify a multi-team migration, so on a busy shared contract the answer is sometimes to leave it alone.
Backward compatibility is the tax you pay for independent deploys. A monolith renames the field in one commit, and that contrast is a fair summary of the whole trade this lesson describes.