When to Break Up a Monolith (and When Not To)

ModernLift · ·11 min read
Part 2 of 10

Break up a monolith when a specific, measurable pressure justifies the cost of distribution — one component needs to scale independently, multiple teams are blocked on a single release train, or a component's failure must be isolated from the rest. Do not break it up because it is large, old, or "not microservices." If the real problem is a tangled codebase, modularize the monolith first; distribution will not fix coupling, only relocate it across the network.

Part 1 ended on a claim: microservices are the right tool only when you can name the pressure that justifies them. This part is about naming it honestly — and about resisting the much more common case, where the only pressure is a slide deck that says the architecture is dated.

The decision to decompose a monolith is one of the most expensive architectural commitments a team can make. It reshapes the codebase, the deployment pipeline, the data model, the org chart, and the on-call rotation. A decision that large deserves a real test, not a vibe. The test is simple: what specific pressure are you under, and is decomposition the cheapest thing that relieves it?

The four pressures that justify extraction

There are essentially four reasons to pull a piece of a monolith out into its own service. Each is concrete and measurable. If you have one, you have a case.

1. Independent scaling. One part of the system consumes a wildly disproportionate share of resources, on a different curve from everything else. A pricing engine recalculates on every price change; a search index rebuilds constantly; a media transcoder pegs the CPU. Cloning the entire monolith to scale that one hot path wastes money on everything else that came along for the ride. Extracting the hot path lets you scale only what needs scaling. The test: can you point to the component, and to the metric that shows its load diverging from the rest?

2. Team autonomy at the deploy boundary. Many teams are serialized behind a single release train — one team’s half-finished work blocks everyone’s deploy, merge conflicts are constant, and the release calendar has become a negotiation. This is the pressure that scales with headcount, not load. Splitting the system along team ownership lines lets each team deploy on its own cadence. The test: are deploys actually blocked on cross-team coordination today, or are you imagining a problem you might have at three times the size?

3. Fault isolation. One component’s instability repeatedly takes down the whole system. A flaky third-party integration, a memory leak in a reporting module, a runaway query — and because it shares a process with checkout, checkout goes down with it. Extracting the unstable component behind a boundary contains its blast radius. The test: can you name the incidents where a non-critical component took down a critical one?

4. Divergent technology needs. One domain genuinely needs a different runtime or data store — a graph database for a recommendations engine, a different language for a compute-heavy numerical service — and forcing it into the monolith’s stack is doing real harm. The test: is this a true mismatch, or just a preference?

If you can write down the pressure and the metric beside it, decomposition is on the table. If you cannot, it is not.

The symptoms that look like a case but aren’t

Most monolith pain presents as an architecture problem and is actually a coupling problem. The distinction is the whole game, because distribution does not fix coupling — it relocates it across a network, where it is slower and harder to debug.

  • “Every change is risky and touches half the codebase.” That is internal coupling, not a deployment-topology problem. You fix it by drawing module boundaries inside the monolith. Splitting it into services without first untangling it just turns risky in-process changes into risky cross-service changes — the distributed monolith this series warns about.
  • “The codebase is too big to understand.” Size is not coupling. A large, well-modularized monolith is navigable. Break it into bounded modules with clear interfaces and it becomes legible — no network required.
  • “Our deploys are slow / our test suite takes an hour.” Those are pipeline and test-architecture problems. Microservices can worsen them by multiplying the number of pipelines. Fix the pipeline first.
  • “Microservices are the modern way to build.” This is not a pressure. It is the single most expensive sentence in software architecture.

The reversal worth keeping: a tangled monolith and a clean one share a name and nothing else. The mud is the problem, and you do not need the network to clean it up.

Signals and anti-signals, side by side

The whole decision compresses into one question: is the thing pushing you a real pressure, or a look-alike you can fix in place? Read across each row. A real signal on the left earns a service. A look-alike on the right earns a module boundary, not a network call.

Extract a service when you can point to…Modularize in place when it is really…
One component’s load diverging from the rest on its own curveA uniform load profile a single app scales horizontally just fine
Teams stalled behind one release train, blocked on cross-team coordinationSlow deploys or an hour-long test suite, a pipeline problem distribution worsens
A named incident where a flaky component took a critical one down with it“Every change is risky and touches half the codebase,” which is internal coupling
One domain that genuinely needs a different runtime or data store“The codebase is too big to understand,” which is size, not topology
A headcount jump you can name and date, invested in as an explicit bet“Microservices are the modern way to build,” which is not a pressure at all

Every left-hand row names a problem distribution actually solves. Every right-hand row names a problem distribution makes worse, more expensive, or both. If your reason lives in the right column, you have a modularization project, not a migration.

What decomposition actually costs

