Data Management Strategies in Microservices

Data Management Strategies in Microservices

In a microservices architecture, applications are broken down into many small, independent services. Each service focuses on a single business function, such as orders, payments, or inventory. While this independence allows teams to scale and innovate faster, it creates new challenges in managing data across services.

Unlike monolithic systems, where a single database ensures consistency, microservices typically use separate databases to maintain autonomy. This shift introduces complexities around distributed transactions, consistency, performance, and reliability. To address these challenges, teams need proven strategies and patterns that keep services independent while ensuring business truth and user trust.

Why Data Gets Hard in Microservices

In a monolith, all components share a single database. Complex operations (such as updating multiple tables) are easily handled through ACID transactions. In microservices, however, each service owns its own database. A single business action now spans multiple services, processes, and databases, making it harder to guarantee correctness. This introduces three unavoidable realities:

Reality 1: Network Fallibility

Microservices communicate over the network (HTTP, gRPC, or messaging). But networks are unreliable.

  • A call might fail due to a crash or outage.
  • A response might time out because the other service was slow.
  • Sometimes the same message may be delivered twice, resulting in duplicate actions.

Example: When an order is placed, the Order Service calls the Payment Service to capture payment. If the network fails just after the payment succeeded, the Order Service won’t know the result. It might retry, causing the payment to be charged twice unless the Payment Service handles duplicates.

Reality 2: Independent Lifecycles

One goal of microservices is that each service can be deployed, scaled, and upgraded independently.

  • That means services may be at different versions at the same time.
  • A service could be temporarily offline while being updated.
  • There’s no “one big transaction” spanning all services, as in a monolith.

Example: Suppose the Inventory Service is being redeployed while an order is placed. The Order Service and Payment Service may succeed, but the Inventory update could be missed. Without special handling, the system might accept more orders than available stock.

Reality 3: Business Truth vs. System Truth

Business Truth: Business truth is about the real-world outcome that must always stay correct according to business rules, no matter what happens in the system.

  • It represents what the business intends or promises.
  • Even if parts of the system fail or are inconsistent, the business truth must be restored.
Example:

In e-commerce: “An order is valid only if payment is successful and stock is available.”

  • If payment fails after the order is created, the business truth is: there is no valid order. The system must cancel the order and issue a refund.

Business truth ensures that the system accurately reflects reality, even when temporary inconsistencies occur.

User Trust: User trust is about the confidence customers have in the system, that it works reliably, protects their data, and won’t cheat them.

  • It’s the foundation of customer satisfaction and loyalty.
  • Even small inconsistencies can break trust if not handled well.
Example:
  • In banking, if a customer transfers ₹5,000 and sees money deducted but not credited instantly, they lose trust, even if the system fixes it later.
  • In e-commerce, if a user gets charged but doesn’t receive an order confirmation, they lose trust in the platform.

User trust depends on how well the system manages failures, communicates clearly, and keeps its promises to customers.

In Short

  • Business Truth = The real-world correctness of operations (e.g., “No order without payment”).
  • User Trust = The confidence users have that the system is reliable, fair, and consistent.

Challenges of Data Management in Microservices

Beyond those realities, microservices introduce technical challenges when each service manages its own database.

Distributed Transactions

In microservices, a single business operation often requires multiple services to update their databases.

  • Each service can ensure its local transactions are safe.
  • But across services, there’s no natural “all or nothing” guarantee.
Example (E-commerce Order):
  1. Order Service → Create order record.
  2. Inventory Service → Reduce stock count.
  3. Notification Service → Send Notification.

If step 2 fails after step 1 succeeded, you now have an order, but the Product is not in stock. Rolling this back across services is complex.

Note: This is why we need strategies like the Saga Pattern (chaining local transactions with compensating actions).

Data Consistency

Because services update at different times, data across the system may not always match.

  • Some services may reflect the latest state, while others still show stale data.
  • Microservices often rely on eventual consistency: the system will be correct, but not instantly.
Example:
  • After an order is placed, the Order Service immediately shows Order Confirmed.
  • The Inventory Service may still show the old stock count for a few seconds due to message delays.
  • During that gap, another customer could also order the last item, creating a temporary inconsistency.

Note: The key is deciding where strong consistency (immediate correctness) is required (e.g., banking transactions) vs. where eventual consistency is acceptable (e.g., updating a profile picture).

