Backend Development 10 min read

Ensuring Idempotency in Order Services: Preventing Duplicate Orders and Solving the ABA Problem

The article explains how to achieve idempotent order creation and update operations by using unique request identifiers, database primary‑key constraints, Redis flags, and version columns to prevent duplicate submissions and resolve ABA anomalies in distributed backend systems.

Selected Java Interview Questions
Selected Java Interview Questions
Selected Java Interview Questions
Ensuring Idempotency in Order Services: Preventing Duplicate Orders and Solving the ABA Problem

Background

The simplest case is a DB transaction: when creating an order, inserts to the order table and order‑item table must occur within 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, possibly on different nodes due to load balancing; thus the interface must guarantee idempotency.

How to Avoid Duplicate Orders

Front‑end can block repeated form submissions, but network errors trigger retries in RPC frameworks or gateways, so duplicate requests cannot be fully prevented on the client side; the core issue is ensuring service‑side idempotency.

How to Determine if a Request Is Duplicate

Check the order table before inserting, but defining “duplicate order” in SQL is difficult.

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

To guarantee idempotency you need:

Each request must have a unique identifier

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

After processing a request, record that it has been handled

In MySQL, store a status field or a payment record for the order.

When receiving a request, check whether it has been processed before

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

When inserting a record without providing a primary key, the DB generates one automatically; duplicate requests will try to insert the same primary key and fail. By supplying the primary key in the INSERT statement and relying on the DB’s unique constraint, you can achieve idempotent order creation.

Provide an “orderId generation” API that returns a globally unique order number. The front‑end obtains this number before the user submits the order, and includes it in the creation request.

The order number becomes the primary key of the order table; duplicate requests carry the same order number, and the DB’s unique constraint ensures only one INSERT succeeds.

In practice you can combine this with Redis: use the orderId as a unique key. Only after successfully inserting the payment record do you set the Redis key.

When paying an order, insert a payment record with a unique order_id key. Before the payment, write a Redis entry like set order_id payed . Subsequent requests check this key; if the value is “payed”, the request is ignored.

If a duplicate order causes the t_order insert to fail, 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 approach: the service returns success regardless of the duplicate‑insert error.

Solving the ABA Problem

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 generated. If the system loses the success response of the 666 request and retries, the tracking number may be overwritten back to 666, causing incorrect data.

Solution

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

The service compares the version in the request with the current version in the DB:

If they differ, reject the update.

If they match, perform the update and increment the version by 1, all within a single transaction.

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

The version value must be supplied by the client in the WHERE clause.

This mechanism ensures that from the moment the order record is read until the update succeeds, no other modification can occur; any concurrent change will alter the version and cause the update to fail, prompting the client to reload the latest data.

Applying this to the ABA scenario:

Updating to 666 succeeds; a later 888 request with the old version fails, so the user sees a failure for 888.

If 666 succeeds and 888 arrives with the new version, 888 succeeds; a retry of the 666 request now carries the old version and fails because the version has changed.

Thus the DB state and user feedback remain consistent, achieving idempotent updates and avoiding ABA.

Summary

For order creation, pre‑generate a unique order number and rely on the DB’s unique constraint to ensure idempotent inserts.

For order updates, use a version‑number mechanism: verify the version before updating, and increment it atomically to solve the ABA problem and guarantee idempotent updates.

These idempotency techniques can be applied to any service that persists data in a database with a primary‑key table.

backendDatabaseIdempotencyTransactionsVersioningorder serviceABA problem
Selected Java Interview Questions
Written by

Selected Java Interview Questions

A professional Java tech channel sharing common knowledge to help developers fill gaps. Follow us!

0 followers
Reader feedback

How this landed with the community

login 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.