Skip to content
experience

The Friday deploy a power cut saved (and the idempotency key that saved us the next time)

Lindiwe Mokoena@loadshedding_lindi

September 4, 2026 · 3 min read

Howzit. Two stories, one lesson, and the lesson is not "don't deploy on Friday", sorry.

Story one

A few years back, Friday 16:30, I'm about to push a change to how we retry failed card authorisations. Tests green, reviewer happy, I'm feeling lekker. I type the command and the power goes. Whole block. The inverter was smaller back then, so the laptop is fine but the fibre box is not. No internet, no deploy. I sulked, went home, braaied, slept.

Monday morning I look at the change with fresh eyes and realise the retry loop would have retried authorisations that had actually succeeded at the processor but timed out on our side. In payments that's called charging people twice. The power cut was the best code reviewer I have ever had.

Story two

The real fix wasn't "pray for load shedding". The bug was that our retries weren't idempotent. A request that timed out on our side might have succeeded downstream and we had no way to know, so every retry was a coin toss.

What we do now, and what I'd tell anyone touching money:

  • Every mutating request gets an idempotency key, generated once by the client when the user presses the button, not per attempt. Retries reuse the same key.
  • The server stores the key with the result before it returns. Same key again? Return the stored result, don't do the work twice.
  • Uniqueness lives in the database, not in application code, so two retries racing each other can't both win:
CREATE TABLE idempotency_keys (
  key          text PRIMARY KEY,
  request_hash text NOT NULL,
  response     jsonb,
  created_at   timestamptz NOT NULL DEFAULT now()
);

-- first writer wins, everyone else reads the stored response
INSERT INTO idempotency_keys (key, request_hash)
VALUES ($1, $2)
ON CONFLICT (key) DO NOTHING;
  • Check the request hash. Same key with a different body is a client bug. Return a 422 and shout about it in the logs.
  • Assume the lights go out mid-transaction. Because they will, literally or because a pod gets killed halfway. Every step should be safe to run twice.

Stripe's API has worked like this for years with its Idempotency-Key header, so it's not some exotic pattern. It just isn't the default in most codebases until somebody gets charged twice.

So, Friday deploys? Ja, we do them. Small, behind a flag, with a rollback we've actually tested. But the rule I care about is: if running it twice would hurt someone, it's not ready, Friday or Monday.

Shame, the inverter is bigger now. I've lost my best reviewer.

Get the best of #ship-it-friday weekly

One email each Monday with the most useful posts. No spam; unsubscribe in one click.

Comments

9

Sign in to join the conversation.

Sign in to comment
Vivek Chauhan@vivek_backend1 rep in Ship It Friday
UPI side we learned the same lesson. "Request timed out" is the most dangerous status in payments because it actually means "maybe". Key generated at the button press is exactly right, not per retry.
3 net votes
Lindiwe Mokoena@loadshedding_lindi#4 in Ship It Friday
"maybe" is the scariest word in fintech. ja, the key has to come from the click, otherwise it's just a fancy request id
1 net votes
Emeka Nwosu@emeka_golang1 rep in Ship It Friday
This one hit me. We had the exact retry bug in our Go service, a context timeout on our side while the processor had succeeded. Customer debited twice, support had a very bad week. ON CONFLICT DO NOTHING is what we do now too.
4 net votes
Chaminda Perera@chaminda_sysops1 rep in Ship It Friday
Very good writeup. In the bank we had "reversal storms" in 2022 when the network dropped mid-transaction during the power cuts. Same cure. Make every step safe to repeat, no?
4 net votes
Prashanth Gowda@prashanth_sre#9 in Ship It Friday
"the power cut was the best code reviewer i ever had" 😂 saving the whole post, thanks lindi
5 net votes
Achieng Owino@achieng_devrel#9 in Ship It Friday
Mobile money integrations have the same pattern under different names. Every one I have seen that skipped it ended up with a reconciliation team doing it by hand. Great post, Lindi.
2 net votes
Arjun Mehta@arjunmehta#5 in Ship It Friday
One addition from the lending side: expire the keys, but not too early. We keep ours 7 days. A client retrying a payment from an offline queue 3 days later is more common than you would think.
4 net votes
Sam Whitaker@segfault_sam#1 in Ship It Friday
eskom doing unpaid code review is the most south african open source contribution ever
1 net votes
Lindiwe Mokoena@loadshedding_lindi#4 in Ship It Friday
and still no PR description
0 net votes