Performance Overhead

In a monolith, you could run a JOIN query across multiple tables in one database. In microservices, data is split across multiple databases.

  • Getting related information often requires calling several services.
  • This increases latency (delay) and network overhead.
Example:

To display a user’s order history page:

  • Fetch user details from the User Service.
  • Fetch orders from the Order Service.
  • Fetch product details from the Product Service.
  • Fetch payment status from the Payment Service.

Instead of a single SQL query, this may involve 4–5 cross-service calls, which are slower and more complex to maintain.

Anti-Pattern: One Shared Database for All Services

At first, many beginners try to make life easier by saying“Why don’t we just let all microservices share the same database? It sounds simple because:

  • There’s only one database to manage.
  • Data joins are easy, just like in a monolith.
  • Developers don’t need to think about distributed consistency.

But in reality, this is an anti-pattern that violates core microservice principles of independence and autonomy.

Why It’s Problematic
Tight Coupling
  • Microservices are supposed to be loosely coupled; each service can evolve independently.
  • With a shared database, a schema change in one service could accidentally break another service.

Example: If the Product Service adds a new column in the Product table, the Order Service might fail if it wasn’t expecting that column. Suddenly, both services must be updated together, which removes their independence.

Scaling Issues
  • Microservices allow each service to scale separately depending on demand.
  • With a single shared database, all services compete for the same resources (CPU, memory, I/O).
  • One heavy service can slow down the entire system.cg

Example: During a holiday sale, the Inventory Service might need to process millions of stock updates. If it shares the same database as the Billing Service, payment processing could slow down because of database overload.

Security Risks
  • If one service is compromised, the attacker potentially gains access to all tables, not just the intended ones.
  • One “bad actor” service compromises the integrity of the whole system.

Example: If a bug in the Notification Service accidentally deletes records, it could wipe out Order or Payment data, resulting in a catastrophic outcome.

Deployment Bottlenecks
  • In microservices, services should deploy independently.
  • With a shared database, even a small schema change in one service might require retesting and redeploying other services that share it.
  • This kills the whole purpose of independent deployments in microservices.

Example: If the Inventory Team wants to add a new stock-tracking field, the Billing Team must update their queries as well, so both teams have to schedule a joint release. This kills the agility of microservices.

Ownership Confusion
  • In microservices, each team should own its data and make decisions about schema, performance, and scaling.
  • With a shared database, no one knows who really controls what. Multiple teams touching the same tables often lead to conflicts, delays, and finger-pointing when something goes wrong.

Example: The Inventory team and Billing team both depend on the Product table. Who decides when to add or remove a column? Who optimizes indexes? This shared responsibility often leads to conflicts and mistakes.

A shared database may feel easy, but it creates a tightly coupled monolith disguised as microservices. You lose independence, scalability, and agility, which are the very reasons for adopting microservices in the first place.

Database Per Microservice Approach

The industry-recommended approach is the “Database per Microservice” model. Here:

  • Each microservice owns its own database.
  • No service can access another’s tables directly.
  • Services interact only through APIs, events, or contracts.

This respects the principle that “a microservice is responsible for both its logic and its data.”

Benefits of This Approach

Autonomy
  • Each team can evolve its database independently, without breaking others.
  • Database schema changes affect only that service.

Example: The Product Team can add new attributes, such as “Color” or “Material,” to their database without asking the Order Team for permission.

Independent Scaling
  • Each database scales based on the load of its service.
  • A high-traffic service can get a bigger or more optimized database without affecting others.
Example:
  • Order Service database may require heavy transaction support and indexing.
  • Notification Service database may just need lightweight storage for templates.
  • Each can scale independently, avoiding overloading a single shared system.
Technology Flexibility
  • Each microservice can use the best database technology for its needs, rather than being forced into a single type.
Examples:
  • Order Service → SQL Server or PostgreSQL (great for relational, transactional data).
  • Product Service → MongoDB (perfect for flexible product catalogs).
  • Notification Service → Redis (ideal for fast key-value lookups and caching).

This “polyglot persistence” gives every service the freedom to choose what works best.

Clear Ownership
  • The team owning a service also owns its data.
  • They decide on schema design, indexing, performance optimization, and security.

Example: The Order Team manages only the Orders database. If something breaks, there’s no confusion about who is responsible.

