Ensuring Idempotency and Solving the ABA Problem in Order Services
This article explains how to achieve idempotent order creation and updates in backend services by using database transactions, unique request identifiers, Redis flags, and optimistic locking with a version column to prevent duplicate submissions and solve 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 performed 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, possibly on different nodes due to load balancing. Therefore, a distributed interface must guarantee idempotency.
How to Avoid Duplicate Orders
Front‑end pages can try to prevent duplicate form submissions, but network errors can cause retries, and many RPC frameworks or gateways have automatic retry mechanisms, so duplicate requests cannot be fully avoided on the client side. The core problem is ensuring the service interface is idempotent.
2.1 Determining Whether a Request Is Duplicate
Before inserting an order, check the order table for an existing order – but it is hard to define “duplicate order” with pure SQL.
Is an order with the same user, product, and price a duplicate? What if the user intentionally places two identical orders?
To guarantee idempotency, the following measures are needed:
2.1.1 Each Request Must Have a Unique Identifier
For example, a payment request should contain an order ID, and each order ID can be paid successfully only once.
2.1.2 Record That a Request Has Been Processed
In MySQL, store a status field or a payment record before the actual payment. This record indicates that the request has already been handled.
2.1.3 Check Before Processing Whether the Request Was Already Handled
If an order has already been paid, a payment record exists. When a duplicate request arrives, inserting a payment record will hit a unique key constraint (e.g., on orderId) and fail, preventing double charging.
Use the database’s primary‑key uniqueness: when inserting, provide the primary key (orderId). If the key already exists, the INSERT fails, achieving idempotency for the create‑order API.
Provide an “orderId generation” API that returns a globally unique order number. The front‑end obtains this orderId before the user submits the order, and the order creation request carries the same orderId. The orderId becomes the primary key in the order table, so duplicate requests use the same key and only one INSERT succeeds.
In practice, combine this with Redis: store the orderId as a unique key. Only after a successful insertion of the payment record should the order be marked as paid in Redis. Subsequent requests check Redis; if the key is marked paid, they skip the payment.
3 Solving the ABA Problem
3.1 What Is ABA?
After an order is paid, the seller fills in a tracking number (e.g., 666). If the seller later corrects it to 888, two update requests are sent. If the system loses the success response of the first update (666) and retries, the tracking number may be overwritten back to 666, causing incorrect data.
3.2 Solution: Version Column (Optimistic Locking)
Add a version column to the order table. Each time the order is read, the version is returned to the client. The client includes this version in the update request.
When processing an update, the service compares the supplied version with the current version in the database:
If they differ, reject the update.
If they match, perform the update and increment version by 1. The comparison, update, and version increment must occur within the same transaction.
UPDATE orders set tracking_number = 666, version = version + 1 WHERE version = 8;The WHERE clause uses the version value supplied by the client. If another update has already changed the version, this statement fails, forcing the client to re‑fetch the latest data and retry.
With this mechanism, the two ABA scenarios are handled:
Updating to 666 succeeds; a subsequent update to 888 carries the new version and succeeds, while a retry of the old 666 request carries the old version and fails.
Any retry with an outdated version is rejected, ensuring the database state always matches the user’s view.
Thus, the data shown to the user remains consistent, achieving idempotent updates and eliminating the ABA issue.
Summary
Create‑order services can pre‑generate a unique order number and rely on the database’s unique constraint to ensure idempotent order creation.
Update‑order services can use a version‑column optimistic‑locking strategy to detect concurrent modifications and prevent ABA problems, guaranteeing idempotent updates.
These idempotency techniques can be applied to any service that persists data in a relational table with a primary key.
Architecture Digest
Focusing on Java backend development, covering application architecture from top-tier internet companies (high availability, high performance, high stability), big data, machine learning, Java architecture, and other popular fields.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.