DEV Community

Cover image for Understanding Idempotency: A Guide to Reliable System Design
Leapcell
Leapcell

Posted on

Understanding Idempotency: A Guide to Reliable System Design

Cover

Abstract Concept

Idempotency is a concept in mathematics and computer science, commonly found in abstract algebra.

In programming, an idempotent operation is characterized by the fact that performing it multiple times has the same effect as performing it once. An idempotent function or method is one that can be executed repeatedly with the same parameters while yielding the same result. These functions do not alter the system state, and repeated executions do not cause any unintended changes. For example, functions like getUsername() and setTrue() are idempotent.

In simple terms: No matter how many times an operation is executed, its effect or return result remains the same.

Examples:

  • When a frontend form is submitted multiple times, the backend should only generate one result.
  • When initiating a payment request, the user's account should only be deducted once. Even in cases of network retries or system bugs causing multiple submissions, only one deduction should occur.
  • When sending a message, it should only be sent once. If the same SMS is sent multiple times, it can frustrate users.
  • When creating a business order, each request should result in the creation of only one order, avoiding duplicate records.

There are many such scenarios where idempotency is essential.

Techniques for Implementing Idempotency

Read (Query) Operations

A query operation, whether executed once or multiple times, returns the same result as long as the underlying data remains unchanged. This makes SELECT queries naturally idempotent.

Delete Operations

Deletion operations are also idempotent. Whether a deletion is executed once or multiple times, the data will be removed. (However, the return value may differ: if the data is absent, the deletion returns 0; if multiple records exist, multiple rows will be affected.)

Unique Indexing to Prevent Dirty Data

For example, in a financial system where each user should have only one financial account, how can we prevent the creation of multiple accounts for a single user?

By adding a unique index on the user ID field in the financial account table, only one request will succeed when attempting to create an account. Any subsequent requests will throw a unique constraint violation error, such as org.springframework.dao.DuplicateKeyException. The system can then simply query the database again to confirm if the data already exists and return the appropriate result.

Token Mechanism to Prevent Duplicate Form Submissions

Requirement: The data on a page should only be submitted once.

Cause: Duplicate clicks, network retries, or re-submissions triggered by Nginx retries may cause the same data to be processed multiple times.

Solution:

  • In a clustered environment: Use a token combined with Redis.
  • In a single JVM environment: Use a token combined with Redis or a token stored in JVM memory.

Process:

  1. Before data submission, request a token from the service, which is stored in Redis or JVM memory with a validity period.
  2. Upon submission, the backend verifies the token, deletes it, and generates a new token for the next request.

Token Characteristics:

  • Must be requested before submission.
  • Can only be used once.
  • Can act as a rate-limiting mechanism.

Important: Redis should use a delete operation to verify the token. If deletion succeeds, the token is considered valid. Using SELECT + DELETE can cause concurrency issues and is not recommended.

Pessimistic Locking

Acquiring a lock when retrieving data:

SELECT * FROM table_xxx WHERE id='xxx' FOR UPDATE;
Enter fullscreen mode Exit fullscreen mode

Note: The id field must be a primary key or a unique index; otherwise, the query will lock the entire table, which can lead to performance issues.

Pessimistic locking is generally used with transactions, and the data remains locked for the duration of the transaction. The longer the lock is held, the greater the potential impact on performance, so use it based on specific requirements.

Optimistic Locking

Optimistic locking only locks data at the moment of an update, avoiding unnecessary locking at other times. Compared to pessimistic locking, it offers higher efficiency.

There are multiple ways to implement optimistic locking, such as using a version number or condition-based constraints:

  1. Version number approach:
   UPDATE table_xxx SET name=#name#, version=version+1 WHERE version=#version#
Enter fullscreen mode Exit fullscreen mode
  1. Condition-based approach:
   UPDATE table_xxx SET avai_amount=avai_amount-#subAmount# WHERE avai_amount-#subAmount# >= 0