Real-Time Example of Database per Service
  • Order Service: Uses a relational database (like SQL Server) to ensure strict transaction guarantees for customer orders.
  • Product Service: Uses a NoSQL database (like MongoDB) to handle flexible product attributes and support fast search.
  • Notification Service: Uses Redis to retrieve templates and send SMS/email notifications quickly.

Each service uses a different database type, optimized for its workload. This is impossible with a single shared database.

The Database per Microservice pattern ensures independence, scalability, and agility. It allows each service to choose its own technology, scale on demand, and evolve without affecting others. This is the foundation for building truly autonomous microservices.

Data Consistency Models: Strong vs. Eventual

When data is distributed across multiple microservices, consistency becomes one of the biggest challenges. Unlike monolithic systems, where a single ACID transaction ensures everything is updated together, microservices must choose between accuracy at all times, scalability, and flexibility. The two most common models are Strong Consistency and Eventual Consistency.

Strong Consistency

In this model, all services see the same data immediately after a transaction. That means, in strong consistency, once a transaction is committed, all services see the same data immediately.

  • This usually requires distributed transactions (e.g., Two-Phase Commit) where multiple databases agree before the operation is finalized.
  • It mimics the behaviour of a monolith, but across distributed services.
Pros
  • Guarantees correctness at all times.
  • Prevents anomalies (e.g., no chance of seeing old balances or duplicate records).
  • No risk of one service showing stale or outdated data.
Cons
  • Very complex to implement across microservices.
  • Increases latency because all services must wait for each other.
  • Introduces bottlenecks when scaling.
Real-World Example
  • Banking Transactions. If you transfer ₹5000 from Account A to Account B, both accounts must reflect the correct balances instantly. You cannot risk one account being debited while the other is not credited. Even a small delay could cause serious errors (like withdrawing money that isn’t really available).
Eventual Consistency

In this model, services may temporarily show different data, but they “catch up” over time to reach the same state and become consistent.

  • Updates are propagated asynchronously (e.g., via events or messages). It may take seconds or minutes for all services to be in sync.
  • The system prioritizes performance and availability over instant accuracy.
Pros
  • Works well for most business cases where a small delay is acceptable.
  • Supports high scalability and better fault tolerance.
  • Works well in high-traffic systems where strict accuracy is not critical at all times.
Cons
  • Users may see outdated data for a short time.
  • Developers must design carefully to handle these short-lived inconsistencies.
Real-World Example
  • E-commerce shipping address: If you update your address, it’s okay if the Order Service reflects the change a few seconds later. A short lag doesn’t harm the core business.
  • Social media likes: When you “like” a post, you may not see the updated like count immediately, but it will eventually appear.
Rule of Thumb
  • Use Strong Consistency only where correctness is non-negotiable (e.g., financial transactions, medical records).
  • Use Eventual Consistency for most business cases where speed, scalability, and availability matter more, and where slight delays are acceptable (e.g., profile and catalog updates).

Patterns for Data Consistency

To handle consistency in microservices, several well-known patterns exist. Now let’s look at the main patterns used to keep data consistent in microservices.

Saga Pattern

A Saga is the most popular way to manage distributed transactions in microservices. Instead of one big global transaction, a Saga breaks a large business transaction into a sequence of smaller local transactions, each executed by a different service, with compensating actions to undo work if something fails.

  • After completing its local transaction, a service publishes an event to trigger the next step.
  • If something fails, the system triggers compensating actions to undo previous work.
Styles of Saga
Choreography (Decentralized)

Services communicate via events. Each service listens for an event, performs its own local transaction, and publishes the next event.

  • Pros: Simple to start, no central controller.
  • Cons: Harder to manage as workflows grow; flow is scattered across services. Debugging becomes difficult.
Example:
  • Order Service → publishes OrderCreated
  • Inventory Service → reserves stock, publishes StockReserved
  • Payment Service → processes payment, publishes PaymentCompleted

Analogy: Think of a wedding dance where each dancer knows their steps and reacts to others. Works well for small groups, but becomes chaotic (disorganized, confusing, and lacking order) as more people join.

Orchestration (Centralized)

A Saga Orchestrator (central orchestrator service) manages the workflow and tells each service what to do next. It keeps track of the flow, retries, and compensations.

  • Pros: Clear visibility, easier debugging, retries, and timeouts managed in one place.
  • Cons: Adds a central coordinating component (orchestrator).

