Microservices Aren't a Performance Optimization


We split a monolith expecting it to get faster. It got slower, and that was the correct outcome.

A team I worked with split a Django monolith into six services because “microservices scale better.” Six months later the same request that used to take 40ms was taking 180ms, and everyone was confused about why the “scalable” architecture felt slower. It felt slower because it was slower — that request now made four network calls instead of four in-process function calls.

The confusion at the root of it

Microservices don’t make individual requests faster. A function call inside one process is a few nanoseconds; the same call over HTTP to another service is milliseconds, even on a fast internal network, even before you count serialization, auth, and retries. Every service boundary you introduce is a tax on latency, paid on every request that crosses it. If your monolith’s problem was request latency, splitting it into services makes that specific problem worse, not better, at least locally.

What microservices actually buy you is independent scaling and independent deployment. If your image-processing workload needs 50 CPU-heavy workers and your user-auth workload needs 3 lightweight ones, a monolith forces you to scale them together — you’re either wasting CPU on auth boxes or starving image processing. Split them, and you scale each to its actual load shape. Similarly, if the image-processing team ships twice a day and the checkout team ships once a quarter because it’s payment-critical, a monolith forces both onto the same deploy cadence and the same blast radius when something goes wrong. That’s the real argument for the split — organizational and scaling independence, not raw speed.

What we actually needed

In our case, the “monolith is too slow” problem was two unindexed queries and an N+1 in a serializer. We fixed those in an afternoon and the p95 dropped by more than the eventual microservices migration ever delivered. The migration wasn’t wasted work — it did make sense a year later once two of those services had genuinely different scaling profiles and different teams owning them — but it solved a problem we didn’t have yet, at a cost (operational complexity, distributed tracing, network failure modes we now had to design for) that we paid immediately.

The question that would’ve saved us months

Before splitting a service, the question isn’t “will this scale better” — nearly anything looks like it scales better on a diagram. The question is: which specific resource is contended (CPU, memory, DB connections, deploy risk), and does splitting this boundary actually relieve that contention, or does it just move the bottleneck into the network while adding retry logic, service discovery, and distributed failure modes you didn’t have before?

If you can’t name the specific resource that’s contended and show that a service boundary relieves it, you’re not solving a scaling problem — you’re buying organizational flexibility at a fixed latency and complexity cost. That might still be the right trade. Just don’t call it a performance win, because the benchmark will call your bluff.