Idempotency in it’s simplest form is:
The ability for a service or consumer to read the same message multiple times yielding the same results as if they only read it once.
To state that as an example:
Imagine you have a project that has a Kafka topic with one producer and one consumer.
The producer takes “new user” data from a web UI and publishes that in a message to Kafka in the form of a “createUserRequest”.
The consumer’s job is to read the createUserRequest and:
Create a row in a database table
Send a welcome email
It should not matter if the consumer reads that createUserRequest 1 time or 100 times, there should only ever be one row in the database and one welcome email sent. Idempotency guarantees this. You will get the same result from that service if you call it once or many times.
I.e. An idempotent service can process the same request more than once without the side effects happening more than once.
Idempotency is essential in distributed systems, where network failures or system crashes can lead to the same message being processed multiple times.
As well as having idempotent services, you also have idempotent functions.
Idempotent functions produce the same output given the same inputs, regardless of the number of times they are applied.
Same input. Same outcome. Same result.
Importance of Idempotency
Imagine you had a system that was dealing with charging a client’s bank account for purchasing a new item. Let’s say an iPad.
You have one service whose job it is to deal with the purchasing (let’s call it purchaseSvc) and another whose sole purpose is to take a request to withdraw funds for a purchased item (let’s call it fundsSvc).
Imagine that purchaseSvc has a bug whereby it sends the same message twice due to a looping error.
This means that for the same user action (buying one iPad), the fundsSvc receives the message twice.
Now let me ask you this.
Would you be happy to be charged twice for the purchase of one iPad? Probably not!
Through the fundsSvc service being idempotent, it should recognise that it has received multiple identical requests and not process it a second time. Your hard earned money is safe!
A duplicate message should not affect the running of a system.
Idempotent operations are crucial for scalability and reliability in distributed systems.
Idempotency simplifies error handling, concurrency management, debugging, and monitoring in operations and APIs.
Idempotency ensures that repeated operations yield the same outcome, while safety ensures that the returned value is not modified.
A safe method does not change the value that is returned, it reads – but it never writes.
Idempotent methods are safe, but not all safe methods are idempotent.
In distributed systems, the use of unique identifiers on the messages is hugely important.
There are many types of IDs that are used on messages in distributed systems: SagaId, TransactionId, correlationId, messageId.
The one we care about for idempotent operations is the messageId. This uniquely identifies that specific message.
Now that we have a way to identify if multiple requests are actually the same message multiple times, we can put in place the code required to be idempotent.
When first reading the message, you want to take note of the ID. You then need to do a check against a local database to see whether or not you have already seen that message.
There are two different outcomes to this check:
You’ve not seen this message before. You should insert a row into your database stating that you have now received the message and process the message
You have seen the message before, and the message is completed. Disregard this new message as you don’t want to apply the same operation multiple times.
Remember, the key point here is that no matter how many times you receive the same message, you don’t want to process it more than once.
Idempotency is crucial in APIs because a resource may be called multiple times if the network is interrupted.
Being Idempotent allows systems to be consistent and predictable. Even when faced with network disruptions, service restarts, and retries, you can guarantee “process at most once” semantics and ensure that you get the same result when calling a service once vs many times.
A service being idempotent also simplifies error situations where having duplicate data may cause bugs. How would you for instance undo the duplicate withdrawal of funds mentioned above?
Some errors such as a duplicate row in a database may be “easy” to undo, but what if the side effect of processing a message is updating an external system to yours? Do you always have a way to undo that?
GET, HEAD, PUT, and DELETE request methods are idempotent, as they don’t change the data or have the same effect when repeated.
POST is not inherently idempotent, as making the same POST request multiple times can result in different outcomes or create multiple instances of submitted data.
Idempotency in API design ensures that the server’s state remains consistent and unchanged, even if the request is repeated multiple times.
Being idempotent is a concept. That concept can be implemented as part of a point-to-point call or in pub/sub architectures.
In distributed systems, one subscriber consuming a message more than once could lead to a chain effect.
If service X reads from your middleware and then produces a message back which downstream consumers pick up, service X reading the message and processing it more than once leads to their downstream services then having multiple messages to consume (probably with their own unique Ids so they seem like two genuinely separate messages).
Idempotency in distributed systems ensures that even with network failures or system crashes, your system will have overall correctness.
It is important that all services in a distributed system define their publish and consume semantics. Do they guarantee to consume at most once? Do they guarantee publish at least once?
To properly be able to test and roll out schema changes to a database, you need to be able to re-run the script multiple times ensuring that each run is not impacted by the previous or next run.
Usually you would do this via checking if assets exist, dropping and then recreating.
Something like:
A message queue can make a system idempotent by ensuring that even if a message is delivered multiple times, the operation it triggers is executed only once.
Idempotency in message queueing systems ensures that the system can handle retries and failures without affecting the overall correctness.
Idempotent message queueing systems ensure that the same message is not processed multiple times.
Idempotency is a powerful concept in software engineering.
Idempotent operations guarantee that the same request is not processed more than once and the receiving service does not have the same effect twice: i.e. sending an email twice, creating two rows in a database etc.
By implementing idempotent functions, APIs and infrastructure architecture (queues) systems become more robust and less prone to failure and data inconsistency.