Live data from Hacker News

Idempotence now prevents pain later

ericlathrop.com

81–90 of 133 posts

Re: Idempotence now prevents pain later

#81
post #70

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

SOmetimes you can write the migrations as a set of idempotent operations:

"If table doesn't have column X, add it. If row Y doesn't exist in table Z, add it." etc.

Re: Idempotence now prevents pain later

#82

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…

Perhaps the rule is "charge all customers - active at rate X, dormant at rate Y".

And then the question adds state, "as of yyyy/mm/dd, which customers were more than $30 into credit?"

Re: Idempotence now prevents pain later

#84
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.

HN is more defensive about some companies than others. The F and G of FAANG mostly.

Edit: Even mentioning the rule to someone who asked can bring out the haters.

Re: Idempotence now prevents pain later

#85

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

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

Re: Idempotence now prevents pain later

#86
post #21
post #12

Earlier quoted context omitted.

X^2 is a weird way to describe it. A function f is idempotent if f(f(x)) = f(x).

It is quite common in some fields. Operator application is written without parentheses, and functions are a kind of operator. Therefore: 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.

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.

Re: Idempotence now prevents pain later

#87
post #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.

I'm going to make an absolutist statement for which I will probably be corrected shortly. For any given table, interactions should either be done entirely with INSERT or be done entirely with UPSERT. The two should never mix on a single table, and UPDATE should never be used.

My reasoning is that if a table represents the current state of the world, then any state changes should be made with UPSERT in order to bring the table up to date with the world. If a table represents the history of changes, then that history should be appended to with INSERT, but not modified.

I'm sure that there are other cases that I'm not currently considering, but I'm also a newbie at SQL and would love to be told of them.

Re: Idempotence now prevents pain later

#88
I have worked on lots of software that involves event-driven actions, and apply this concept throughout.

"Need to send a notification email when x condition becomes true". Naive way: during processing, check the condition and call the SendEmail() function. Idempotent way: Run a query that finds all x conditions, join to a list of notifications based on email+id+time, and only if there's no entry, send the notification and save it to the list.

"Send a report at midnight". Naive way: have a cron entry that runs at midnight or run a loop that checks the time, and if midnight, sends the report. Idempotent way: cross-check against a list of reports that should have been sent, and if missing, send it. Run this check every few minutes.

The nice thing about this is it doesn't stop you from also doing the event-based stuff (for "real-time" notifications, for example), so long as it's the secondary approach from design point of view. Ironically when everything is working fine, the event-driven stuff will actually be doing all the work -- but as soon as there's a failure (like, the system happens to reboot at 23:59) you'll be grateful to build this way.

I could write paragraphs about the nuance, different approaches, failure modes and the trade-offs with all this -- suffice to say treating events as optional in event-based systems has served me well.

Re: Idempotence now prevents pain later

#89
Declarative idempotency is even better.

#1 : collect the state the "user" (might be you) desires the system to be in.

#2 : collect the state the system is actually in.

#3 : compare #1 to #2 and see if you're already there and if you are, you're done.

#4 : if they differ, then do the work once to fix them.

#5 : bonus: go collect the system state again and send it out to report on what changed

Re: Idempotence now prevents pain later

#90
It's a pity the author doesn't get into implementation details as it's quite fun for that specific example because it involves payments.

A couple of key aspects are:

1. The payments API must be idempotent for the Cron to be idempotent. Otherwise they can't safely retry timeout failures.

2. The operations and have to be (to loosely use the term) atomic.

For #1, a payment processor should explicitly state idempotency semantics. Look at Stripe [1] for example. They require you to pass Idempotency Key header in charge request. Clients must carefully choose this key to fit the needs for their business flow and use case. For the author since they charge monthly the idempotency key must be such that it should be constant for a given customer and given month. So something like "customerid-YYYY-MM" makes sense. Now no matter which day of the month cron runs the customer is guaranteed to charge at most once.

As for #2 most of data stores now a days support atomic operations so it should be reasonably straight forward. Get-and-set in Redis, start/end transaction with "for update" in MySQL etc.,

Note that #2 is optional if you don't mind spurious retries to Stripe.

Stepping back from the details it can be noticed that for a system/operation to be idempotent all its downstream dependencies should support idempotent operations. In this example, if Stripe weren't idempotent then you have to resort to manual fixes if charge operation times out.

Another observation is, if a downstream system can assure idempotency then don't bother making your operation idempotent. Failed payment? Retry. No need to store if a payment was attempted or not. Make sure the idempotency-key generation logic is robust.

PS: Source - I've been in payments industry for a while so spend a lot of time thinking about and implementing idempotency. The latest one being when my team worked on payments API at Uber. We included a idempotency in the glossary [2] section ;-)

[1] https://stripe.com/docs/api/idempotent_requests [2] https://developer.uber.com/docs/payments/glossary

Post reply on HN