
Art lightWhen we first split our monolith into microservices, it felt like a victory. Smaller services....
When we first split our monolith into microservices, it felt like a victory.
Smaller services. Independent deployments. Clean boundaries. We even had a diagram with boxes and arrows that made us feel like Netflix engineers.
Then production traffic hit.
The services scaled fine.
Kubernetes was happy.
Auto-scaling worked exactly as advertised.
And the database absolutely melted.
At first, we blamed everything except the obvious.
“Maybe we need more replicas.”
“Let’s increase the connection pool.”
“Postgres just doesn’t scale like NoSQL.”
But the truth was simpler and more uncomfortable:
Our microservices weren’t the problem.
Our shared database was.
Microservices sell a seductive idea: scale each part of your system independently. In theory, that’s exactly what you get.
In practice, most teams do this:
Congratulations.
You’ve just built a distributed monolith with network latency.
Each service may scale horizontally, but every single one still funnels its traffic into the same bottleneck. When load increases, the database doesn’t see “microservices.” It sees chaos.
More connections
More concurrent queries
More locks
More contention
The database doesn’t scale horizontally just because your services do. It just cries louder.
The first cracks showed up as latency spikes.
No errors. No crashes. Just requests getting slower… and slower… and slower.
Here’s what was really happening:
Individually, none of these changes looked dangerous.
Together, they turned the database into a shared trauma center.
This is the microservices trap: local decisions with global consequences.
Here’s the part that surprised newer engineers on the team.
Scaling a service from 2 pods to 20 pods doesn’t just multiply throughput. It multiplies:
The database doesn’t know these pods belong to the same service. It treats them as 18 new strangers aggressively asking for attention.
So while your dashboards show:
“Service latency looks fine!”
The database is over here thinking:
“WHY ARE THERE SO MANY OF YOU?”
At this point, someone always suggests caching.
And yes, caching helps.
But it doesn’t fix the underlying issue.
Most teams add:
Now the system is faster… until it isn’t.
Why?
Because:
Caching is a painkiller.
The database problem is structural.
The moment it clicked for me was realizing this:
We didn’t have microservices.
We had microservices sharing the same state.
That breaks the core promise of the architecture.
When multiple services:
They are no longer independent. They’re tightly coupled through the database, just in a quieter, harder-to-debug way.
Your services can deploy independently.
Your data cannot.
Here’s what didn’t solve it:
Here’s what did help:
Each service owns its data. Period.
If another service needs it:
No “just this one join across services.” That’s how the crying starts again.
Distributed transactions feel elegant until you try to operate them.
We replaced synchronous dependencies with:
Not everything needs to be instant. Most systems just need to be reliable.
We stopped asking:
“Can this service scale?”
And started asking:
“What does this do to the database at 10x traffic?”
That one question changed architecture reviews completely.
Microservices don’t automatically give you scalability. They give you options — at the cost of discipline.
Without strict boundaries, they amplify database problems instead of solving them.
Microservices didn’t fail us.
Our database design did.
We optimized for developer velocity early on and paid for it later with operational pain. That’s not a mistake — that’s a tradeoff. The mistake is pretending microservices magically remove scaling limits.
They don’t.
They move those limits somewhere less visible.
Usually into your database.
If your system slows down every time traffic increases, don’t just look at your services.
Look at:
Because nine times out of ten, when “microservices don’t scale”…
They do.
Your database is just crying for help.