beginner~2h

Data Consistency & Duplication Challenges

Explore what happens when a single business action must update several services' databases together, why classic distributed transactions struggle here, and how deliberate data duplication can be handled safely.

Learning objectives

  • Explain why a business action spanning multiple services can partially fail in ways a single-database transaction never could.
  • Describe why Two-Phase Commit is a poor fit for typical microservice architectures.
  • Explain the trade-off between always calling the owning service and duplicating data locally.
  • Identify when deliberate data duplication is an acceptable, well-engineered choice.

◆ Story

A customer applies for a loan. Approving it isn't really one action — it's three. Loans has to create the loan record. Accounts has to deposit the loan amount into the customer's account. Customer has to update the customer's credit profile to reflect the new debt. In a bank with one shared filing room, a single clerk could update all three folders in one continuous motion, and if they were interrupted halfway through, they could simply put everything back exactly as it was before.

In the split, four-department bank, three different clerks in three different rooms each have to do their own part of this — and nothing automatically guarantees that all three actually happen, or that none of them happen, together. If the clerk in Accounts gets interrupted after Loans has already finished, there is no single hand available to put everything back the way it was.

This exact scenario — one meaningful business action that needs several independent systems to agree — shows up constantly in real systems: an order that needs inventory reserved, payment charged, and a confirmation email sent; a signup that needs an account created, a welcome email queued, and a billing profile set up. Whenever "one action" secretly means "several systems each doing their own part," this same risk is lurking underneath.

◆ The problem

Suppose Loans successfully creates the loan record, but right afterward, the Accounts database becomes briefly unreachable and the deposit fails. The customer now has an official loan on record — with no money actually deposited into their account. This isn't a rare, exotic failure mode; across enough transactions over enough time, something in this shape is close to guaranteed to eventually happen to someone, unless the system is deliberately designed to handle it.

This is the same underlying problem that a single database's transactions solve with COMMIT and ROLLBACK — making sure a group of changes either all happen or none of them do — except here it spans three completely separate databases, owned by three separate services, that might not even be running in the same data center. A regular transaction can't help, because a regular transaction only has authority over one database connection at a time.

The practical damage from getting this wrong is rarely subtle. A customer with a "phantom" loan and no deposited funds will call support, and whoever picks up the phone will need to manually reconcile three different systems by hand — exactly the kind of expensive, error-prone cleanup a well-designed system is meant to avoid needing in the first place. Guaranteeing that multi-service actions behave predictably, either fully completing or being deliberately undone, requires a pattern built specifically for that purpose.

The traditional database answer to "make several operations atomic together" is a single transaction ending in COMMIT or ROLLBACK. Some distributed systems attempt a similar idea across multiple databases using Two-Phase Commit, often shortened to 2PC: a coordinator asks every participating database to "prepare" its part of the change, waits for all of them to confirm they're ready, and only then tells everyone to actually commit — or tells everyone to roll back if even one participant couldn't prepare.

▲ Common mistake

Reaching for Two-Phase Commit as the default answer to multi-service consistency in a microservices architecture. On paper it sounds like exactly what's needed; in practice it directly undermines the reasons the databases were split in the first place.

2PC requires every participating database to stay synchronously available and to hold locks on the affected rows throughout the entire process, from "prepare" until the final "commit" or "rollback" arrives. If the coordinator itself crashes partway through, participants can be left holding those locks indefinitely, waiting for a decision that may never come. That directly works against independent availability and independent scaling — the very benefits that motivated splitting into separate services and separate databases to begin with. A service that's supposed to be independently deployable and independently scalable can't also be blocked, mid-transaction, on a coordinator somewhere else being healthy.

This is exactly why modern microservice architectures generally reach for a different approach instead: a sequence of local, independent transactions, each one committed on its own, paired with a deliberate, explicit plan for undoing earlier steps if a later one fails. That approach — usually called the saga pattern — trades a single all-or-nothing guarantee for a carefully designed sequence of smaller, reversible steps.

◆ Story

Recall the receptionist, calling all four departments every single time someone wants a customer profile. That's simple, but it means every profile request costs several network calls, every single time — even for data that barely ever changes, like a customer's name. Some teams solve this by having Accounts Service keep its own small, local copy of the customer's name, avoiding a call to Customer Service for every single request that needs it.

◆ The problem

The moment Accounts keeps its own copy of the customer's name, that copy can drift out of sync. If the customer updates their name in Customer Service, Accounts' local copy has no way of knowing about it, unless something explicitly tells it to update. Now there are two "sources of truth" for the exact same fact, silently disagreeing with each other, and nobody watching either one would necessarily notice.

OptionTrade-off
Don't duplicate — always call the owning serviceAlways accurate, but slower, and creates a runtime dependency on that other service being available.
Duplicate deliberately, kept in sync via eventsFast, and keeps working even if the owning service is briefly down — but requires real engineering discipline to keep the copies honestly in sync.

Duplicating data on purpose, kept in sync through a reliable stream of events, is a completely normal, well-established practice in real microservices systems — the key word is deliberate. The failure mode worth avoiding isn't duplication itself; it's duplication nobody planned for, with no mechanism keeping the copies honestly in sync over time. A local copy that's updated within a few hundred milliseconds of the source changing is usually a perfectly acceptable trade for a service that no longer has to make a network call on every request.

Q: Why can't a normal database transaction fix a business action that spans three separate microservice databases? A: A regular transaction only has authority over one database connection at a time; across three independent databases there's no single transaction boundary that could commit or roll back all three together.

Q: Why does Two-Phase Commit work against the reasons services were split into their own databases in the first place? A: It requires every participant to stay synchronously available and hold locks throughout the whole process, directly conflicting with the goal of independent availability and independent scaling.

Q: Is duplicating the same piece of data across two services always a design mistake? A: No — deliberate duplication, kept in sync through a reliable mechanism like events, is a normal and accepted practice; the real risk is unplanned duplication with no synchronization mechanism at all.

Q: What approach do modern microservice architectures generally use instead of Two-Phase Commit for multi-service consistency? A: A sequence of local, independent transactions with an explicit plan for undoing earlier steps if a later one fails, commonly known as the saga pattern.

Want a visual for this concept?

Generate a diagram tailored to “Data Consistency & Duplication Challenges” — the AI picks whichever visual (flowchart, comparison, sequence, etc.) best fits.

Sign in to generate a visual →

Practice quiz

Next Step

Continue to Introduction to CQRS← Back to all Event-Driven Microservices chapters