Distributed Monolith
Understand why microservices that share a database are worse than a monolith, how to detect a distributed monolith, and how to fix service boundaries without a rewrite.
Introduction
A distributed monolith has service-shaped code but monolith-shaped ownership. Services communicate over the network and fail independently, yet remain coupled through shared tables, schemas, transactions, or coordinated deployments.
Mental model: splitting processes is not the same as splitting boundaries. A service is meaningfully independent only when it owns its data and can evolve, deploy, and recover without hidden database dependencies.
TL;DR
- A distributed monolith has the operational complexity of microservices (network calls, partial failures, versioning) but the tight coupling of a monolith (shared database, shared schema, lock-step deployments).
- It is the worst of both worlds: you cannot deploy services independently because a schema change in one breaks another, yet you still pay the network overhead of inter-service communication.
- The signature: services that point at the same database tables, or services that must be deployed in a specific order.
- The fix requires splitting ownership of data first. Each service owns its tables. This is harder and more disruptive than any code change.
What It Is
Your team decomposed the monolith into eight services over six months. Deployments are still coordinated events requiring a 4-hour Saturday night window. A schema change in the Users table requires touching the User, Auth, Notification, and Analytics services. Service B can't start until Service A has finished its migration because they both write to the same orders table.
You split the code into services but never split ownership of data. Every service still reads and writes the same shared Postgres database. The database is your secret monolith, the part you didn't decompose.
The tell is not the number of containers; it is whether services can evolve independently. If they cannot, the architecture is a monolith with extra network hops.
This is common because database decomposition is the hard part. Code decomposition is refactoring. Data decomposition requires migrating tables, duplicating data, accepting eventual consistency, and changing all the places that relied on ACID transactions spanning service boundaries.
Every service reaches into every table. A column rename on users breaks four services simultaneously. That is the distributed monolith in one picture.
What deployment coupling looks like
Here's the deployment ceremony for a simple feature: adding a preferred_name column to the users table.
Adding a single column requires four coordinated deployments. In a properly decomposed system, only the User Service would need to change.
How It Develops
Nobody sets out to build a distributed monolith. It emerges from a series of individually reasonable decisions.
"We'll share the database for now." The team plans to split data later. Later never comes because the codebase grows around the shared access pattern.
"It's faster to query directly." Building an API for internal data feels like overhead when the table is right there. Every shortcut compounds into coupling.
"We followed the microservices playbook." Most tutorials focus on code decomposition (separate repos, separate containers) and barely mention data ownership. Teams split code because that's what the guides show.
"The domain boundaries aren't clear yet." When you don't know which service owns which data, sharing a database feels like the safe default. It isn't. It's the default that locks you in.
The "just query the table directly" shortcut saves time early but makes future schema changes and ownership decisions increasingly expensive.
The coupling progression
Distributed monoliths don't appear overnight. They grow through a predictable progression:
- Week 1: Service B needs user data. "Let's just query the users table directly for now."
- Month 2: Service C also needs user data. "Service B already does it this way, so we'll do the same."
- Month 6: Five services query the users table. Nobody remembers which columns each service actually needs.
- Year 1: Someone renames a column. Three services break in production. The post-mortem reveals nobody knew about the cross-service dependencies.
- Year 2: The team wants to shard the users table for performance. They can't, because five services have hardcoded queries against the table schema.
Each step was reasonable in isolation. The compound effect is a system that's harder to change than the monolith it replaced.
Symptoms and Diagnosis
Ask these questions about your "microservices" architecture:
Continue Reading with Premium
Unlock this article and every other in-depth system design guide on the platform with SDEpedia Premium.