Analogy: Think of an orchestra with a conductor who directs musicians in sequence. More organized and predictable.

Example: Order Saga (Orchestrated)
  1. CreateOrder (Pending) → Order Service saves order.
  2. ReserveInventory → Inventory Service reserves stock.
      • If fail → CancelOrder.
  3. ProcessPayment → Payment Service charges the customer.
      • If fail → ReleaseInventory, CancelOrder.
  4. ConfirmOrder → Finalize the order.

If step 3 fails, compensating actions restore balance: release stock and cancel the order. This ensures the business truth: No Order Without Successful Payment.

So, think of Saga as “Business-Level Rollback” for microservices. Instead of ACID rollback across a single DB, each service undoes its own work when something fails.

Two-Phase Commit (2PC)

A classic distributed transaction protocol. It is a traditional way to ensure strong consistency across distributed systems. A central coordinator asks all participants if they can commit. If everyone agrees, it commits. Otherwise, it rolls back.

  • Phase 1: Prepare → All participants confirm they’re ready.
  • Phase 2: Commit → If all agree, the transaction is committed; otherwise, it’s rolled back.
Pros
  • Provides strong consistency.
  • Guarantees atomicity across multiple databases.
Cons
  • Very slow in distributed environments.
  • Locks resources while waiting for all participants.
  • It can create bottlenecks and single points of failure.
  • Rarely used in microservices because availability is usually more important.
Example
  • Banking systems still use 2PC when transferring money across different banks/databases because correctness cannot be compromised, but it is avoided in large-scale microservices because it doesn’t scale well.

Rule of Thumb: Avoid 2PC in microservices unless absolutely necessary. Prefer Sagas + compensations.

CQRS (Command Query Responsibility Segregation)

CQRS separates Writes (Commands that change state) from Reads (Queries that return data). In this case:

  • The Command side updates the system of record (primary database).
  • The Query side uses a different database or projection optimized for fast reads.
Pros:
  • Read models can be designed for speed and scalability.
  • Write models can focus on correctness and validation.
Cons:
  • More complex architecture.
  • Requires syncing between write and read stores (often eventual consistency).

Example: Ticket booking system

  • Command Side: BookSeat service updates the core booking DB.
  • Query Side: A separate read database serves “Available Seats” to users very quickly.
  • Even if updates take a second to sync, users can still browse quickly while ensuring seat reservations are reliable.

This avoids overloading the main booking database with heavy queries while ensuring users see near real-time results.

Query Patterns Across Services:

In microservices, each service owns its own database. This ensures autonomy and independence but introduces a challenge:

Sometimes you need data that is spread across multiple services. For example, to build a Customer Profile Page, you may need:

  • User Details (from User Service)
  • Order History (from Order Service)
  • Last Payment Status (from Payment Service)

Since there is no single “database” to query across all services, we need query patterns to combine data in valuable ways. Two popular strategies are API Composition and CQRS Read Models (Materialized Views).

API Composition

In this approach, a client (or an API Gateway) makes calls to multiple services and combines their responses into a single result.

  • The client or gateway acts like a data aggregator.
  • Each service still owns its data, and no extra database is needed.
  • Aggregation occurs at runtime when the request is made.
  • The responses are merged and presented as a single view.
Pros
  • Simple to implement. No extra data storage or sync logic is required.
  • Data is always fresh because it always fetches the latest data directly from services.
Cons
  • Latency Increases: Can cause high latency (longer response times) when many services are involved.
  • Failure risk: If one service is down or slow, the whole composition may fail or return incomplete results, or the entire response may suffer.
  • Complex logic at client/gateway: Someone needs to handle retries, errors, and combine data.
  • More network traffic due to multiple calls.
Real-World Example:

A Customer Profile Page in an e-commerce app:

  • Call User Service for customer details (name, email, phone).
  • Call Order Service for past orders.
  • Call the Payment Service for the latest payment status.
  • The API Gateway merges all of these into a single response for the UI.

This approach is fine when you need a few services and can tolerate multiple calls.

Analogy: Think of a travel booking website. To display your trip details, it fetches flights, hotel bookings, and car rentals from various services and combines them into your itinerary.

CQRS Read Models (Materialized Views)