Everything above is the benefit side. The bill arrives the moment the first service leaves the process, and it is worth naming in full, because it is the cost the “modern architecture” pitch always leaves out.

  • In-process calls become network calls. A function call that could not fail now has latency, timeouts, retries, and partial failure. Code that never had to ask “what if the other half is down” has to ask it everywhere.
  • One transaction becomes a distributed workflow. Work that used to commit or roll back atomically now spans services. You trade strong consistency for eventual consistency, saga logic, and compensating actions for the cases that half-complete.
  • You buy a second system just to see the first. Service discovery, distributed tracing, centralized logging, and per-service metrics stop being optional. Without them, “where did this request die?” has no answer.
  • Deploys become contract management. Independent deploys are the point, but they mean versioning the interfaces between services, keeping every change backward compatible, and sequencing rollouts so a new caller never meets an old callee.
  • On-call gets harder. A single user action fans out across services, and the failures move into the seams between them, which are the hardest place to reproduce and the easiest place to page someone at 3am.

None of this is an argument against microservices. It is the price of admission. If one of the four pressures is real, you pay it and get back something worth more. If none of them is, you pay it and get back nothing.

When NOT to break it up

Honesty is the brand promise, so the boundary gets stated plainly. Leave the monolith alone — or modularize it in place — when:

  • The team is too small to operate a distributed system. A fleet of services needs observability, service discovery, distributed tracing, and people on call who understand all of it. If you are a handful of engineers, microservices will consume the capacity you meant to spend on the product.
  • The load profile is uniform. If nothing scales on a meaningfully different curve from anything else, independent scaling buys you nothing — and a single application scales horizontally just fine.
  • The system is stable and under no roadmap pressure. A quiet system nobody is asking anything new of does not need re-architecting. Modernization is a means, not a virtue; if nothing the business needs is blocked, the right answer is to leave it running.
  • You haven’t modularized yet. Always exhaust the in-process option first. A modular monolith is cheaper, faster to reach, and the correct foundation if you do extract services later.

This is why the pendulum swings both ways. The Amazon Prime Video case from Part 1 — consolidating a distributed pipeline back into a monolith for a ~90% infrastructure cost reduction (Amazon Prime Video Tech blog, 2023) — is exactly this principle applied in reverse: the pressure that once justified distribution stopped justifying it, and a good team noticed.

A short decision path

  1. Can you name a specific scaling, autonomy, isolation, or technology pressure, with a metric? If no, stop — modularize in place or leave it alone.
  2. Have you already drawn clean module boundaries inside the monolith? If no, do that first; it may resolve the pain entirely and it is a prerequisite either way.
  3. Can your team operate a distributed system without starving the product? If no, the cost outweighs the benefit today.
  4. Does the pressure apply to a specific component, not the whole system? If yes, extract that component — not the whole thing. Decomposition is surgical, not wholesale.

Most teams that walk this path honestly extract one or two services, not forty — and many discover that modularizing the monolith was the entire fix.

Betting on a pressure that hasn’t arrived yet

This decision is rarely as clean as a checklist suggests, because the pressures are often anticipated rather than present. A team genuinely expecting to triple its engineering headcount next year may reasonably invest in autonomy boundaries before the pain arrives. That is legitimate forward planning — as long as it is named as a bet on the future and not disguised as a present emergency. The failure mode is not planning ahead; it is paying the full cost of distribution for benefits you will never collect.

Where this leads

Say you’ve made the call — a real pressure, a clean target. The next fork is about method, and it is where the most money is won or lost: do you evolve the existing code into the new shape, or rebuild it fresh? Part 3, Refactoring vs Rewriting a Monolith, makes the case that the rewrite you’re picturing is almost never the right one — and what to do instead.

Frequently asked questions

What are the signs you should migrate to microservices?
Concrete, measurable pressures: one part of the system consumes most of the compute and must scale on its own; many teams are serialized behind one shared release pipeline; a single component's failures keep taking the whole system down; or one domain needs a fundamentally different data store or runtime. Each of these names a problem microservices actually solve. Absent one of them, the migration adds cost without buying anything.
Is a large or old monolith a reason to adopt microservices?
No. Size and age are not architectural problems by themselves — a large, well-modularized monolith can be perfectly healthy. The thing that makes a monolith painful is internal coupling: everything depending on everything, so every change is risky. That is fixed by drawing internal module boundaries, which you can do without any network calls. Distribution is warranted only when you also have a scaling, autonomy, or isolation pressure.
When should you NOT break up a monolith?
When the codebase is healthy and the only motivation is fashion; when the team is too small to operate a distributed system; when the load profile is uniform and nothing needs to scale separately; or when the system is stable and under no roadmap pressure at all. Running a fleet of services has a fixed operational cost. If nothing the business needs is blocked, that cost buys you nothing.
What does running microservices actually cost you?
Distribution turns in-process calls into network calls with latency, timeouts, and partial failure, turns single transactions into eventually-consistent workflows with compensating logic, and forces you to run service discovery, distributed tracing, and centralized logging just to answer "where did the request die?" On-call also gets harder, because one user action fans out across services and failures hide in the seams between them. It is a real, permanent operational tax, worth paying when a concrete pressure justifies it and pure overhead when nothing does.
All 10 parts of Monolith to Microservices →