Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 60

Database-Per-Service: Challenges with Data Consistency

Recommended resources

Hello! In our previous lesson, we established that Domain-Driven Design (DDD), particularly the concept of a Bounded Context, is our primary tool for defining logical microservice boundaries. We learned that each Bounded Context has its own model and language, ensuring it is cohesive and decoupled from others.

Today, we'll explore the natural architectural consequence of this logical separation. If services are independent, their data should be too. Your goal is to explain the database-per-service pattern and its data consistency challenges. This is a cornerstone principle of microservice architecture and a frequent point of discussion in senior-level system design interviews, as it forces you to confront the fundamental trade-offs in distributed systems.

1. The Anti-Pattern: The Shared Monolithic Database

Before we dive into the recommended pattern, let's understand what we're trying to avoid. In many legacy systems, and even in poorly designed microservice architectures, multiple services directly access a single, shared database. This is considered an architectural anti-pattern.

Microservices with Databases can be challenging...

To understand why we need a dedicated database per service, let's first see the problems with the shared database approach. This video from 'Software Developer Diaries' clearly explains the pitfalls.

Watch the segment from 00:30 to 02:57. Focus on the reasons why a shared database is an anti-pattern, such as loss of modularity, scalability challenges, and technical issues like deadlocks.

As the video highlights, a shared database creates several critical problems:

  • Tight Coupling: If the Order Service team wants to change a table schema, they must coordinate with the Inventory Service team and any other service using that table. This kills the agility that microservices promise.
  • Performance Bottlenecks: One service running a heavy, unoptimized query can degrade performance for all other services by consuming database resources.
  • Loss of Autonomy: The development teams are not truly independent. They are tethered together by the shared database schema and infrastructure.

2. The Database-per-Service Pattern

The solution to these problems is to give each microservice its own private data store. This is the database-per-service pattern.

Monolithic vs. Microservices Architecture with Database per Service Pattern
This image starkly contrasts a monolithic architecture, where all modules share one database, with a microservices architecture where each service (Stock, Payment, Order) has its own dedicated database.

The core rule of this pattern is simple but strict: a microservice's data is private and can only be accessed through its public API. No other service is allowed to connect directly to its database. This enforces the logical boundary we defined with our Bounded Context at the physical data layer.

Data considerations for microservices

For a formal definition and a clear articulation of this rule, let's turn to the official Microsoft Azure documentation.

Read the introductory section. It clearly states the core principle: 'Two services shouldn't share a data store. Each service manages its own private data store, and other services can't access it directly.'


3. The Advantages of Data Encapsulation

By enforcing this strict data separation, we unlock the key benefits of a microservices architecture.

Database per Service Pattern in Microservices

This video by Arpit Bhayani provides an excellent overview of the advantages gained from adopting the database-per-service pattern.

Watch the video from the beginning until 13:46. It covers three main areas: The core concept of loose coupling (00:00 - 01:19). Polyglot Persistence: The ability to use different database technologies for different services, with a great social network example (01:19 - 04:51). Other key advantages: Including independent scaling, improved fault isolation, and easier compliance management (04:51 - 13:46).

To summarize the key benefits discussed in the video:

  • Loose Coupling: Services are truly independent. A service's internal data model and database technology can be changed without impacting any other service.
  • Polyglot Persistence: Teams can choose the best database technology for their specific service's needs. This is a powerful concept.
    • An Authentication service might use a simple relational database like PostgreSQL.
    • A Product Search service could use Elasticsearch for its powerful text search capabilities.
    • A User Feed service might use a NoSQL database like Cassandra, optimized for high write throughput.
  • Independent Scaling: Each database can be scaled independently. If the Order service database is under heavy load, you can scale it up without affecting the User Profile service.
  • Improved Fault Isolation: If the Recommendation service's database goes down, it doesn't take the Payment service with it. The blast radius of a failure is contained.
AWS Microservices Architecture with Database-per-Service Pattern
This AWS architecture diagram shows a real-world example of polyglot persistence. The Sales service uses Amazon Aurora (relational), the Customer service uses DynamoDB (NoSQL), and the Compliance service uses SQL Server, all within the same application.

4. The Challenge: Maintaining Data Consistency

So far, this pattern seems ideal. However, it introduces one of the most significant challenges in distributed systems: how do you maintain data consistency across multiple, independent databases?