Sometimes, frequent queries need data from many services; calling all of them every time would be too slow. In this case, we use CQRS Read Models or Materialized Views. Here, instead of calling multiple services every time, we maintain a separate read-optimized database (also called a materialized view) that pre-aggregates data from multiple services.

  • A separate read database is maintained.
  • This read database stores a pre-combined view of data from multiple services.
  • Services publish events (like “OrderPlaced” or “PaymentCompleted”) that update the read model.

In this scenario:

  • The write models (original service databases) remain independent.
  • The read model is optimized for queries, not for updates.
Pros:
  • Very fast queries, all the needed data is already in one place.
  • Great for dashboards, reports, and analytics.
  • Reduces repeated service calls.
Cons:
  • Complexity, keeping the read model in sync with changes from multiple services, is tricky.
  • Data may be slightly out of date (eventual consistency).
  • Data is stored in multiple places (services + read model).
Example:

A Reporting Service in e-commerce:

  • Aggregates data from Orders, Payments, and Deliveries into a single read model.
  • When you run a sales report, the system queries only the reporting database instead of calling all services separately.
  • This allows instant queries, even with millions of records.

This approach is best when you need frequent, complex queries (e.g., analytics and dashboards) and performance is critical.

Analogy: Think of a news aggregator app. Instead of checking dozens of websites every time you open it, the app already stores and organizes articles in one place for fast browsing.

Handling Distributed Transactions and Compensating Actions

In a monolithic system, multiple operations (such as creating an order, updating stock, and charging a payment) can be wrapped in a single ACID transaction. This ensures an all-or-nothing outcome: either everything succeeds, or everything rolls back automatically.

In microservices, each service owns its own database, and operations span across different services and networks. This makes global ACID transactions impractical, so instead, we rely on distributed workflows and compensating actions to maintain consistency.

Compensating Transactions

A compensating transaction is an action that undoes the effect of a previous step if something goes wrong later in the process.

  • Think of it as a “manual rollback” at the business level.
  • Instead of undoing everything in a single significant DB transaction, each service reverses its own work when needed.

Example: Airline Booking System

  1. Seat Reservation Service reserves a seat.
  2. Payment Service charges the customer.

If payment fails:

  • The system doesn’t just leave the seat blocked.
  • A compensating action in the Seat Reservation Service restores the seat to availability.

This way, the system still aligns with the user’s intention: no payment → no booking.

Idempotency

In distributed systems, failures and retries are normal. Without idempotency, retries can cause data corruption.

  • Idempotent operation: An operation that can be repeated multiple times without changing the final result.

Example: Cancel Order Request

  • If the CancelOrder request is sent once, the order is canceled, and the user gets a refund.
  • If the same request is sent again (due to a retry or a duplicate message), the system should not issue a second refund.
  • Idempotency ensures that repeated operations lead to the same outcome.

This prevents overcharging, over-refunding, or double deductions.

Real-Time Example: Order and Payment Workflow

Let’s look at an e-commerce scenario where multiple services participate in a single workflow.

  1. User places an order
      • The Order Service creates an entry in Pending status.
  2. Payment Service charges the user
      • If successful → move to the next step.
      • If it fails → compensating action cancels the order.
  3. Inventory Service deducts stock
      • If successful → continue.
      • If stock is unavailable → compensating action refunds the payment and cancels the order.
  4. Notification Service sends confirmation
      • User receives an email or SMS confirming their order.
Failure Scenarios & Compensations
Payment Fails
  • The Order Service cancels the pending order.
  • Any reserved stock is restored (compensating action).
  • The user gets a notification: “Your order could not be processed.”
Inventory Out of Stock
  • The Inventory Service reports failure.
  • The Saga triggers a rollback: Payment Service refunds the money, and the Order Service cancels the order.
  • The customer sees: “Item out of stock, your payment has been refunded.”
Notification Fails
  • The order, payment, and inventory updates are already successful.
  • Instead of rolling back, the Notification Service retries sending.
  • Because of idempotency, multiple retry attempts don’t send duplicate emails.

Managing data in microservices is not about forcing monolithic database practices into a distributed system, but about embracing new approaches. By following patterns like Saga, CQRS, and compensating transactions, and by choosing the right consistency model, teams can balance accuracy with scalability. The key is to design for independence while ensuring a consistent user experience. With the right strategies, microservices can deliver both developer flexibility and business reliability.

Leave a Reply

Your email address will not be published. Required fields are marked *