I'm assuming a lot of people click on it to see what the word Idempotence means. From the article:
"Idempotence is the property of a software that when run 1 or more times, it only has the effect of being run once."
And the example is, instead of a chron job just running a process once a month or on some other schedule, it runs more frequently but checks if the change has already been made.
(From the latin Idem which means "same" and potence is of course power/potent, so it has the same power/effect however many times you run it)
Mathematically, it's x^2 = x, which implies x^n = x for all positive integers n.
Nilpotence (x^2 = 0) is also very helpful some times: it's a process which is self-reversing. Like the discrete Fourier transform (if you set up the constants properly).
Careful, for nilpotence the power doesn't have to be 2.
Also you may be confusing it with x^n = 1 (which I'm not sure how to name, 'root of unity' perhaps). This would be the case for the Fourier transform (with n=4).
If x^2 = 0 then applying the Fourier transform twice would null your function, which isn't the case.
It's not that weird. People often write iterated composition as f^k, and this is especially true with matrices, where composition and multiplication mean the same thing.
Depends on if you think of functional notation or matrix operators and linear algebra first. There's also dirac bra-ket notation from QM. I tend to reach for the latter two more than the former.
Particularly in this case O^2 = O makes more sense to me than f(f(x)) = f(x). And I just naturally think about A B - B A = 0 rather than f(g(x)) - g(f(x)) = 0 for commutativity.
In mathematics, a self-reversing function is called an involution, and it's f^2 (or f(f) ) = Id, the identity function.
Nilpotence is very different. It says that if you apply your function a certain number of times, you end up with zero no matter what the input is. For example, projection on x axis + 90 ° rotation of a vector is nilpotent.
One of my first jobs was at an investment bank. They had a lot of programs that ran overnight, in a batch fashion. Everything had to be done before the markets opened the next morning. The term they used for idempotency was "free rerun." Being able to rerun any program with no special setup work was a high priority.
The value in programs being a "free rerun" was that every so often the program would barf on a bad bit of data in a record.
The programming environemnt was interpreted BASIC, so if an error occurred the program would print a message on the console and drop to an interactive prompt.
The operators running the batch schedule would see this and call the programmer on call for that night. You'd log in (over dial up at this time) and attach to the process, look at the error, figure out what went wrong, either correct the data or (more likely) skip the record and deal with it the next day. It was more important to have the programs finish on time; individual issues could be dealt with later.
Often you could just start up the program from where it left off, but if things were more screwed up it was important to be able to re-run it without any negative consequence.
Edit: this was ~30 years ago, so my point is that it's not any kind of new idea or something that wasn't recognized long ago.
I hope this example makes it evident that one of the primary innovations of the last 30 years is defaulting to Latin terms so that they are taken more seriously in business and technology circles to acquire ... you know... gravitas.
I used to be an operator in night shift in my twenties and the job was exactly how you said. Good memories. Lots of sleeping at work and some days of panic when shit broke.
And a lot of "secret" scripts that automated a big part of our job.
> And the example is, instead of a chron job just running a process once a month or on some other schedule, it runs more frequently but checks if the change has already been made.
As a property, I think it's even nicer if a script can literally fully run twice and for the outcome to be the same if it only ran once (so skipping the 'did I run before?' check).
Even though this check is useful in general, if you can define your data in such a way if it did somehow run, that this is not destructive / creates incorrect data, it makes the system more robust.
Of course this is not always possible though. For example, if the process results in an email being sent, you need an explicit check to not do that twice.
In situations like these, it's a legitimate goal to implement an idempotent, or "functional" core.
So the goal of your functional core is to fully construct the email, and return it to the caller, who then has the choice to send the email, print it, write it to disk, etc.
The program you deploy looks like this
EmailSender().send_email(construct_email(args))
You can test by implementing a "safe" EmailSender interface, so that you're executing the same code that's in prod.
In general, if a job/function is mutating state deep in the syntax tree (i.e. sending emails in the middle of a batch job), I personally see that as a violation of the Single Responsibility Principle.
PUT requests are intended to be idempotent, which is one of the things that distinguishes them from POST requests. This (in my experience) is most non-CS-backgrounded software developers' exposure to idempotence, but it actually has tons of value pretty much wherever you can apply it. The ability to (sometimes accidentally) do a thing twice and have it leave no unintended consequence is huge.
UPSERTs can be idempotent as well. "If this doesn't exist, create it, and if it does, update it to match this state", implies that running it twice will leave no unintended side effects.
In the last couple years I've come to realize that nearly all POSTs should actually be PUTs, sometimes with a client-provided uniqueness token in the URL (like a UUID) that becomes part of the newly-created resource's ID.
Now that I think about it, all the situations where I'm fine using POST are also idempotent, such as changing an object's metadata (technically PUT or PATCH), or sending a batch of HTTP requests to an endpoint bundled as a single request.
It's a basic property of HTTP "GET". Or it's supposed to be. "GET" is not supposed to change server site state. That's what "POST" is for. This matters if there's a cache in the middle, since caches tend to assume that GET requests are idempotent and can be served from cache. Cloudflare assumes that. POST requests have to go through to the real server.
I try to write idempotent software whenever I can. It's usually not much more difficult to make it work and affords so much more flexibility and less worry when it's done.
If you can build a system with ACID 2.0 life gets really easy. You can reason about your system without worrying about ordering, time, 'exactly once' semantics, etc.
Idempotency is usually one of the simplest pieces to implement, and you definitely get a ton of benefit right off the bat - it's worth designing systems from scratch with it in mind.
I can honestly say that Eric is 100% right with his approach. It always leads to less headaches, more flexibility (oh trust me, someone is always gonna have a "but... there's like a special thing that I sometimes have to do" and it breaks some assumptions.
In any case... yeah... let's just say any time you have to be worried "did we already schedule this", really think "can this never care if it was or not? Should be always safe to schedule it again"
Idempotency is a pretty critical concept in system design, and I think most developers have run into issues related to it even if they aren't directly familiar with the term.
To give another simple example as the OP - Suppose you have a product that relies on time series data. For demo purposes you might create a curated data set to present to clients, but the presenter doesn't want to show data from 2019 as the "most recent"
Naturally, you decide to write a script. Do you
A) Write as script that moves the data forward by 1 week explicitly, and simply run this once per week or
B) Write a script that compares the current date to the data and moves it forward as much as it needs
At first glance, these two approaches work the same, but what if (A) triggers twice? What if it runs once every 6 days by mistake? (B) is idempotent however - subsequent executions won't change the state. It's usually impossible to predict all of the ways that software breaks, but designing with idempotency in mind eliminates a lot of them.
I don't think B is technically idempotent either. Change still occurs but with minimal difference. You cannot cache the results and use them again next week.
An idempotent change would be to pass in the current time instead of checking system time. In this case, as long as the input is the same, the result is the same. You could use cached results, but most likely you want to use new inputs.
Yes but this doesn't matter really. What's important is that running the script twice will still give you right values. From a practical point of vue it is the same as idempotency, which is what matters.
If you design for it from the start it makes your system much less complex. Consider all the errors, special cases, and ultimately data cleanup you need to handle about if your transactions are not idempotent. Idempotency is table stakes for any production app.
We had one "special" team member who insisted on everything being idempotent.
This was his only leading principle. Result: absolute chaos - the code aspired to be idempotent, but due to idempotency he avoided thinking problems through and just created a mess of individual functions - each being idempotent, aside from the unavoidable bugs - which didn't form a coherent flow at all.
We did a major refactoring, threw out about all that code, rewrote everything in a logical manner. Now everything is still idempotent, but comprehensible.
TLDR: idempotency is the same snakeoil as the majority of guiding principles: alone, it doesn't help at all. There are lots of other factors to consider, which make the developer/architect role demanding (and fun).
Craftmanship at least, a sense for architecture (better) or understanding the whole picture of the requirements as a team of developers (best) is still required.
I don't see this as any reflection on idempotency as principle (or other principles in general). Building systems poorly, without a plan, and no testing, will result in a bug-riddled mess, regardless of what pattern is being used.
That is a general problem with any principle used as rigid ideology. Almost very principle becomes a problem if applied too dogmatically . This applies to software dev but also others like politics or economics.
Sure, one little hobby horse, e.g. "inversion of control" can run amok to negative effect (looking at you, Java projects with object traces 75 layers deep) but that doesn't make idempotency or inversion of control into bad ideas.
A bit of pragmatism goes a long way, like Python's odd
I feel like immutability and a 100% leak-proof layer of domain methods which in turn manage domain mutations would ultimately bring more value than explicitly adding idempotence throughout.
If I have an idempotent method like "CreateCustomerRecord", this can cause a lot of pain for audit features and other aspects of the domain model if it is internally making determinations about whether to actually create or silently skip creation. For me, I would much rather that the method throw an exception if there is a duplicate business key than have it silently complete without taking any actual action. Exceptions indicating attempts at invalid state transitions can be extremely valuable if you have the discipline to create & use them properly.
Generally, seeking idempotence in otherwise mutable methods is a band-aid for when you have broken immutability rules and allowed things to leak out of the sacred garden of unit-tested state machines and other provably-correct code items.
If you should only conditionally execute some method, perhaps the solution is to investigate the caller(s) of the method, rather than attempt to infer the intent of all possible callers within the method itself.
This can create false negatives when a request must be retried due to network failure but actually succeeded because the failure was during the response.
Idempotency is great for "debouncing" requests. If you want to tell difference between identical requests that are different transactions, add a unique transaction id of some kind.
There's a place for idempotency tokens. They're relatively easy to retrofit onto old systems, and occasionally they are the best way to go about making changes idempotent, but they should be a red flag – an indication that you should step back and see if maybe you can redesign an API to make idempotency a natural guarantee rather than something you artificially strap on with a token. As a rule of thumb, I would always mention the idea of adding an idempotency token, and prompt for alternatives, with all stakeholders present.
(In this particular example, I agree that an idempotency token is probably the way to go, as otherwise callers would need to somehow distinguish between their callers trying to create a duplicate account, versus something going wrong with them resulting in duplicate requests. I just have too often seen developers conflate "idempotency" with "use an idempotency token)
The state machine is great in a vacuum but it falls apart when someone trips over the power cord at exactly the wrong time. Thats the benefit of idempotent design, that it is resilient to real world failure modes that dont typically get modeled into the systems you describe.
In general, you should be thinking about the delivery semantics of the systems calling your code. Many very useful callers offer "at least once" delivery guarantees, implying that your system should behave idempotently to their calls.
> 1. Query the database to find all dormant accounts with a balance, which haven't been charged the fee this month.
> 2. Charge each of these accounts a fee
> 3. Setup a cron job to run this every hour
Note that if this job ever runs successfully, but takes more than an hour, you will double-count. Can easily happen if the box running these crons is overloaded. One fix is to automatically halt the job after 55 minutes, another would be to have the middle step be impotent, for each user you're doing the process on, ensure (ideally in a threadsafe manner) that they need the operation to be done still.
The article didn't really go into the details of the implementation but my mental image was basically your second proposal: the deduction of the fee should happen in the same database transaction as the updating of a "fee-last-charged" timestamp, relying on the database for thread safety and allowing the cron job itself to be stateless (and inherently protected against simultaneous execution of multiple instances of itself).
If some rogue deploy script creates fifty of the cron job, and weirder things happen every day, one of those could be checking the stale state during the transaction of another one. A classic data race.
Eliminating this problem is left as an exercise for the interested reader...
The way to fix that is to remove state altogether.
1. Balance is not a simple variable, but the sum of all credits and debits to an account
2. A fee is a charge record in your database
3. This fee has a database constraint that you can have only one record per month
Now you can run the script that charges dormant fees as often as you want.
This is exactly the right approach, and the easiest way to implement idempotency in many realistic systems. Instead of thinking about idempotency in verbs - "perform action iff action has not yet been performed" - think about it in nouns - "create a piece of data whose key is the tuple of its inputs".
Practically speaking, this narrows your "transaction window" significantly - instead of:
1. Begin transaction
2. Check to see if work has already been done
3. Do work
4. Persist work
5. Commit
With a potentially long transaction spanning from 1-5, you do this:
1. Do work
2. Persist work to key / table with uniqueness constraint
3. On conflict, do nothing (looks like you already did the work before)
Of course if "Do work" is very expensive, you can bring back in "Check to see if work has already been done" as an optimization, but for many simple CRUD examples, it's actually /cheaper/ to learn that the work has already been done via the conflict check failing than via an explicit pre-flight check.
This is good but not enough. You also need to be sure that you can’t charge twice if the job runs twice. When you do that same query twice, you will get the same list of users. This could be done by exploiting database consistency rules, like using strongly isolated transactions. One simple more general approach is to use an idempotence token. You could, say, have a table with a uniqueness constraint, and generate IDs that will match for the same user in the same month. Then add that in the same transaction that subtracts the money. The table could be cleaned up periodically.
If you’re making or using an API where repeating would be bad, consider using idempotency tokens for those too. I believe Stripe supports them. The basic idea is the same: if you pass a token into them, they will guarantee that in a certain time frame, no other requests with that ID can be duplicated. This is useful when the network flakes during the response. Is it safe to retry?
Things get trickier when you combine network and database consistency measures; that’s when you get into locks and multi stage commit and etc. and it helps to know your database’s consistency model, since it’s often not as solid as you think! (In the past, even PostgreSQL had issues with providing serializable isolation.)
> This is good but not enough. You also need to be sure that you can’t charge twice if the job runs twice. When you do that same query twice, you will get the same list of users.
That's handled by the extra condition in step 1 of the altered rules:
OP is talking about concurrent execution of the task without serialization on the database (serializable isolation is almost never the default). Which results in 2 concurrent queries getting the same list of customers to charge.
Have you used a iterator as the token? I had data that could be accessed and mutated from multiple different sources at the same time. With just a token and blocking the data caused race condition (cross server race conditions/deadlocks are just the worst). I solved this by giving an iterator with every read and a write required the same iterator back with the changes. If 2 servers try to write at the same time the first processed will go through and the other will get rejected. The rejected server will reread the data with a new iterator, apply its mutation, then attempt to write. It worked really well for me and I never had any race/deadlock issues.
I handle the deployment pipeline at my office and I've got a hard rule that every step in the CI chain has to be idempotent. Considering how many points of failure there can be in CI, being able to retry without undoing the mess of the previous deployment is a lifesaver.
Years ago when I was a green programmer filled with p*ss and vinegar - I had to write a system to send a few million emails every day.
That’s when I came across Qmail and studied it’s design. Qmail was designed so well that it literally absolutely would not send an email twice.
But my scheduler did.
To send a few hundred thousand emails more than once is not good is not good for your reputation lol.
It taught me a valuable lesson about Idempotency from an impressionable stage of my career and now, with creating workers running under Sidekiq, Oban and other job schedulers that run very frequently, this has become quite useful.
At one point I used to lean on Redis to “help” me be more idempotent. But with the high throughput of some systems I was working on, even redis with its atomic nature of put and get and handy dandy sets and lists, would have race conditions.
Now I hardly touch redis because my skills in making sure things get processed just once have improved greatly. And I can just use PostgreSQL and my code.
Being Idempotent from the beginning is now the center of my life.
I feel that all of the protections let us chase the problems into smaller areas. Rust's unsafe doesn't eliminate unsafe code, it just means you put it in a small auditable area.
Similarly, some part of the system remains imperative. The network card at least, will always resend a packet. The goal is to pass around idempotent messages except for the very leaves.
For instance, instead of an endpoint 'email(customer, data)' you might have 'email_or_report_on_send_status(customer, data)' and the later endpoint would check the cache for (customer, data) and merely report the previous results if it found them.
I agree though, this stuff used to keep me up at night and eventually I've grown more natural about not mutating things unless I mean it. (This phrase sounds like a comic-book villain.)
Googler opinions are my own. I work on payments stuff.
Payments at Google defined some specs for payment companies to build to, which allows easy onboarding as a form of payment on google's platform. We have a page talking about idempotency and expected behavior.
I don't think it's that different from other payment companies, like adyen or worldpay, but we attempted to explain our view of idempotency on a single page.
Sorry if it's a bit off topic but are you required by your employer to tell us you work from them and whether your opinion represents them or not? I read such disclaimers a lot about people working in your company, but extremely rarely for any other company.
Another tech giant engineer here. All too often random opinionated comments get taken way out of context and misconstrued as something pertaining to the company they work for. Separate from any NDA concerns, no one wants to have some reporter quoting them as "Google insiders reveal targeting of Republicans". I can't speak for Google employees, but I certainly don't have to preface anything with "this is my own opinion", but I certainly won't say anything that implies that I'm voicing some stance or opinion of my employer.
Google has some wording in our contract thst says we are required to put a disclaimer when we're posting publicly, I don't remember the exact wording, but I always include it on any topic that is even tangentially related to the company.
This is actually a lot better than previous companies. My employee contract when I was at Cisco, basically said I couldn't talk about anything Cisco related on the internet without sign off from upper management or PR or something.
Great article. This is really about a mental shift. When given a problem like "dormant customers should be charged", it's natural to model the problem as verbs: "find the dormant customers and charge them".
However, that description relies on implicit state: it assumes a world where all dormant customers have not been charged. As we all know, relying on hidden implicit state is the source of all evil. When you describe your problem using verbs, you are essentially modeling it in terms of a diff between how the world is and how you want it to be. But that diff is only correct if you have a perfectly correct description of how the world currently is.
It's better to take a step back and think of your problem purely in terms of the world you want to end up in as a result: "all dormant customers have been charged". Framing it that way makes questions like "which dormant customers have already been charged?" more obvious to consider.
A declarative programming language should indeed allow you to think mostly about the result you want and not much about the how, but I'd say GP's comment is more about the design phase. The actual implementation could still be highly imperative.
Possibly way OT but this reminds me of a question I had. Lets say I have a task that creates a daily invoice that failed to run for X days, but when it's run again it needs to go back and create X invoices from when it was last run. Is there a name / concept for that?
This is closely related to commutativity, which is another useful property of a system, especially a distributed one, where events commonly arrive out of order.
I had to solve this for charges and interest and I solved it in a more elegant way than described (I think).
Using mutation iterator, last update time and knowing the rate of change over time you can back apply any any calculations that have been missed regardless of how long it’s been since your cron job ran.
I setup a server to run the job on startup and 1 time a day at midnight. This also allows multiple different charges and interests to be applied to the same account. I also set it up to apply this process every time an account was accessed to make sure the accounts were up to date.
It's 2021 and we still don't have scalable computer systems with continuations support - that's why his cronjob does not work reliably in the first manner if that script crashed or the machine reboots, it will run again.
The idempotence is nice and I use it as much as possible, but, his case is very simplistic. Imagine you have a complex data migration process, which crashes in the middle - how you safely continue from where you crashed or safely roll back?
Well, I did not mean a sequential one and only involving the database, but, let's say, you have forks (i.e. parallel executions) and merges (waiting to sme dependent tasks to complete), etc. What if execution halts in the middle of a step and you don't really have transaction support outside of the DB? I typically would use a workflow engine, but rollback is not something workflow engines support either.
Great idea if you control all the inputs (I'd also suggest passing in a time instead of relying on time.Now or whatever your language gives you). Sometimes you don't and it's very hard to do.
Every system I write uses some sort of time object/function that can be set to whatever time input is desired. In a lot of cases it’s just the system time with an offset from some synchronization service. It makes testing easier, makes replaying inputs possible, and can make time related features based off local or server time with only changing inputs.
132 comments
[ 2.8 ms ] story [ 191 ms ] threadWhat Is Idempotence? - https://news.ycombinator.com/item?id=19570815 - April 2019 (51 comments)
Idempotence: What is it and why should I care? - https://news.ycombinator.com/item?id=17804617 - Aug 2018 (73 comments)
You know how HTTP GET requests are meant to be idempotent? - https://news.ycombinator.com/item?id=16964907 - May 2018 (304 comments)
Implementing Stripe-Like Idempotency Keys in Postgres - https://news.ycombinator.com/item?id=15569478 - Oct 2017 (41 comments)
APIs, robustness, and idempotency - https://news.ycombinator.com/item?id=13707681 - Feb 2017 (50 comments)
A simple distributed algorithm for small idempotent information - https://news.ycombinator.com/item?id=7276491 - Feb 2014 (14 comments)
Idempotent Web APIs: What benefit do I get? - https://news.ycombinator.com/item?id=5662138 - May 2013 (53 comments)
A word like that is particularly easy to search for:
https://hn.algolia.com/?dateRange=all&page=0&prefix=true&que...
"Idempotence is the property of a software that when run 1 or more times, it only has the effect of being run once."
And the example is, instead of a chron job just running a process once a month or on some other schedule, it runs more frequently but checks if the change has already been made.
(From the latin Idem which means "same" and potence is of course power/potent, so it has the same power/effect however many times you run it)
Nilpotence (x^2 = 0) is also very helpful some times: it's a process which is self-reversing. Like the discrete Fourier transform (if you set up the constants properly).
Squaring a upper triangular matrix with 0 on the diagonal is nilpotent. Derivatiting a polynomial of degree N is nilpotent after N iteration.
Also you may be confusing it with x^n = 1 (which I'm not sure how to name, 'root of unity' perhaps). This would be the case for the Fourier transform (with n=4).
If x^2 = 0 then applying the Fourier transform twice would null your function, which isn't the case.
f(x) = f(f(x)) = f f x = f^2 x = f x
And leaving out the x, because it is just a placeholder anyways:
f f = f^2 = f
And of course this means that
f^n = f because f^n-1 f = f^n-1 by induction.
Particularly in this case O^2 = O makes more sense to me than f(f(x)) = f(x). And I just naturally think about A B - B A = 0 rather than f(g(x)) - g(f(x)) = 0 for commutativity.
In mathematics, a self-reversing function is called an involution, and it's f^2 (or f(f) ) = Id, the identity function.
Nilpotence is very different. It says that if you apply your function a certain number of times, you end up with zero no matter what the input is. For example, projection on x axis + 90 ° rotation of a vector is nilpotent.
The value in programs being a "free rerun" was that every so often the program would barf on a bad bit of data in a record.
The programming environemnt was interpreted BASIC, so if an error occurred the program would print a message on the console and drop to an interactive prompt.
The operators running the batch schedule would see this and call the programmer on call for that night. You'd log in (over dial up at this time) and attach to the process, look at the error, figure out what went wrong, either correct the data or (more likely) skip the record and deal with it the next day. It was more important to have the programs finish on time; individual issues could be dealt with later.
Often you could just start up the program from where it left off, but if things were more screwed up it was important to be able to re-run it without any negative consequence.
Edit: this was ~30 years ago, so my point is that it's not any kind of new idea or something that wasn't recognized long ago.
And a lot of "secret" scripts that automated a big part of our job.
As a property, I think it's even nicer if a script can literally fully run twice and for the outcome to be the same if it only ran once (so skipping the 'did I run before?' check).
Even though this check is useful in general, if you can define your data in such a way if it did somehow run, that this is not destructive / creates incorrect data, it makes the system more robust.
Of course this is not always possible though. For example, if the process results in an email being sent, you need an explicit check to not do that twice.
So the goal of your functional core is to fully construct the email, and return it to the caller, who then has the choice to send the email, print it, write it to disk, etc.
The program you deploy looks like this
EmailSender().send_email(construct_email(args))
You can test by implementing a "safe" EmailSender interface, so that you're executing the same code that's in prod.
In general, if a job/function is mutating state deep in the syntax tree (i.e. sending emails in the middle of a batch job), I personally see that as a violation of the Single Responsibility Principle.
Article made me think it's actually applicable to other management as well
UPSERTs can be idempotent as well. "If this doesn't exist, create it, and if it does, update it to match this state", implies that running it twice will leave no unintended side effects.
Now that I think about it, all the situations where I'm fine using POST are also idempotent, such as changing an object's metadata (technically PUT or PATCH), or sending a batch of HTTP requests to an endpoint bundled as a single request.
A more interesting function is PUT (idempotent) vs POST (not idempotent)
If you can build a system with ACID 2.0 life gets really easy. You can reason about your system without worrying about ordering, time, 'exactly once' semantics, etc.
Idempotency is usually one of the simplest pieces to implement, and you definitely get a ton of benefit right off the bat - it's worth designing systems from scratch with it in mind.
I can honestly say that Eric is 100% right with his approach. It always leads to less headaches, more flexibility (oh trust me, someone is always gonna have a "but... there's like a special thing that I sometimes have to do" and it breaks some assumptions.
In any case... yeah... let's just say any time you have to be worried "did we already schedule this", really think "can this never care if it was or not? Should be always safe to schedule it again"
To give another simple example as the OP - Suppose you have a product that relies on time series data. For demo purposes you might create a curated data set to present to clients, but the presenter doesn't want to show data from 2019 as the "most recent"
Naturally, you decide to write a script. Do you
A) Write as script that moves the data forward by 1 week explicitly, and simply run this once per week or
B) Write a script that compares the current date to the data and moves it forward as much as it needs
At first glance, these two approaches work the same, but what if (A) triggers twice? What if it runs once every 6 days by mistake? (B) is idempotent however - subsequent executions won't change the state. It's usually impossible to predict all of the ways that software breaks, but designing with idempotency in mind eliminates a lot of them.
An idempotent change would be to pass in the current time instead of checking system time. In this case, as long as the input is the same, the result is the same. You could use cached results, but most likely you want to use new inputs.
This was his only leading principle. Result: absolute chaos - the code aspired to be idempotent, but due to idempotency he avoided thinking problems through and just created a mess of individual functions - each being idempotent, aside from the unavoidable bugs - which didn't form a coherent flow at all.
We did a major refactoring, threw out about all that code, rewrote everything in a logical manner. Now everything is still idempotent, but comprehensible.
TLDR: idempotency is the same snakeoil as the majority of guiding principles: alone, it doesn't help at all. There are lots of other factors to consider, which make the developer/architect role demanding (and fun).
Craftmanship at least, a sense for architecture (better) or understanding the whole picture of the requirements as a team of developers (best) is still required.
A bit of pragmatism goes a long way, like Python's odd
x = (some_tuple,)
. . .syntax amidst its generally clean approach.
Inflexibility itself is the bugaboo.
If I have an idempotent method like "CreateCustomerRecord", this can cause a lot of pain for audit features and other aspects of the domain model if it is internally making determinations about whether to actually create or silently skip creation. For me, I would much rather that the method throw an exception if there is a duplicate business key than have it silently complete without taking any actual action. Exceptions indicating attempts at invalid state transitions can be extremely valuable if you have the discipline to create & use them properly.
Generally, seeking idempotence in otherwise mutable methods is a band-aid for when you have broken immutability rules and allowed things to leak out of the sacred garden of unit-tested state machines and other provably-correct code items.
If you should only conditionally execute some method, perhaps the solution is to investigate the caller(s) of the method, rather than attempt to infer the intent of all possible callers within the method itself.
Idempotency is great for "debouncing" requests. If you want to tell difference between identical requests that are different transactions, add a unique transaction id of some kind.
(In this particular example, I agree that an idempotency token is probably the way to go, as otherwise callers would need to somehow distinguish between their callers trying to create a duplicate account, versus something going wrong with them resulting in duplicate requests. I just have too often seen developers conflate "idempotency" with "use an idempotency token)
> 2. Charge each of these accounts a fee
> 3. Setup a cron job to run this every hour
Note that if this job ever runs successfully, but takes more than an hour, you will double-count. Can easily happen if the box running these crons is overloaded. One fix is to automatically halt the job after 55 minutes, another would be to have the middle step be impotent, for each user you're doing the process on, ensure (ideally in a threadsafe manner) that they need the operation to be done still.
If some rogue deploy script creates fifty of the cron job, and weirder things happen every day, one of those could be checking the stale state during the transaction of another one. A classic data race.
Eliminating this problem is left as an exercise for the interested reader...
Practically speaking, this narrows your "transaction window" significantly - instead of:
With a potentially long transaction spanning from 1-5, you do this: Of course if "Do work" is very expensive, you can bring back in "Check to see if work has already been done" as an optimization, but for many simple CRUD examples, it's actually /cheaper/ to learn that the work has already been done via the conflict check failing than via an explicit pre-flight check.If you’re making or using an API where repeating would be bad, consider using idempotency tokens for those too. I believe Stripe supports them. The basic idea is the same: if you pass a token into them, they will guarantee that in a certain time frame, no other requests with that ID can be duplicated. This is useful when the network flakes during the response. Is it safe to retry?
Things get trickier when you combine network and database consistency measures; that’s when you get into locks and multi stage commit and etc. and it helps to know your database’s consistency model, since it’s often not as solid as you think! (In the past, even PostgreSQL had issues with providing serializable isolation.)
That's handled by the extra condition in step 1 of the altered rules:
> which haven't been charged the fee this month.
That’s when I came across Qmail and studied it’s design. Qmail was designed so well that it literally absolutely would not send an email twice.
But my scheduler did.
To send a few hundred thousand emails more than once is not good is not good for your reputation lol.
It taught me a valuable lesson about Idempotency from an impressionable stage of my career and now, with creating workers running under Sidekiq, Oban and other job schedulers that run very frequently, this has become quite useful.
At one point I used to lean on Redis to “help” me be more idempotent. But with the high throughput of some systems I was working on, even redis with its atomic nature of put and get and handy dandy sets and lists, would have race conditions.
Now I hardly touch redis because my skills in making sure things get processed just once have improved greatly. And I can just use PostgreSQL and my code.
Being Idempotent from the beginning is now the center of my life.
Similarly, some part of the system remains imperative. The network card at least, will always resend a packet. The goal is to pass around idempotent messages except for the very leaves.
For instance, instead of an endpoint 'email(customer, data)' you might have 'email_or_report_on_send_status(customer, data)' and the later endpoint would check the cache for (customer, data) and merely report the previous results if it found them.
I agree though, this stuff used to keep me up at night and eventually I've grown more natural about not mutating things unless I mean it. (This phrase sounds like a comic-book villain.)
Payments at Google defined some specs for payment companies to build to, which allows easy onboarding as a form of payment on google's platform. We have a page talking about idempotency and expected behavior.
https://developers.google.com/standard-payments/reference/id...
I don't think it's that different from other payment companies, like adyen or worldpay, but we attempted to explain our view of idempotency on a single page.
Edit: Even mentioning the rule to someone who asked can bring out the haters.
This is actually a lot better than previous companies. My employee contract when I was at Cisco, basically said I couldn't talk about anything Cisco related on the internet without sign off from upper management or PR or something.
However, that description relies on implicit state: it assumes a world where all dormant customers have not been charged. As we all know, relying on hidden implicit state is the source of all evil. When you describe your problem using verbs, you are essentially modeling it in terms of a diff between how the world is and how you want it to be. But that diff is only correct if you have a perfectly correct description of how the world currently is.
It's better to take a step back and think of your problem purely in terms of the world you want to end up in as a result: "all dormant customers have been charged". Framing it that way makes questions like "which dormant customers have already been charged?" more obvious to consider.
Great framing!
Isn't this the difference between imperative and declarative statements/code/systems?
And then the question adds state, "as of yyyy/mm/dd, which customers were more than $30 into credit?"
Interesting I know floating point arithmetic is not associative, but I think it is communitative.
Using mutation iterator, last update time and knowing the rate of change over time you can back apply any any calculations that have been missed regardless of how long it’s been since your cron job ran.
I setup a server to run the job on startup and 1 time a day at midnight. This also allows multiple different charges and interests to be applied to the same account. I also set it up to apply this process every time an account was accessed to make sure the accounts were up to date.
The idempotence is nice and I use it as much as possible, but, his case is very simplistic. Imagine you have a complex data migration process, which crashes in the middle - how you safely continue from where you crashed or safely roll back?
"If table doesn't have column X, add it. If row Y doesn't exist in table Z, add it." etc.