Sharing Databases in Microservices: Anti-Pattern or Pragmatic Architecture?

Arpit Bhayani

Arpit Bhayani

Apr 27, 2022 • 7 min read

Play

In textbook microservices architecture, each service must strictly encapsulate its own data: one database per service. Any other service requiring access to that data must communicate through well-defined APIs (REST, gRPC, messaging).

However, in practice, teams frequently turn to the Shared Database Pattern, where multiple autonomous services connect directly to a single central database to read and write records without an intermediary API gateway or service layer.

While purists routinely discard this approach as an anti-pattern, system design is never black and white. Direct database sharing has immense operational and developmental advantages under the right constraints, provided you understand the associated trade-offs.


The Shared Database Pattern vs. The API-Driven Approach

Consider a multi-user blogging platform consisting of:

  1. Blog Service: Manages publishing, updating, and rendering articles via the blogs_db database.
  2. Analytics Service: Tracks views, likes, and engagement metrics and persists aggregate totals.
  3. Recommendation Service: Analyzes reading patterns to suggest relevant blogs.
flowchart TD
    subgraph API-Driven Architecture [Pattern A: API-Driven Encapsulation]
        AS1[Analytics Service] -->|RPC / HTTP| BS1[Blog Service]
        BS1 --> DB1[(Blogs Database)]
    end

    subgraph Shared-DB Architecture [Pattern B: Shared Database Pattern]
        AS2[Analytics Service] -->|Direct SQL Query| DB2[(Blogs Database)]
        BS2[Blog Service] -->|Direct SQL Query| DB2
        RS2[Recommendation Service] -->|Direct SQL Query| DB2
    end
  • Pattern A (API-Driven): The Analytics Service calls an endpoint exposed by the Blog Service, and the Blog Service handles updates to its persistence layer.
  • Pattern B (Shared Database): The Analytics Service connects directly to blogs_db using its own database connection pool and issues queries directly against the underlying tables.

Advantages of Sharing the Database

Bypassing the intermediary API service yields several immediate, tangible benefits:

1. Radically Simplified Integration

There is no need to define, maintain, version, and negotiate inter-service communication contracts. Teams avoid protocol mismatches (e.g., Service A prefers gRPC while Service B only supports REST/JSON).

2. Zero Network Latency Overhead

Every network hop introduces latency—serialization, socket establishment, wire transit, deserialization, and controller handling. By removing the middleman API server, queries travel directly from client service to the database instance.

3. Accelerated Development Velocity

Feature teams can move independently without waiting for upstream teams to prioritize, implement, review, and deploy an endpoint. If the Analytics team needs an additional counter column, they write to it directly.

4. Reduced Operational Surface Area

Eliminating an intermediary API layer removes a point of failure. There is no need to provision, scale, monitor, or manage persistent connection pools, ingress controllers, or load balancers for a passthrough service.


The 4 Critical Challenges of Sharing Databases

Despite the operational speed direct database access provides, it exposes systems to four major architectural risks. The first two directly undermine the core tenets of microservices: Loose Coupling and High Cohesion.

flowchart LR
    subgraph Challenges
        C1[1. Leaking Internal Schema Details] --> LC[Breaks Loose Coupling]
        C2[2. Replicating Query Business Logic] --> HC[Breaks High Cohesion]
        C3[3. Unintended Data Corruption & Deletion]
        C4[4. Resource Exhaustion & Uncontrolled Abuse]
    end

1. Leaking Internal Details & Destroying Autonomy (Breaks Loose Coupling)

When an external team connects directly to a database, internal storage details cease to be private implementation details:

  • The external team must know internal conventions: column naming, normalization structures, soft-delete flags vs. hard deletes, and foreign key hierarchies.
  • Schema changes become hazardous: Renaming a column or splitting a table requires coordinated migrations and redeployments across all consuming services. Backward compatibility becomes mandatory at the database layer.
  • Loss of architectural freedom: The primary service owner loses the ability to independently migrate data technologies—such as switching from a relational database to a key-value store, or implementing horizontal sharding—because external services rely on raw SQL queries against specific tables.

2. Duplicating Business Logic (Breaks High Cohesion)

Cohesion dictates that related logic should reside together. When multiple services access tables directly, the domain logic to interpret and fetch data is inevitably copied across codebases:

-- To fetch an active, published blog with its author details:
SELECT b.id, b.title, u.name, u.badge 
FROM blogs b 
JOIN users u ON b.author_id = u.id 
WHERE b.status = 'PUBLISHED' 
  AND b.is_deleted = FALSE 
  AND u.is_active = TRUE;