Imagine a simple e-commerce transaction: "Place Order". In a monolith, this might be a single ACID transaction that does the following:

  1. Creates an Order record.
  2. Decrements the stock in the Inventory table.
  3. Records a transaction in the Payment table.

If any step fails, the entire database transaction is rolled back, and the system remains in a consistent state.

In a microservices world with separate databases for Order, Inventory, and Payment services, this simple operation becomes a distributed transaction. What happens if the Order Service creates the order, but the call to the Inventory Service fails? You now have an order for a product that hasn't been reserved, leading to data inconsistency.

This is the central problem. Let's explore its nuances.

Data Consistency in Microservices: Challenges, Strategies, ...

This article from Medium provides excellent context on the consistency challenges, differentiating between the types of consistency we can achieve.

Read the 'Introduction' and 'Understanding Data Consistency in Microservices' sections. Pay close attention to the definitions of: Strong Consistency: Every read gets the most recent write. (The monolith's world) Eventual Consistency: Given enough time, all data will sync up. (The microservices world) CAP Theorem: The trade-off between Consistency, Availability, and Partition Tolerance that forces us towards eventual consistency.

As the article explains, because microservices are a distributed system, the CAP theorem forces us to make a choice. We must have Partition Tolerance (the system works even if network messages between services are lost). This leaves us with a trade-off between strong Consistency and Availability. Most modern, large-scale systems choose Availability, accepting Eventual Consistency as a result.

This means we need strategies to manage transactions and ensure the system eventually becomes consistent, even if it's not instantaneous.

Database per Service Pattern in Microservices

Let's conclude with a segment from the Arpit Bhayani video that summarizes the key disadvantages, focusing on the complexity of cross-service transactions.

Watch the final part of the video from 13:46 to 19:28. This section discusses the primary drawbacks, including: The complexity and performance cost of distributed transactions. The challenge of propagating updates across services (eventual consistency). The operational overhead of managing multiple database technologies.

Test your understanding! (Interview Scenario)

An interviewer asks: "We've decided to use the database-per-service pattern. The Order service uses PostgreSQL and the Inventory service uses MongoDB. A business requirement states that an order can only be created if stock is available. Describe the data consistency problem here and the risks involved."

Show answer

Here's a structured response:

"This scenario perfectly illustrates the core challenge of the database-per-service pattern: maintaining consistency across service boundaries, which involves a distributed transaction.

The Problem:
The business logic spans two services: Order and Inventory. We need to perform two separate operations—creating an order in PostgreSQL and decrementing stock in MongoDB—and have them behave as a single atomic unit. Since we cannot use a single ACID transaction across two different databases, we have a consistency problem.

The Risks (Failure Scenarios):

  1. Inventory Check Fails, Order Is Placed (Phantom Stock): The Order service successfully reserves inventory in MongoDB. It then tries to create the order in its own PostgreSQL database, but this fails (e.g., database connection issue). Now, the inventory is reserved, but no order exists. This 'phantom' reservation will prevent other customers from buying the item until the lock expires or is manually fixed.

  2. Order Is Placed, Inventory Check Fails (Overselling): The Order service creates the order record in PostgreSQL first. It then attempts to call the Inventory service to decrement the stock, but this call fails (e.g., network error, Inventory service is down). Now we have a confirmed order for an item whose stock was never reserved. This leads to overselling and a very poor customer experience when we have to cancel the order later.

In both cases, the system is left in an inconsistent state. We can't rely on simple, synchronous API calls. We need a more robust mechanism to coordinate this multi-step process and handle failures gracefully, which leads to patterns that manage eventual consistency."


Conclusion

Today we've seen that the database-per-service pattern is essential for achieving the goals of microservices, but it comes at a significant cost: the loss of simple, transactional consistency.

Key Takeaways:

  • The Database-per-Service Pattern dictates that each microservice encapsulates its own data, accessible only via its API. This enforces loose coupling and team autonomy.
  • Benefits include the ability to use different technologies (Polyglot Persistence), scale services independently, and contain failures.
  • The Core Challenge is managing business transactions that span multiple services, as this requires coordinating updates across separate databases.
  • This challenge forces a shift from Strong Consistency (guaranteed by single-database ACID transactions) to Eventual Consistency, where the system takes time to reconcile state after an operation.

Preview of the Next Lesson:
We've thoroughly defined the problem of data consistency. In our next lesson, we will dive deep into the most prevalent solution to this problem: the Saga pattern. We will explore how to use a sequence of local transactions to implement a reliable, eventually consistent distributed transaction.