Live data from Hacker News

Idempotence now prevents pain later

ericlathrop.com

71–80 of 133 posts

Re: Idempotence now prevents pain later

#72
post #36

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 ID…

> 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:

> which haven't been charged the fee this month.

Re: Idempotence now prevents pain later

#74
post #36

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 ID…

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.

Re: Idempotence now prevents pain later

#75

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.

Re: Idempotence now prevents pain later

#76

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 descri…

> think of your problem purely in terms of the world you want to end up in as a result Great framing! Isn't this the difference between imperative and declarative statements/code/systems?

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.

Re: Idempotence now prevents pain later

#77

Earlier quoted context omitted.

This could still lead to a double-charge, though. 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.

Re: Idempotence now prevents pain later

#78

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 descri…

Your summary is the second application of understanding paint: the clarity coat, the second view into the problem space.

Re: Idempotence now prevents pain later

#79
post #51

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. 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…

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.

Re: Idempotence now prevents pain later

#80
Totally agree. I wish UPSERT had been invented earlier, or was a more canonical part of SQL than INSERT and UPDATE for this reason.

Also, in web front end programming, there are lots of cases where X needs to cause Y to happen, but Z also needs to cause Y to happen. It's much much simpler if Y is idempotent, rather than X checking if Z has already happened etc.

Post reply on HN