If the Blog Service, Analytics Service, and Recommendation Service all run this exact join, the underlying business rule is triplicated. If the Blog team replaces users with a federated identity schema or introduces an approval status status = 'REVIEW_APPROVED', all three services must modify their queries simultaneously, or data inconsistency will emerge.

3. Risk of Data Corruption and Cascading Failures

Granting read-write permissions across multiple decoupled services drastically increases the blast radius of operational errors:

  • A buggy migration script, unindexed batch update, or erroneous deletion script run by the Analytics Service can corrupt or wipe critical production data in blogs_db.
  • Required Mitigation: To mitigate this, granular database-level Access Control Lists (ACLs) and dedicated database users must be created:
    • Blog Service User: Full read-write permissions on all domain tables.
    • Analytics Service User: Write access restricted solely to aggregate counter columns/tables; read-only elsewhere.
    • Recommendation Service User: Read-only access across required tables.

4. Database Abuse and Lack of Rate Limiting

In an API-driven architecture, the Blog Service can throttle aggressive consumers using token buckets, rate limiters, or request prioritization.

When external services query the database directly:

  • An unoptimized, CPU-intensive analytical join (e.g., a massive aggregation over historical logs) can choke the database engine’s thread pool, buffer pool, and I/O capacity.
  • When the database freezes under analytical load, core user-facing transactional queries from the Blog Service fail, triggering system-wide degradation.

When Is the Shared Database Pattern Practical?

Direct database sharing should not be viewed as an absolute anti-pattern. There are specific, high-leverage scenarios where it is an optimal engineering choice: Pragmatism over dogma.

1. Early-Stage Startups and Lean Engineering Teams

When validating product-market fit with a small team, engineering bandwidth is your scarcest resource. Building out separate microservices, boilerplate RPC interfaces, distributed tracing, and cross-team contract testing consumes time that could determine company survival. Sharing a database maintains velocity.

2. Highly Stable Schemas

If the underlying data model is mature and unlikely to undergo major structural shifts over a multi-year horizon, the risk of broken downstream contracts drops significantly.

3. Isolating Read Traffic via Read Replicas

To resolve the challenge of database abuse and noisy-neighbor workloads without writing API middleware, split your database architecture using Read Replicas:

flowchart TD
    AS[Analytics / Recommendation Service] -->|Heavy Queries & Analytics| RR[(Read Replica)]
    BS[Blog Service] -->|Critical Reads & Writes| P[(Primary Database)]
    P -.->|Asynchronous Replication Lag: < 1-2s| RR

By routing analytical or background queries to an asynchronous read replica:

  1. The primary database remains dedicated to high-priority transactional traffic.
  2. Expensive analytical joins do not impact latency-sensitive customer requests.
  3. External services retain direct SQL flexibility while operating on data that is only seconds behind the source of truth.

Architectural Comparison Matrix

DimensionShared Database PatternAPI-First Encapsulation
Integration SpeedExtremely fast (immediate table access)Slower (requires defining & deploying APIs)
Operational OverheadLow (single datastore to monitor)High (multiple services, connection pools, gateways)
Coupling LevelTight (coupled directly to internal schema)Loose (coupled only to abstract API contract)
CohesionLow (logic replicated across callers)High (logic encapsulated inside service boundary)
Blast RadiusHigh (risk of system-wide DB choking)Isolated (failures bounded by circuit breakers & limits)
SuitabilityLean startups, read-heavy reporting, stable schemasLarge organizations, independent teams, complex domains

Summary

Architectural patterns should never be evaluated as purely “right” or “wrong.” While the shared database pattern compromises loose coupling and high cohesion, it delivers unmatched integration speed, operational simplicity, and low latency.

Evaluate the constraints of your team and system:

  • If you need immediate development velocity with a small team or have a stable data model, sharing a database (particularly when paired with read replicas and role-based ACLs) is a completely justifiable choice.
  • As organizational complexity scales and teams step on each other’s toes through uncoordinated schema alterations or database resource exhaustion, migrate toward strict API-driven encapsulation.
Arpit Bhayani

Principal Engineer II at Razorpay - building Agent Studio, Ex-staff engg at GCP Memorystore & Dataproc, Creator of DiceDB, ex-Amazon Fast Data, ex-Director of Engg. SRE and Data Engineering at Unacademy. I spark engineering curiosity through my no-fluff engineering videos on YouTube and my courses