← Insights
build

When to Introduce Microservices (And Why Your Monolith Isn't the Problem)

Someone on your team wants microservices. The monolith ships fine. Here are the three signals you've actually outgrown it and the failure mode in between

A senior engineer just put "migrate to microservices" on the roadmap. The deck has a diagram with eleven boxes and a lot of arrows. The pitch is that the monolith is "becoming a bottleneck" and "won't scale." Meanwhile, the monolith ships features every week, has 200ms p95 latency, and serves your entire customer base off two instances and a database that's 40% utilized.

You're being asked to approve a six-month re-platforming of a system that works. And the people asking are smart, which makes it harder to say no. So you don't say no. You say "let's discuss," and the discussion never resolves, and now half the team is quietly building service number one while the other half maintains the monolith they're supposedly abandoning.

Here's the thing nobody on the pro-microservices side will tell you: the monolith is almost never the problem. The problem is a module inside it that's badly factored, and you can fix that without distributing it across a network.

What this costs you when you guess wrong

Microservices are not an architecture. They're an operational tax you pay in exchange for independent deployability. The tax is real and it's front-loaded.

The moment you split one process into eleven, every function call that used to be a nanosecond becomes a network hop that can fail, time out, retry, or arrive out of order. You now need service discovery, distributed tracing, a message bus or a mesh, per-service CI/CD, per-service on-call, and a way to run the whole thing locally so a new hire can ./build without provisioning a cluster. You need to answer "which service owns this data" for every table, and you will get some of those answers wrong, and fixing them later means a cross-service migration instead of an ALTER TABLE.

Put a number on it. A team that re-platforms a working monolith into services prematurely loses roughly four to six months of feature velocity to plumbing. On a six-person team at $15k per engineer-month, that's $360k to $540k of payroll spent rebuilding what you already had — and at the end you have the same product, now harder to debug. The 2am page that used to be "read one stack trace" is now "correlate logs across five services to find which one swallowed the request."

The startups that win this decision treat distribution as a cost they defer as long as possible, not a maturity badge they rush toward.

The modular monolith is the right default

The false choice is "spaghetti monolith" versus "microservices." There's a third option, and it's the correct default for almost every funded startup before Series B: a modular monolith.

One deployable. One database. But internally organized into modules with hard boundaries — each module owns its tables, exposes a typed interface, and other modules call that interface instead of reaching into its tables directly. No module imports another module's internals. You enforce this with the compiler and lint rules, not with good intentions.

// the boundary that matters
billing/      → owns invoices, charges; exposes BillingService
identity/     → owns users, orgs; exposes IdentityService
projects/     → calls IdentityService, never SELECTs from users

This gets you 90% of what people actually want from microservices — clear ownership, testable seams, the ability to reason about one domain at a time — with none of the network tax. And it gives you the thing that makes a future split cheap: if a module already talks to the rest of the system through one interface and owns its own data, extracting it into a separate service later is a contained project, not a rewrite. You draw the seams now in code and cut them later, in production, only where the cut pays for itself.

The teams that skip this step and go straight to services end up with the worst of both worlds, which has a name.

The distributed monolith — the failure mode in the middle

A distributed monolith is what you get when you split a tangled codebase into services without first untangling it. You now have eleven deployables, but they're still coupled: service A can't deploy without service B, a schema change ripples across four repos, and one request fans out into a synchronous chain of six calls where any failure fails the whole thing.

You've taken on every cost of microservices and kept every downside of the monolith. Deploys are more coordinated than before, not less. Local development requires the whole constellation running. And debugging is now a forensic exercise because the call that failed happened two services away from the error you're staring at.

This is the default outcome of a premature split, because the coupling that made the monolith hard to reason about doesn't disappear when you add network boundaries — it just gets harder to see. If two modules share a database table, putting them in separate services doesn't decouple them. It hides the coupling behind an API and adds latency.

The tell: if you can't cleanly separate the data first, you have no business separating the services. Ownership of data is the boundary. Everything else is decoration.

The three signals you've actually outgrown the monolith

You split a module out when the cost of not splitting it exceeds the operational tax. That happens for specific, observable reasons — not because the codebase "feels big."

1. Genuinely divergent scaling profiles. One part of the system needs 20 instances during business hours; another is a nightly batch job that needs 64GB of RAM for ten minutes and nothing the rest of the day. When you can't right-size them in one deployable without wasting money on both, that's a real reason to split. Note: "this endpoint is slow" is not this signal. Slow endpoints get fixed with an index or a cache, not a new repo.

2. Independent deploy cadence that the monolith is actively blocking. Your payments team needs to ship multiple times a day under compliance review; your marketing-site team ships weekly. If a shared deploy pipeline means the payments team is gated behind unrelated changes — and you've measured that this costs real time — extracting payments buys back that independence. The key word is measured. "We might want to deploy separately someday" is not a signal.

3. Team topology that the monolith can't absorb. Past roughly 25 to 40 engineers, a single codebase with a single deploy starts generating coordination overhead that a module boundary can't fully contain — merge contention, shared-ownership ambiguity, release trains that stall. At that scale, services align to teams (Conway's Law, used on purpose) and the operational tax is worth it because the coordination tax was higher.

If you can't point at one of these three with a number attached, you haven't outgrown the monolith. You have a factoring problem inside it, and the fix is a refactor, not a re-platform.

What fixed looks like

Fixed is a modular monolith with enforced internal boundaries: each domain owns its data, exposes a typed interface, and is independently testable. It ships on one pipeline, runs locally with one command, and produces a single coherent trace per request.

Fixed is a written rule for when a module graduates to a service — tied to the three signals above, with a metric, not a vibe. When billing genuinely needs its own scaling and deploy cadence, you extract it in a contained two-week project because the seam was already there.

Fixed is the team agreeing that "the monolith won't scale" is a claim that requires evidence — a saturated resource, a measured deploy bottleneck, a coordination cost — before it goes on a roadmap. Most of the time the evidence isn't there, and that's good news: the cheapest architecture is the one you didn't have to distribute.

This is for you if

You're a funded founder or a technical leader staring at a microservices proposal for a system that currently works, and you need to know whether it's a real need or résumé-driven design. We do architecture reviews that end in a decision, not a diagram: keep the monolith and fix the factoring, or extract these two specific services for these specific reasons. Engagements typically start at $25k+ for a scoped review and modularization plan, $50k+ if we're refactoring the monolith into clean modules, $100k+ for a staged extraction of the services that actually warrant it.

This is not for you if you're pre-revenue with three users and want microservices because the architecture looks impressive on a slide — you'll burn your runway plumbing instead of finding product-market fit. It's not for you if you have a genuine, measured 40-engineer coordination crisis and just need hands to execute a split you've already validated. And it's not for you if "it works fine but a recruiter said monolith is a bad word" is the actual driver. The monolith is fine. Go ship something.