Backend Development 11 min read

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

This article explains how to achieve idempotent order creation and updates by using unique request identifiers, database primary‑key constraints, Redis flags, and optimistic locking with version columns, thereby preventing duplicate orders and solving the ABA problem in distributed backend systems.

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

Problem Background

The simplest case is a DB transaction: when creating an order, inserts to the order table and order‑item table must be executed 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 on different nodes. Therefore, a distributed interface must guarantee idempotency.

How to Avoid Duplicate Orders

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

2.1 How to Determine a Request Is Duplicate

Before inserting an order, check the order table for an existing order – 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 achieve idempotency, the following practices are needed:

2.1.1 Each request must have a unique identifier

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

2.1.2 After processing a request, record a flag indicating it has been handled

In MySQL, add a status field or a payment‑flow record for the order before payment.

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

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

When inserting records, usually no primary key is supplied; the DB generates it. If an INSERT statement provides a primary key that already exists, the statement fails. Thus, leveraging the DB’s primary‑key uniqueness constraint can enforce idempotency for order creation.

Provide an “orderId generation” API that returns a globally unique order number. The front‑end calls this API before showing the order page, obtains the order number, and includes it in the order‑creation request. The order number becomes the primary key of the order table, so duplicate INSERT statements use the same key and only one succeeds.

In practice, combine this with Redis: use the orderId as a unique key. Only after successfully inserting the payment flow should the service set set order_id payed in Redis. Subsequent duplicate requests check Redis; if the value is “payed”, they skip payment.

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

Correct approach: the order service returns success regardless of the duplicate‑insert error, because the order already exists.

Solving the ABA Problem

3.1 What Is the ABA Problem

After an order is paid, the seller fills in a tracking number. Suppose the seller first enters “666”, then corrects it to “888”. Two update requests are sent: one with 666, one with 888. If the system crashes after the 666 update succeeds but before the response reaches the caller, the caller retries, sending 666 again. The tracking number may flip back to 666, corrupting data.

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 when updating.

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, all within a single transaction.

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

The client supplies the version value in the WHERE clause. If another user modifies the order in the meantime, the version changes and the update fails, forcing the client to re‑fetch the latest data.

With this version check, the ABA scenario is handled in two ways:

The 666→888 update carries the old version and fails, so the user sees a failure for the 888 update.

If the 666 update succeeds, the 888 request carries the new version and succeeds; a later retry of the 666 request uses the old version and fails because the version has changed.

Thus the DB state and the user’s view remain consistent, achieving idempotent updates and eliminating ABA.

Summary

For order creation, pre‑generate an order number and rely on the DB’s unique constraint to prevent duplicate inserts, achieving idempotent order creation.

For order updates, use an optimistic‑locking version column to verify the version before updating and increment it atomically, solving the ABA problem and ensuring idempotent updates.

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

BackendDatabaseIdempotencyorder serviceABA problemOptimistic Locking
Architect
Written by

Architect

Professional architect sharing high‑quality architecture insights. Topics include high‑availability, high‑performance, high‑stability architectures, big data, machine learning, Java, system and distributed architecture, AI, and practical large‑scale architecture case studies. Open to ideas‑driven architects who enjoy sharing and learning.

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.