Two Services, One Database
microservices databases
System Design Scenario
Two Services, One Database
When microservices share storage, independence becomes an illusion - and every deploy becomes a coordination nightmare
Tuesday morning standup. The product team announces a new feature for Service A - adding a priority column to the tasks table. Simple change, right? Ship it Friday. But then DevOps speaks up: “Service B also uses that table. We need to coordinate the deployment.”
The room goes quiet. Your microservices architecture - the one that promised independent deployments and team autonomy - just revealed its dirty secret. Both services write to the same PostgreSQL schema. A database column change in Service A breaks Service B’s queries. Schema migrations must be synchronized across teams. Deployments require cross-team coordination calls.
It’s like having two separate houses that share the same electrical panel. You want to upgrade your kitchen outlets, but your neighbor has to shut down their home office for the rewiring. The houses look independent from the outside, but they’re coupled at the most fundamental level - the power source that keeps everything running.
This is the shared database anti-pattern in microservices. Independence is an illusion when services couple through storage.
Why This Happens
Most teams adopt microservices for organizational benefits - independent deployments, team autonomy, and technology diversity. But when services share a database schema, they retain all the coupling of a monolith while adding the complexity of distributed systems.
The failure chain is predictable:
Service A needs schema change
-> Service B queries depend on old schema
-> Migration breaks Service B
-> Rollback breaks Service A
-> Both teams blocked on coordination
The trap emerges gradually. Early in a project, sharing a database feels pragmatic. Both services need user data and order data. Why duplicate it? The shared schema starts simple and manageable. Teams can coordinate easily when there are only two services and three developers.
But as the system grows, the coordination tax compounds. Each new service that touches shared tables adds another dependency. Schema changes require impact analysis across all consuming services. Database migrations become multi-team release events with rollback coordination plans.
Microservices sharing a database schema have all the deployment coupling of a monolith with none of the transaction safety or simplicity.
The Naive Solution (and where it breaks)
Most teams reach for one of two approaches when they hit this wall:
Option 1: Careful Coordination Schedule all schema changes through a central committee. Create elaborate deployment orchestration. Require cross-team approval for any database modification.
This is like solving traffic jams by adding more traffic lights. The coordination overhead grows quadratically with the number of services. What started as a simple column addition becomes a three-week process involving four teams, two meetings, and a detailed rollback plan.
Option 2: Backward-Compatible Changes Only Never drop columns, never change data types, never add NOT NULL constraints. Only append new columns and tables. Deprecate old fields but never remove them.
The database becomes a museum of historical decisions. After two years, the users table has email, email_address, user_email, and primary_email columns because teams were afraid to clean up deprecated fields. Query performance degrades as tables accumulate zombie columns. New developers can’t tell which fields are actually used.
At 10 services accessing shared tables, backward-compatibility constraints become impossibly complex. You can’t evolve your data model without archaeological research into every consuming service.
The "backward-compatible only" approach turns your database into an append-only museum where deprecated fields never die and schema evolution becomes impossible.
Premium Content
Unlock the full article along with everything else in the archive — all in one place.