<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>Backend on Vedant Andhale</title>
    <link>https://www.vedant.me/tags/backend/</link>
    <description>Recent content in Backend on Vedant Andhale</description>
    <image>
      <url>https://www.vedant.me/</url>
      <link>https://www.vedant.me/</link>
    </image>
    <generator>Hugo -- gohugo.io</generator>
    <language>en-us</language>
    <lastBuildDate>Thu, 10 Sep 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://www.vedant.me/tags/backend/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>A database migration has to coexist with old code</title>
      <link>https://www.vedant.me/notebook/migrations-that-do-not-wake-you-up/</link>
      <pubDate>Thu, 10 Sep 2026 00:00:00 +0000</pubDate>
      
      <guid>https://www.vedant.me/notebook/migrations-that-do-not-wake-you-up/</guid>
      <description>An expand-and-contract example for schema changes that must work while application versions overlap.</description>
      <content:encoded><![CDATA[<p>Changing a database column is easy to describe when one application process owns the database and can stop for the change. A deployed service often has a messier interval: old and new application instances may run at the same time.</p>
<p>That interval is where a simple column rename can break otherwise correct code. The new version asks for the new name while the old version still asks for the old one.</p>
<h2 id="add-before-removing">Add before removing</h2>
<p>Suppose <code>customers.name</code> needs to become <code>customers.display_name</code>. An illustrative transition is:</p>
<ol>
<li>Add the new nullable column while keeping the old column usable.</li>
<li>Deploy code that writes both values in the same transaction and can read through the transition.</li>
<li>Backfill existing rows in bounded batches, with a retry-safe condition.</li>
<li>Check that all writers have migrated and the backfill is complete.</li>
<li>Switch reads to the new field; remove the old field in a later change.</li>
</ol>
<p>The details depend on the database and workload. A batch backfill can race with live updates, and a forgotten background job may still write only the old column. The compatibility plan needs to include those writers, not just the web application.</p>
<h2 id="separate-reversible-steps">Separate reversible steps</h2>
<p>Before removing a column, rolling the application back may still be straightforward. After deletion, the previous code can no longer rely on the data it expects. Treat that deletion as a separate decision with its own verification and recovery plan.</p>
<p>The SQL statement&rsquo;s apparent simplicity also says little about its operational cost. Check the database version&rsquo;s behaviour for locks, table scans and constraint validation. Rehearse on a representative dataset when those costs could interrupt traffic.</p>
<p>A useful migration note explains what can run during each stage, how completion is checked, and what rollback means at that point. That note is often more valuable than a clever one-line migration.</p>
<p>Reference: <a href="https://www.postgresql.org/docs/current/ddl-alter.html">PostgreSQL documentation on modifying tables</a>.</p>
]]></content:encoded>
    </item>
    
    <item>
      <title>Before caching a result, define when it is wrong</title>
      <link>https://www.vedant.me/notebook/caching-is-a-correctness-problem/</link>
      <pubDate>Thu, 10 Sep 2026 00:00:00 +0000</pubDate>
      
      <guid>https://www.vedant.me/notebook/caching-is-a-correctness-problem/</guid>
      <description>Cache keys, stale data and invalidation are part of the result contract, not just performance settings.</description>
      <content:encoded><![CDATA[<p>A cached response can be fast and still belong to the wrong user, the wrong currency or an old version of the underlying data. The first caching question should be: when would reusing this value produce the wrong answer?</p>
<p>Imagine an endpoint that returns a price. A key containing only the product ID looks reasonable until the price also depends on currency, customer tier or region. Those inputs are part of the result even if they do not appear in the URL.</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">cache_key</span> <span class="o">=</span> <span class="p">(</span><span class="n">product_id</span><span class="p">,</span> <span class="n">currency</span><span class="p">,</span> <span class="n">customer_tier</span><span class="p">,</span> <span class="n">pricing_version</span><span class="p">)</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>This is an illustrative key, not a complete pricing design. Its purpose is to make the dependencies visible. If account-specific permissions affect the response, those boundaries also need to be reflected in the caching strategy.</p>
<h2 id="freshness-is-a-product-decision">Freshness is a product decision</h2>
<p>A five-minute-old avatar and a five-minute-old available balance carry different consequences. A time-to-live is meaningful only after someone decides what stale data is acceptable for that operation.</p>
<p>Expiry also does not solve every invalidation problem. An update followed immediately by a read can still return an old cached value. Options include invalidating on writes, versioning keys or accepting a documented delay. Each choice creates different failure modes.</p>
<h2 id="check-the-uncomfortable-cases">Check the uncomfortable cases</h2>
<p>Before measuring a hit rate, I would check that:</p>
<ul>
<li>Two users cannot receive each other&rsquo;s private result.</li>
<li>A change in an input that affects the result changes the cache lookup.</li>
<li>An update behaves as promised on the next read.</li>
<li>A cache outage has an intentional failure or fallback path.</li>
</ul>
<p>For HTTP caches, headers add another layer to this contract. <code>no-cache</code> permits storage but requires validation before reuse; <code>no-store</code> tells a cache not to store the response. They are not interchangeable ways to say “please be fresh.”</p>
<p>A cache is easier to operate when its correctness rule fits in a sentence. The performance measurements come after that rule is clear.</p>
<p>Reference: <a href="https://www.rfc-editor.org/rfc/rfc9111.html">HTTP caching specification, RFC 9111</a>.</p>
]]></content:encoded>
    </item>
    
  </channel>
</rss>
