<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Sri Writes</title><description>Deep-dives on software architecture, AWS, and mobile engineering — plus shorter notes, by Srivaths Aripirala.</description><link>https://srivathsahr.com/</link><language>en-gb</language><item><title>Notes on Idempotency</title><link>https://srivathsahr.com/posts/notes-on-idempotency/</link><guid isPermaLink="true">https://srivathsahr.com/posts/notes-on-idempotency/</guid><description>The property that makes retries safe is also the property that makes systems boring in the best way — and boring is the goal.</description><pubDate>Sat, 01 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Every outage post-mortem I have ever written has the same villain: a retry that was not safe to
retry. A payment fires twice. A webhook double-processes. A queue redelivers a message the consumer
already handled, and the consumer handles it again with enthusiasm.&lt;/p&gt;
&lt;p&gt;The fix is rarely cleverness. It is making the operation idempotent, so that retrying it is a no-op
instead of a hazard.&lt;/p&gt;
&lt;h2 id=&quot;the-shape-of-the-fix&quot;&gt;The shape of the fix&lt;/h2&gt;
&lt;p&gt;An idempotency key is just a promise from the caller: &lt;em&gt;this request and the one I sent nine seconds
ago are the same request, and you should only honour one of them.&lt;/em&gt; The server’s side of the bargain
is to remember what it answered.&lt;/p&gt;
&lt;figure class=&quot;code-block&quot; data-code-block=&quot;&quot;&gt;&lt;figcaption class=&quot;code-block__bar&quot;&gt;&lt;span class=&quot;code-block__name&quot;&gt;handlers/payment.js&lt;/span&gt;&lt;span class=&quot;code-block__meta&quot; data-pagefind-ignore=&quot;&quot;&gt;&lt;span class=&quot;code-block__lang&quot;&gt;js&lt;/span&gt;&lt;button type=&quot;button&quot; class=&quot;code-block__copy&quot; data-code-copy=&quot;&quot; aria-label=&quot;Copy code from handlers/payment.js&quot;&gt;&lt;span data-code-copy-label=&quot;&quot;&gt;Copy&lt;/span&gt;&lt;/button&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre class=&quot;astro-code vitesse-dark&quot; style=&quot;color:#dbd7caee; overflow-x: auto&quot; tabindex=&quot;0&quot; data-language=&quot;js&quot; role=&quot;region&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CB7676&quot;&gt;async&lt;/span&gt;&lt;span style=&quot;color:#CB7676&quot;&gt; function&lt;/span&gt;&lt;span style=&quot;color:#80A665&quot;&gt; handlePayment&lt;/span&gt;&lt;span style=&quot;color:#666666&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#BD976A&quot;&gt;req&lt;/span&gt;&lt;span style=&quot;color:#666666&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#BD976A&quot;&gt; res&lt;/span&gt;&lt;span style=&quot;color:#666666&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color:#666666&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CB7676&quot;&gt;  const&lt;/span&gt;&lt;span style=&quot;color:#BD976A&quot;&gt; key&lt;/span&gt;&lt;span style=&quot;color:#666666&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#BD976A&quot;&gt; req&lt;/span&gt;&lt;span style=&quot;color:#666666&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#BD976A&quot;&gt;headers&lt;/span&gt;&lt;span style=&quot;color:#666666&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color:#C98A7D77&quot;&gt;&apos;&lt;/span&gt;&lt;span style=&quot;color:#C98A7D&quot;&gt;idempotency-key&lt;/span&gt;&lt;span style=&quot;color:#C98A7D77&quot;&gt;&apos;&lt;/span&gt;&lt;span style=&quot;color:#666666&quot;&gt;];&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CB7676&quot;&gt;  const&lt;/span&gt;&lt;span style=&quot;color:#BD976A&quot;&gt; seen&lt;/span&gt;&lt;span style=&quot;color:#666666&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#4D9375&quot;&gt; await&lt;/span&gt;&lt;span style=&quot;color:#BD976A&quot;&gt; store&lt;/span&gt;&lt;span style=&quot;color:#666666&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#80A665&quot;&gt;get&lt;/span&gt;&lt;span style=&quot;color:#666666&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#BD976A&quot;&gt;key&lt;/span&gt;&lt;span style=&quot;color:#666666&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#4D9375&quot;&gt;  if&lt;/span&gt;&lt;span style=&quot;color:#666666&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#BD976A&quot;&gt;seen&lt;/span&gt;&lt;span style=&quot;color:#666666&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color:#4D9375&quot;&gt; return&lt;/span&gt;&lt;span style=&quot;color:#BD976A&quot;&gt; res&lt;/span&gt;&lt;span style=&quot;color:#666666&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#80A665&quot;&gt;json&lt;/span&gt;&lt;span style=&quot;color:#666666&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#BD976A&quot;&gt;seen&lt;/span&gt;&lt;span style=&quot;color:#666666&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CB7676&quot;&gt;  const&lt;/span&gt;&lt;span style=&quot;color:#BD976A&quot;&gt; result&lt;/span&gt;&lt;span style=&quot;color:#666666&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#4D9375&quot;&gt; await&lt;/span&gt;&lt;span style=&quot;color:#80A665&quot;&gt; charge&lt;/span&gt;&lt;span style=&quot;color:#666666&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#BD976A&quot;&gt;req&lt;/span&gt;&lt;span style=&quot;color:#666666&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#BD976A&quot;&gt;body&lt;/span&gt;&lt;span style=&quot;color:#666666&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#4D9375&quot;&gt;  await&lt;/span&gt;&lt;span style=&quot;color:#BD976A&quot;&gt; store&lt;/span&gt;&lt;span style=&quot;color:#666666&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#80A665&quot;&gt;set&lt;/span&gt;&lt;span style=&quot;color:#666666&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#BD976A&quot;&gt;key&lt;/span&gt;&lt;span style=&quot;color:#666666&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#BD976A&quot;&gt; result&lt;/span&gt;&lt;span style=&quot;color:#666666&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#666666&quot;&gt; {&lt;/span&gt;&lt;span style=&quot;color:#B8A965&quot;&gt; ttl&lt;/span&gt;&lt;span style=&quot;color:#666666&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#C98A7D77&quot;&gt; &apos;&lt;/span&gt;&lt;span style=&quot;color:#C98A7D&quot;&gt;24h&lt;/span&gt;&lt;span style=&quot;color:#C98A7D77&quot;&gt;&apos;&lt;/span&gt;&lt;span style=&quot;color:#666666&quot;&gt; });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#4D9375&quot;&gt;  return&lt;/span&gt;&lt;span style=&quot;color:#BD976A&quot;&gt; res&lt;/span&gt;&lt;span style=&quot;color:#666666&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#80A665&quot;&gt;json&lt;/span&gt;&lt;span style=&quot;color:#666666&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#BD976A&quot;&gt;result&lt;/span&gt;&lt;span style=&quot;color:#666666&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#666666&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;That costs one extra lookup per request. In exchange, every client, proxy and load balancer between
you and the caller is allowed to be a little bit sloppy about delivery — which, in practice, they
always are.&lt;/p&gt;
&lt;h2 id=&quot;where-it-gets-subtle&quot;&gt;Where it gets subtle&lt;/h2&gt;
&lt;p&gt;The naive version above has a race: two concurrent requests with the same key both miss the cache,
and both charge. The store needs to be the thing that serialises them, not an afterthought.&lt;/p&gt;
&lt;figure class=&quot;code-block&quot; data-code-block=&quot;&quot;&gt;&lt;figcaption class=&quot;code-block__bar&quot;&gt;&lt;span class=&quot;code-block__name&quot;&gt;migrations/003_idempotency.sql&lt;/span&gt;&lt;span class=&quot;code-block__meta&quot; data-pagefind-ignore=&quot;&quot;&gt;&lt;span class=&quot;code-block__lang&quot;&gt;sql&lt;/span&gt;&lt;button type=&quot;button&quot; class=&quot;code-block__copy&quot; data-code-copy=&quot;&quot; aria-label=&quot;Copy code from migrations/003_idempotency.sql&quot;&gt;&lt;span data-code-copy-label=&quot;&quot;&gt;Copy&lt;/span&gt;&lt;/button&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre class=&quot;astro-code vitesse-dark&quot; style=&quot;color:#dbd7caee; overflow-x: auto&quot; tabindex=&quot;0&quot; data-language=&quot;sql&quot; role=&quot;region&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#4D9375&quot;&gt;create&lt;/span&gt;&lt;span style=&quot;color:#4D9375&quot;&gt; table&lt;/span&gt;&lt;span style=&quot;color:#80A665&quot;&gt; idempotency&lt;/span&gt;&lt;span style=&quot;color:#DBD7CAEE&quot;&gt; (&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#4D9375&quot;&gt;  key&lt;/span&gt;&lt;span style=&quot;color:#CB7676&quot;&gt;         text&lt;/span&gt;&lt;span style=&quot;color:#CB7676&quot;&gt; primary key&lt;/span&gt;&lt;span style=&quot;color:#DBD7CAEE&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DBD7CAEE&quot;&gt;  response    jsonb,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DBD7CAEE&quot;&gt;  created_at  &lt;/span&gt;&lt;span style=&quot;color:#CB7676&quot;&gt;timestamptz&lt;/span&gt;&lt;span style=&quot;color:#4D9375&quot;&gt; not null&lt;/span&gt;&lt;span style=&quot;color:#CB7676&quot;&gt; default&lt;/span&gt;&lt;span style=&quot;color:#4D9375&quot;&gt; now&lt;/span&gt;&lt;span style=&quot;color:#666666&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DBD7CAEE&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#758575DD&quot;&gt;-- The insert is the lock. Whoever wins goes on to charge; the loser waits&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#758575DD&quot;&gt;-- and reads the winner&apos;s response.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#4D9375&quot;&gt;insert into&lt;/span&gt;&lt;span style=&quot;color:#DBD7CAEE&quot;&gt; idempotency (&lt;/span&gt;&lt;span style=&quot;color:#4D9375&quot;&gt;key&lt;/span&gt;&lt;span style=&quot;color:#DBD7CAEE&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#4D9375&quot;&gt;values&lt;/span&gt;&lt;span style=&quot;color:#DBD7CAEE&quot;&gt; ($&lt;/span&gt;&lt;span style=&quot;color:#4C9A91&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#DBD7CAEE&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#4D9375&quot;&gt;on&lt;/span&gt;&lt;span style=&quot;color:#DBD7CAEE&quot;&gt; conflict (&lt;/span&gt;&lt;span style=&quot;color:#4D9375&quot;&gt;key&lt;/span&gt;&lt;span style=&quot;color:#DBD7CAEE&quot;&gt;) do nothing&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DBD7CAEE&quot;&gt;returning &lt;/span&gt;&lt;span style=&quot;color:#4D9375&quot;&gt;key&lt;/span&gt;&lt;span style=&quot;color:#DBD7CAEE&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;If the insert returns no row, someone else is already working. The correct behaviour then is to poll
briefly and return their answer — not to charge, and not to error.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Write it while you still might be wrong. That is when it is useful to someone else who is wrong
the same way.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;the-part-nobody-writes-down&quot;&gt;The part nobody writes down&lt;/h2&gt;
&lt;p&gt;Idempotency keys need a retention policy. Keep them forever and the table becomes the largest thing
in your database; expire them too eagerly and a client retrying after a long network partition gets
charged twice, which is the exact failure you built this to prevent.&lt;/p&gt;
&lt;p&gt;Twenty-four hours is the number I keep landing on. It is longer than any sane client retry window
and short enough that the table stays small. It is not a principled answer. It is just one that has
not bitten me yet.&lt;/p&gt;</content:encoded><category>Architecture</category><category>Musings</category><author>Srivaths Aripirala</author></item><item><title>A Static Blog for a Dollar a Month</title><link>https://srivathsahr.com/posts/a-static-blog-for-a-dollar-a-month/</link><guid isPermaLink="true">https://srivathsahr.com/posts/a-static-blog-for-a-dollar-a-month/</guid><description>S3, CloudFront and nothing else. Here is the exact stack, the parts that are easy to get wrong, and the bill to prove it is actually cheap.</description><pubDate>Thu, 30 Jul 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;The brief I gave myself: host a static blog for less than a coffee a month, without babysitting it
every week. No Kubernetes. No CDN I do not need yet. No managed database for content that is just
files on disk.&lt;/p&gt;
&lt;p&gt;What I ended up with has two moving parts, and neither of them moves very often.&lt;/p&gt;
&lt;h2 id=&quot;the-shape&quot;&gt;The shape&lt;/h2&gt;
&lt;p&gt;Markdown lives in Git. A build turns it into HTML. The HTML goes into a private S3 bucket, and
CloudFront serves it. There is no origin server to overwhelm, because there is no origin server.&lt;/p&gt;
&lt;p&gt;The bucket is private — genuinely private, with Block Public Access fully on. CloudFront reaches it
through Origin Access Control, which signs every origin request with SigV4.&lt;/p&gt;
&lt;figure class=&quot;code-block&quot; data-code-block=&quot;&quot;&gt;&lt;figcaption class=&quot;code-block__bar&quot;&gt;&lt;span class=&quot;code-block__name&quot;&gt;infra/bucket-policy.json&lt;/span&gt;&lt;span class=&quot;code-block__meta&quot; data-pagefind-ignore=&quot;&quot;&gt;&lt;span class=&quot;code-block__lang&quot;&gt;json&lt;/span&gt;&lt;button type=&quot;button&quot; class=&quot;code-block__copy&quot; data-code-copy=&quot;&quot; aria-label=&quot;Copy code from infra/bucket-policy.json&quot;&gt;&lt;span data-code-copy-label=&quot;&quot;&gt;Copy&lt;/span&gt;&lt;/button&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre class=&quot;astro-code vitesse-dark&quot; style=&quot;color:#dbd7caee; overflow-x: auto&quot; tabindex=&quot;0&quot; data-language=&quot;json&quot; role=&quot;region&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#666666&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B8A96577&quot;&gt;  &quot;&lt;/span&gt;&lt;span style=&quot;color:#B8A965&quot;&gt;Version&lt;/span&gt;&lt;span style=&quot;color:#B8A96577&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#666666&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#C98A7D77&quot;&gt; &quot;&lt;/span&gt;&lt;span style=&quot;color:#C98A7D&quot;&gt;2012-10-17&lt;/span&gt;&lt;span style=&quot;color:#C98A7D77&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#666666&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B8A96577&quot;&gt;  &quot;&lt;/span&gt;&lt;span style=&quot;color:#B8A965&quot;&gt;Statement&lt;/span&gt;&lt;span style=&quot;color:#B8A96577&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#666666&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#666666&quot;&gt; [&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#666666&quot;&gt;    {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B8A96577&quot;&gt;      &quot;&lt;/span&gt;&lt;span style=&quot;color:#B8A965&quot;&gt;Sid&lt;/span&gt;&lt;span style=&quot;color:#B8A96577&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#666666&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#C98A7D77&quot;&gt; &quot;&lt;/span&gt;&lt;span style=&quot;color:#C98A7D&quot;&gt;AllowCloudFrontServicePrincipalReadOnly&lt;/span&gt;&lt;span style=&quot;color:#C98A7D77&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#666666&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B8A96577&quot;&gt;      &quot;&lt;/span&gt;&lt;span style=&quot;color:#B8A965&quot;&gt;Effect&lt;/span&gt;&lt;span style=&quot;color:#B8A96577&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#666666&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#C98A7D77&quot;&gt; &quot;&lt;/span&gt;&lt;span style=&quot;color:#C98A7D&quot;&gt;Allow&lt;/span&gt;&lt;span style=&quot;color:#C98A7D77&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#666666&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B8A96577&quot;&gt;      &quot;&lt;/span&gt;&lt;span style=&quot;color:#B8A965&quot;&gt;Principal&lt;/span&gt;&lt;span style=&quot;color:#B8A96577&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#666666&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#666666&quot;&gt; {&lt;/span&gt;&lt;span style=&quot;color:#B8A96577&quot;&gt; &quot;&lt;/span&gt;&lt;span style=&quot;color:#B8A965&quot;&gt;Service&lt;/span&gt;&lt;span style=&quot;color:#B8A96577&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#666666&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#C98A7D77&quot;&gt; &quot;&lt;/span&gt;&lt;span style=&quot;color:#C98A7D&quot;&gt;cloudfront.amazonaws.com&lt;/span&gt;&lt;span style=&quot;color:#C98A7D77&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#666666&quot;&gt; },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B8A96577&quot;&gt;      &quot;&lt;/span&gt;&lt;span style=&quot;color:#B8A965&quot;&gt;Action&lt;/span&gt;&lt;span style=&quot;color:#B8A96577&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#666666&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#C98A7D77&quot;&gt; &quot;&lt;/span&gt;&lt;span style=&quot;color:#C98A7D&quot;&gt;s3:GetObject&lt;/span&gt;&lt;span style=&quot;color:#C98A7D77&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#666666&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B8A96577&quot;&gt;      &quot;&lt;/span&gt;&lt;span style=&quot;color:#B8A965&quot;&gt;Resource&lt;/span&gt;&lt;span style=&quot;color:#B8A96577&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#666666&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#C98A7D77&quot;&gt; &quot;&lt;/span&gt;&lt;span style=&quot;color:#C98A7D&quot;&gt;arn:aws:s3:::example-blog/*&lt;/span&gt;&lt;span style=&quot;color:#C98A7D77&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#666666&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B8A96577&quot;&gt;      &quot;&lt;/span&gt;&lt;span style=&quot;color:#B8A965&quot;&gt;Condition&lt;/span&gt;&lt;span style=&quot;color:#B8A96577&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#666666&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#666666&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B8A96577&quot;&gt;        &quot;&lt;/span&gt;&lt;span style=&quot;color:#B8A965&quot;&gt;StringEquals&lt;/span&gt;&lt;span style=&quot;color:#B8A96577&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#666666&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#666666&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B8A96577&quot;&gt;          &quot;&lt;/span&gt;&lt;span style=&quot;color:#B8A965&quot;&gt;AWS:SourceArn&lt;/span&gt;&lt;span style=&quot;color:#B8A96577&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#666666&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#C98A7D77&quot;&gt; &quot;&lt;/span&gt;&lt;span style=&quot;color:#C98A7D&quot;&gt;arn:aws:cloudfront::111122223333:distribution/E1EXAMPLE&lt;/span&gt;&lt;span style=&quot;color:#C98A7D77&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#666666&quot;&gt;        }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#666666&quot;&gt;      }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#666666&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#666666&quot;&gt;  ]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#666666&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;The &lt;code&gt;AWS:SourceArn&lt;/code&gt; condition is the part people leave out. Without it the policy trusts the
CloudFront &lt;em&gt;service&lt;/em&gt;, which means any CloudFront distribution in any account can read your bucket.
With it, only yours can.&lt;/p&gt;
&lt;h2 id=&quot;the-part-that-will-catch-you&quot;&gt;The part that will catch you&lt;/h2&gt;
&lt;p&gt;An S3 REST origin does not resolve directory indexes. Request &lt;code&gt;/posts/something/&lt;/code&gt; and S3 looks for
an object with that literal key, finds nothing, and returns 404. The static website endpoint would
handle this, but using it forces the bucket public and drops HTTPS to the origin.&lt;/p&gt;
&lt;p&gt;So the rewrite moves to the edge:&lt;/p&gt;
&lt;figure class=&quot;code-block&quot; data-code-block=&quot;&quot;&gt;&lt;figcaption class=&quot;code-block__bar&quot;&gt;&lt;span class=&quot;code-block__name&quot;&gt;infra/viewer-request.js&lt;/span&gt;&lt;span class=&quot;code-block__meta&quot; data-pagefind-ignore=&quot;&quot;&gt;&lt;span class=&quot;code-block__lang&quot;&gt;js&lt;/span&gt;&lt;button type=&quot;button&quot; class=&quot;code-block__copy&quot; data-code-copy=&quot;&quot; aria-label=&quot;Copy code from infra/viewer-request.js&quot;&gt;&lt;span data-code-copy-label=&quot;&quot;&gt;Copy&lt;/span&gt;&lt;/button&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre class=&quot;astro-code vitesse-dark&quot; style=&quot;color:#dbd7caee; overflow-x: auto&quot; tabindex=&quot;0&quot; data-language=&quot;js&quot; role=&quot;region&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CB7676&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color:#80A665&quot;&gt; handler&lt;/span&gt;&lt;span style=&quot;color:#666666&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#BD976A&quot;&gt;event&lt;/span&gt;&lt;span style=&quot;color:#666666&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color:#666666&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CB7676&quot;&gt;    var&lt;/span&gt;&lt;span style=&quot;color:#BD976A&quot;&gt; request&lt;/span&gt;&lt;span style=&quot;color:#666666&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#BD976A&quot;&gt; event&lt;/span&gt;&lt;span style=&quot;color:#666666&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#BD976A&quot;&gt;request&lt;/span&gt;&lt;span style=&quot;color:#666666&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CB7676&quot;&gt;    var&lt;/span&gt;&lt;span style=&quot;color:#BD976A&quot;&gt; uri&lt;/span&gt;&lt;span style=&quot;color:#666666&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#BD976A&quot;&gt; request&lt;/span&gt;&lt;span style=&quot;color:#666666&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#BD976A&quot;&gt;uri&lt;/span&gt;&lt;span style=&quot;color:#666666&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#4D9375&quot;&gt;    if&lt;/span&gt;&lt;span style=&quot;color:#666666&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#BD976A&quot;&gt;uri&lt;/span&gt;&lt;span style=&quot;color:#666666&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#80A665&quot;&gt;charAt&lt;/span&gt;&lt;span style=&quot;color:#666666&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#BD976A&quot;&gt;uri&lt;/span&gt;&lt;span style=&quot;color:#666666&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B8A965&quot;&gt;length&lt;/span&gt;&lt;span style=&quot;color:#CB7676&quot;&gt; -&lt;/span&gt;&lt;span style=&quot;color:#4C9A91&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#666666&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color:#CB7676&quot;&gt; ===&lt;/span&gt;&lt;span style=&quot;color:#C98A7D77&quot;&gt; &apos;&lt;/span&gt;&lt;span style=&quot;color:#C98A7D&quot;&gt;/&lt;/span&gt;&lt;span style=&quot;color:#C98A7D77&quot;&gt;&apos;&lt;/span&gt;&lt;span style=&quot;color:#666666&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color:#666666&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#BD976A&quot;&gt;        request&lt;/span&gt;&lt;span style=&quot;color:#666666&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#BD976A&quot;&gt;uri&lt;/span&gt;&lt;span style=&quot;color:#666666&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#BD976A&quot;&gt; uri&lt;/span&gt;&lt;span style=&quot;color:#CB7676&quot;&gt; +&lt;/span&gt;&lt;span style=&quot;color:#C98A7D77&quot;&gt; &apos;&lt;/span&gt;&lt;span style=&quot;color:#C98A7D&quot;&gt;index.html&lt;/span&gt;&lt;span style=&quot;color:#C98A7D77&quot;&gt;&apos;&lt;/span&gt;&lt;span style=&quot;color:#666666&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#666666&quot;&gt;    }&lt;/span&gt;&lt;span style=&quot;color:#4D9375&quot;&gt; else&lt;/span&gt;&lt;span style=&quot;color:#4D9375&quot;&gt; if&lt;/span&gt;&lt;span style=&quot;color:#666666&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#BD976A&quot;&gt;uri&lt;/span&gt;&lt;span style=&quot;color:#666666&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#80A665&quot;&gt;lastIndexOf&lt;/span&gt;&lt;span style=&quot;color:#666666&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#C98A7D77&quot;&gt;&apos;&lt;/span&gt;&lt;span style=&quot;color:#C98A7D&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#C98A7D77&quot;&gt;&apos;&lt;/span&gt;&lt;span style=&quot;color:#666666&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color:#666666&quot;&gt; &amp;#x3C;=&lt;/span&gt;&lt;span style=&quot;color:#BD976A&quot;&gt; uri&lt;/span&gt;&lt;span style=&quot;color:#666666&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#80A665&quot;&gt;lastIndexOf&lt;/span&gt;&lt;span style=&quot;color:#666666&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#C98A7D77&quot;&gt;&apos;&lt;/span&gt;&lt;span style=&quot;color:#C98A7D&quot;&gt;/&lt;/span&gt;&lt;span style=&quot;color:#C98A7D77&quot;&gt;&apos;&lt;/span&gt;&lt;span style=&quot;color:#666666&quot;&gt;))&lt;/span&gt;&lt;span style=&quot;color:#666666&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#4D9375&quot;&gt;        return&lt;/span&gt;&lt;span style=&quot;color:#666666&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B8A965&quot;&gt;            statusCode&lt;/span&gt;&lt;span style=&quot;color:#666666&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#4C9A91&quot;&gt; 301&lt;/span&gt;&lt;span style=&quot;color:#666666&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B8A965&quot;&gt;            statusDescription&lt;/span&gt;&lt;span style=&quot;color:#666666&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#C98A7D77&quot;&gt; &apos;&lt;/span&gt;&lt;span style=&quot;color:#C98A7D&quot;&gt;Moved Permanently&lt;/span&gt;&lt;span style=&quot;color:#C98A7D77&quot;&gt;&apos;&lt;/span&gt;&lt;span style=&quot;color:#666666&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B8A965&quot;&gt;            headers&lt;/span&gt;&lt;span style=&quot;color:#666666&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#666666&quot;&gt; {&lt;/span&gt;&lt;span style=&quot;color:#B8A965&quot;&gt; location&lt;/span&gt;&lt;span style=&quot;color:#666666&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#666666&quot;&gt; {&lt;/span&gt;&lt;span style=&quot;color:#B8A965&quot;&gt; value&lt;/span&gt;&lt;span style=&quot;color:#666666&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#BD976A&quot;&gt; uri&lt;/span&gt;&lt;span style=&quot;color:#CB7676&quot;&gt; +&lt;/span&gt;&lt;span style=&quot;color:#C98A7D77&quot;&gt; &apos;&lt;/span&gt;&lt;span style=&quot;color:#C98A7D&quot;&gt;/&lt;/span&gt;&lt;span style=&quot;color:#C98A7D77&quot;&gt;&apos;&lt;/span&gt;&lt;span style=&quot;color:#666666&quot;&gt; }&lt;/span&gt;&lt;span style=&quot;color:#666666&quot;&gt; }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#666666&quot;&gt;        };&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#666666&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#4D9375&quot;&gt;    return&lt;/span&gt;&lt;span style=&quot;color:#BD976A&quot;&gt; request&lt;/span&gt;&lt;span style=&quot;color:#666666&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#666666&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;A CloudFront Function, not Lambda@Edge. It runs in about a millisecond, costs a sixth as much, and
this logic is never going to outgrow the runtime.&lt;/p&gt;
&lt;h2 id=&quot;cache-headers-earn-their-keep&quot;&gt;Cache headers earn their keep&lt;/h2&gt;
&lt;p&gt;Two rules do almost all the work. Fingerprinted assets are immutable forever; HTML must always be
revalidated, or a deploy takes a day to become visible.&lt;/p&gt;





















