How to Ensure Order Service Idempotency and Prevent Duplicate Orders

This article explains how to achieve idempotent order creation and updates in backend services by using unique request identifiers, database unique constraints, Redis flags, and optimistic locking with version columns, while also addressing the ABA problem with practical code examples and diagrams.

Java Backend Technology
Java Backend Technology
Java Backend Technology
How to Ensure Order Service Idempotency and Prevent Duplicate Orders

Problem Background

The simplest solution is a DB transaction: when creating an order, inserts into the order table and order‑item table must run in the same transaction.

If the Order service calls the Pay service and a network timeout occurs, the Order service may retry, causing the Pay service to receive the same payment request twice on different nodes. Therefore, a distributed interface must guarantee idempotency.

How to Avoid Duplicate Orders

Front‑end can try to prevent duplicate submissions, but network errors and automatic retries in RPC frameworks or gateways mean duplicate requests can still reach the back‑end, so the service itself must ensure idempotency.

2.1 How to Determine a Duplicate Request

Before inserting an order, check the order table for an existing order – but defining “duplicate” in SQL is hard.

Is an order with the same user, product, and price a duplicate? What if the user legitimately places two identical orders?

2.1.1 Each Request Must Have a Unique Identifier

For example, a payment request should include an order ID that can be used only once for successful payment.

2.1.2 Record That the Request Has Been Processed

In MySQL, add a status field or insert a payment record before the actual payment; the presence of this record indicates the request was handled.

2.1.3 Check Before Processing If It Was Already Handled

If an order is already paid, a payment record exists. A duplicate request will attempt to insert the same primary key, triggering a unique‑key violation and preventing double charging.

Use a pre‑generated global unique order ID as the primary key. The front‑end obtains this ID before the user submits the order, and the same ID is sent with the creation request. The DB’s unique constraint guarantees only one successful INSERT.

In practice, combine this with Redis: store the order ID as a unique key, set it to “paid” after a successful payment record, and check Redis on subsequent requests to reject duplicates.

If a duplicate order causes an INSERT failure, the Order service should not return the error to the front‑end; otherwise the user may see a failure message even though the order was created.

Correct behavior: the service returns a success response regardless of the duplicate INSERT error.

3 Solving the ABA Problem

3.1 What Is ABA

After an order is paid, the seller fills in a tracking number. If the seller first enters 666, then corrects it to 888, two update requests are sent. If the 666 request succeeds but its response is lost, a retry may resend 666, overwriting the correct 888 value.

3.2 Solution

Add a version column to the order table. Each time the order is read, the version is returned to the client, which must send it back with the update request.

The service compares the provided version with the current one:

If they differ, reject the update.

If they match, update the data and increment the version in the same transaction.

UPDATE orders set tracking_number = 666, version = version + 1 WHERE version = 8;

The version check guarantees that no other modification occurred between reading and updating the record; otherwise the update fails and the client must retry with the latest version.

Using this versioning, the ABA scenario is handled:

The 666 update succeeds; the subsequent 888 request carries a stale version and is rejected.

If 888 carries the new version, it succeeds, and any later retry of 666 fails because the version has changed.

This ensures the database state matches the user’s feedback, achieving idempotent updates and eliminating ABA.

Summary

Create orders idempotently by pre‑generating a global unique order ID and relying on the DB’s primary‑key uniqueness.

Update orders idempotently by using an optimistic‑locking version column to detect and prevent ABA scenarios.

These two idempotency techniques can be applied to any service that writes to a database with a primary key.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

redisIdempotencyorder serviceABA problemoptimistic locking
Java Backend Technology
Written by

Java Backend Technology

Focus on Java-related technologies: SSM, Spring ecosystem, microservices, MySQL, MyCat, clustering, distributed systems, middleware, Linux, networking, multithreading. Occasionally cover DevOps tools like Jenkins, Nexus, Docker, and ELK. Also share technical insights from time to time, committed to Java full-stack development!

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.