In a monolithic application, several related updates can often be handled within a single database transaction. Either all changes commit, or none do.
In a microservices architecture, those same operations may involve multiple independent systems:- A database storing an order
- A message broker distributing an event
- A payment service processing a transaction
- Another database maintaining inventory
And that's where things get interesting.
Most microservice data consistency problems don't appear during normal operation. They emerge when something unexpected happens: a process crashes, a network connection fails, a message is redelivered, or a service restarts at precisely the wrong moment.
The result? Lost messages, duplicate updates, inconsistent databases, and business operations that appear successful in one system but fail in another.
Let's examine seven common failure scenarios, why they happen, and what you can do to prevent them.1. Lost Messages: The Database Commits, but the Event Never Arrives
Imagine an order-processing service that performs two operations:- Saves a new order in its database.
- Publishes an OrderCreated event to a message broker.
That seems reasonable. After all, why publish an event before the order actually exists?
But consider this sequence:- The application saves order #1234.
- The database transaction commits successfully.
- The application crashes before publishing OrderCreated.
- The service restarts.
Downstream services may never learn that the order was created. Inventory remains unchanged. Shipping is never initiated.
From the database's perspective, everything worked. From the business perspective, the workflow is incomplete.Why retries don't necessarily solve this
A retry can help only if the application knows that an event still needs to be published.
After a crash, the original process may have lost that information. Unless the application durably records the pending publication, there may be nothing to retry.How to prevent lost messages
Several approaches can address this problem:- Transactional outbox: Store the business update and an outgoing event record in the same local database transaction. A separate process publishes the event afterward.
- Transaction coordination: Use a distributed transaction to coordinate database and message-broker operations when both resources support a compatible transactional protocol.
- Reconciliation: Detect missing downstream updates and repair them, although this generally provides eventual rather than immediate consistency.
2. Ghost Messages: The Event Exists, but the Database Update Doesn't
What if you reverse the order?
Instead of committing the database first, the application publishes the message before committing the database transaction.
That might seem to solve the lost-message problem.
Unfortunately, it creates the opposite failure.
Consider the same order-processing service:- The application receives an order request.
- It publishes an OrderCreated event.
- Another service receives the event and starts processing it.
- The original database transaction fails or rolls back.
Why ghost messages are dangerous
A ghost message can trigger real business consequences.
A shipping service might prepare an order that doesn't exist. A billing service might initiate payment processing. An inventory service might reserve products unnecessarily.
The receiving services have no reason to assume that the published event represents an uncommitted operation.Why changing commit order doesn't help
Committing the database first risks lost messages.
Publishing the message first risks ghost messages.
Neither ordering guarantees atomicity because the database and broker are independent transactional resources.How to prevent ghost messages
The transactional outbox pattern avoids publishing an event before the corresponding business update commits.
Alternatively, when supported, XA transaction coordination can ensure that the database update and message publication participate in one coordinated transaction.
The lesson: Commit order alone cannot guarantee consistency across independent systems.3. Duplicate Messages: One Business Event Gets Published Twice
Distributed systems frequently encounter situations where an operation succeeds, but the caller never receives confirmation.
Imagine that an order service publishes an event:- The application sends OrderCreated.
- The broker accepts and persists the message.
- The application crashes before it marks the message as sent.
- The application assumes publication failed.
- It retries the publication.
Why duplicates matter
Duplicate messages aren't always harmless.
Suppose a downstream consumer processes each event by creating a shipment, updating an account balance, or issuing a customer notification.
Without protection, duplicate events can produce duplicate business effects.Why retries can make the problem worse
Retries improve availability when operations fail temporarily.
But when the outcome is unknown, retrying can repeat an operation that already succeeded.
This is one of the fundamental challenges of distributed systems: an acknowledgement failure is not proof that the original operation failed.How to prevent duplicate messages
Possible solutions include:- Idempotent consumers: Ensure processing the same business event multiple times produces only one intended business effect.
- Deduplication: Track processed event identifiers and reject duplicates. Often used to achieve “idempotency” like above.
- In general, these approaches require significant complexity in ALL downstream consumers of a message. The complexity becomes viral.
- Transactional messaging: Coordinate publication with the relevant transactional resources where supported. This avoids an explosion of downstream complexity so it pays off many times.
4. Lost Consumer Updates: The Message Disappears Without Changing the Database
The previous examples focused on message producers.
But consumers face similar problems.
Imagine an inventory service receiving an OrderCreated event.
Its job is to update the inventory database and acknowledge the message to the broker.
Suppose it acknowledges the message first.- The inventory service receives OrderCreated.
- It acknowledges the message.
- The broker considers the message handled.
- The inventory service attempts to update the database.
- The database update fails.
Why this is difficult to detect
The broker may report successful message consumption.
The database may show no corresponding update.
Neither system necessarily identifies the mismatch as a distributed transaction failure.
Monitoring message queues alone won't reveal the missing business effect.How to prevent lost consumer updates
Where supported, coordinate the database transaction and broker acknowledgement in one distributed transaction.
For example, a Java application using a compatible JMS broker and XA-capable JDBC database can use Atomikos to coordinate both resources.
If transactional coordination isn't available, applications need a carefully designed consumption strategy, durable state tracking, and reconciliation.
The goal is to ensure that a message is not permanently considered processed before its required database effects are safely committed.5. Duplicate Message Processing: One Message Creates Two Business Effects
What if the consumer updates the database first and acknowledges the message afterward?
This avoids acknowledging messages before the business update commits.
But it introduces another failure window.
Consider the following:- The inventory service receives an order event.
- It reduces the available inventory by one unit.
- The database transaction commits.
- The application crashes before acknowledging the message.
- The broker redelivers the message.
- The service reduces inventory again.
The broker behaved correctly according to its redelivery semantics. The database also committed both updates successfully.
Yet the business outcome is wrong.Isn't at-least-once delivery enough?
At-least-once delivery is designed to avoid losing messages, but it permits redelivery.
That means the application must be prepared to encounter the same message more than once.
If the consumer isn't idempotent, redelivery can create duplicate effects.How to prevent duplicate processing
One common approach is to store a unique message identifier alongside the business update, in the same local database transaction.
Before processing, the consumer checks whether the identifier has already been handled.
If it has, the duplicate can be ignored.
For compatible transactional resources, XA coordination provides another approach by coupling the database update with the broker's transactional message consumption.
However, no transaction manager can automatically make arbitrary non-transactional side effects, such as an external HTTP call, execute exactly once.
Exactly-once business processing requires guarantees across the entire relevant operation, not just the message broker.6. Partial and Duplicate Remote Commits: A Timeout Doesn't Tell You What Happened
Not every microservice architecture uses asynchronous messaging.
Many applications communicate through synchronous REST or SOAP calls.
These interactions introduce a different consistency problem: the caller may not know whether a remote operation committed.
Imagine a payment service receiving a request to charge a customer €100.- The order service sends the payment request.
- The payment service processes the charge.
- The payment transaction commits.
- The network connection fails before the response reaches the order service.
- The order service receives a timeout.
No. The payment may have succeeded.
But the caller doesn't know.The danger of retrying
If the order service retries the request, the payment service might process a second charge.
A single timeout can therefore lead to a duplicate remote commit.
The opposite problem is also possible: the remote operation may have failed before committing, leaving the caller uncertain about what happened.How to handle unknown outcomes
The right solution depends on the remote system's capabilities.
Common techniques include:- Idempotency keys: Associate repeated requests with one logical business operation.
- Status queries: Check the remote operation's outcome before retrying.
- Transaction-aware integration: Use coordinated transactional resources where the underlying protocols and systems support them.
- Reconciliation: Compare records across systems and resolve discrepancies.
A circuit breaker can tell you to stop calling a service. It cannot tell you whether the previous business operation committed.
This distinction is especially important for payments, transfers, reservations, and other operations where duplicate execution has real consequences.7. Inconsistent Recovery: Your Databases Are Restored, but Your Business Data Isn't
Microservice applications often use multiple databases.
An order service might use PostgreSQL. An inventory service might use another relational database. A reporting service might use a document database.
This approach, often called polyglot persistence, allows teams to choose storage technologies that suit their requirements. But it introduces a subtle disaster-recovery problem.The recovery scenario
Imagine a business operation that updates two databases.
At 10:00:00, database A records a new order.
At 10:00:01, database B records the corresponding inventory change.
Now imagine the databases are backed up independently.- Database A is restored to a point after the order was committed.
- Database B is restored to a point before the inventory change.
But the combined system is inconsistent.
The order exists, while the corresponding inventory update does not.Why successful backups aren't enough
A valid backup of each individual database doesn't guarantee a transactionally consistent snapshot of the entire application.
The problem is not necessarily backup corruption.
It's the absence of a common recovery boundary across systems.How to improve recovery consistency
Possible approaches include coordinated recovery procedures, transaction-log retention, consistent snapshot mechanisms, and application-level reconciliation.
Where compatible XA resources participate in distributed transactions, transaction recovery mechanisms can also help resolve transactions that were incomplete during a failure.
Atomikos provides transaction coordination for Java applications, while capabilities such as LogCloud support transaction recovery in distributed environments.
However, transaction recovery and disaster recovery are not identical. Recovering an in-doubt transaction doesn't automatically make independently captured database backups mutually consistent.
A complete recovery strategy must account for both transaction outcomes and the consistency of restored data.Summary: Seven Microservice Data Consistency Failures
These seven failures share a common theme: independent systems can each behave correctly while the overall business process becomes inconsistent.| Failure | Example outcome | Underlying problem |
| 1. Lost producer message | Order commits, event never appears | Database and broker commit independently |
| 2. Ghost message | Event is published, order rolls back | Publication precedes business commit |
| 3. Duplicate producer message | One event is published twice | Unknown publication outcome followed by retry |
| 4. Lost consumer update | Message acknowledged, database unchanged | Acknowledgement and database commit are separate |
| 5. Duplicate processing | One event causes two inventory updates | Redelivery repeats a committed business effect |
| 6. Partial or duplicate remote commit | Payment succeeds despite timeout, then runs again | Caller cannot determine remote commit outcome |
| 7. Inconsistent recovery | Restored databases disagree about an order | Recovery points lack a shared consistency boundary |
Why Microservice Data Consistency Is Harder Than It Looks
The central problem isn't that databases, message brokers, or HTTP APIs are inherently unreliable.
It's that they provide different transactional guarantees.
A database transaction can protect changes inside one database.
A message broker can provide durable delivery and acknowledgement mechanisms.
An HTTP API can report whether a request received a successful response.
But none of these guarantees automatically makes a multi-system business operation atomic.
This is why choosing the right consistency mechanism matters.Common approaches to microservice consistency
Idempotency and deduplication help prevent repeated requests or message deliveries from producing duplicate business effects. They are especially useful when at-least-once delivery is unavoidable. The transactional outbox pattern couples a business database update with durable event creation. It can eliminate the gap between committing business data and recording an outgoing event, although publication and downstream processing still require appropriate duplicate handling. Saga patterns coordinate a sequence of local transactions through events or commands, often using compensating actions when later steps fail. They can be appropriate for long-running workflows, but compensation is not equivalent to atomic rollback and will always introduce additional application complexity. XA transaction coordination enables compatible transactional resources to participate in a distributed transaction, typically using two-phase commit. This can be particularly useful when a Java application must coordinate JDBC database operations and JMS messaging without implementing custom compensation or deduplication for those coordinated operations.No single approach fits every architecture.
For example, XA cannot directly coordinate arbitrary REST APIs or every Kafka deployment. And even when XA is available, applications must account for resource compatibility, availability, recovery, and operational trade-offs.
The key is to select a consistency model based on the actual failure scenarios your application must handle.Where Atomikos Fits
For Java teams working with transactional databases and messaging systems, Atomikos provides a lightweight alternative to managing distributed transactions through heavyweight application servers.
Atomikos is an XA-compliant transaction manager that integrates with Java technologies such as JDBC, JMS, Spring Boot, and Hibernate.
It helps coordinate supported resources so related database and messaging operations can commit or roll back as one transaction.
This can eliminate entire classes of lost-message, ghost-message, and duplicate-processing failure windows associated with independently committed operations.
Unlike approaches that require developers to implement compensation workflows across multiple services, transaction coordination can keep consistency logic closer to the infrastructure layer when the participating resources support it.
Atomikos also supports cloud-native Java deployments without requiring a traditional application server, with LogCloud providing capabilities for decentralized transaction recovery.
For teams building financial systems, order-processing platforms, or other applications where data consistency is critical, this can significantly reduce the amount of custom reliability logic they need to maintain.Diagnostic Checklist: Is Your Microservice Architecture at Risk?
Before assuming your system is reliable, ask these questions:- Does any service update a database and publish a message as separate operations?
- Can a message be acknowledged before the corresponding database transaction commits?
- Can a consumer process the same business event more than once?
- Does your application retry operations after a timeout without checking whether the original request succeeded?
- Do you rely on commit order to guarantee consistency between independent resources?
- Are your database backups created and restored without checking cross-system consistency?
- Have you tested what happens when a service crashes between two related operations?
- Do you know which parts of your application provide at-most-once, at-least-once, or effectively exactly-once business processing?
The most important test isn't whether your application works when every component is healthy.
It's whether the system remains correct when something fails halfway through a business operation.Final Thoughts: Don't Let the Happy Path Define Reliability
Microservices are designed to make applications more modular, scalable, and independently deployable.
But distributing an application also distributes its failure modes.
Lost messages, ghost events, duplicate processing, uncertain remote commits, and inconsistent recovery aren't necessarily the result of poor programming.
They're predictable consequences of coordinating operations across independent systems without sufficient transactional guarantees.
The good news is that these failures can be addressed.
Depending on your architecture, the solution may involve transactional messaging, idempotency, an outbox, recovery coordination, or XA transactions.
What matters is understanding the failure windows before they affect production.
Because reliable microservices aren't just about making individual operations succeed. They're about keeping the entire business transaction consistent—even when things go wrong.Build More Reliable Java Microservices with Atomikos
Are you coordinating database updates and messaging in a Java microservices architecture?
Discover how Atomikos helps you simplify distributed transaction management, prevent inconsistent updates, and coordinate supported JDBC and JMS resources without a heavyweight application server.
Explore Atomikos transaction management → Or download our Microservice Data Consistency Pitfalls cheat sheet for a quick reference to the seven failure scenarios covered in this article.
Comments
Add a comment