&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Path&lt;/th&gt;&lt;th&gt;Cache-Control&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;/_astro/*&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;max-age=31536000, immutable&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;*.html&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;max-age=0, must-revalidate&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;/feed.xml&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;max-age=3600&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;Get this backwards — long TTLs on HTML — and you will spend the rest of the site’s life issuing
invalidations to work around it.&lt;/p&gt;
&lt;h2 id=&quot;the-bill&quot;&gt;The bill&lt;/h2&gt;
&lt;p&gt;Storage is under ten cents. Requests are negligible, because CloudFront only touches the origin on
a cache miss. Transfer is dominated by images, and there are not many. The Route 53 hosted zone is
fifty cents a month flat, and is genuinely the largest line item.&lt;/p&gt;
&lt;p&gt;Call it a dollar. It has not moved in three months, including the week a post reached the front
page of a link aggregator and served forty thousand people — which cost nothing extra, because
serving cached bytes from an edge is what the whole arrangement is for.&lt;/p&gt;</content:encoded><category>AWS</category><category>Architecture</category><category>DevOps</category><author>Srivaths Aripirala</author></item><item><title>Why I&apos;m Leaving Medium</title><link>https://srivathsahr.com/posts/why-im-leaving-medium/</link><guid isPermaLink="true">https://srivathsahr.com/posts/why-im-leaving-medium/</guid><description>A paywall, an algorithm, and someone else&apos;s roadmap were never really mine to begin with. What finally pushed me to self-host.</description><pubDate>Tue, 28 Jul 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;For a few years, Medium was the path of least resistance. Write, hit publish, let their distribution
do the rest. It worked, until the friction started outweighing the convenience.&lt;/p&gt;
&lt;p&gt;A paywall that turns away the readers I actually want to reach. A partner programme that pays out by
algorithmic mood. An editor that fights me every time I want a code block that does not look like
stock art.&lt;/p&gt;
&lt;p&gt;None of that is a scandal. It is just someone else’s platform, run for someone else’s incentives.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;A blog you do not control is a rented apartment. Nice view, no say in the renovations.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;what-owning-it-actually-means&quot;&gt;What “owning it” actually means&lt;/h2&gt;
&lt;p&gt;It is not really about the hosting. It is about three things that only work if you hold them
yourself:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;The URLs.&lt;/strong&gt; If a post’s address can be taken away or rewritten, every link anyone ever made to
it is on loan.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The format.&lt;/strong&gt; Markdown in a Git repository will still open in twenty years. A proprietary
editor’s export button is a promise, not a guarantee.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The reader.&lt;/strong&gt; No interstitial, no “you have two free articles left”, no sign-in wall between
what I wrote and the person who came to read it.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;the-migration-is-the-boring-part&quot;&gt;The migration is the boring part&lt;/h2&gt;
&lt;p&gt;Export the archive, convert the HTML to Markdown, download the images out of Medium’s CDN instead of
hotlinking them, and keep the original publication dates so the archive still reads chronologically.&lt;/p&gt;
&lt;p&gt;The one step that is easy to skip and expensive to skip: set &lt;code&gt;canonicalUrl&lt;/code&gt; on every migrated post
to its Medium address &lt;em&gt;first&lt;/em&gt;, so search engines do not see two copies of the same article while
both exist. Then, once the new site is indexed, reverse it — point Medium at the new URLs and drop
the field.&lt;/p&gt;
&lt;p&gt;Leave the canonicals pointing at Medium permanently and the traffic keeps accruing to Medium, which
defeats the entire point of moving. I have a calendar reminder six weeks out. I recommend one.&lt;/p&gt;
&lt;p&gt;So this is post one on the new setup: a domain I already owned, a static site, and a few dollars of
infrastructure. The next post covers exactly how cheap that ended up being.&lt;/p&gt;</content:encoded><category>Musings</category><author>Srivaths Aripirala</author></item><item><title>A Small Caching Layer for a Slow API</title><link>https://srivathsahr.com/posts/a-small-caching-layer/</link><guid isPermaLink="true">https://srivathsahr.com/posts/a-small-caching-layer/</guid><description>One upstream call was taking 900ms and getting hit on every page load. Forty lines of cache took p95 latency from 910ms to 4ms.</description><pubDate>Fri, 24 Jul 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;A dashboard I maintain calls a third-party API for exchange rates on every page load. The endpoint
is slow — 800 to 900 milliseconds — and the data changes maybe once an hour. There was no reason to
pay that latency tax on every request.&lt;/p&gt;
&lt;figure class=&quot;code-block&quot; data-code-block=&quot;&quot;&gt;&lt;figcaption class=&quot;code-block__bar&quot;&gt;&lt;span class=&quot;code-block__name&quot;&gt;lib/rates-cache.js&lt;/span&gt;&lt;span class=&quot;code-block__meta&quot; data-pagefind-ignore=&quot;&quot;&gt;&lt;span class=&quot;code-block__lang&quot;&gt;js&lt;/span&gt;&lt;button type=&quot;button&quot; class=&quot;code-block__copy&quot; data-code-copy=&quot;&quot; aria-label=&quot;Copy code from lib/rates-cache.js&quot;&gt;&lt;span data-code-copy-label=&quot;&quot;&gt;Copy&lt;/span&gt;&lt;/button&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre class=&quot;astro-code vitesse-dark&quot; style=&quot;color:#dbd7caee; overflow-x: auto&quot; tabindex=&quot;0&quot; data-language=&quot;js&quot; role=&quot;region&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CB7676&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#BD976A&quot;&gt; TTL_SECONDS&lt;/span&gt;&lt;span style=&quot;color:#666666&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#4C9A91&quot;&gt; 3600&lt;/span&gt;&lt;span style=&quot;color:#666666&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#4D9375&quot;&gt;export&lt;/span&gt;&lt;span style=&quot;color:#CB7676&quot;&gt; async&lt;/span&gt;&lt;span style=&quot;color:#CB7676&quot;&gt; function&lt;/span&gt;&lt;span style=&quot;color:#80A665&quot;&gt; getRates&lt;/span&gt;&lt;span style=&quot;color:#666666&quot;&gt;()&lt;/span&gt;&lt;span style=&quot;color:#666666&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CB7676&quot;&gt;  const&lt;/span&gt;&lt;span style=&quot;color:#BD976A&quot;&gt; cached&lt;/span&gt;&lt;span style=&quot;color:#666666&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#4D9375&quot;&gt; await&lt;/span&gt;&lt;span style=&quot;color:#BD976A&quot;&gt; redis&lt;/span&gt;&lt;span style=&quot;color:#666666&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#80A665&quot;&gt;get&lt;/span&gt;&lt;span style=&quot;color:#666666&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#C98A7D77&quot;&gt;&apos;&lt;/span&gt;&lt;span style=&quot;color:#C98A7D&quot;&gt;rates:latest&lt;/span&gt;&lt;span style=&quot;color:#C98A7D77&quot;&gt;&apos;&lt;/span&gt;&lt;span style=&quot;color:#666666&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#4D9375&quot;&gt;  if&lt;/span&gt;&lt;span style=&quot;color:#666666&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#BD976A&quot;&gt;cached&lt;/span&gt;&lt;span style=&quot;color:#666666&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color:#4D9375&quot;&gt; return&lt;/span&gt;&lt;span style=&quot;color:#BD976A&quot;&gt; JSON&lt;/span&gt;&lt;span style=&quot;color:#666666&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#80A665&quot;&gt;parse&lt;/span&gt;&lt;span style=&quot;color:#666666&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#BD976A&quot;&gt;cached&lt;/span&gt;&lt;span style=&quot;color:#666666&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CB7676&quot;&gt;  const&lt;/span&gt;&lt;span style=&quot;color:#BD976A&quot;&gt; fresh&lt;/span&gt;&lt;span style=&quot;color:#666666&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#4D9375&quot;&gt; await&lt;/span&gt;&lt;span style=&quot;color:#80A665&quot;&gt; fetchRatesFromUpstream&lt;/span&gt;&lt;span style=&quot;color:#666666&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#4D9375&quot;&gt;  await&lt;/span&gt;&lt;span style=&quot;color:#BD976A&quot;&gt; redis&lt;/span&gt;&lt;span style=&quot;color:#666666&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#80A665&quot;&gt;set&lt;/span&gt;&lt;span style=&quot;color:#666666&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#C98A7D77&quot;&gt;&apos;&lt;/span&gt;&lt;span style=&quot;color:#C98A7D&quot;&gt;rates:latest&lt;/span&gt;&lt;span style=&quot;color:#C98A7D77&quot;&gt;&apos;&lt;/span&gt;&lt;span style=&quot;color:#666666&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#BD976A&quot;&gt; JSON&lt;/span&gt;&lt;span style=&quot;color:#666666&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#80A665&quot;&gt;stringify&lt;/span&gt;&lt;span style=&quot;color:#666666&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#BD976A&quot;&gt;fresh&lt;/span&gt;&lt;span style=&quot;color:#666666&quot;&gt;),&lt;/span&gt;&lt;span style=&quot;color:#C98A7D77&quot;&gt; &apos;&lt;/span&gt;&lt;span style=&quot;color:#C98A7D&quot;&gt;EX&lt;/span&gt;&lt;span style=&quot;color:#C98A7D77&quot;&gt;&apos;&lt;/span&gt;&lt;span style=&quot;color:#666666&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#BD976A&quot;&gt; TTL_SECONDS&lt;/span&gt;&lt;span style=&quot;color:#666666&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#4D9375&quot;&gt;  return&lt;/span&gt;&lt;span style=&quot;color:#BD976A&quot;&gt; fresh&lt;/span&gt;&lt;span style=&quot;color:#666666&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#666666&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;p95 on that route dropped from 910ms to 4ms. The cache miss — once an hour, when the TTL expires —
still costs the full round trip, but every other request rides on data that is at most sixty minutes
stale, which the dashboard’s users have never once noticed.&lt;/p&gt;
&lt;h2 id=&quot;the-miss-is-not-free&quot;&gt;The miss is not free&lt;/h2&gt;
&lt;p&gt;The version above has a stampede problem. When the key expires, every concurrent request misses at
once and they all call upstream together. At low traffic you will never see it. At the moment you
most want the cache working, you will.&lt;/p&gt;
&lt;p&gt;A single-flight lock fixes it:&lt;/p&gt;
&lt;figure class=&quot;code-block&quot; data-code-block=&quot;&quot;&gt;&lt;figcaption class=&quot;code-block__bar&quot;&gt;&lt;span class=&quot;code-block__name&quot;&gt;lib/rates-cache.js&lt;/span&gt;&lt;span class=&quot;code-block__meta&quot; data-pagefind-ignore=&quot;&quot;&gt;&lt;span class=&quot;code-block__lang&quot;&gt;js&lt;/span&gt;&lt;button type=&quot;button&quot; class=&quot;code-block__copy&quot; data-code-copy=&quot;&quot; aria-label=&quot;Copy code from lib/rates-cache.js&quot;&gt;&lt;span data-code-copy-label=&quot;&quot;&gt;Copy&lt;/span&gt;&lt;/button&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre class=&quot;astro-code vitesse-dark&quot; style=&quot;color:#dbd7caee; overflow-x: auto&quot; tabindex=&quot;0&quot; data-language=&quot;js&quot; role=&quot;region&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#4D9375&quot;&gt;export&lt;/span&gt;&lt;span style=&quot;color:#CB7676&quot;&gt; async&lt;/span&gt;&lt;span style=&quot;color:#CB7676&quot;&gt; function&lt;/span&gt;&lt;span style=&quot;color:#80A665&quot;&gt; getRates&lt;/span&gt;&lt;span style=&quot;color:#666666&quot;&gt;()&lt;/span&gt;&lt;span style=&quot;color:#666666&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CB7676&quot;&gt;  const&lt;/span&gt;&lt;span style=&quot;color:#BD976A&quot;&gt; cached&lt;/span&gt;&lt;span style=&quot;color:#666666&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#4D9375&quot;&gt; await&lt;/span&gt;&lt;span style=&quot;color:#BD976A&quot;&gt; redis&lt;/span&gt;&lt;span style=&quot;color:#666666&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#80A665&quot;&gt;get&lt;/span&gt;&lt;span style=&quot;color:#666666&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#C98A7D77&quot;&gt;&apos;&lt;/span&gt;&lt;span style=&quot;color:#C98A7D&quot;&gt;rates:latest&lt;/span&gt;&lt;span style=&quot;color:#C98A7D77&quot;&gt;&apos;&lt;/span&gt;&lt;span style=&quot;color:#666666&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#4D9375&quot;&gt;  if&lt;/span&gt;&lt;span style=&quot;color:#666666&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#BD976A&quot;&gt;cached&lt;/span&gt;&lt;span style=&quot;color:#666666&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color:#4D9375&quot;&gt; return&lt;/span&gt;&lt;span style=&quot;color:#BD976A&quot;&gt; JSON&lt;/span&gt;&lt;span style=&quot;color:#666666&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#80A665&quot;&gt;parse&lt;/span&gt;&lt;span style=&quot;color:#666666&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#BD976A&quot;&gt;cached&lt;/span&gt;&lt;span style=&quot;color:#666666&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#758575DD&quot;&gt;  // Whoever wins the lock refreshes; everyone else serves the stale copy&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#758575DD&quot;&gt;  // rather than piling onto an already-slow upstream.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CB7676&quot;&gt;  const&lt;/span&gt;&lt;span style=&quot;color:#BD976A&quot;&gt; won&lt;/span&gt;&lt;span style=&quot;color:#666666&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#4D9375&quot;&gt; await&lt;/span&gt;&lt;span style=&quot;color:#BD976A&quot;&gt; redis&lt;/span&gt;&lt;span style=&quot;color:#666666&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#80A665&quot;&gt;set&lt;/span&gt;&lt;span style=&quot;color:#666666&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#C98A7D77&quot;&gt;&apos;&lt;/span&gt;&lt;span style=&quot;color:#C98A7D&quot;&gt;rates:lock&lt;/span&gt;&lt;span style=&quot;color:#C98A7D77&quot;&gt;&apos;&lt;/span&gt;&lt;span style=&quot;color:#666666&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#C98A7D77&quot;&gt; &apos;&lt;/span&gt;&lt;span style=&quot;color:#C98A7D&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#C98A7D77&quot;&gt;&apos;&lt;/span&gt;&lt;span style=&quot;color:#666666&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#C98A7D77&quot;&gt; &apos;&lt;/span&gt;&lt;span style=&quot;color:#C98A7D&quot;&gt;NX&lt;/span&gt;&lt;span style=&quot;color:#C98A7D77&quot;&gt;&apos;&lt;/span&gt;&lt;span style=&quot;color:#666666&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#C98A7D77&quot;&gt; &apos;&lt;/span&gt;&lt;span style=&quot;color:#C98A7D&quot;&gt;EX&lt;/span&gt;&lt;span style=&quot;color:#C98A7D77&quot;&gt;&apos;&lt;/span&gt;&lt;span style=&quot;color:#666666&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#4C9A91&quot;&gt; 30&lt;/span&gt;&lt;span style=&quot;color:#666666&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#4D9375&quot;&gt;  if&lt;/span&gt;&lt;span style=&quot;color:#666666&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#CB7676&quot;&gt;!&lt;/span&gt;&lt;span style=&quot;color:#BD976A&quot;&gt;won&lt;/span&gt;&lt;span style=&quot;color:#666666&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color:#666666&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CB7676&quot;&gt;    const&lt;/span&gt;&lt;span style=&quot;color:#BD976A&quot;&gt; stale&lt;/span&gt;&lt;span style=&quot;color:#666666&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#4D9375&quot;&gt; await&lt;/span&gt;&lt;span style=&quot;color:#BD976A&quot;&gt; redis&lt;/span&gt;&lt;span style=&quot;color:#666666&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#80A665&quot;&gt;get&lt;/span&gt;&lt;span style=&quot;color:#666666&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#C98A7D77&quot;&gt;&apos;&lt;/span&gt;&lt;span style=&quot;color:#C98A7D&quot;&gt;rates:stale&lt;/span&gt;&lt;span style=&quot;color:#C98A7D77&quot;&gt;&apos;&lt;/span&gt;&lt;span style=&quot;color:#666666&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#4D9375&quot;&gt;    if&lt;/span&gt;&lt;span style=&quot;color:#666666&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#BD976A&quot;&gt;stale&lt;/span&gt;&lt;span style=&quot;color:#666666&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color:#4D9375&quot;&gt; return&lt;/span&gt;&lt;span style=&quot;color:#BD976A&quot;&gt; JSON&lt;/span&gt;&lt;span style=&quot;color:#666666&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#80A665&quot;&gt;parse&lt;/span&gt;&lt;span style=&quot;color:#666666&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#BD976A&quot;&gt;stale&lt;/span&gt;&lt;span style=&quot;color:#666666&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#666666&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CB7676&quot;&gt;  const&lt;/span&gt;&lt;span style=&quot;color:#BD976A&quot;&gt; fresh&lt;/span&gt;&lt;span style=&quot;color:#666666&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#4D9375&quot;&gt; await&lt;/span&gt;&lt;span style=&quot;color:#80A665&quot;&gt; fetchRatesFromUpstream&lt;/span&gt;&lt;span style=&quot;color:#666666&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#4D9375&quot;&gt;  await&lt;/span&gt;&lt;span style=&quot;color:#BD976A&quot;&gt; redis&lt;/span&gt;&lt;span style=&quot;color:#666666&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#80A665&quot;&gt;set&lt;/span&gt;&lt;span style=&quot;color:#666666&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#C98A7D77&quot;&gt;&apos;&lt;/span&gt;&lt;span style=&quot;color:#C98A7D&quot;&gt;rates:latest&lt;/span&gt;&lt;span style=&quot;color:#C98A7D77&quot;&gt;&apos;&lt;/span&gt;&lt;span style=&quot;color:#666666&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#BD976A&quot;&gt; JSON&lt;/span&gt;&lt;span style=&quot;color:#666666&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#80A665&quot;&gt;stringify&lt;/span&gt;&lt;span style=&quot;color:#666666&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#BD976A&quot;&gt;fresh&lt;/span&gt;&lt;span style=&quot;color:#666666&quot;&gt;),&lt;/span&gt;&lt;span style=&quot;color:#C98A7D77&quot;&gt; &apos;&lt;/span&gt;&lt;span style=&quot;color:#C98A7D&quot;&gt;EX&lt;/span&gt;&lt;span style=&quot;color:#C98A7D77&quot;&gt;&apos;&lt;/span&gt;&lt;span style=&quot;color:#666666&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#BD976A&quot;&gt; TTL_SECONDS&lt;/span&gt;&lt;span style=&quot;color:#666666&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#4D9375&quot;&gt;  await&lt;/span&gt;&lt;span style=&quot;color:#BD976A&quot;&gt; redis&lt;/span&gt;&lt;span style=&quot;color:#666666&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#80A665&quot;&gt;set&lt;/span&gt;&lt;span style=&quot;color:#666666&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#C98A7D77&quot;&gt;&apos;&lt;/span&gt;&lt;span style=&quot;color:#C98A7D&quot;&gt;rates:stale&lt;/span&gt;&lt;span style=&quot;color:#C98A7D77&quot;&gt;&apos;&lt;/span&gt;&lt;span style=&quot;color:#666666&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#BD976A&quot;&gt; JSON&lt;/span&gt;&lt;span style=&quot;color:#666666&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#80A665&quot;&gt;stringify&lt;/span&gt;&lt;span style=&quot;color:#666666&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#BD976A&quot;&gt;fresh&lt;/span&gt;&lt;span style=&quot;color:#666666&quot;&gt;));&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#4D9375&quot;&gt;  return&lt;/span&gt;&lt;span style=&quot;color:#BD976A&quot;&gt; fresh&lt;/span&gt;&lt;span style=&quot;color:#666666&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#666666&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;The second key has no expiry. It exists purely so there is always &lt;em&gt;something&lt;/em&gt; to serve while the
refresh is in flight. Serving data that is seventy minutes old beats serving a spinner for nine
hundred milliseconds, and it beats hammering an upstream that is already struggling.&lt;/p&gt;
&lt;h2 id=&quot;when-not-to-bother&quot;&gt;When not to bother&lt;/h2&gt;
&lt;p&gt;If the upstream is fast, caching adds a failure mode and buys nothing. If the data must be current
to the second, a cache is a correctness bug wearing a performance costume.&lt;/p&gt;
&lt;p&gt;This one was worth it because the numbers were lopsided: a slow call, data that changes hourly, and
users who genuinely cannot tell. That combination is rarer than the number of caches in the average
codebase would suggest.&lt;/p&gt;</content:encoded><category>Tools</category><category>Architecture</category><author>Srivaths Aripirala</author></item></channel></rss>