Enter fullscreen mode Exit fullscreen mode
  • The condition avai_amount - subAmount >= 0 ensures safe updates without requiring a version number.
  • This is ideal for inventory models where stock is deducted and rolled back as needed.
  • It provides better performance.

Best Practices:

  • Use primary keys or unique indexes in UPDATE queries to avoid table locks. The queries above should be modified as follows:
  UPDATE table_xxx SET name=#name#, version=version+1 WHERE id=#id# AND version=#version#

  UPDATE table_xxx SET avai_amount=avai_amount-#subAmount# WHERE id=#id# AND avai_amount-#subAmount# >= 0
Enter fullscreen mode Exit fullscreen mode

Distributed Locking

When inserting data in a distributed system, it may be difficult to enforce a globally unique index (e.g., due to the lack of a universal unique key across distributed nodes).

In such cases, a distributed lock can be implemented using Redis or Zookeeper to manage concurrent data insertion or updates. The system acquires a lock, performs the operation, and then releases the lock. This approach helps prevent concurrent writes and is a common solution in distributed systems.

SELECT + INSERT Pattern

For backend systems with low concurrency or scheduled job tasks, idempotency can be ensured by checking whether the operation has already been executed before proceeding.

  1. First, query the database for critical data to determine whether the operation has already been executed.
  2. If it has not been executed, proceed with processing.

Note: Avoid using this approach in high-concurrency scenarios.

State Machine Idempotency

In business applications involving order processing or task execution, state transitions must be carefully managed.

  • Business records usually have a state field, and transitions occur based on predefined finite state machines.
  • If a request attempts to transition from an older state to an already updated state, it should be rejected.
  • This ensures idempotency in state transitions.

Key Consideration: For order-based workflows, where states evolve over time, a strong understanding of state machines is essential to designing robust business systems.

Ensuring Idempotency in API Calls

For example, a bank providing a payment API requires merchants to include two fields in their request:

  • source: The origin of the request.
  • seq: A unique sequence number.

By enforcing a unique index on source + seq in the database, duplicate payments can be prevented, ensuring that only one request is processed in concurrent scenarios.

Key Points:

To support idempotency in external APIs:

  1. Require two key fields: source and seq.
  2. Enforce a unique constraint on these fields in the service provider’s system.
  3. Before processing a request, check if it has already been handled.
    • If it has been processed, return the existing result.
    • If it has not been processed, proceed with execution.

Best Practice: Always query whether a request has been processed before inserting new records. Directly inserting data without a check can result in errors, even though the operation may have already succeeded.

Final Thoughts

Idempotency is a fundamental principle of good software design. It is a critical consideration when designing high-reliability systems, especially in industries like third-party payment platforms, banking, and fintech, where accuracy is paramount. Issues like duplicate deductions or multiple payouts can be extremely difficult to rectify and significantly impact user experience.

By implementing idempotent operations, developers can build robust, fault-tolerant systems that ensure consistency, correctness, and a seamless user experience.


We are Leapcell, your top choice for hosting backend projects.

Leapcell

Leapcell is the Next-Gen Serverless Platform for Web Hosting, Async Tasks, and Redis:

Multi-Language Support

  • Develop with Node.js, Python, Go, or Rust.

Deploy unlimited projects for free

  • pay only for usage — no requests, no charges.

Unbeatable Cost Efficiency

  • Pay-as-you-go with no idle charges.
  • Example: $25 supports 6.94M requests at a 60ms average response time.

Streamlined Developer Experience

  • Intuitive UI for effortless setup.
  • Fully automated CI/CD pipelines and GitOps integration.
  • Real-time metrics and logging for actionable insights.

Effortless Scalability and High Performance

  • Auto-scaling to handle high concurrency with ease.
  • Zero operational overhead — just focus on building.

Explore more in the Documentation!

Try Leapcell

Follow us on X: @LeapcellHQ


Read on our blog

Top comments (0)