<?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>Mohit's Page</title><link>https://mohit-k-s.github.io/</link><description>Recent content on Mohit's Page</description><generator>Hugo -- gohugo.io</generator><language>en</language><copyright>Mohit Kumar</copyright><lastBuildDate>Thu, 01 Aug 2030 00:00:00 +0000</lastBuildDate><atom:link href="https://mohit-k-s.github.io/index.xml" rel="self" type="application/rss+xml"/><item><title>Read-Locks Are Writes in Disguise: Achieving 84x Concurrency on Apple M2</title><link>https://mohit-k-s.github.io/blog/2026/read_locks_are_writes_in_disguise/</link><pubDate>Tue, 10 Nov 2026 00:00:00 +0000</pubDate><guid>https://mohit-k-s.github.io/blog/2026/read_locks_are_writes_in_disguise/</guid><description>&lt;p&gt;Every computer science curriculum teaches the same comfortable truth about concurrency:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;&amp;ldquo;Use a mutex when writing. Use a Read-Write Lock (&lt;code&gt;shared_mutex&lt;/code&gt;, &lt;code&gt;sync.RWMutex&lt;/code&gt;, or &lt;code&gt;pthread_rwlock&lt;/code&gt;) when reading, because multiple readers can read concurrently without blocking each other.&amp;rdquo;&lt;/em&gt;&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;It sounds great on paper.&lt;/p&gt;
&lt;p&gt;Yesterday, after optimizing binary search on the Apple M2, I decided to test this textbook wisdom on an in-memory B+ tree. I wrapped the root with a standard &lt;code&gt;std::shared_mutex&lt;/code&gt;, spawned threads, and ran 10 million pure read queries across M2&amp;rsquo;s 8 physical cores.&lt;/p&gt;</description><content:encoded><![CDATA[<p>Every computer science curriculum teaches the same comfortable truth about concurrency:</p>
<blockquote>
<p><em>&ldquo;Use a mutex when writing. Use a Read-Write Lock (<code>shared_mutex</code>, <code>sync.RWMutex</code>, or <code>pthread_rwlock</code>) when reading, because multiple readers can read concurrently without blocking each other.&rdquo;</em></p></blockquote>
<p>It sounds great on paper.</p>
<p>Yesterday, after optimizing binary search on the Apple M2, I decided to test this textbook wisdom on an in-memory B+ tree. I wrapped the root with a standard <code>std::shared_mutex</code>, spawned threads, and ran 10 million pure read queries across M2&rsquo;s 8 physical cores.</p>
<p>Here is what happened:</p>
<ul>
<li>On <strong>1 core</strong>: <strong>23.1 million ops/sec</strong></li>
<li>On <strong>8 cores</strong>: <strong>6.0 million ops/sec</strong></li>
</ul>
<p>Adding 7 more CPU cores made the database <strong>nearly 4x slower</strong>. A 74% throughput collapse on a workload that was doing <strong>100% reads and zero writes</strong>.</p>
<p>How can an algorithm doing zero writes get slower as you add more CPU cores?</p>
<p>Because at the silicon level, <strong>read-locks are writes in disguise</strong>.</p>
<hr>
<h2 id="1-what-a-read-lock-actually-executes">1. What a &ldquo;Read Lock&rdquo; Actually Executes</h2>
<p>Open the implementation of <code>pthread_rwlock_rdlock</code>, C++&rsquo;s <code>std::shared_lock</code>, or Go&rsquo;s <code>sync.RWMutex.RLock()</code>.</p>
<p>To allow multiple readers while keeping writers out, the mutex has to keep count of how many readers are currently active.</p>
<p>So when your thread calls <code>shared_lock()</code>, the CPU executes:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1">// Inside pthread_rwlock / std::shared_mutex:
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="c1"></span><span class="nf">atomic_fetch_add</span><span class="p">(</span><span class="o">&amp;</span><span class="n">rwlock</span><span class="o">-&gt;</span><span class="n">reader_count</span><span class="p">,</span> <span class="mi">1</span><span class="p">);</span>  <span class="c1">// &lt;-- THIS IS AN ATOMIC WRITE
</span></span></span></code></pre></div><p>And when your thread is done reading:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="ln">1</span><span class="cl"><span class="nf">atomic_fetch_sub</span><span class="p">(</span><span class="o">&amp;</span><span class="n">rwlock</span><span class="o">-&gt;</span><span class="n">reader_count</span><span class="p">,</span> <span class="mi">1</span><span class="p">);</span>  <span class="c1">// &lt;-- ANOTHER ATOMIC WRITE
</span></span></span></code></pre></div><p>You thought you were just reading data. In reality, <strong>every single read query performs two atomic writes to shared memory</strong>.</p>
<hr>
<h2 id="2-the-crime-scene-cache-coherence--mesi">2. The Crime Scene: Cache Coherence &amp; MESI</h2>
<p>Why does writing an atomic integer matter if nobody is modifying the database keys?</p>
<p>Because modern multi-core processors do not share L1 caches. Every core on my Apple M2 has its own private, blazing-fast 64 KB L1 Data Cache.</p>
<p>To ensure all 8 cores see the same view of memory, the CPU hardware runs a cache coherence protocol called <strong>MESI</strong> (Modified, Exclusive, Shared, Invalid).</p>
<p>Here is what physically happens on the CPU bus when 8 cores try to acquire a &ldquo;read lock&rdquo;:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln"> 1</span><span class="cl">           ┌──────────────────────────────────────────────┐
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">           │      rwlock.reader_count (Atomic Integer)    │
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">           └──────────────────────┬───────────────────────┘
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">                                  │ Ping-Pong!
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">          ┌───────────────────────┴───────────────────────┐
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">          ▼                                               ▼
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">   ┌─────────────┐                                 ┌─────────────┐
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">   │ Core 0 (L1) │ ────[ BUS INVALIDATE Core 1 ]─&gt; │ Core 1 (L1) │
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">   │ State: [M]  │ &lt;───[ BUS INVALIDATE Core 0 ]── │ State: [M]  │
</span></span><span class="line"><span class="ln">10</span><span class="cl">   └─────────────┘                                 └─────────────┘
</span></span><span class="line"><span class="ln">11</span><span class="cl">      Atomic +1                                       Atomic +1
</span></span><span class="line"><span class="ln">12</span><span class="cl">   (Pipeline Stalled)                              (Pipeline Stalled)</span></span></code></pre></div><ol>
<li><strong>Core 0</strong> wants to increment <code>reader_count</code>. To write to that memory address, Core 0 must acquire <strong>Exclusive / Modified (M)</strong> ownership of that cache line. It broadcasts an <code>INVALIDATE</code> signal across the Apple M2 interconnect.</li>
<li><strong>Cores 1 through 7</strong> are forced to evict that cache line from their private L1 caches.</li>
<li>One nanosecond later, <strong>Core 1</strong> wants to acquire its read-lock. It suffers an <strong>L1 Cache Miss</strong> because Core 0 just invalidated its line.</li>
<li>Core 1 stalls its instruction pipeline, waits for Core 0 to flush the cache line, and broadcasts its own <code>INVALIDATE</code> signal back across the crossbar.</li>
<li>Repeat across all 8 cores, 20 million times.</li>
</ol>
<p>This phenomenon is known in database research as <strong>Cache-Line Bouncing</strong> or the <strong>Reader-Lock Scalability Collapse</strong>.</p>
<p>Even though your threads only wanted to read tree nodes, your CPU cores spent 90% of their clock cycles stalled, waiting for ownership of that single 128-byte cache line to ping-pong across the inter-core interconnect.</p>
<hr>
<h2 id="3-the-fix-optimistic-lock-coupling-olc">3. The Fix: Optimistic Lock Coupling (OLC)</h2>
<p>The solution was popularized by the &ldquo;German database school&rdquo; (Viktor Leis, Alfons Kemper, and Thomas Neumann at TU Munich) in high-performance engines like HyPer and LeanStore:</p>
<blockquote>
<p><strong>If you want linear multi-core scaling, readers must write ZERO bytes to memory.</strong></p></blockquote>
<p>In <strong>Optimistic Lock Coupling (OLC)</strong>, readers do not acquire any lock. They do not increment any reader counter. They execute with pure speculation:</p>
<h3 id="the-64-bit-version-primitive">The 64-Bit Version Primitive</h3>
<p>Every node in the tree stores a single 64-bit integer:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl"> 63                                                         1   0
</span></span><span class="line"><span class="ln">2</span><span class="cl">┌────────────────────────────────────────────────────────────┬───┐
</span></span><span class="line"><span class="ln">3</span><span class="cl">│                    Version Counter                         │ L │
</span></span><span class="line"><span class="ln">4</span><span class="cl">│   (Even number when unlocked, incremented on write_unlock) │ K │
</span></span><span class="line"><span class="ln">5</span><span class="cl">└────────────────────────────────────────────────────────────┴───┘
</span></span><span class="line"><span class="ln">6</span><span class="cl">                                                               ▲
</span></span><span class="line"><span class="ln">7</span><span class="cl">                                         0 = Unlocked, 1 = Locked</span></span></code></pre></div><ul>
<li><strong>Bit 0 (<code>LOCKED</code>)</strong>: 1 = exclusive write lock held, 0 = unlocked.</li>
<li><strong>Bits 1..63 (<code>VERSION</code>)</strong>: A monotonic version counter incremented on every unlock.</li>
</ul>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-cpp" data-lang="cpp"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="k">class</span> <span class="nc">OptLock</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="n">std</span><span class="o">::</span><span class="n">atomic</span><span class="o">&lt;</span><span class="kt">uint64_t</span><span class="o">&gt;</span> <span class="n">version_</span><span class="p">{</span><span class="mi">0</span><span class="p">};</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="k">static</span> <span class="k">constexpr</span> <span class="kt">uint64_t</span> <span class="n">LOCKED_BIT</span> <span class="o">=</span> <span class="mh">0x1ULL</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="k">public</span><span class="o">:</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="c1">// Read Path: ZERO writes to shared memory!
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="c1"></span>    <span class="kr">inline</span> <span class="kt">uint64_t</span> <span class="n">read_lock_optimistic</span><span class="p">()</span> <span class="k">const</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">        <span class="kt">uint64_t</span> <span class="n">v</span> <span class="o">=</span> <span class="n">version_</span><span class="p">.</span><span class="n">load</span><span class="p">(</span><span class="n">std</span><span class="o">::</span><span class="n">memory_order_acquire</span><span class="p">);</span> <span class="c1">// Compiles to ARM64 LDAR
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="c1"></span>        <span class="k">while</span> <span class="p">(</span><span class="n">__builtin_expect</span><span class="p">((</span><span class="n">v</span> <span class="o">&amp;</span> <span class="n">LOCKED_BIT</span><span class="p">)</span> <span class="o">!=</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">))</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">            <span class="k">asm</span> <span class="nf">volatile</span><span class="p">(</span><span class="s">&#34;yield&#34;</span> <span class="o">:::</span> <span class="s">&#34;memory&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">            <span class="n">v</span> <span class="o">=</span> <span class="n">version_</span><span class="p">.</span><span class="n">load</span><span class="p">(</span><span class="n">std</span><span class="o">::</span><span class="n">memory_order_acquire</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">        <span class="k">return</span> <span class="n">v</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">
</span></span><span class="line"><span class="ln">16</span><span class="cl">    <span class="c1">// Validate: Did any writer touch this node while I was reading?
</span></span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="c1"></span>    <span class="kr">inline</span> <span class="kt">bool</span> <span class="nf">validate</span><span class="p">(</span><span class="kt">uint64_t</span> <span class="n">old_version</span><span class="p">)</span> <span class="k">const</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">        <span class="k">return</span> <span class="n">version_</span><span class="p">.</span><span class="n">load</span><span class="p">(</span><span class="n">std</span><span class="o">::</span><span class="n">memory_order_acquire</span><span class="p">)</span> <span class="o">==</span> <span class="n">old_version</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">
</span></span><span class="line"><span class="ln">21</span><span class="cl">    <span class="c1">// Write Path: Exclusive lock via CAS on bit 0
</span></span></span><span class="line"><span class="ln">22</span><span class="cl"><span class="c1"></span>    <span class="kr">inline</span> <span class="kt">void</span> <span class="nf">write_lock</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">        <span class="kt">uint64_t</span> <span class="n">expected</span> <span class="o">=</span> <span class="n">version_</span><span class="p">.</span><span class="n">load</span><span class="p">(</span><span class="n">std</span><span class="o">::</span><span class="n">memory_order_relaxed</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">        <span class="k">while</span> <span class="p">(</span><span class="nb">true</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">            <span class="k">if</span> <span class="p">((</span><span class="n">expected</span> <span class="o">&amp;</span> <span class="n">LOCKED_BIT</span><span class="p">)</span> <span class="o">==</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">                <span class="k">if</span> <span class="p">(</span><span class="n">version_</span><span class="p">.</span><span class="n">compare_exchange_weak</span><span class="p">(</span><span class="n">expected</span><span class="p">,</span> <span class="n">expected</span> <span class="o">|</span> <span class="n">LOCKED_BIT</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">                                                   <span class="n">std</span><span class="o">::</span><span class="n">memory_order_acquire</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">                                                   <span class="n">std</span><span class="o">::</span><span class="n">memory_order_relaxed</span><span class="p">))</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl">                    <span class="k">return</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">                <span class="p">}</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">            <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl">                <span class="k">asm</span> <span class="k">volatile</span><span class="p">(</span><span class="s">&#34;yield&#34;</span> <span class="o">:::</span> <span class="s">&#34;memory&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl">                <span class="n">expected</span> <span class="o">=</span> <span class="n">version_</span><span class="p">.</span><span class="n">load</span><span class="p">(</span><span class="n">std</span><span class="o">::</span><span class="n">memory_order_relaxed</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">34</span><span class="cl">            <span class="p">}</span>
</span></span><span class="line"><span class="ln">35</span><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="ln">37</span><span class="cl">
</span></span><span class="line"><span class="ln">38</span><span class="cl">    <span class="c1">// Unlock: Advance version by 2 and clear locked bit
</span></span></span><span class="line"><span class="ln">39</span><span class="cl"><span class="c1"></span>    <span class="kr">inline</span> <span class="kt">void</span> <span class="nf">write_unlock</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">40</span><span class="cl">        <span class="kt">uint64_t</span> <span class="n">current</span> <span class="o">=</span> <span class="n">version_</span><span class="p">.</span><span class="n">load</span><span class="p">(</span><span class="n">std</span><span class="o">::</span><span class="n">memory_order_relaxed</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">41</span><span class="cl">        <span class="n">version_</span><span class="p">.</span><span class="n">store</span><span class="p">(</span><span class="n">current</span> <span class="o">+</span> <span class="mi">1</span><span class="p">,</span> <span class="n">std</span><span class="o">::</span><span class="n">memory_order_release</span><span class="p">);</span> <span class="c1">// Compiles to ARM64 STLR
</span></span></span><span class="line"><span class="ln">42</span><span class="cl"><span class="c1"></span>    <span class="p">}</span>
</span></span><span class="line"><span class="ln">43</span><span class="cl"><span class="p">};</span></span></span></code></pre></div><h3 id="how-an-olc-read-works-hand-over-hand-validation">How an OLC Read Works: Hand-over-Hand Validation</h3>
<p>Instead of holding locks from parent to child down the tree, readers validate each hop optimistically:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln"> 1</span><span class="cl">               [ Parent Node ]  (Read Version: V_parent)
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">                      │
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">                      │ 1. Speculative read child pointer
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">                      ▼
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">               [ Child Node  ]  (Read Version: V_child)
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">                      │
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">                      │ 2. Validate Parent: version == V_parent ?
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">                      ├──────────────────────────┐
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">                     YES                         NO
</span></span><span class="line"><span class="ln">10</span><span class="cl">                      │                          │
</span></span><span class="line"><span class="ln">11</span><span class="cl">              3. Parent was intact!       Concurrently modified!
</span></span><span class="line"><span class="ln">12</span><span class="cl">                 Traverse down child         Abort &amp; Restart from Root</span></span></code></pre></div><p>When traversing down a tree from parent to child:</p>
<ol>
<li>The reader reads <code>parent-&gt;version</code> using ARM64&rsquo;s single-cycle <code>LDAR</code> (Load-Acquire).</li>
<li>It finds the child pointer and reads <code>child-&gt;version</code>.</li>
<li>It validates: <code>parent-&gt;validate(parent_version)</code>.</li>
<li>If valid, it moves down. If invalid (a writer split the parent while it was reading), it restarts the search from the root.</li>
</ol>
<p>Because readers execute only <code>LDAR</code> and <strong>never write a single bit</strong>:</p>
<ul>
<li>The cache line stays in state <strong>Shared (S)</strong> across all 8 L1 caches.</li>
<li>Zero invalidate messages travel across the bus.</li>
<li>All 8 cores read at full hardware bandwidth simultaneously.</li>
</ul>
<hr>
<h2 id="4-the-hidden-trap-memory-reclamation-ebr">4. The Hidden Trap: Memory Reclamation (EBR)</h2>
<p>There is a catch.</p>
<p>If readers do not acquire locks, what happens when a writer splits or deletes a node while a reader is midway through traversing it?</p>
<p>If the writer calls <code>delete node</code>, the reader will dereference freed memory and crash with a segmentation fault (Use-After-Free).</p>
<p>To fix this without a heavyweight garbage collector, we use <strong>Epoch-Based Reclamation (EBR)</strong>:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln"> 1</span><span class="cl">                 ┌───────────────┐
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">                 │  Epoch [ 0 ]  │
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">                 └───────┬───────┘
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">                         │
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">                 Advance │ (Active Readers Pin Current Epoch)
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">                         ▼
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">  ┌───────────────┐              ┌───────────────┐
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">  │  Epoch [ 2 ]  │ &lt;─────────── │  Epoch [ 1 ]  │
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">  └───────┬───────┘   Advance    └───────────────┘
</span></span><span class="line"><span class="ln">10</span><span class="cl">          │
</span></span><span class="line"><span class="ln">11</span><span class="cl">          │ Objects retired in Epoch 2 can be freed
</span></span><span class="line"><span class="ln">12</span><span class="cl">          ▼ when ALL active threads have moved past Epoch 2!
</span></span><span class="line"><span class="ln">13</span><span class="cl">     Safe Bulk Free</span></span></code></pre></div><ul>
<li>A global epoch rotates through <code>0 → 1 → 2 → 0</code>.</li>
<li>When a reader thread begins a query, it pins the current epoch:
<code>local_epoch.store(global_epoch);</code></li>
<li>When a writer unlinks a node from the tree, it does not <code>free()</code> it. It pushes the pointer into an epoch retirement queue:
<code>retired_buckets[current_epoch].push_back(node);</code></li>
<li>Once all threads have advanced past <code>epoch G</code>, objects retired in <code>epoch (G + 1) % 3</code> are mathematically guaranteed to have <strong>zero active readers</strong>. They can be deallocated immediately in bulk without locks.</li>
</ul>
<h3 id="the-apple-silicon-rule-128-byte-alignment">The Apple Silicon Rule: 128-Byte Alignment</h3>
<p>On Apple Silicon, each thread&rsquo;s epoch tracking struct must be aligned to <strong>128 bytes</strong> (<code>alignas(128)</code>):</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-cpp" data-lang="cpp"><span class="line"><span class="ln">1</span><span class="cl"><span class="k">struct</span> <span class="nf">alignas</span><span class="p">(</span><span class="mi">128</span><span class="p">)</span> <span class="n">ThreadEpochState</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">    <span class="n">std</span><span class="o">::</span><span class="n">atomic</span><span class="o">&lt;</span><span class="kt">uint64_t</span><span class="o">&gt;</span> <span class="n">local_epoch</span><span class="p">{</span><span class="n">UNPINNED</span><span class="p">};</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">    <span class="n">std</span><span class="o">::</span><span class="n">atomic</span><span class="o">&lt;</span><span class="kt">bool</span><span class="o">&gt;</span> <span class="n">in_use</span><span class="p">{</span><span class="nb">false</span><span class="p">};</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">    <span class="n">std</span><span class="o">::</span><span class="n">array</span><span class="o">&lt;</span><span class="n">std</span><span class="o">::</span><span class="n">vector</span><span class="o">&lt;</span><span class="n">RetiredNode</span><span class="o">&gt;</span><span class="p">,</span> <span class="mi">3</span><span class="o">&gt;</span> <span class="n">retired_buckets</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">    <span class="n">size_t</span> <span class="n">pending_count</span><span class="p">{</span><span class="mi">0</span><span class="p">};</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl">    <span class="kt">char</span> <span class="n">pad</span><span class="p">[...];</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="p">};</span></span></span></code></pre></div><p>If you don&rsquo;t align it to 128 bytes, adjacent cores dirty each other&rsquo;s epoch variables, re-introducing false sharing and silently destroying your throughput.</p>
<hr>
<h2 id="5-the-benchmark-real-numbers-on-apple-m2">5. The Benchmark: Real Numbers on Apple M2</h2>
<p>I built a concurrent B+ tree using OLC and EBR and benchmarked it against <code>std::shared_mutex</code> across all 8 physical cores of the Apple M2.</p>
<h3 id="workload-1-100-reads-10000000-lookups">Workload 1: 100% Reads (10,000,000 Lookups)</h3>
<table>
  <thead>
      <tr>
          <th style="text-align: center">Threads</th>
          <th style="text-align: center"><code>std::shared_mutex</code> (RWLock)</th>
          <th style="text-align: center">OLC (Apple Silicon Speculative <code>LDAR</code>)</th>
          <th style="text-align: center">OLC Scaling</th>
          <th style="text-align: center">OLC Speedup</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td style="text-align: center"><strong>1 Core</strong></td>
          <td style="text-align: center">23.1 Mops/sec</td>
          <td style="text-align: center">32.4 Mops/sec</td>
          <td style="text-align: center">1.00x</td>
          <td style="text-align: center"><strong>1.40x</strong></td>
      </tr>
      <tr>
          <td style="text-align: center"><strong>2 Cores</strong></td>
          <td style="text-align: center">12.5 Mops/sec <em>(collapses)</em></td>
          <td style="text-align: center">59.7 Mops/sec <em>(near-linear)</em></td>
          <td style="text-align: center">1.84x</td>
          <td style="text-align: center"><strong>4.78x</strong></td>
      </tr>
      <tr>
          <td style="text-align: center"><strong>4 Cores</strong></td>
          <td style="text-align: center">6.6 Mops/sec <em>(collapses)</em></td>
          <td style="text-align: center">116.0 Mops/sec <em>(linear)</em></td>
          <td style="text-align: center">3.58x</td>
          <td style="text-align: center"><strong>17.49x</strong></td>
      </tr>
      <tr>
          <td style="text-align: center"><strong>8 Cores</strong></td>
          <td style="text-align: center"><strong>6.0 Mops/sec</strong> <em>(collapses)</em></td>
          <td style="text-align: center"><strong>154.5 Mops/sec</strong></td>
          <td style="text-align: center"><strong>4.77x</strong></td>
          <td style="text-align: center"><strong>25.78x Faster</strong></td>
      </tr>
  </tbody>
</table>
<p>Look at the difference:</p>
<ul>
<li><code>std::shared_mutex</code> collapsed by <strong>74%</strong> as core count increased.</li>
<li>OLC scaled smoothly up to <strong>154.5 million lookups per second</strong> (a <strong>25.8x leap</strong>).</li>
</ul>
<hr>
<h3 id="workload-2-90-read--10-write-5000000-ops">Workload 2: 90% Read / 10% Write (5,000,000 Ops)</h3>
<p>In real workloads, writers are constantly modifying the tree. Under standard locking, exclusive writers block all readers, and queued readers starve writers:</p>
<table>
  <thead>
      <tr>
          <th style="text-align: center">Threads</th>
          <th style="text-align: center"><code>std::shared_mutex</code> (RWLock)</th>
          <th style="text-align: center">OLC + EBR (Apple Silicon)</th>
          <th style="text-align: center">Speedup</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td style="text-align: center"><strong>1 Core</strong></td>
          <td style="text-align: center">22.5 Mops/sec</td>
          <td style="text-align: center">31.4 Mops/sec</td>
          <td style="text-align: center"><strong>1.40x</strong></td>
      </tr>
      <tr>
          <td style="text-align: center"><strong>2 Cores</strong></td>
          <td style="text-align: center">6.9 Mops/sec</td>
          <td style="text-align: center">57.9 Mops/sec</td>
          <td style="text-align: center"><strong>8.45x</strong></td>
      </tr>
      <tr>
          <td style="text-align: center"><strong>4 Cores</strong></td>
          <td style="text-align: center">3.4 Mops/sec</td>
          <td style="text-align: center">111.4 Mops/sec</td>
          <td style="text-align: center"><strong>32.58x</strong></td>
      </tr>
      <tr>
          <td style="text-align: center"><strong>8 Cores</strong></td>
          <td style="text-align: center"><strong>1.3 Mops/sec</strong></td>
          <td style="text-align: center"><strong>109.0 Mops/sec</strong></td>
          <td style="text-align: center"><strong>83.63x Faster (84x Leap!)</strong></td>
      </tr>
  </tbody>
</table>
<p>At 8 threads under mixed read/write contention, <code>std::shared_mutex</code> ground to a halt at <strong>1.3 million ops/sec</strong>.</p>
<p>The OLC + EBR engine maintained over <strong>109 million ops/sec</strong>—an <strong>84x speedup</strong>—while safely retiring and reclaiming tens of thousands of nodes with zero data races and zero memory leaks.</p>
<hr>
<h2 id="takeaways-for-systems-engineers">Takeaways for Systems Engineers</h2>
<ol>
<li><strong>Read-locks are writes in disguise</strong>: Any lock that increments an atomic counter turns concurrent reads into a cache-line invalidation storm.</li>
<li><strong>Zero-write reading is the only path to linear scaling</strong>: Readers must use speculative loads (<code>LDAR</code>) and version checks instead of writing lock state.</li>
<li><strong>EBR is essential for lock-free memory safety</strong>: You don&rsquo;t need a heavy garbage collector to safely free memory in concurrent systems; a 3-epoch rotation does it with zero reader locks.</li>
<li><strong>Respect 128-byte cache lines</strong>: Always isolate thread-local state with <code>alignas(128)</code> on Apple Silicon to prevent adjacent cores from invalidating each other&rsquo;s state.</li>
</ol>
<hr>
<h2 id="reproducing-the-benchmark">Reproducing the Benchmark</h2>
<p>The complete self-contained C++ benchmark is hosted directly on this site:</p>
<ul>
<li><strong>Source Code</strong>: <a href="/scripts/olc_ebr_benchmark.cpp"><code>/scripts/olc_ebr_benchmark.cpp</code></a></li>
</ul>
<p>You can run it on your own Mac with:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="ln">1</span><span class="cl">curl -sO https://mohit-k-s.github.io/scripts/olc_ebr_benchmark.cpp
</span></span><span class="line"><span class="ln">2</span><span class="cl">clang++ -O3 -std<span class="o">=</span>c++20 -mcpu<span class="o">=</span>apple-m2 -pthread olc_ebr_benchmark.cpp -o olc_bench
</span></span><span class="line"><span class="ln">3</span><span class="cl">./olc_bench</span></span></code></pre></div>]]></content:encoded></item><item><title>Making Binary Search Faster on Apple M2</title><link>https://mohit-k-s.github.io/blog/2026/making_binary_search_faster_on_apple_m2/</link><pubDate>Mon, 09 Nov 2026 00:00:00 +0000</pubDate><guid>https://mohit-k-s.github.io/blog/2026/making_binary_search_faster_on_apple_m2/</guid><description>&lt;p&gt;Binary search is one of the first algorithms every programmer learns. It is &lt;code&gt;O(log N)&lt;/code&gt;, elegant, and standard libraries like C++&amp;rsquo;s &lt;code&gt;std::lower_bound&lt;/code&gt; have had decades to optimize it.&lt;/p&gt;
&lt;p&gt;If you have a sorted array and need to find an element, you call &lt;code&gt;std::lower_bound&lt;/code&gt; and move on.&lt;/p&gt;
&lt;p&gt;A few days ago, I started wondering: on modern superscalar CPUs—specifically Apple Silicon (M2)—is &lt;code&gt;std::lower_bound&lt;/code&gt; actually running as fast as the hardware allows?&lt;/p&gt;
&lt;p&gt;Or is it leaving performance on the table?&lt;/p&gt;</description><content:encoded><![CDATA[<p>Binary search is one of the first algorithms every programmer learns. It is <code>O(log N)</code>, elegant, and standard libraries like C++&rsquo;s <code>std::lower_bound</code> have had decades to optimize it.</p>
<p>If you have a sorted array and need to find an element, you call <code>std::lower_bound</code> and move on.</p>
<p>A few days ago, I started wondering: on modern superscalar CPUs—specifically Apple Silicon (M2)—is <code>std::lower_bound</code> actually running as fast as the hardware allows?</p>
<p>Or is it leaving performance on the table?</p>
<p>To find out, I wrote a microbenchmark suite and tested several search implementations across array sizes ranging from 1,024 elements (4 KB, fits in L1 cache) to 8,388,608 elements (32 MB, spills into DRAM).</p>
<p>It turns out you can beat <code>std::lower_bound</code> by <strong>up to 2.9x</strong>.</p>
<p>Here is how, and why the hardware makes it possible.</p>
<hr>
<h2 id="the-hardware-reality-what-happens-on-apple-m2">The Hardware Reality: What Happens on Apple M2</h2>
<p>To understand why traditional binary search struggles, you have to look at the CPU executing it.</p>
<p>My test machine is an M2 MacBook Pro:</p>
<ul>
<li><strong>8 CPU cores</strong> (4 Avalanche performance cores + 4 Blizzard efficiency cores)</li>
<li><strong>64 KB L1 Data Cache</strong> per performance core</li>
<li><strong>4 MB L2 Cache</strong></li>
<li><strong>128-byte cache line size</strong> (<code>hw.cachelinesize = 128</code>)</li>
</ul>
<p>Apple&rsquo;s performance cores are beasts. They have an <strong>8-wide instruction decode engine</strong> and a massive reorder buffer (~600+ instructions). In an ideal scenario, the CPU executes several instructions per clock cycle.</p>
<p>But binary search exposes two major weaknesses in modern CPU architectures:</p>
<ol>
<li><strong>The Branch Misprediction Penalty</strong>:
When you search for random keys, the comparison <code>arr[mid] &lt; target</code> is true 50% of the time and false 50% of the time. It is mathematically the worst-case scenario for branch predictors. Every misprediction flushes the execution pipeline, costing 15 to 20 clock cycles.</li>
<li><strong>The Cache-Locality Penalty</strong>:
In a traditional sorted array, the first step checks index N/2. The next step jumps to N/4 or 3N/4 (megabytes away). The next step jumps hundreds of kilobytes away. Every step jumps to an unpredictable cache line.</li>
</ol>
<hr>
<h2 id="step-1-the-baseline-stdlower_bound">Step 1: The Baseline (<code>std::lower_bound</code>)</h2>
<p>Here is the standard binary search approach:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-cpp" data-lang="cpp"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="k">template</span> <span class="o">&lt;</span><span class="k">typename</span> <span class="n">T</span><span class="o">&gt;</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="kt">int</span> <span class="n">binary_search_branching</span><span class="p">(</span><span class="k">const</span> <span class="n">T</span><span class="o">*</span> <span class="n">arr</span><span class="p">,</span> <span class="kt">int</span> <span class="n">n</span><span class="p">,</span> <span class="n">T</span> <span class="n">target</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="kt">int</span> <span class="n">low</span> <span class="o">=</span> <span class="mi">0</span><span class="p">,</span> <span class="n">high</span> <span class="o">=</span> <span class="n">n</span> <span class="o">-</span> <span class="mi">1</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="k">while</span> <span class="p">(</span><span class="n">low</span> <span class="o">&lt;=</span> <span class="n">high</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">        <span class="kt">int</span> <span class="n">mid</span> <span class="o">=</span> <span class="n">low</span> <span class="o">+</span> <span class="p">(</span><span class="n">high</span> <span class="o">-</span> <span class="n">low</span><span class="p">)</span> <span class="o">/</span> <span class="mi">2</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">        <span class="k">if</span> <span class="p">(</span><span class="n">arr</span><span class="p">[</span><span class="n">mid</span><span class="p">]</span> <span class="o">&lt;</span> <span class="n">target</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">            <span class="n">low</span> <span class="o">=</span> <span class="n">mid</span> <span class="o">+</span> <span class="mi">1</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">        <span class="p">}</span> <span class="k">else</span> <span class="nf">if</span> <span class="p">(</span><span class="n">arr</span><span class="p">[</span><span class="n">mid</span><span class="p">]</span> <span class="o">&gt;</span> <span class="n">target</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">            <span class="n">high</span> <span class="o">=</span> <span class="n">mid</span> <span class="o">-</span> <span class="mi">1</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">        <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">            <span class="k">return</span> <span class="n">mid</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">    <span class="k">return</span> <span class="n">low</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="p">}</span></span></span></code></pre></div><p>On a 65,536-element array (256 KB), <code>std::lower_bound</code> takes <strong>32.26 ns</strong> per lookup.</p>
<p>For an algorithm that only does ~16 iterations (log₂(65536) = 16), 32 nanoseconds means every iteration is costing around 2 nanoseconds (~7 to 8 CPU clock cycles). The CPU is spending most of its time stalling on mispredicted branches.</p>
<hr>
<h2 id="step-2-branchless-binary-search-on-sorted-arrays">Step 2: Branchless Binary Search on Sorted Arrays</h2>
<p>Can we eliminate the <code>if-else</code> branches entirely?</p>
<p>Yes. Instead of branching on the comparison, we use conditional pointer arithmetic (often called Shar&rsquo;s algorithm or branchless binary search):</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-cpp" data-lang="cpp"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kt">int</span> <span class="nf">binary_search_branchless</span><span class="p">(</span><span class="k">const</span> <span class="kt">uint32_t</span><span class="o">*</span> <span class="n">arr</span><span class="p">,</span> <span class="kt">int</span> <span class="n">n</span><span class="p">,</span> <span class="kt">uint32_t</span> <span class="n">target</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="k">const</span> <span class="kt">uint32_t</span><span class="o">*</span> <span class="n">base</span> <span class="o">=</span> <span class="n">arr</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="kt">int</span> <span class="n">len</span> <span class="o">=</span> <span class="n">n</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="k">while</span> <span class="p">(</span><span class="n">len</span> <span class="o">&gt;</span> <span class="mi">1</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">        <span class="kt">int</span> <span class="n">half</span> <span class="o">=</span> <span class="n">len</span> <span class="o">/</span> <span class="mi">2</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">        <span class="c1">// On ARM64, this compiles to CSEL (Conditional Select)
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="c1"></span>        <span class="n">base</span> <span class="o">=</span> <span class="p">(</span><span class="n">base</span><span class="p">[</span><span class="n">half</span><span class="p">]</span> <span class="o">&lt;</span> <span class="n">target</span><span class="p">)</span> <span class="o">?</span> <span class="n">base</span> <span class="o">+</span> <span class="nl">half</span> <span class="p">:</span> <span class="n">base</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">        <span class="n">len</span> <span class="o">-=</span> <span class="n">half</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="k">return</span> <span class="p">(</span><span class="o">*</span><span class="n">base</span> <span class="o">&lt;</span> <span class="n">target</span><span class="p">)</span> <span class="o">?</span> <span class="p">(</span><span class="n">base</span> <span class="o">-</span> <span class="n">arr</span><span class="p">)</span> <span class="o">+</span> <span class="mi">1</span> <span class="o">:</span> <span class="p">(</span><span class="n">base</span> <span class="o">-</span> <span class="n">arr</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="p">}</span></span></span></code></pre></div><p>Notice what changed: there are no branches inside the <code>while</code> loop.</p>
<p>When compiled with <code>clang++ -O3 -mcpu=apple-m2</code>, Clang emits ARM64&rsquo;s <code>CSEL</code> (Conditional Select) instruction. <code>CSEL</code> evaluates in <strong>1 clock cycle</strong> regardless of whether the condition was true or false. Zero pipeline flushes.</p>
<h3 id="the-result">The Result</h3>
<ul>
<li>At N = 1,024 (4 KB): drops from <strong>19.33 ns → 7.71 ns (2.5x faster)</strong>.</li>
<li>At N = 65,536 (256 KB): drops from <strong>32.26 ns → 18.49 ns (1.7x faster)</strong>.</li>
</ul>
<p>By simply removing the branch, we doubled the speed. But when the array grows larger than L2 cache, another bottleneck takes over: memory access patterns.</p>
<hr>
<h2 id="step-3-the-eytzinger-breadth-first-heap-layout">Step 3: The Eytzinger (Breadth-First Heap) Layout</h2>
<p>In a sorted array, the memory addresses accessed during binary search are scattered:</p>





<pre tabindex="0"><code>Sorted:   [ 10,  20,  30,  40,  50,  60,  70 ]
Index:       0    1    2    3    4    5    6
Step 1:                     ^ (mid)
Step 2:          ^ (or)               ^</code></pre><p>Each step jumps across huge memory strides.</p>
<p>In the 1970s, Michael Eytzinger proposed a layout for binary trees that is identical to a binary heap:</p>
<ul>
<li>The root is stored at <strong>index 1</strong>.</li>
<li>The left child of node k is stored at <strong>2k</strong>.</li>
<li>The right child of node k is stored at <strong>2k + 1</strong>.</li>
</ul>





<pre tabindex="0"><code>Sorted Array:      [ 10,  20,  30,  40,  50,  60,  70 ]
Eytzinger Layout:  [ 40,  20,  60,  10,  30,  50,  70 ]
Index:                1    2    3    4    5    6    7</code></pre><h3 id="why-is-this-brilliant-for-cpu-caches">Why is this brilliant for CPU caches?</h3>
<p>Look at where the most frequently accessed nodes live:</p>
<ul>
<li>Index 1 (the root) is visited on <strong>every single search</strong>.</li>
<li>Indices 2 and 3 (level 1) are visited on <strong>every single search</strong>.</li>
<li>Indices 4, 5, 6, 7 (level 2) are visited on <strong>every single search</strong>.</li>
</ul>
<p>Indices 1 through 15 take 15 × 4 = 60 bytes.</p>
<p>On Apple Silicon, a single cache line is <strong>128 bytes</strong>.</p>
<p>That means the entire top 4 levels of the tree fit inside <strong>one single cache line</strong>! Once fetched, it stays permanently hot in the L1 Data Cache. The first 4 steps of any search will never incur a cache miss.</p>
<h3 id="the-search-implementation">The Search Implementation</h3>
<p>The traversal loop is astonishingly simple:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-cpp" data-lang="cpp"><span class="line"><span class="ln">1</span><span class="cl"><span class="kt">int</span> <span class="nf">search_eytzinger_branchless</span><span class="p">(</span><span class="k">const</span> <span class="kt">uint32_t</span><span class="o">*</span> <span class="n">arr</span><span class="p">,</span> <span class="kt">int</span> <span class="n">n</span><span class="p">,</span> <span class="kt">uint32_t</span> <span class="n">target</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">    <span class="kt">int</span> <span class="n">k</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">    <span class="k">while</span> <span class="p">(</span><span class="n">k</span> <span class="o">&lt;=</span> <span class="n">n</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">        <span class="c1">// Pure conditional arithmetic: compiles to 1-cycle CSEL on ARM64
</span></span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="c1"></span>        <span class="n">k</span> <span class="o">=</span> <span class="mi">2</span> <span class="o">*</span> <span class="n">k</span> <span class="o">+</span> <span class="p">(</span><span class="n">target</span> <span class="o">&gt;=</span> <span class="n">arr</span><span class="p">[</span><span class="n">k</span><span class="p">]</span> <span class="o">?</span> <span class="mi">1</span> <span class="o">:</span> <span class="mi">0</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl">    <span class="c1">// Restore the index
</span></span></span><span class="line"><span class="ln">8</span><span class="cl"><span class="c1"></span>    <span class="k">return</span> <span class="n">k</span> <span class="o">&gt;&gt;</span> <span class="p">(</span><span class="n">__builtin_ffs</span><span class="p">(</span><span class="o">~</span><span class="n">k</span><span class="p">));</span>
</span></span><span class="line"><span class="ln">9</span><span class="cl"><span class="p">}</span></span></span></code></pre></div><p>Every single iteration is two operations:</p>
<ol>
<li>Compare target with <code>arr[k]</code></li>
<li>Update <code>k = 2 * k + condition</code></li>
</ol>
<p>Zero branches. Zero branch mispredictions. Perfect cache locality at the top of the tree.</p>
<hr>
<h2 id="the-benchmark-across-cache-boundaries">The Benchmark: Across Cache Boundaries</h2>
<p>I ran 2,000,000 random lookups across five array sizes on the Apple M2:</p>
<table>
  <thead>
      <tr>
          <th style="text-align: left">Elements</th>
          <th style="text-align: left">Memory Size</th>
          <th style="text-align: left">Cache Level</th>
          <th style="text-align: left"><code>std::lower_bound</code></th>
          <th style="text-align: left">Branchless Sorted</th>
          <th style="text-align: left">Eytzinger Heap</th>
          <th style="text-align: left">Speedup</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td style="text-align: left"><strong>1,024</strong></td>
          <td style="text-align: left">4 KB</td>
          <td style="text-align: left">L1 Cache (64 KB)</td>
          <td style="text-align: left">19.33 ns</td>
          <td style="text-align: left">7.71 ns</td>
          <td style="text-align: left"><strong>7.19 ns</strong></td>
          <td style="text-align: left"><strong>2.7x faster</strong></td>
      </tr>
      <tr>
          <td style="text-align: left"><strong>16,384</strong></td>
          <td style="text-align: left">64 KB</td>
          <td style="text-align: left">L1 Boundary</td>
          <td style="text-align: left">17.37 ns</td>
          <td style="text-align: left"><strong>9.66 ns</strong></td>
          <td style="text-align: left">11.20 ns</td>
          <td style="text-align: left"><strong>1.8x faster</strong></td>
      </tr>
      <tr>
          <td style="text-align: left"><strong>65,536</strong></td>
          <td style="text-align: left">256 KB</td>
          <td style="text-align: left">L2 Cache (4 MB)</td>
          <td style="text-align: left">32.26 ns</td>
          <td style="text-align: left">18.49 ns</td>
          <td style="text-align: left"><strong>14.57 ns</strong></td>
          <td style="text-align: left"><strong>2.2x faster</strong></td>
      </tr>
      <tr>
          <td style="text-align: left"><strong>1,048,576</strong></td>
          <td style="text-align: left">4 MB</td>
          <td style="text-align: left">L2 Boundary</td>
          <td style="text-align: left">58.97 ns</td>
          <td style="text-align: left">38.84 ns</td>
          <td style="text-align: left"><strong>27.69 ns</strong></td>
          <td style="text-align: left"><strong>2.1x faster</strong></td>
      </tr>
      <tr>
          <td style="text-align: left"><strong>8,388,608</strong></td>
          <td style="text-align: left">32 MB</td>
          <td style="text-align: left">DRAM (Main Memory)</td>
          <td style="text-align: left">240.60 ns</td>
          <td style="text-align: left">158.91 ns</td>
          <td style="text-align: left"><strong>84.27 ns</strong></td>
          <td style="text-align: left"><strong>2.9x faster</strong></td>
      </tr>
  </tbody>
</table>
<hr>
<h2 id="why-eytzinger-dominates-at-32-mb">Why Eytzinger Dominates at 32 MB</h2>
<p>Look at the 32 MB result:</p>
<ul>
<li><code>std::lower_bound</code>: <strong>240.60 ns</strong></li>
<li>Eytzinger: <strong>84.27 ns (nearly 3x faster)</strong></li>
</ul>
<p>When an array is 32 MB, standard binary search incurs cold DRAM cache misses right from the beginning because the root and first few subdivisions are megabytes apart.</p>
<p>With Eytzinger, the first 6 to 8 levels of the search are served directly out of L1 and L2 cache. The search only has to hit DRAM for the very last leaf accesses. Combined with zero branch mispredictions, latency drops from 240 ns down to 84 ns.</p>
<hr>
<h2 id="what-about-software-prefetching">What About Software Prefetching?</h2>
<p>In academic literature (e.g. Paul-Virak Khuong and Pat Morin&rsquo;s 2017 paper <em>&ldquo;Array Layouts for Comparison-Based Searching&rdquo;</em>), they recommend adding software prefetch instructions to Eytzinger search:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-cpp" data-lang="cpp"><span class="line"><span class="ln">1</span><span class="cl"><span class="n">__builtin_prefetch</span><span class="p">(</span><span class="o">&amp;</span><span class="n">arr</span><span class="p">[</span><span class="n">k</span> <span class="o">*</span> <span class="mi">16</span><span class="p">]);</span> <span class="c1">// Prefetch 4 levels ahead
</span></span></span></code></pre></div><p>I tested this on the M2:</p>





<pre tabindex="0"><code>At 65,536 elements:
Without prefetch:  14.57 ns
With prefetch:     67.62 ns  (4.6x SLOWER!)</code></pre><p>Why did software prefetching make it worse?</p>
<p>Apple Silicon&rsquo;s Avalanche cores have an aggressive hardware prefetcher. When you inject explicit <code>PRFM</code> (prefetch) instructions into a tight 1-cycle loop, you clog the instruction decode ports and instruction queue. For in-memory data structures on Apple Silicon, <strong>letting the hardware prefetcher do its job beats manual software prefetching</strong>.</p>
<hr>
<h2 id="summary-of-takeaways">Summary of Takeaways</h2>
<ol>
<li><strong><code>std::lower_bound</code> is branch-bound</strong>: Random binary searches on sorted arrays trigger ~50% branch mispredictions, stalling wide out-of-order CPUs.</li>
<li><strong><code>CSEL</code> is your friend</strong>: On ARM64, branchless conditional arithmetic executes in 1 cycle without pipeline flushes.</li>
<li><strong>Data layout is an optimization</strong>: Sorted order is human-friendly; Eytzinger breadth-first layout is cache-friendly.</li>
<li><strong>Hardware boundaries dictate wins</strong>:
<ul>
<li>For arrays fitting in L1 cache (N ≤ 1,024), branchless sorted search is fastest (7.1 ns).</li>
<li>For arrays exceeding L2 cache (N ≥ 1,000,000), Eytzinger layout wins by almost 3x because the tree root and top layers never leave CPU cache.</li>
</ul>
</li>
</ol>
<h2 id="reproducing-the-benchmark">Reproducing the Benchmark</h2>
<p>The full C++ benchmark source code and runner script are available directly on this site:</p>
<ul>
<li><strong>Benchmark Source</strong>: <a href="/scripts/binary_search_benchmark.cpp"><code>/scripts/binary_search_benchmark.cpp</code></a></li>
<li><strong>Runner Script</strong>: <a href="/scripts/run_binary_search_benchmark.sh"><code>/scripts/run_binary_search_benchmark.sh</code></a></li>
</ul>
<p>You can reproduce the exact numbers on your Mac with:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="ln">1</span><span class="cl">curl -sO https://mohit-k-s.github.io/scripts/binary_search_benchmark.cpp
</span></span><span class="line"><span class="ln">2</span><span class="cl">clang++ -O3 -std<span class="o">=</span>c++20 -mcpu<span class="o">=</span>apple-m2 binary_search_benchmark.cpp -o bs_bench
</span></span><span class="line"><span class="ln">3</span><span class="cl">./bs_bench</span></span></code></pre></div>]]></content:encoded></item><item><title>How Clang Knows You Forgot &lt;math.h&gt;</title><link>https://mohit-k-s.github.io/blog/2026/how-clang-knows/</link><pubDate>Thu, 15 Oct 2026 00:00:00 +0000</pubDate><guid>https://mohit-k-s.github.io/blog/2026/how-clang-knows/</guid><description>&lt;h5 id="its-been-a-while"&gt;It&amp;rsquo;s been a while.&lt;/h5&gt;
&lt;p&gt;Recently I started learning C again and i was writing a small C program to calculate normal distribution intervals and intentionally omitted &lt;code&gt;&amp;lt;math.h&amp;gt;&lt;/code&gt; just to see what would happen:&lt;/p&gt;





&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-c" data-lang="c"&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;1&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;#include&lt;/span&gt; &lt;span class="cpf"&gt;&amp;lt;stdio.h&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;2&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;3&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;4&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="nf"&gt;printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;between -1.96 and 1.96 is: %g&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;erf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;1.96&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nf"&gt;sqrt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;2.&lt;/span&gt;&lt;span class="p"&gt;)));&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;5&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;6&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;I expected a generic &lt;em&gt;&amp;ldquo;unknown function&amp;rdquo;&lt;/em&gt; or link error. Instead, Clang hit me with this:&lt;/p&gt;





&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;1&lt;/span&gt;&lt;span class="cl"&gt;hello.c:6:36: error: call to undeclared library function &amp;#39;erf&amp;#39; with type &amp;#39;double (double)&amp;#39;; ISO C99 and later do not support implicit function declarations [-Wimplicit-function-declaration]
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;2&lt;/span&gt;&lt;span class="cl"&gt;hello.c:6:36: note: include the header &amp;lt;math.h&amp;gt; or explicitly provide a declaration for &amp;#39;erf&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;3&lt;/span&gt;&lt;span class="cl"&gt;hello.c:6:45: error: call to undeclared library function &amp;#39;sqrt&amp;#39; with type &amp;#39;double (double)&amp;#39;; ISO C99 and later do not support implicit function declarations [-Wimplicit-function-declaration]
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;4&lt;/span&gt;&lt;span class="cl"&gt;hello.c:6:45: note: include the header &amp;lt;math.h&amp;gt; or explicitly provide a declaration for &amp;#39;sqrt&amp;#39;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Wait.&lt;/p&gt;</description><content:encoded><![CDATA[<h5 id="its-been-a-while">It&rsquo;s been a while.</h5>
<p>Recently I started learning C again and i was writing a small C program to calculate normal distribution intervals and intentionally omitted <code>&lt;math.h&gt;</code> just to see what would happen:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="ln">1</span><span class="cl"><span class="cp">#include</span> <span class="cpf">&lt;stdio.h&gt;</span><span class="cp">
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="cp"></span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="kt">int</span> <span class="nf">main</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">    <span class="nf">printf</span><span class="p">(</span><span class="s">&#34;between -1.96 and 1.96 is: %g</span><span class="se">\n</span><span class="s">&#34;</span><span class="p">,</span> <span class="nf">erf</span><span class="p">(</span><span class="mf">1.96</span> <span class="o">*</span> <span class="nf">sqrt</span><span class="p">(</span><span class="mi">1</span><span class="o">/</span><span class="mf">2.</span><span class="p">)));</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">    <span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="p">}</span></span></span></code></pre></div><p>I expected a generic <em>&ldquo;unknown function&rdquo;</em> or link error. Instead, Clang hit me with this:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">hello.c:6:36: error: call to undeclared library function &#39;erf&#39; with type &#39;double (double)&#39;; ISO C99 and later do not support implicit function declarations [-Wimplicit-function-declaration]
</span></span><span class="line"><span class="ln">2</span><span class="cl">hello.c:6:36: note: include the header &lt;math.h&gt; or explicitly provide a declaration for &#39;erf&#39;
</span></span><span class="line"><span class="ln">3</span><span class="cl">hello.c:6:45: error: call to undeclared library function &#39;sqrt&#39; with type &#39;double (double)&#39;; ISO C99 and later do not support implicit function declarations [-Wimplicit-function-declaration]
</span></span><span class="line"><span class="ln">4</span><span class="cl">hello.c:6:45: note: include the header &lt;math.h&gt; or explicitly provide a declaration for &#39;sqrt&#39;</span></span></code></pre></div><p>Wait.</p>
<p>How does the compiler know <code>erf</code> takes a <code>double</code> and returns a <code>double</code>? How does it know <code>sqrt</code> belongs to <code>&lt;math.h&gt;</code> when <code>&lt;math.h&gt;</code> was never mentioned anywhere in the file?</p>
<p>A normal person would just add <code>#include &lt;math.h&gt;</code> and move on with their life.</p>
<p>I am not that person. Everything becomes a rabbit hole for me. I wanted to understand the exact mechanics of how the compiler knows this without the header, so I asked Gemini to dig around the Clang/LLVM source code and find where this is actually hardcoded.</p>
<h2 id="1-the-compilers-secret-table">1. The Compiler&rsquo;s Secret Table</h2>
<p>It turns out modern compilers don&rsquo;t just parse C syntax—they have an internal database of the C standard library baked right into the compiler binary.</p>
<p>In Clang, this database historically lives in <a href="https://github.com/llvm/llvm-project/blob/llvmorg-18.1.8/clang/include/clang/Basic/Builtins.def#L238"><code>Builtins.def</code></a>:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="ln">1</span><span class="cl"><span class="nf">BUILTIN</span><span class="p">(</span><span class="n">__builtin_erf</span> <span class="p">,</span> <span class="s">&#34;dd&#34;</span><span class="p">,</span> <span class="s">&#34;Fne&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="nf">BUILTIN</span><span class="p">(</span><span class="n">__builtin_sqrt</span><span class="p">,</span> <span class="s">&#34;dd&#34;</span><span class="p">,</span> <span class="s">&#34;Fne&#34;</span><span class="p">)</span></span></span></code></pre></div><p>At first glance, <code>&quot;dd&quot;</code> looks like gibberish, but it&rsquo;s Clang&rsquo;s internal type encoding shorthand:</p>
<ul>
<li>First letter = return type (<code>d</code> = <code>double</code>)</li>
<li>Subsequent letters = argument types (<code>d</code> = <code>double</code>)</li>
<li>Attribute <code>&quot;F&quot;</code> = this is a standard library function that also exists <em>without</em> the <code>__builtin_</code> prefix</li>
<li>Attribute <code>&quot;n&quot;</code> = nothrow (won&rsquo;t throw C++ exceptions)</li>
<li>Attribute <code>&quot;e&quot;</code> = pure/const (no side-effects when floating-point exceptions are ignored)</li>
</ul>
<p>And the required header name (<code>math.h</code>) is registered alongside it in Clang&rsquo;s header descriptors.</p>
<h2 id="2-why-is-this-an-error-and-not-just-a-warning">2. Why is this an Error and Not Just a Warning?</h2>
<p>Back in C89, if you called a function without declaring it, the compiler shrugged and assumed it returned an <code>int</code>:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="ln">1</span><span class="cl"><span class="kt">int</span> <span class="nf">erf</span><span class="p">();</span> <span class="c1">// C89 implicit declaration assumption
</span></span></span></code></pre></div><p>On modern 64-bit architectures, this is a recipe for silent, catastrophic bugs:</p>
<ul>
<li>An <code>int</code> return value is passed in a general-purpose integer register (<code>%rax</code> on x86-64, <code>x0</code> on ARM64).</li>
<li>A <code>double</code> return value is passed in a floating-point/vector register (<code>%xmm0</code> on x86-64, <code>d0</code> on ARM64).</li>
</ul>
<p>If the caller expects the result in an integer register while <code>libm</code> placed it in a floating-point register, you end up reading garbage memory.</p>
<p>Starting with ISO C99, implicit function declarations were deprecated, and modern Clang treats <code>-Wimplicit-function-declaration</code> as a <strong>hard error</strong> by default.</p>
<h2 id="3-the-diagnostic-engine">3. The Diagnostic Engine</h2>
<p>When Clang parses an identifier like <code>erf(...)</code> and can&rsquo;t find its declaration in scope, it doesn&rsquo;t immediately crash or emit a generic error. It queries its built-in catalog:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-cpp" data-lang="cpp"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1">// Inside clang/lib/Sema/SemaDecl.cpp
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="c1"></span><span class="k">if</span> <span class="p">(</span><span class="n">Context</span><span class="p">.</span><span class="n">BuiltinInfo</span><span class="p">.</span><span class="n">isLibFunction</span><span class="p">(</span><span class="n">BuiltinID</span><span class="p">))</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">    <span class="k">const</span> <span class="kt">char</span> <span class="o">*</span><span class="n">Header</span> <span class="o">=</span> <span class="n">Context</span><span class="p">.</span><span class="n">BuiltinInfo</span><span class="p">.</span><span class="n">getHeaderName</span><span class="p">(</span><span class="n">BuiltinID</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">    <span class="n">Diag</span><span class="p">(</span><span class="n">Loc</span><span class="p">,</span> <span class="n">diag</span><span class="o">::</span><span class="n">ext_implicit_lib_function_decl_c99</span><span class="p">)</span> <span class="o">&lt;&lt;</span> <span class="p">...;</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="n">Header</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl">        <span class="n">Diag</span><span class="p">(</span><span class="n">Loc</span><span class="p">,</span> <span class="n">diag</span><span class="o">::</span><span class="n">note_include_header_or_declare</span><span class="p">)</span> <span class="o">&lt;&lt;</span> <span class="n">Header</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="p">}</span></span></span></code></pre></div><p>The error message and note you see are defined in <code>DiagnosticSemaKinds.td</code>:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-tablegen" data-lang="tablegen"><span class="line"><span class="ln">1</span><span class="cl"><span class="k">def</span> <span class="nv">ext_implicit_lib_function_decl_c99</span> <span class="p">:</span> <span class="nv">ExtWarn</span><span class="p">&lt;</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">  <span class="s">&#34;call to undeclared library function &#39;%0&#39; with type %1; ISO C99 and later &#34;</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">  <span class="s">&#34;do not support implicit function declarations&#34;</span><span class="p">&gt;,</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">  <span class="nv">InGroup</span><span class="p">&lt;</span><span class="nv">ImplicitFunctionDeclare</span><span class="p">&gt;,</span> <span class="nv">DefaultError</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">
</span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="k">def</span> <span class="nv">note_include_header_or_declare</span> <span class="p">:</span> <span class="nv">Note</span><span class="p">&lt;</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl">  <span class="s">&#34;include the header &lt;%0&gt; or explicitly provide a declaration for &#39;%1&#39;&#34;</span><span class="p">&gt;;</span></span></span></code></pre></div><h2 id="4-how-its-stored-in-memory-array-or-map">4. How It&rsquo;s Stored in Memory: Array or Map?</h2>
<p>Once you realize the compiler has this catalog, the next natural question is: <em>how is this actually loaded in memory? Does Clang parse a table from disk on startup, or search a giant hash map every time you type a word?</em></p>
<p>It turns out to be an elegant combination of both:</p>
<h3 id="the-database-a-static-array-in-rodata">The Database: A Static Array in <code>.rodata</code></h3>
<p>The metadata is <strong>not</strong> parsed or loaded dynamically. It is defined as a compile-time <code>constexpr static</code> array (<code>BuiltinInfos[]</code>):</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-cpp" data-lang="cpp"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1">// clang/lib/Basic/Builtins.cpp
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="c1"></span><span class="k">static</span> <span class="k">constexpr</span> <span class="n">Builtin</span><span class="o">::</span><span class="n">Info</span> <span class="n">BuiltinInfos</span><span class="p">[]</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">    <span class="c1">// ...
</span></span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="c1"></span>    <span class="c1">// [238] = { NameOffset, TypeOffset, AttributesOffset, HeaderDesc::MATH_H } // erf
</span></span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="c1"></span><span class="p">};</span></span></span></code></pre></div><p>Because it is compile-time constant data, it gets baked directly into the <strong><code>.rodata</code> (read-only data)</strong> segment of the <code>clang</code> binary on disk.</p>
<p>When you run <code>clang hello.c</code>, the OS kernel memory-maps the binary into RAM. Zero parsing, zero disk reads, zero heap allocations. It is already in memory before your code is even touched. Accessing metadata by ID is an instant $O(1)$ array index.</p>
<h3 id="the-lookup-the-identifiertable-tag">The Lookup: The <code>IdentifierTable</code> Tag</h3>
<p>An array is great if you already have the integer ID. But when the compiler lexes your code, it starts with the string <code>&quot;erf&quot;</code>. Doing a linear scan over thousands of builtins for every single word would make compilation crawl.</p>
<p>Instead, Clang uses its central <strong><code>IdentifierTable</code></strong> (a string-interning hash table). When Clang initializes, it loops through the builtins once and tags each name inside the hash table with its integer ID:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-cpp" data-lang="cpp"><span class="line"><span class="ln">1</span><span class="cl"><span class="kt">void</span> <span class="n">Builtin</span><span class="o">::</span><span class="n">Context</span><span class="o">::</span><span class="n">initializeBuiltins</span><span class="p">(</span><span class="n">IdentifierTable</span> <span class="o">&amp;</span><span class="n">Table</span><span class="p">,</span> <span class="k">const</span> <span class="n">LangOptions</span> <span class="o">&amp;</span><span class="n">LangOpts</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">    <span class="k">for</span> <span class="p">(</span><span class="kt">unsigned</span> <span class="n">id</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span> <span class="n">id</span> <span class="o">&lt;</span> <span class="n">NumBuiltins</span><span class="p">;</span> <span class="o">++</span><span class="n">id</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">        <span class="n">IdentifierInfo</span> <span class="o">&amp;</span><span class="n">II</span> <span class="o">=</span> <span class="n">Table</span><span class="p">.</span><span class="n">get</span><span class="p">(</span><span class="n">getName</span><span class="p">(</span><span class="n">id</span><span class="p">));</span> 
</span></span><span class="line"><span class="ln">4</span><span class="cl">        <span class="n">II</span><span class="p">.</span><span class="n">setBuiltinID</span><span class="p">(</span><span class="n">id</span><span class="p">);</span> <span class="c1">// Stamps the integer ID onto the hash table entry
</span></span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="c1"></span>    <span class="p">}</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="p">}</span></span></span></code></pre></div><p>Inside the <code>IdentifierInfo</code> struct for <code>&quot;erf&quot;</code>, there is a tiny integer field: <code>unsigned ObjCOrBuiltinID</code>.</p>
<h3 id="the-lifecycle">The Lifecycle</h3>
<p>When your file compiles, the whole chain runs in fractions of a microsecond:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln"> 1</span><span class="cl">Source Code: erf(...)
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">     │
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">     ▼
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">1. Lexer hashes token &#34;erf&#34; into IdentifierTable
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">     │  (O(1) string lookup)
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">     ▼
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">2. IdentifierInfo already has integer tag set:
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">     │  II-&gt;getBuiltinID() -&gt; returns 238
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">     ▼
</span></span><span class="line"><span class="ln">10</span><span class="cl">3. Sema detects no user prototype, checks ID != 0
</span></span><span class="line"><span class="ln">11</span><span class="cl">     │  (O(1) integer check)
</span></span><span class="line"><span class="ln">12</span><span class="cl">     ▼
</span></span><span class="line"><span class="ln">13</span><span class="cl">4. Direct array index:
</span></span><span class="line"><span class="ln">14</span><span class="cl">     │  BuiltinInfos[238] -&gt; signature &#34;dd&#34;, header &lt;math.h&gt;
</span></span><span class="line"><span class="ln">15</span><span class="cl">     ▼
</span></span><span class="line"><span class="ln">16</span><span class="cl">5. Diagnostic emitted!</span></span></code></pre></div><p>By separating the <strong>data</strong> (a flat, immutable array in <code>.rodata</code>) from the <strong>string index</strong> (a cached integer tag in the lexer&rsquo;s hash table), both steps are pure $O(1)$ memory reads.</p>
<h2 id="5-even-the-rabbit-hole-had-a-rabbit-hole">5. Even the Rabbit Hole Had a Rabbit Hole</h2>
<p>When Gemini pulled up the links to check the definitions, even the rabbit hole had its own rabbit hole: the links on LLVM&rsquo;s <code>main</code> branch seemed empty. Recent commits had stripped almost everything out of <code>Builtins.def</code>.</p>
<p>It turns out LLVM is in the middle of migrating all built-in definitions from old C-preprocessor macros to LLVM&rsquo;s TableGen format (<a href="https://github.com/llvm/llvm-project/pull/68324">PR #68324</a>). Now they live in declarative <code>.td</code> files:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-tablegen" data-lang="tablegen"><span class="line"><span class="ln">1</span><span class="cl"><span class="k">def</span> <span class="nv">ErfF128</span> <span class="p">:</span> <span class="nv">Builtin</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">  <span class="k">let</span> <span class="nv">Spellings</span> <span class="p">=</span> <span class="p">[</span><span class="s">&#34;__builtin_erff128&#34;</span><span class="p">];</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">  <span class="k">let</span> <span class="nv">Attributes</span> <span class="p">=</span> <span class="p">[</span><span class="nv">FunctionWithBuiltinPrefix</span><span class="p">,</span> <span class="nv">NoThrow</span><span class="p">,</span> <span class="nv">ConstIgnoringErrnoAndExceptions</span><span class="p">];</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">  <span class="k">let</span> <span class="nv">Prototype</span> <span class="p">=</span> <span class="s">&#34;__float128(__float128)&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="p">}</span></span></span></code></pre></div><h2 id="the-rabbit-hole-tax">The &ldquo;Rabbit Hole&rdquo; Tax</h2>
<p>Sometimes I feel like I learn things way too slowly because I can&rsquo;t resist pulling on every loose thread. What started as a 2-minute C exercise turned into a whole session with an AI digging through LLVM compiler source code, CPU calling conventions, and TableGen refactors.</p>
<p>It&rsquo;s easy to feel frustrated by that kind of detour. But the more I do this, the more I realize: <strong>I enjoy this more than just moving on</strong></p>
<p><mark> Follow the rabbit hole once in a while. </mark></p>
]]></content:encoded></item><item><title>Putting My ❤️ Rate in My Terminal Prompt</title><link>https://mohit-k-s.github.io/blog/2026/heart-rate-terminal-prompt/</link><pubDate>Sun, 28 Jun 2026 00:00:00 +0000</pubDate><guid>https://mohit-k-s.github.io/blog/2026/heart-rate-terminal-prompt/</guid><description>&lt;p&gt;My terminal prompt now starts like this:&lt;/p&gt;
&lt;img src="../images/hrterm.png" alt="Terminal prompt showing heart rate" width="700"/&gt;
&lt;p&gt;That little heart is my latest Apple Health heart rate reading.&lt;/p&gt;
&lt;p&gt;This is not a product. It is not a polished app. It is a tiny local bridge between my iPhone and my shell prompt, held together by an iPhone Shortcut, a small Python HTTP server, and a JSON file.&lt;/p&gt;
&lt;h2 id="flow"&gt;Flow&lt;/h2&gt;
&lt;p&gt;The whole thing is intentionally boring:&lt;/p&gt;





&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;1&lt;/span&gt;&lt;span class="cl"&gt;iPhone Shortcut
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;2&lt;/span&gt;&lt;span class="cl"&gt; ↓ POST JSON
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;3&lt;/span&gt;&lt;span class="cl"&gt;local Python server
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;4&lt;/span&gt;&lt;span class="cl"&gt; ↓ writes latest-health.json
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;5&lt;/span&gt;&lt;span class="cl"&gt;prompt helper
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;6&lt;/span&gt;&lt;span class="cl"&gt; ↓ reads JSON
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;7&lt;/span&gt;&lt;span class="cl"&gt;zsh prompt&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The iPhone reads the latest heart rate sample from Apple Health and posts it to a server running on my laptop. The server stores only the latest reading. The shell prompt does not call the server at all. It just reads the local cache file and prints a compact prompt segment.&lt;/p&gt;</description><content:encoded><![CDATA[<p>My terminal prompt now starts like this:</p>
<img src="../images/hrterm.png" alt="Terminal prompt showing heart rate" width="700"/>
<p>That little heart is my latest Apple Health heart rate reading.</p>
<p>This is not a product. It is not a polished app. It is a tiny local bridge between my iPhone and my shell prompt, held together by an iPhone Shortcut, a small Python HTTP server, and a JSON file.</p>
<h2 id="flow">Flow</h2>
<p>The whole thing is intentionally boring:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">iPhone Shortcut
</span></span><span class="line"><span class="ln">2</span><span class="cl">    ↓ POST JSON
</span></span><span class="line"><span class="ln">3</span><span class="cl">local Python server
</span></span><span class="line"><span class="ln">4</span><span class="cl">    ↓ writes latest-health.json
</span></span><span class="line"><span class="ln">5</span><span class="cl">prompt helper
</span></span><span class="line"><span class="ln">6</span><span class="cl">    ↓ reads JSON
</span></span><span class="line"><span class="ln">7</span><span class="cl">zsh prompt</span></span></code></pre></div><p>The iPhone reads the latest heart rate sample from Apple Health and posts it to a server running on my laptop. The server stores only the latest reading. The shell prompt does not call the server at all. It just reads the local cache file and prints a compact prompt segment.</p>
<p>THe phone POSTs below payload to my server</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="ln">1</span><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">  <span class="nt">&#34;heart_rate&#34;</span><span class="p">:</span> <span class="mi">82</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">  <span class="nt">&#34;unit&#34;</span><span class="p">:</span> <span class="s2">&#34;bpm&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">  <span class="nt">&#34;sent_at&#34;</span><span class="p">:</span> <span class="s2">&#34;2026-06-28T14:30:00+05:30&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">  <span class="nt">&#34;device&#34;</span><span class="p">:</span> <span class="s2">&#34;iphone&#34;</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="p">}</span></span></span></code></pre></div><h2 id="why-this-was-worth-building">Why This Was Worth Building</h2>
<p>I had <em>fun</em> making it , Not everything needs to be a product. Sometimes you can just piece up small things to make something useful for yourself. Build for yourself first !</p>
]]></content:encoded></item><item><title>Unknown Place</title><link>https://mohit-k-s.github.io/blog/2026/unknown_place/</link><pubDate>Sun, 24 May 2026 00:00:00 +0000</pubDate><guid>https://mohit-k-s.github.io/blog/2026/unknown_place/</guid><description>&lt;p&gt;What is this place? (says everyone). Humans have always been searching for meaning and will possibly not stop searching for their own version until the population gets wiped out by its own creations and doings, or by something external. I write this to reflect on my own struggle with this feeling and how it is sometimes the only thing that I think of.&lt;/p&gt;
&lt;p&gt;You see, all literature has been created by humans. Yet no one has actually arrived at why humans actually exist. This has to be true because the entity itself cannot ever determine why it exists in a primitive way.&lt;/p&gt;</description><content:encoded><![CDATA[<p>What is this place? (says everyone). Humans have always been searching for meaning and will possibly not stop searching for their own version until the population gets wiped out by its own creations and doings, or by something external. I write this to reflect on my own struggle with this feeling and how it is sometimes the only thing that I think of.</p>
<p>You see, all literature has been created by humans. Yet no one has actually arrived at why humans actually exist. This has to be true because the entity itself cannot ever determine why it exists in a primitive way.</p>
<p>Let&rsquo;s do a thought experiment where we are training some subject on what it is supposed to do, how it is supposed to do it, and why that is important. The subject will learn the thing; however, in the back of his mind, there will always be a question as to “why?” Despite explicitly being told why, the subject must want to ascertain only the answer to the question “why” by himself. Just run this experiment, and you will always arrive at the same conclusion: out of all the questions, the answer to “why” is more incomplete.</p>
<p>And now get this: humans were never told any of the answers to any of the questions. They have to figure out everything by themselves. In the current running experiment, humans are somewhat aware of where they should be, what they should be doing, and how they should be doing it. But ask them why they should be doing it, and they will not actually have an answer. They might spew out things like this is important for mankind and that their work is “important.” But if you see closely, those are just placeholders. The question of “why” is so deep that it requires more than the common man enquires into.</p>
<p>Questions like “how?” can be discovered by the entity by running experiments and eventually arriving at the outcome it wants and the method.
Questions like “where?” are obviously referring to the physical or mental location of the entity’s topic of interest.
Questions like “when?” are matters of time.
Questions like “what?” refer to the description of the topic of interest in the entity’s view of its life.</p>
<p>But questions like “why?” are difficult to answer and can always be traced back to another unsolved “why,” like in the argument below:</p>
<blockquote>
<p>Why do you breathe?<br>
→ Because the entity wants to live.<br>
→ Why does the entity want to live?<br>
→ Because it thinks that living is good.<br>
→ Why is living good?<br>
→ Perhaps because of someone that they want to live life with.<br>
→ Why do they think that living with them is worth it?<br>
→ Because they feel alive.<br>
→ …keep on questioning, and still no substantial answer arrives.</p></blockquote>
<p>There exists infinite amount of literature on this piece, however it was actually fun to arrive at this on my own this sunday night.</p>
<p>We are in an unknown place, trying to answer the whys one by one, yet perhaps never truly arriving at a primitive why. There are attempts to answer the why. Religion is one such mechanism, but that too is only an attempt.</p>
<h6 id="keep-reading--keep-asking-why">Keep reading , Keep asking &ldquo;why?&rdquo;</h6>
]]></content:encoded></item><item><title>Friday 6PM</title><link>https://mohit-k-s.github.io/blog/2026/friday_6pm/</link><pubDate>Fri, 15 May 2026 00:00:00 +0000</pubDate><guid>https://mohit-k-s.github.io/blog/2026/friday_6pm/</guid><description>&lt;p&gt;The last week again was like any other week I have been through. I don’t know how many kilometers I scrolled across my phone’s screen.
Documentaries, podcasts on doomerism, AI, and whatever else the algorithm decided I should think about.&lt;/p&gt;
&lt;p&gt;I watched all that but when I retrospect, I really didn&amp;rsquo;t learn anything. What is it that a human wants, a purpose? Something to dedicate their lives to? To love someone? What is it? I keep asking questions to myself and no answer comes because there is not a single original thought that I can possibly have with all this shit in my brain.&lt;/p&gt;</description><content:encoded><![CDATA[<p>The last week again was like any other week I have been through. I don’t know how many kilometers I scrolled across my phone’s screen.
Documentaries, podcasts on doomerism, AI, and whatever else the algorithm decided I should think about.</p>
<p>I watched all that but when I retrospect, I really didn&rsquo;t learn anything. What is it that a human wants, a purpose? Something to dedicate their lives to? To love someone? What is it? I keep asking questions to myself and no answer comes because there is not a single original thought that I can possibly have with all this shit in my brain.</p>
<p>Here is how I got addicted to consuming.</p>
<p>It all started when I was maybe in 11th. It was Netflix at first. To me, who didn&rsquo;t know anything, everything on it was interesting. I would go on and on to watch that series, that movie, then repeat for days. I was maybe 17. Didn&rsquo;t know about anything else, social media still far away. Times were actually good. It was probably in those days I watched One Punch Man, peak action! (to date)</p>
<p>While I kept watching, my grades kept slipping. I would now watch everything that would come on television. I would come from school and start watching and go upstairs at 6-7 PM. Of course I didn&rsquo;t get a good enough college. (which ig now is somewhat better?)</p>
<p>I met a friend who introduced me to mainstream anime. Netflix had anime too but I really didn&rsquo;t know the word anime till then. I was pulled in again, not by that friend but myself. Wow, more content. Let me watch more and more!</p>
<p>Fast forward 8 years.</p>
<p>What I am now is probably the worst version of me, or wait, that is yet to come.</p>
<p>Days pass. Months go away. Years sift by. I have no realisation. One can ask but why am I BS&rsquo;ing here about all this. Your attention span probably is smaller than me, you just don&rsquo;t know it yet. I know no one reads this blog so I take it up on myself to document what I go through. People call me, I look like a stoner while I am not. People ask me, what happened to that project you were working on?</p>
<p>What happened to you?</p>
<p>What can truly happen to one&rsquo;s mind if all he does is just consume, consume and consume? Heck I can even write now with my eyes closed, that is some achievement&hellip; lol. Moving on.</p>
<p>Although I try to observe other people more, today I tried to observe myself because I was alone here.</p>
<p>Day starts.</p>
<p>Phone somehow is already in my hand. I am not awake yet&hellip; looking through Teams. Did something happen? All good?</p>
<p>Ok then let&rsquo;s try to sleep some more. Nope&hellip; hands automatically scrolling on Instagram. Everyone still sound asleep in the deserted neighbourhood.</p>
<p>I now wake up. Hands on keyboard, trying to see if there is some issue, replying to yesterday&rsquo;s messages. I work on my feature a bit. I deploy it. YouTube opens. Some random video starts buffering and soon begins playing. I see to it. What is it about? I don&rsquo;t actually remember what I watched this morning.</p>
<p>I eat something out of the fridge, drink some water. Immediately I start watching every video on my homefeed and refreshing it after every 2.</p>
<p>Oh this? Oh that? OH what?<br>
Ok, I can see why they will do that.<br>
Oh look at her, damn.<br>
Damn these guys are having fun, good for them.</p>
<p>I have a friend I don&rsquo;t talk to. I had friends that are not near me anymore. Thoughts come and then it starts playing. There&rsquo;s an ongoing meme about how Nishant Kumar (Bihar&rsquo;s current Health minister) is maybe not so healthy himself and is probably a ganja addict. The song Roll Paper, it starts playing in my mind. I keep watching every second reel on him.</p>
<p>Then I somehow end up on X, to see <a href="https://x.com/Resorcinolworks/status/2055188724020113703?s=20">this post</a>. If they decide to delete it, here is the post:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln"> 1</span><span class="cl">For all the tech bros and sisters, what will you even do with all that -
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">&gt;hollow money
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">&gt;make my parents proud hoax
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">&gt;moving away of “freedom”
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">if your young years of life doesn’t look like this?
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">get off the foggish ego, show that trust in dm, money will come and go.
</span></span><span class="line"><span class="ln">10</span><span class="cl">
</span></span><span class="line"><span class="ln">11</span><span class="cl">But not the innocent love you are secretly running from.</span></span></code></pre></div><p>I get why they will try to remind us that there is actually more to life. It sounds easy right? I mean try to live however you want, ig that was in movies as well. But for me I don&rsquo;t know why it is so difficult. Maybe I need belt treatment.</p>
<p>I take this essay to examine what is it that I want that I cannot have?</p>
<p>Well I don&rsquo;t have a high paying job.</p>
<p>Just yesterday I somehow heard some guy&rsquo;s conversation with ChatGPT in which the guy asked, &ldquo;You are not human, you can&rsquo;t even have a girlfriend.&rdquo; To that the LLM agent replied&hellip; in that aspect we both are same ig!</p>
<p>(While in the real one the agent replied, yes, you also don&rsquo;t have it.)</p>
<p>I heard it somewhat different. To me, that colleague&rsquo;s laugh and this conversation that they were having did something. I heard it differently.</p>
<p>Ahh shit, this is turning out to be long&hellip; Well I have all the time and it is Friday 6PM, I can write on and on.</p>
<p>What is it that it means to be young? Who are adults?</p>
<p>While the internet has painted a picture in my mind that teenagers are the ones who are always partying, roaming here and there with friends and, to blame TV shows probably, doing some hanky panky because that is normal.</p>
<p>And that adults are the ones who go to work, go to gym, focus on career, probably dating and maybe planning to marry. Happy overall. This is one side, the other side is that these guys are depressed souls who want to <i>exit this world asap</i>.</p>
<p>I am 28 and I don&rsquo;t feel I am any of that. Yes I am maybe slightly more depressed since maybe last 4 years, and yes, I have these thoughts. While I have obviously been on the second side, I was never on the first one. To me days are without joy, there are no highlights.</p>
<p>Then there are 30 yo, 40 yo, so on. They all have their own take. I will probably have some version of mine.</p>
<p>I watch so much stuff about every random thing but yet I don&rsquo;t think I know anything. I don&rsquo;t feel I am even what I get paid to do. Who am I? What do I want?</p>
<p>It is so easy to blame the tools and existing times that I keep running in these loops of trying to do better. Enough!</p>
<p>Ahh did you think I will finally be better? No, I will continue to be my worst.</p>
<p>It&rsquo;s so confusing right? Well none of it makes sense, but yet we live, yet we fear to die.</p>
<h5 id="well--maybe-i-will-get-better-at-writing-too">Well .. maybe i will get better at writing too.</h5>
]]></content:encoded></item><item><title>Hype</title><link>https://mohit-k-s.github.io/blog/2026/hype/</link><pubDate>Fri, 08 May 2026 00:00:00 +0000</pubDate><guid>https://mohit-k-s.github.io/blog/2026/hype/</guid><description>&lt;p&gt;People have become addicted to hype. Not just in technology. In everything.&lt;/p&gt;
&lt;p&gt;Every new thing arrives with the same proclamation:
this changes everything.
And somehow, in order for the new thing to survive, the old thing must first be declared dead.&lt;/p&gt;
&lt;p&gt;Movies are dead.&lt;br&gt;
Books are dead.&lt;br&gt;
Programming is dead.&lt;br&gt;
Friendship is dead.&lt;br&gt;
Art is dead.&lt;/p&gt;
&lt;p&gt;The cycle repeats so often that people no longer pause to ask whether something actually disappeared, or whether we were simply taught to stop valuing it.&lt;/p&gt;</description><content:encoded><![CDATA[<p>People have become addicted to hype. Not just in technology. In everything.</p>
<p>Every new thing arrives with the same proclamation:
this changes everything.
And somehow, in order for the new thing to survive, the old thing must first be declared dead.</p>
<p>Movies are dead.<br>
Books are dead.<br>
Programming is dead.<br>
Friendship is dead.<br>
Art is dead.</p>
<p>The cycle repeats so often that people no longer pause to ask whether something actually disappeared, or whether we were simply taught to stop valuing it.</p>
<p>The word “hype” itself comes from hyperbole.
And exaggeration has quietly become the default language of modern life.</p>
<p>For a moment, think about social media.</p>
<p>Social media was once surprisingly simple.<br>
People uploaded blurry photos from cheap cameras.<br>
Friends commented on each other’s posts.<br>
Someone shared pictures from a birthday party.<br>
Someone organized a meetup.<br>
Someone staying far away felt a little closer.</p>
<p>That was enough.</p>
<p>Now every platform feels like an infinite marketplace of attention.
Everyone is performing.
Everyone is consuming.
Everyone is selling a lifestyle, an opinion, a productivity system, a body, a belief.</p>
<p>Instagram especially feels strange to me.</p>
<p>It began as a place to share moments.
Now it often feels like a showroom for wealth, status, outrage, and endless stimulation.
Sometimes I wonder what the original creators envisioned.
Was this always the destination?
Or did the machine simply grow beyond human intent?</p>
<p>Maybe this is inevitable when billions of people gather in one place.
Maybe scale changes the meaning of things.</p>
<p>What was once a small room full of friends eventually became a stadium full of strangers.</p>
<p>And perhaps that is what I miss most:
not older technology,
but smaller worlds.</p>
<p>One may ask, “Why not just stop using it?”
Try it.
You leave for a while, only to slowly find yourself returning again.</p>
<p>Maybe there is no solution.
Perhaps every generation eventually builds its own noise.
The only thing an individual can do is quietly choose what matters to them before the world chooses for them.</p>
]]></content:encoded></item><item><title>How fast can Postgres insert 10 million rows?</title><link>https://mohit-k-s.github.io/blog/2026/postgres_10m_inserts/</link><pubDate>Fri, 17 Apr 2026 00:00:00 +0000</pubDate><guid>https://mohit-k-s.github.io/blog/2026/postgres_10m_inserts/</guid><description>&lt;p&gt;I kept hearing that Postgres is slow for bulk inserts.
So I wanted to test it myself and see how fast I could get 10 million rows in.&lt;/p&gt;
&lt;h2 id="test-setup"&gt;Test setup&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Postgres 16 (Docker image: &lt;code&gt;postgres:16&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Data shape: &lt;code&gt;id BIGINT, payload TEXT&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Benchmark machine:
&lt;ul&gt;
&lt;li&gt;MacBook Pro (Mac14,7)&lt;/li&gt;
&lt;li&gt;Apple M2 (8 cores)&lt;/li&gt;
&lt;li&gt;8 GB RAM&lt;/li&gt;
&lt;li&gt;macOS 26.3.1&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Docker runtime during test:
&lt;ul&gt;
&lt;li&gt;Docker 29.1.2&lt;/li&gt;
&lt;li&gt;8 CPUs&lt;/li&gt;
&lt;li&gt;~4.1 GB memory available to Docker&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I measured wall-clock time and computed rows/sec.&lt;/p&gt;</description><content:encoded><![CDATA[<p>I kept hearing that Postgres is slow for bulk inserts.
So I wanted to test it myself and see how fast I could get 10 million rows in.</p>
<h2 id="test-setup">Test setup</h2>
<ul>
<li>Postgres 16 (Docker image: <code>postgres:16</code>)</li>
<li>Data shape: <code>id BIGINT, payload TEXT</code></li>
<li>Benchmark machine:
<ul>
<li>MacBook Pro (Mac14,7)</li>
<li>Apple M2 (8 cores)</li>
<li>8 GB RAM</li>
<li>macOS 26.3.1</li>
</ul>
</li>
<li>Docker runtime during test:
<ul>
<li>Docker 29.1.2</li>
<li>8 CPUs</li>
<li>~4.1 GB memory available to Docker</li>
</ul>
</li>
</ul>
<p>I measured wall-clock time and computed rows/sec.</p>
<h2 id="10-million-row-bulk-load-results">10 million row bulk load results</h2>
<p>For this benchmark, each run inserted <strong>10,000,000 rows</strong>.</p>
<p>I ran each strategy in a <strong>fresh Postgres container</strong>, randomized order every round, and repeated for <strong>5 rounds</strong> to reduce warm-cache and order bias.</p>
<h3 id="what-i-tested">What I tested</h3>
<ol>
<li>Logged table + <code>INSERT ... SELECT generate_series(...)</code></li>
<li>Logged table with PK + <code>INSERT ... SELECT</code></li>
<li>UNLOGGED table + <code>INSERT ... SELECT</code></li>
<li>UNLOGGED table + <code>COPY FROM PROGRAM</code></li>
</ol>
<h3 id="results-isolated-runs-n5">Results (isolated runs, n=5)</h3>
<table>
  <thead>
      <tr>
          <th>Strategy</th>
          <th style="text-align: right">Min (s)</th>
          <th style="text-align: right">Median (s)</th>
          <th style="text-align: right">P95 (s)</th>
          <th style="text-align: right">Max (s)</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>logged insert-select</td>
          <td style="text-align: right">12.505</td>
          <td style="text-align: right">13.720</td>
          <td style="text-align: right">17.205</td>
          <td style="text-align: right">17.205</td>
      </tr>
      <tr>
          <td>logged insert-select + primary key</td>
          <td style="text-align: right">20.979</td>
          <td style="text-align: right">23.919</td>
          <td style="text-align: right">31.859</td>
          <td style="text-align: right">31.859</td>
      </tr>
      <tr>
          <td>unlogged insert-select</td>
          <td style="text-align: right">4.753</td>
          <td style="text-align: right">6.347</td>
          <td style="text-align: right">10.510</td>
          <td style="text-align: right">10.510</td>
      </tr>
      <tr>
          <td>unlogged copy</td>
          <td style="text-align: right"><strong>4.103</strong></td>
          <td style="text-align: right"><strong>4.967</strong></td>
          <td style="text-align: right"><strong>6.305</strong></td>
          <td style="text-align: right"><strong>6.305</strong></td>
      </tr>
  </tbody>
</table>
<h2 id="multi-row-insert-benchmark">Multi-row INSERT benchmark</h2>
<p>Then I wanted to isolate one specific question: how much does batching help if you are using plain <code>INSERT ... VALUES</code> statements?</p>
<p>For this benchmark, each case inserted <strong>1,000,000 rows</strong> into a logged table with a primary key, all in one transaction.</p>
<p>I ran each strategy in a <strong>fresh Postgres container</strong>, randomized order every round, and repeated for <strong>5 rounds</strong>.</p>
<h3 id="what-i-tested-1">What I tested</h3>
<ol>
<li>Single-row inserts (<code>VALUES (..)</code> one row at a time)</li>
<li>Batched inserts with 100 rows/statement</li>
<li>Batched inserts with 1,000 rows/statement</li>
<li>Batched inserts with 5,000 rows/statement</li>
</ol>
<h3 id="results-isolated-runs-n5-1">Results (isolated runs, n=5)</h3>
<table>
  <thead>
      <tr>
          <th>Strategy</th>
          <th style="text-align: right">Min (s)</th>
          <th style="text-align: right">Median (s)</th>
          <th style="text-align: right">P95 (s)</th>
          <th style="text-align: right">Max (s)</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>single-row insert</td>
          <td style="text-align: right">86.297</td>
          <td style="text-align: right">90.726</td>
          <td style="text-align: right">97.220</td>
          <td style="text-align: right">97.220</td>
      </tr>
      <tr>
          <td>batch 100 rows</td>
          <td style="text-align: right">4.170</td>
          <td style="text-align: right">5.306</td>
          <td style="text-align: right">9.657</td>
          <td style="text-align: right">9.657</td>
      </tr>
      <tr>
          <td>batch 1,000 rows</td>
          <td style="text-align: right"><strong>3.081</strong></td>
          <td style="text-align: right"><strong>5.275</strong></td>
          <td style="text-align: right">9.850</td>
          <td style="text-align: right">9.850</td>
      </tr>
      <tr>
          <td>batch 5,000 rows</td>
          <td style="text-align: right">4.797</td>
          <td style="text-align: right">5.383</td>
          <td style="text-align: right">6.439</td>
          <td style="text-align: right">6.439</td>
      </tr>
  </tbody>
</table>
<p>Batching gave a massive jump over single-row inserts (~17x using median values).
At this scale, batch sizes 100 to 5000 were in a similar range, with 1,000 rows slightly ahead on median.</p>
<h2 id="what-i-learned">What I learned</h2>
<p>Biggest jump for bulk loading came from <strong>UNLOGGED + COPY</strong>.</p>
<p>After isolating runs, logged insert with primary key was clearly slower than logged insert without primary key, which is what you would expect from index maintenance overhead.</p>
<p>For normal application-style inserts, batching multiple rows per statement is still a huge win over one-row-per-statement, and isolated runs showed roughly a 17x median improvement.</p>
<p>Postgres is not &ldquo;slow&rdquo; here, it is doing durability and consistency work by default, and your write pattern decides how much overhead you pay.</p>
<h2 id="practical-takeaway">Practical takeaway</h2>
<p>If you care about raw insert speed:</p>
<ol>
<li>Prefer <code>COPY</code> for large bulk loads</li>
<li>Use <code>UNLOGGED</code> tables for staging/bulk pipelines</li>
<li>Batch inserts (<code>VALUES (...), (...), ...</code>) instead of single-row statements</li>
<li>Add indexes/constraints after loading (when possible)</li>
</ol>
<p>For production flows where durability matters at all times, stick with logged tables and accept lower throughput.</p>
]]></content:encoded></item><item><title>The Reservation Pattern: Claim First, Build Later</title><link>https://mohit-k-s.github.io/blog/2026/stack-orchestration/</link><pubDate>Wed, 15 Apr 2026 00:00:00 +0000</pubDate><guid>https://mohit-k-s.github.io/blog/2026/stack-orchestration/</guid><description>&lt;p&gt;I was thinking about what happens when you double-click that &amp;ldquo;Create&amp;rdquo; button on AWS. Or when two teammates simultaneously try to spin up environments that share the same VPC.&lt;/p&gt;
&lt;p&gt;Seems simple - just check if the resource already exists. But it&amp;rsquo;s one of those deceptively deep distributed systems problems.&lt;/p&gt;
&lt;h2 id="the-double-click-problem"&gt;The Double-Click Problem&lt;/h2&gt;
&lt;p&gt;A user clicks &amp;ldquo;Create Infrastructure&amp;rdquo; and their browser fires two identical API requests.&lt;/p&gt;
&lt;p&gt;Naive solution: before creating a resource, check if it already exists by name. If AWS returns &lt;code&gt;AlreadyExistsException&lt;/code&gt;, catch it and move on.&lt;/p&gt;</description><content:encoded><![CDATA[<p>I was thinking about what happens when you double-click that &ldquo;Create&rdquo; button on AWS. Or when two teammates simultaneously try to spin up environments that share the same VPC.</p>
<p>Seems simple - just check if the resource already exists. But it&rsquo;s one of those deceptively deep distributed systems problems.</p>
<h2 id="the-double-click-problem">The Double-Click Problem</h2>
<p>A user clicks &ldquo;Create Infrastructure&rdquo; and their browser fires two identical API requests.</p>
<p>Naive solution: before creating a resource, check if it already exists by name. If AWS returns <code>AlreadyExistsException</code>, catch it and move on.</p>
<p><strong>This works.</strong> If every resource has a natural unique identifier (like a VPC name), the cloud provider&rsquo;s own uniqueness constraint is your deduplication layer.</p>
<p>But what if you&rsquo;re creating multiple resources?</p>
<h2 id="multiple-interdependent-resources">Multiple Interdependent Resources</h2>
<p>A single &ldquo;create my environment&rdquo; request might translate to five resources:</p>





<pre tabindex="0"><code>VPC -&gt; Subnet -&gt; Security Group -&gt; EC2 Instance -&gt; DNS Record</code></pre><p>Each depends on the one before it. Now the interesting failures show up:</p>
<p><strong>Partial failure.</strong> You create the VPC and Subnet, then crash while creating the Security Group. User retries. You need to <em>resume from where you left off</em> - not start over, not create a second VPC.</p>
<p><strong>Concurrent overlapping requests.</strong> Two stacks want some of the same resources:</p>





<pre tabindex="0"><code>Stack X: [VPC-1, Subnet-1, SG-1, EC2-A, DNS-A]
Stack Y: [VPC-1, Subnet-1, SG-1, EC2-B, DNS-B]
                ^^^    ^^^   ^^^
            overlapping resources</code></pre><p>Who wins? What happens to the resources the loser already created?</p>
<h2 id="the-naive-approach-build-first-ask-questions-later">The Naive Approach: Build First, Ask Questions Later</h2>
<p>Create resources one by one, check for conflicts as you go.</p>





<pre tabindex="0"><code>for each resource in stack:
    if resource exists and belongs to another stack:
        → conflict, roll back everything
    else:
        → create it, claim ownership</code></pre><p>This has a fundamental flaw. By the time you detect a conflict at step 3, you&rsquo;ve already created real infrastructure at steps 1 and 2. Now you have to tear it down. If <em>that</em> teardown fails? Orphaned resources floating in the cloud, owned by a stack that no longer exists.</p>
<p>You&rsquo;re doing expensive, hard-to-reverse work before you even know if the operation is valid.</p>
<h2 id="claim-first-build-later">Claim First, Build Later</h2>
<p>Here&rsquo;s the key insight: <strong>the conflict detection problem and the resource creation problem are completely independent.</strong> You don&rsquo;t need to create anything to know if there&rsquo;s a conflict - just check if any requested resource is already claimed by another stack.</p>
<p>This gives you a three-phase architecture:</p>
<h3 id="phase-1-admission-gate-synchronous">Phase 1: Admission Gate (Synchronous)</h3>
<p>When a stack creation request comes in, don&rsquo;t create any infrastructure. Write every requested resource name into an ownership table with a <strong>primary key on <code>resource_name</code></strong>.</p>





<pre tabindex="0"><code>Request: Create Stack X with [VPC-1, Subnet-1, SG-1, EC2-A, DNS-A]

→ INSERT (resource_name=&#34;VPC-1&#34;,    stack_id=X, status=RESERVED)
→ INSERT (resource_name=&#34;Subnet-1&#34;, stack_id=X, status=RESERVED)
→ INSERT (resource_name=&#34;SG-1&#34;,     stack_id=X, status=RESERVED)
→ INSERT (resource_name=&#34;EC2-A&#34;,    stack_id=X, status=RESERVED)
→ INSERT (resource_name=&#34;DNS-A&#34;,    stack_id=X, status=RESERVED)</code></pre><p>If any insert fails (primary key violation), the resource is already claimed. Mark this stack as <strong>INVALID</strong>. No infrastructure was touched. No rollback needed.</p>
<p>Worst case O(n) - one check per resource - and each check is a single atomic database write. Race conditions are impossible because the database constraint handles concurrency for you.</p>
<h3 id="phase-2-creator-scheduler-asynchronous">Phase 2: Creator Scheduler (Asynchronous)</h3>
<p>A background scheduler polls for valid stacks with resources in <code>RESERVED</code> status:</p>
<ol>
<li>Look at the dependency DAG</li>
<li>Find all resources whose dependencies are already <code>CREATED</code></li>
<li>Create that batch in parallel</li>
<li>Mark them as <code>CREATED</code>, come back for the next wave</li>
</ol>





<pre tabindex="0"><code>Tick 1: VPC-1 has no dependencies → create it
Tick 2: Subnet-1 depends on VPC-1 (done) → create it
        SG-1 depends on VPC-1 (done) → create it     ← parallel!
Tick 3: EC2-A depends on Subnet-1 + SG-1 (done) → create it
Tick 4: DNS-A depends on EC2-A (done) → create it</code></pre><p>The scheduler doesn&rsquo;t worry about conflicts - admission already handled that. It doesn&rsquo;t worry about ordering - the DAG tells it what&rsquo;s ready. It just picks up work and executes it.</p>
<p>If a creation fails, the resource stays <code>RESERVED</code>. Scheduler retries on the next tick. Too many retries? Mark the stack as <code>FAILED</code>.</p>
<p><strong>Resumability comes for free.</strong> System crashes after creating VPC and Subnet? Scheduler picks up on the next tick, sees those are <code>CREATED</code>, continues from the Security Group.</p>
<h3 id="phase-3-cleanup-scheduler-asynchronous">Phase 3: Cleanup Scheduler (Asynchronous)</h3>
<p>A second scheduler polls for stacks marked <code>INVALID</code> or <code>FAILED</code>:</p>
<ol>
<li>Find all resources owned by this stack</li>
<li>If a resource was actually created (<code>CREATED</code>), delete it from the cloud provider</li>
<li>Delete succeeds → remove the ownership claim</li>
<li>Delete fails → mark as <code>ORPHANED</code></li>
</ol>
<p>A garbage collection sweep periodically retries <code>ORPHANED</code> resources until they&rsquo;re gone.</p>
<h2 id="the-data-model">The Data Model</h2>





<pre tabindex="0"><code>Stack:
  stack_id        : UUID
  status          : VALIDATING | VALID | INVALID | IN_PROGRESS | COMPLETE | FAILED
  created_at      : timestamp

Resource Ownership:
  resource_name   : string    ← PRIMARY KEY (enforces uniqueness)
  stack_id        : UUID
  status          : RESERVED | CREATING | CREATED | DELETING | ORPHANED
  claimed_at      : timestamp

Execution Log:
  stack_id        : UUID
  resource_name   : string
  action          : RESERVE | CREATE | DELETE
  result          : SUCCESS | FAILED | CONFLICT
  timestamp       : timestamp</code></pre><h2 id="why-this-works">Why This Works</h2>
<p>Each phase only cares about one thing:</p>
<table>
  <thead>
      <tr>
          <th>Phase</th>
          <th>Responsible For</th>
          <th>Doesn&rsquo;t Care About</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Admission</td>
          <td>Conflict detection</td>
          <td>Resource creation, ordering, rollback</td>
      </tr>
      <tr>
          <td>Creator</td>
          <td>Building infrastructure</td>
          <td>Conflicts, validation</td>
      </tr>
      <tr>
          <td>Cleanup</td>
          <td>Tearing down failures</td>
          <td>Conflicts, creation order</td>
      </tr>
  </tbody>
</table>
<p>Admission is just database writes. Creator is just a DAG walker. Cleanup is just a list of things to delete. Compare this to the &ldquo;build first&rdquo; approach where a single code path handles creation, conflict detection, rollback, and orphan cleanup - all interleaved, all compounding on each other.</p>
<h2 id="this-isnt-new">This Isn&rsquo;t New</h2>
<p>This separation shows up everywhere:</p>
<ul>
<li><strong>CloudFormation</strong> separates change set creation from execution</li>
<li><strong>Kubernetes</strong> has the API server (admission + etcd writes) and the kubelet (container creation) as separate processes</li>
<li><strong>Database engines</strong> use write-ahead logs - decide what to do, write it down, then execute</li>
<li><strong>Airline booking</strong> reserves seats before issuing tickets</li>
</ul>
<p>The pattern is always the same: make the decision durable before you make it real. Undoing a row in a table is trivial. Undoing a half-provisioned EC2 instance with an attached EBS volume and a security group is not.</p>
<h2 id="wrapping-up">Wrapping Up</h2>
<p>What started as &ldquo;how do I handle a double-click&rdquo; turned into a distributed systems design problem. But the core insight is simple:</p>
<blockquote>
<p>Don&rsquo;t let the system create anything until it has already decided, atomically, that every resource is available.</p></blockquote>
<p>Separate planning from execution. Let the database handle concurrency. Make everything else async and idempotent.</p>
]]></content:encoded></item><item><title>An Observer</title><link>https://mohit-k-s.github.io/blog/2026/observer/</link><pubDate>Sat, 24 Jan 2026 00:00:00 +0000</pubDate><guid>https://mohit-k-s.github.io/blog/2026/observer/</guid><description>&lt;p&gt;Recently, I’ve been stressed, and things I never bothered to acknowledge started surfacing.
Questions that don’t ask for answers.
Questions that keep me awake.&lt;/p&gt;
&lt;p&gt;It’s been over two months of this.
Life was never easy for me, but this is different.
This is the fear of my own mortality, gripping me in a way nothing else has.&lt;/p&gt;
&lt;p&gt;I can’t sleep at night.
I can’t think in the morning.&lt;/p&gt;
&lt;p&gt;Tick.
Tock.
Tick.
Tock.
Tick.
Tock.&lt;/p&gt;</description><content:encoded><![CDATA[<p>Recently, I’ve been stressed, and things I never bothered to acknowledge started surfacing.
Questions that don’t ask for answers.
Questions that keep me awake.</p>
<p>It’s been over two months of this.
Life was never easy for me, but this is different.
This is the fear of my own mortality, gripping me in a way nothing else has.</p>
<p>I can’t sleep at night.
I can’t think in the morning.</p>
<p>Tick.
Tock.
Tick.
Tock.
Tick.
Tock.</p>
<p>This could be my last entry, or maybe there will be more. I don’t know.
The uncertainty itself feels unbearable.</p>
<p>As if that wasn’t enough, I rewatched The Lunchbox.
This film is special to me because it shows the most likely version of my future except without the dead wife.
Just me.</p>
<p>Watching life from a balcony.
Children playing.
Trying to reason my way into an explanation for my own existence.</p>
<p>I see young people and old people.
Poor people and rich people.
Couples. Friends.</p>
<p>And then I see myself.</p>
<p>When you live in a place filled with younger people, and you have to walk everywhere, you see life constantly in motion.
But none of it relates to you.
You remain an observer.</p>
<p>The years you didn’t act begin to compound.
So do the consequences.</p>
<p>Comparison becomes a habit.
Happiness feels <mark><i>inaccessible</i></mark>.</p>
<p>Walking helps.<br>
Talking helps.</p>
<p>And then it returns.</p>
<p>They say this too shall pass.<br>
But some things don’t pass through you ; they stay.</p>
<p>Like an appendage.<br>
Useless.<br>
Always there.</p>
]]></content:encoded></item><item><title>Creating custom notifications for reminders</title><link>https://mohit-k-s.github.io/blog/2026/custom-notif/</link><pubDate>Fri, 02 Jan 2026 00:00:00 +0000</pubDate><guid>https://mohit-k-s.github.io/blog/2026/custom-notif/</guid><description>&lt;p&gt;Humans weren&amp;rsquo;t meant for sitting, and it has caused me a great deal of issues personally. I wanted to schedule reminders on Mac for standing up and drinking water, since I lose track of time pretty easily.&lt;/p&gt;
&lt;p&gt;Turns out this is very simple to do.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Create a shell script (for example, &lt;code&gt;drink-water.sh&lt;/code&gt;):&lt;/li&gt;
&lt;/ol&gt;





&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;1&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;#!/bin/bash
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;2&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;&lt;/span&gt;osascript -e &lt;span class="s1"&gt;&amp;#39;display notification &amp;#34;You need water&amp;#34; with title &amp;#34;Drink water&amp;#34;&amp;#39;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ol start="2"&gt;
&lt;li&gt;
&lt;p&gt;Make the script executable, then register it with &lt;code&gt;launchd&lt;/code&gt;.&lt;/p&gt;</description><content:encoded><![CDATA[<p>Humans weren&rsquo;t meant for sitting, and it has caused me a great deal of issues personally. I wanted to schedule reminders on Mac for standing up and drinking water, since I lose track of time pretty easily.</p>
<p>Turns out this is very simple to do.</p>
<ol>
<li>Create a shell script (for example, <code>drink-water.sh</code>):</li>
</ol>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="ln">1</span><span class="cl"><span class="cp">#!/bin/bash
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="cp"></span>osascript -e <span class="s1">&#39;display notification &#34;You need water&#34; with title &#34;Drink water&#34;&#39;</span></span></span></code></pre></div><ol start="2">
<li>
<p>Make the script executable, then register it with <code>launchd</code>.</p>
</li>
<li>
<p>If it doesn&rsquo;t already exist, create <code>~/Library/LaunchAgents</code>.</p>
</li>
<li>
<p>Create <code>~/Library/LaunchAgents/com.my.drink-water.plist</code>:</p>
</li>
</ol>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="cp">&lt;?xml version=&#34;1.0&#34; encoding=&#34;UTF-8&#34;?&gt;</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="cp">&lt;!DOCTYPE plist PUBLIC &#34;-//Apple//DTD PLIST 1.0//EN&#34;
</span></span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="cp"> &#34;http://www.apple.com/DTDs/PropertyList-1.0.dtd&#34;&gt;</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="nt">&lt;plist</span> <span class="na">version=</span><span class="s">&#34;1.0&#34;</span><span class="nt">&gt;</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="nt">&lt;dict&gt;</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">  <span class="nt">&lt;key&gt;</span>Label<span class="nt">&lt;/key&gt;</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">  <span class="nt">&lt;string&gt;</span>drink.water.reminder<span class="nt">&lt;/string&gt;</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">  <span class="nt">&lt;key&gt;</span>ProgramArguments<span class="nt">&lt;/key&gt;</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">  <span class="nt">&lt;array&gt;</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="nt">&lt;string&gt;</span>/full/path/to/drink-water.sh<span class="nt">&lt;/string&gt;</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">  <span class="nt">&lt;/array&gt;</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">
</span></span><span class="line"><span class="ln">14</span><span class="cl">  <span class="nt">&lt;key&gt;</span>StartInterval<span class="nt">&lt;/key&gt;</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">  <span class="nt">&lt;integer&gt;</span>1800<span class="nt">&lt;/integer&gt;</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">
</span></span><span class="line"><span class="ln">17</span><span class="cl">  <span class="nt">&lt;key&gt;</span>StandardErrorPath<span class="nt">&lt;/key&gt;</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">  <span class="nt">&lt;string&gt;</span>/tmp/water.err<span class="nt">&lt;/string&gt;</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">  <span class="nt">&lt;key&gt;</span>StandardOutPath<span class="nt">&lt;/key&gt;</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">  <span class="nt">&lt;string&gt;</span>/tmp/water.out<span class="nt">&lt;/string&gt;</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl"><span class="nt">&lt;/dict&gt;</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl"><span class="nt">&lt;/plist&gt;</span></span></span></code></pre></div><ol start="5">
<li>Load it:</li>
</ol>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="ln">1</span><span class="cl">launchctl load /full/path/to/com.my.drink-water.plist</span></span></code></pre></div>]]></content:encoded></item><item><title>WalJet: Building a High-Performance CDC Pipeline</title><link>https://mohit-k-s.github.io/blog/2025/waljet/</link><pubDate>Sun, 07 Dec 2025 00:00:00 +0000</pubDate><guid>https://mohit-k-s.github.io/blog/2025/waljet/</guid><description>&lt;p&gt;Change Data Capture (CDC) sounds simple on paper: take database changes, stream them somewhere else. How hard could it be?&lt;/p&gt;
&lt;p&gt;Very hard.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s what I hacked away when i was bored at work.&lt;/p&gt;
&lt;h2 id="the-problem"&gt;The Problem&lt;/h2&gt;
&lt;p&gt;Modern applications often need to react to database changes in real-time. Whether it&amp;rsquo;s updating a cache, triggering workflows, or syncing data across services, you need a reliable way to capture and stream database changes.&lt;/p&gt;
&lt;p&gt;Postgres has a powerful feature called Write-Ahead Logging (WAL) that records every change before it&amp;rsquo;s applied. The challenge is turning that low-level stream into something useful.&lt;/p&gt;</description><content:encoded><![CDATA[<p>Change Data Capture (CDC) sounds simple on paper: take database changes, stream them somewhere else. How hard could it be?</p>
<p>Very hard.</p>
<p>Here&rsquo;s what I hacked away when i was bored at work.</p>
<h2 id="the-problem">The Problem</h2>
<p>Modern applications often need to react to database changes in real-time. Whether it&rsquo;s updating a cache, triggering workflows, or syncing data across services, you need a reliable way to capture and stream database changes.</p>
<p>Postgres has a powerful feature called Write-Ahead Logging (WAL) that records every change before it&rsquo;s applied. The challenge is turning that low-level stream into something useful.</p>
<h2 id="architecture">Architecture</h2>
<p>WalJet&rsquo;s architecture is straightforward but carefully designed:</p>
<img src="../images/waljet_arch.png" alt="Waljet architecture" width="600"/>
<p><strong>Key components:</strong></p>
<ol>
<li><strong>WAL Listener</strong>: Connects to Postgres using logical replication protocol (<code>pgoutput</code>)</li>
<li><strong>Decoder</strong>: Parses binary WAL messages into structured change events</li>
<li><strong>Encoder Pool</strong>: Parallel workers that serialize events to Protobuf</li>
<li><strong>Publisher Pool</strong>: Batches and publishes events to Redis Streams</li>
<li><strong>Checkpoint Manager</strong>: Tracks replication progress with dual storage (Redis + file)</li>
</ol>
<h2 id="why-this-stack">Why This Stack?</h2>
<p><strong>Postgres WAL + pglogrepl</strong>: Direct access to the write-ahead log using Go&rsquo;s <code>pglogrepl</code> library. No JVM overhead, no Kafka Connect complexity.</p>
<p><strong>Protobuf encoding</strong>: Smaller payloads, type safety, and faster serialization than JSON. When you&rsquo;re processing thousands of events per second, this matters.</p>
<p><strong>Redis Streams</strong>: Ordered delivery, consumer groups, and sub-millisecond latency. Perfect for real-time event streaming without the operational overhead of Kafka.</p>
<p>The entire pipeline is designed around one goal: get database changes to downstream consumers as fast as possible, with minimal resource usage.</p>
<p>At no point this is a debezium replacer , debezium is a much more complex system, waljet is just a simple hack to get things done.</p>
<h2 id="the-tricky-parts">The Tricky Parts</h2>
<h3 id="graceful-shutdown">Graceful Shutdown</h3>
<p>This one caught me off guard. You can&rsquo;t just kill a CDC process - you&rsquo;ll leak replication slots and lose checkpoint data.</p>





<pre tabindex="0"><code>1. Stop accepting new events
2. Close event channels
3. Wait for workers to finish (flush batches)
4. Save final checkpoint (Redis + file)
5. Close replication connection
6. Open regular connection
7. Drop replication slot
8. Close regular connection
9. Close Redis connection</code></pre><p>The gotcha? You can&rsquo;t drop a replication slot while you&rsquo;re still using the replication connection. You need to:</p>
<ol>
<li>Close the replication connection</li>
<li>Open a regular Postgres connection</li>
<li>Drop the slot</li>
<li>Close the regular connection</li>
</ol>
<p>Miss this, and you leak storage in Postgres. The WAL files pile up, and eventually, you run out of disk space.</p>
<h3 id="checkpoint-durability">Checkpoint Durability</h3>
<p>When WalJet crashes (and it will), where do you resume? You need to track the last processed Log Sequence Number (LSN).</p>
<p>I went with a dual checkpoint system:</p>
<ul>
<li><strong>Primary</strong>: Redis key (<code>waljet:checkpoint:waljet_slot</code>)</li>
<li><strong>Backup</strong>: Local file (<code>.waljet_checkpoint_waljet_slot</code>)</li>
</ul>
<p>Why both? If Redis restarts, you still have the local file. If the container dies, Redis has the checkpoint. On startup, WalJet checks both and uses whichever is more recent.</p>
<p>Simple, but it works.</p>
<h2 id="why-build-this">Why Build This?</h2>
<p>The idea was simple: what if you could react to database changes in real-time without the complexity of Debezium or Kafka?</p>
<p>WalJet is designed for use cases where you need fast CDC with minimal infrastructure:</p>
<ul>
<li><strong>Cache invalidation</strong>: Update Redis/Memcached the moment data changes</li>
<li><strong>Webhook triggers</strong>: Call external APIs when specific rows are modified</li>
<li><strong>Event-driven architectures</strong>: Stream changes to downstream services</li>
</ul>
<p>It&rsquo;s not trying to replace Kafka or Debezium for large-scale data pipelines. It&rsquo;s for when you need something lightweight, fast, and easy to deploy.</p>
<h2 id="benchmark">Benchmark</h2>
<p>I ran a 5-minute benchmark comparing WalJet with Debezium under identical conditions:</p>
<table>
  <thead>
      <tr>
          <th>Metric</th>
          <th>Debezium</th>
          <th>WalJet</th>
          <th>Improvement</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><strong>Events/sec</strong></td>
          <td>92.32</td>
          <td>95.07</td>
          <td>+3%</td>
      </tr>
      <tr>
          <td><strong>P50 Latency</strong></td>
          <td>541.44ms</td>
          <td>23.44ms</td>
          <td><strong>23x faster</strong></td>
      </tr>
      <tr>
          <td><strong>P95 Latency</strong></td>
          <td>871.11ms</td>
          <td>50.16ms</td>
          <td><strong>17x faster</strong></td>
      </tr>
      <tr>
          <td><strong>P99 Latency</strong></td>
          <td>952.98ms</td>
          <td>53.15ms</td>
          <td><strong>18x faster</strong></td>
      </tr>
      <tr>
          <td><strong>Avg Latency</strong></td>
          <td>541.08ms</td>
          <td>25.17ms</td>
          <td><strong>21x faster</strong></td>
      </tr>
      <tr>
          <td><strong>Max Latency</strong></td>
          <td>1131.87ms</td>
          <td>171.33ms</td>
          <td><strong>6.6x faster</strong></td>
      </tr>
      <tr>
          <td><strong>Events Lost</strong></td>
          <td>0</td>
          <td>0</td>
          <td>✓</td>
      </tr>
  </tbody>
</table>
<p>The throughput is similar, but latency tells the real story. WalJet&rsquo;s P99 latency is 53ms compared to Debezium&rsquo;s 953ms - nearly 20x faster.</p>
<blockquote>
<p>&ldquo;When you&rsquo;re invalidating a cache or triggering a webhook, waiting 950ms vs 50ms is the difference between a system that feels instant and one that feels sluggish.&rdquo;</p>
<ul>
<li>Me, after running this benchmark</li>
</ul></blockquote>
<p>Remember <a href="https://letterboxd.com/film/the-hummingbird-project/"><em>The Hummingbird Project</em></a>? The movie where they&rsquo;re trying to build a fiber optic cable in a straight line from Kansas to New Jersey just to shave off a few milliseconds for high-frequency trading. Every millisecond was worth millions.</p>
<p>WalJet isn&rsquo;t for HFT, but the principle is the same: in systems where you need to react to changes immediately like cache invalidation, real-time webhooks, event-driven architectures - latency isn&rsquo;t just a nice-to-have. It&rsquo;s the whole point.</p>
<h2 id="why-not-open-source-yet">Why Not Open Source (Yet)?</h2>
<p>WalJet started as a fun experiment - something I hacked for fun,  It works, the benchmarks are promising, but it&rsquo;s not production-ready in the way an OSS project should be.</p>
<p>There&rsquo;s still work to do:</p>
<ul>
<li><strong>More performance testing</strong>: The benchmark above is just one scenario. Need to test under different loads, failure modes, and edge cases</li>
<li><strong>Better error handling</strong>: Right now it works for the happy path, but production systems need to handle every unhappy path too</li>
<li><strong>Documentation</strong>: The code needs better docs, examples, and a proper getting-started guide</li>
<li><strong>Battle testing</strong>: It hasn&rsquo;t been run in production long enough to discover all the weird bugs</li>
</ul>
<p>Maybe it&rsquo;ll be open source someday. For now, it&rsquo;s a proof of concept that taught me a lot about Postgres internals and CDC pipelines. And honestly? That was the whole point.</p>
]]></content:encoded></item><item><title>Missing Entries</title><link>https://mohit-k-s.github.io/blog/2025/gaps_in_pkeys/</link><pubDate>Thu, 13 Nov 2025 00:00:00 +0000</pubDate><guid>https://mohit-k-s.github.io/blog/2025/gaps_in_pkeys/</guid><description>&lt;p&gt;I ran into an odd issue recently, a service threw an error saying the &lt;strong&gt;primary key max value had been reached&lt;/strong&gt;.&lt;br&gt;
The column was &lt;code&gt;serial4&lt;/code&gt;, so its max value was &lt;strong&gt;2,147,483,647&lt;/strong&gt;, but the last inserted ID was only &lt;strong&gt;2,146,602,558&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;At first, I had no idea why.&lt;br&gt;
As a quick fix, I changed the column to &lt;code&gt;bigserial&lt;/code&gt;, which solved the immediate problem, but I wanted to know &lt;strong&gt;why IDs were missing&lt;/strong&gt;.&lt;/p&gt;</description><content:encoded><![CDATA[<p>I ran into an odd issue recently, a service threw an error saying the <strong>primary key max value had been reached</strong>.<br>
The column was <code>serial4</code>, so its max value was <strong>2,147,483,647</strong>, but the last inserted ID was only <strong>2,146,602,558</strong>.</p>
<p>At first, I had no idea why.<br>
As a quick fix, I changed the column to <code>bigserial</code>, which solved the immediate problem, but I wanted to know <strong>why IDs were missing</strong>.</p>
<p>Listing rows in descending order showed <strong>gaps</strong> in the primary keys.<br>
There were only about 16k entries in the table, so it clearly wasn’t full.</p>
<p>Digging into the code confirmed my suspicion:<br>
it used an <strong>INSERT &hellip; ON CONFLICT DO UPDATE</strong> pattern.</p>
<p>Here’s a minimal reproduction:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sql" data-lang="sql"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="k">CREATE</span><span class="w"> </span><span class="k">TABLE</span><span class="w"> </span><span class="n">seq_test</span><span class="w"> </span><span class="p">(</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="w">    </span><span class="n">id</span><span class="w"> </span><span class="nb">SERIAL</span><span class="w"> </span><span class="k">PRIMARY</span><span class="w"> </span><span class="k">KEY</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="w">    </span><span class="n">device_id</span><span class="w"> </span><span class="nb">INT</span><span class="w"> </span><span class="k">UNIQUE</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="w">    </span><span class="n">status</span><span class="w"> </span><span class="nb">TEXT</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="w"></span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="w"></span><span class="k">INSERT</span><span class="w"> </span><span class="k">INTO</span><span class="w"> </span><span class="n">seq_test</span><span class="w"> </span><span class="p">(</span><span class="n">device_id</span><span class="p">,</span><span class="w"> </span><span class="n">status</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="w"></span><span class="k">VALUES</span><span class="w"> </span><span class="p">(</span><span class="mi">1</span><span class="p">,</span><span class="w"> </span><span class="s1">&#39;active&#39;</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="w"></span><span class="k">INSERT</span><span class="w"> </span><span class="k">INTO</span><span class="w"> </span><span class="n">seq_test</span><span class="w"> </span><span class="p">(</span><span class="n">device_id</span><span class="p">,</span><span class="w"> </span><span class="n">status</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="w"></span><span class="k">VALUES</span><span class="w"> </span><span class="p">(</span><span class="mi">1</span><span class="p">,</span><span class="w"> </span><span class="s1">&#39;inactive&#39;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="w"></span><span class="k">ON</span><span class="w"> </span><span class="n">CONFLICT</span><span class="w"> </span><span class="p">(</span><span class="n">device_id</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="w"></span><span class="k">DO</span><span class="w"> </span><span class="k">UPDATE</span><span class="w"> </span><span class="k">SET</span><span class="w"> </span><span class="n">status</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">EXCLUDED</span><span class="p">.</span><span class="n">status</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="w"></span><span class="k">INSERT</span><span class="w"> </span><span class="k">INTO</span><span class="w"> </span><span class="n">seq_test</span><span class="w"> </span><span class="p">(</span><span class="n">device_id</span><span class="p">,</span><span class="w"> </span><span class="n">status</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="w"></span><span class="k">VALUES</span><span class="w"> </span><span class="p">(</span><span class="mi">2</span><span class="p">,</span><span class="w"> </span><span class="s1">&#39;active&#39;</span><span class="p">);</span></span></span></code></pre></div><p>Now check what’s stored:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sql" data-lang="sql"><span class="line"><span class="ln">1</span><span class="cl"><span class="k">select</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="k">from</span><span class="w"> </span><span class="n">seq_test</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="w"></span><span class="s2">&#34;id&#34;</span><span class="p">,</span><span class="s2">&#34;device_id&#34;</span><span class="p">,</span><span class="s2">&#34;status&#34;</span><span class="w">
</span></span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="w"></span><span class="mi">1</span><span class="p">,</span><span class="mi">1</span><span class="p">,</span><span class="n">inactive</span><span class="w">
</span></span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="w"></span><span class="mi">3</span><span class="p">,</span><span class="mi">2</span><span class="p">,</span><span class="n">active</span></span></span></code></pre></div><p>Notice the jump - the id for the second device is 3, not 2.</p>
<p>That’s because Postgres still calls nextval() for every insert, even if it hits a conflict and performs an update instead.</p>
<p>A small detail, but a good one to remember.</p>
]]></content:encoded></item><item><title>Building Meteo</title><link>https://mohit-k-s.github.io/blog/2025/meteo/</link><pubDate>Thu, 21 Aug 2025 00:00:00 +0000</pubDate><guid>https://mohit-k-s.github.io/blog/2025/meteo/</guid><description>&lt;p&gt;Another weather app ?&lt;/p&gt;
&lt;img src="../images/i-cant-hear-you-yes.gif" alt="Can't hear you over the metro noise" width="200" /&gt;
&lt;p&gt;No !&lt;/p&gt;
&lt;p&gt;I built this app for myself. The design isn&amp;rsquo;t perfect (yet), but it works. Here&amp;rsquo;s how I put it together.
One might ask , why ? To that i have no answer as i just thought that this can be cool !&lt;/p&gt;
&lt;div class="iframe-container"&gt;
&lt;iframe src="https://meteo-v1.vercel.app/" title="Meteo" loading="lazy" &gt;&lt;/iframe&gt;
&lt;/div&gt;
&lt;h2 id="the-data-challenge"&gt;The Data Challenge&lt;/h2&gt;
&lt;p&gt;Building any transit app starts with data - lots of it. I needed:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;All metro stations with accurate coordinates&lt;/li&gt;
&lt;li&gt;Line connections and routes&lt;/li&gt;
&lt;li&gt;Station elevations (for that authentic metro map feel)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="station-data-collection"&gt;Station Data Collection&lt;/h3&gt;
&lt;p&gt;I started with the official DMRC website, manually extracting all lines and stations. Getting coordinates was trickier. My first attempt used &lt;a href="https://operations.osmfoundation.org/policies/nominatim/"&gt;Nominatim&lt;/a&gt;, OpenStreetMap&amp;rsquo;s geocoding service, but the results were disappointing - many stations were missing or had wildly inaccurate locations.&lt;/p&gt;</description><content:encoded><![CDATA[<p>Another weather app ?</p>
<img src="../images/i-cant-hear-you-yes.gif" alt="Can't hear you over the metro noise" width="200" />
<p>No !</p>
<p>I built this app for myself. The design isn&rsquo;t perfect (yet), but it works. Here&rsquo;s how I put it together.
One might ask , why ? To that i have no answer as i just thought that this can be cool !</p>
<div class="iframe-container">
<iframe src="https://meteo-v1.vercel.app/" title="Meteo" loading="lazy" ></iframe>
</div>
<h2 id="the-data-challenge">The Data Challenge</h2>
<p>Building any transit app starts with data - lots of it. I needed:</p>
<ul>
<li>All metro stations with accurate coordinates</li>
<li>Line connections and routes</li>
<li>Station elevations (for that authentic metro map feel)</li>
</ul>
<h3 id="station-data-collection">Station Data Collection</h3>
<p>I started with the official DMRC website, manually extracting all lines and stations. Getting coordinates was trickier. My first attempt used <a href="https://operations.osmfoundation.org/policies/nominatim/">Nominatim</a>, OpenStreetMap&rsquo;s geocoding service, but the results were disappointing - many stations were missing or had wildly inaccurate locations.</p>
<p>Plan B: Google Maps API. More reliable, but required careful rate limiting and cost management.</p>
<p>The final dataset lives in <a href="https://github.com/mohit-k-s/meteo/blob/main/public/dmrc.json">dmrc.json</a> on GitHub. It includes:</p>
<ul>
<li>250+ metro stations</li>
<li>Coordinate data for each station</li>
<li>Line connections and transfers</li>
<li>(Mostly fictional) elevation data</li>
</ul>
<h3 id="the-elevation-problem">The Elevation Problem</h3>
<p>Metro maps show elevation changes, but this data isn&rsquo;t publicly available. My current solution? I generated random elevations that look reasonable. Not ideal, but functional.</p>
<p>Future plan: Use OCR on official metro elevation maps to extract real data.</p>
<h2 id="route-planning-algorithm">Route Planning Algorithm</h2>
<p>The core routing uses a simple depth-first search (DFS) to find paths between stations. Delhi Metro&rsquo;s network guarantees connectivity - you can get from any station to any other station, even if it requires multiple transfers.</p>
<p>The algorithm could be optimized with Dijkstra&rsquo;s for shortest paths, but DFS works fine for this use case.</p>
<h2 id="frontend-implementation">Frontend Implementation</h2>
<p>I used Leaflet.js for the interactive map - it&rsquo;s lightweight and handles the station plotting beautifully. The UI is minimal by design:</p>
<ul>
<li>Search for start/end stations</li>
<li>Visual route display on the map</li>
<li>Step-by-step directions</li>
<li>Transfer information</li>
</ul>
<p>The interface needs work. I know it&rsquo;s not intuitive, but hey:</p>
<img src="../images/honest_works.jpg" alt="It ain't much, but it's honest work" width="300"/>
<h2 id="deployment">Deployment</h2>
<p>Vercel made deployment trivial. Push to GitHub, automatic builds and done!</p>
<h2 id="lessons-learned">Lessons Learned</h2>
<ol>
<li><strong>Start with data</strong>: Good data is harder to find than you think</li>
<li><strong>Simple algorithms work</strong>: DFS solved the routing problem perfectly</li>
<li><strong>Open source everything</strong>: Someone else might find this useful</li>
</ol>
<p>The code is on <a href="https://github.com/mohit-k-s/meteo">GitHub</a> <br>
The website is live, <a href="https://meteo-v1.vercel.app/">Meteo</a></p>
<hr>
<p><em>Building tools for problems you actually have is the best kind of side project. Even if the UI looks like it was designed in 2005.</em></p>
]]></content:encoded></item><item><title>169220804: Yet Another Magic Number</title><link>https://mohit-k-s.github.io/blog/2025/js/arrays/</link><pubDate>Sun, 10 Aug 2025 00:00:00 +0000</pubDate><guid>https://mohit-k-s.github.io/blog/2025/js/arrays/</guid><description>&lt;p&gt;I was poking around with Node.js arrays, trying to see how far I could push them
Here’s the simple code I ran:&lt;/p&gt;





&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-js" data-lang="js"&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;1&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;at&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;2&lt;/span&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;3&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mf"&gt;1e9&lt;/span&gt; &lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;4&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;at&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;5&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This is just a standard array, and of course it fails - there’s no way my system has ~7.5 GB available just for this process.
But it fails with an interesting error:&lt;/p&gt;





&lt;pre tabindex="0"&gt;&lt;code class="language-log" data-lang="log"&gt;#
# Fatal error in , line 0
# Fatal JavaScript invalid size error 169220804 (see crbug.com/1201626)
#
#
#
#FailureMessage Object: 0x16bc59878
----- Native stack trace -----

 1: 0x104301560 node::NodePlatform::GetStackTracePrinter()::$_0::__invoke() [/Users/mohit/.nvm/versions/node/v20.19.0/bin/node]
 ...&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;What’s that number &lt;em&gt;169220804&lt;/em&gt;?&lt;/p&gt;</description><content:encoded><![CDATA[<p>I was poking around with Node.js arrays, trying to see how far I could push them
Here’s the simple code I ran:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="ln">1</span><span class="cl"><span class="kr">const</span> <span class="nx">at</span> <span class="o">=</span> <span class="p">[]</span> 
</span></span><span class="line"><span class="ln">2</span><span class="cl">
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="k">for</span><span class="p">(</span><span class="kd">let</span> <span class="nx">i</span> <span class="o">=</span><span class="mi">0</span> <span class="p">;</span> <span class="nx">i</span> <span class="o">&lt;</span> <span class="mf">1e9</span> <span class="p">;</span> <span class="o">++</span><span class="nx">i</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">    <span class="nx">at</span><span class="p">.</span><span class="nx">push</span><span class="p">(</span><span class="kc">true</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="p">}</span></span></span></code></pre></div><p>This is just a standard array, and of course it fails - there’s no way my system has ~7.5 GB available just for this process.
But it fails with an interesting error:</p>





<pre tabindex="0"><code class="language-log" data-lang="log">#
# Fatal error in , line 0
# Fatal JavaScript invalid size error 169220804 (see crbug.com/1201626)
#
#
#
#FailureMessage Object: 0x16bc59878
----- Native stack trace -----

 1: 0x104301560 node::NodePlatform::GetStackTracePrinter()::$_0::__invoke() [/Users/mohit/.nvm/versions/node/v20.19.0/bin/node]
  ...</code></pre><p>What’s that number <em>169220804</em>?</p>
<p>You might think: “Maybe if I just create an array of length <em>169220803</em>, it’ll work?” Nope !</p>
<p>Each node version ships with a particular v8, here is my config</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl">➜ node -p process.versions
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="o">{</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">  node: <span class="s1">&#39;20.19.0&#39;</span>,
</span></span><span class="line"><span class="ln">4</span><span class="cl">  v8: <span class="s1">&#39;11.3.244.8-node.26&#39;</span>,
</span></span><span class="line"><span class="ln">5</span><span class="cl">  ...
</span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="o">}</span></span></span></code></pre></div><p>So, I checked that branch of the V8 source.
kMaxSize is defined <a href="https://chromium.googlesource.com/v8/v8/&#43;/refs/heads/11.3.244/src/objects/fixed-array.h#91">here</a>.</p>
<p>That’s the max size in bytes, but the max number of elements comes from <a href="https://chromium.googlesource.com/v8/v8/&#43;/refs/heads/11.3.244/src/objects/fixed-array.h#207">kMaxLength</a> which works out to <em>134,217,728</em> elements.</p>
<p>So in theory, creating an array of that length is fine.
But my program is using .push().</p>
<h2 id="the-growth-rule">The growth rule</h2>
<p>When .push() needs more capacity, V8 allocates a new backing store at ~1.5× the old size and copies the elements over.</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-cpp" data-lang="cpp"><span class="line"><span class="ln">1</span><span class="cl">  <span class="c1">// file src/builtins/growable-fixed-array-gen.cc
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="c1"></span>  <span class="c1">// Growth rate is analog to JSObject::NewElementsCapacity:
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="c1"></span>  <span class="c1">// new_capacity = (current_capacity + (current_capacity &gt;&gt; 1)) + 16.
</span></span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="c1"></span>  <span class="k">const</span> <span class="n">TNode</span><span class="o">&lt;</span><span class="n">IntPtrT</span><span class="o">&gt;</span> <span class="n">new_capacity</span> <span class="o">=</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">      <span class="n">IntPtrAdd</span><span class="p">(</span><span class="n">IntPtrAdd</span><span class="p">(</span><span class="n">current_capacity</span><span class="p">,</span> <span class="n">WordShr</span><span class="p">(</span><span class="n">current_capacity</span><span class="p">,</span> <span class="mi">1</span><span class="p">)),</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl">                <span class="n">IntPtrConstant</span><span class="p">(</span><span class="mi">16</span><span class="p">));</span></span></span></code></pre></div><h2 id="finding-the-actual-limit">Finding the actual limit</h2>
<p>At first , I didn&rsquo;t know the above formula and just wanted to find the point at which i start seeing these errors so , I wrote a quick binary search to find the exact point where the above error appears.</p>
<p>The magic number turned out to be <em>112,813,859</em> elements.</p>
<p>If we plug that into the growth formula:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="ln">1</span><span class="cl"><span class="nx">new_capacity</span> <span class="o">=</span> <span class="mi">112_813_859</span> <span class="o">+</span> <span class="nx">floor</span><span class="p">(</span><span class="mi">112_813_859</span> <span class="o">/</span> <span class="mi">2</span><span class="p">)</span> <span class="o">+</span> <span class="mi">16</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">             <span class="o">=</span> <span class="mi">112_813_859</span> <span class="o">+</span> <span class="mi">56_406_929</span> <span class="o">+</span> <span class="mi">16</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">             <span class="o">=</span> <span class="mi">169_220_804</span></span></span></code></pre></div><p>Ahh, that exact same number.
It all makes sense now. <br>
I tried running this in later node versions , the program always breaks at that limit.</p>
<h2 id="takeaways">Takeaways</h2>
<ol>
<li>
<p>While the theoritical limit is way higher for array length, their maximum size and growth behavior are constrained by V8’s internal constants like <code>kMaxSize</code> and <code>kMaxLength</code>.</p>
</li>
<li>
<p>Understanding these engine-level limits is essential when working with extremely large arrays or developing performance-critical applications that rely on dynamic array growth.</p>
</li>
<li>
<p>For predictable and efficient handling of large numeric datasets, consider using typed arrays (<code>Uint8Array</code>, <code>Float64Array</code>, etc.), which have fixed size and avoid the overhead and limits of dynamic arrays.</p>
</li>
</ol>
]]></content:encoded></item><item><title>Python's Native Proxy Pattern</title><link>https://mohit-k-s.github.io/blog/2025/method_interceptor/</link><pubDate>Fri, 08 Aug 2025 00:00:00 +0000</pubDate><guid>https://mohit-k-s.github.io/blog/2025/method_interceptor/</guid><description>&lt;p&gt;While making &lt;a href="https://github.com/mohit-k-s/cwgrep"&gt;cwgrep&lt;/a&gt;, I wanted to log every AWS call the tool made without wrapping each method manually.&lt;/p&gt;
&lt;p&gt;Turns out, Python lets you do that cleanly.&lt;/p&gt;
&lt;h2 id="__getattr__"&gt;__getattr__&lt;/h2&gt;
&lt;p&gt;This is a Python dunder method that’s only called when:
The attribute named name is not found in the usual places (&lt;strong&gt;__dict__&lt;/strong&gt;, class, etc.).
So if self.describe_log_groups doesn’t exist on the AWSInterceptor instance, Python calls &lt;strong&gt;__getattr__&lt;/strong&gt;(&amp;lsquo;describe_log_groups&amp;rsquo;).
This lets you act as a transparent proxy, forwarding unknown method accesses to the real client.&lt;/p&gt;</description><content:encoded><![CDATA[<p>While making <a href="https://github.com/mohit-k-s/cwgrep">cwgrep</a>, I wanted to log every AWS call the tool made without wrapping each method manually.</p>
<p>Turns out, Python lets you do that cleanly.</p>
<h2 id="__getattr__">__getattr__</h2>
<p>This is a Python dunder method that’s only called when:
The attribute named name is not found in the usual places (<strong>__dict__</strong>, class, etc.).
So if self.describe_log_groups doesn’t exist on the AWSInterceptor instance, Python calls <strong>__getattr__</strong>(&lsquo;describe_log_groups&rsquo;).
This lets you act as a transparent proxy, forwarding unknown method accesses to the real client.</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">import</span> <span class="nn">time</span> 
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="kn">import</span> <span class="nn">functools</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="n">SENSITIVE_KEYS</span> <span class="o">=</span> <span class="p">{</span><span class="s2">&#34;password&#34;</span><span class="p">,</span> <span class="s2">&#34;secret&#34;</span><span class="p">,</span> <span class="s2">&#34;token&#34;</span><span class="p">,</span> <span class="s2">&#34;key&#34;</span><span class="p">}</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="k">def</span> <span class="nf">sanitize</span><span class="p">(</span><span class="n">params</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="k">def</span> <span class="nf">_sanitize</span><span class="p">(</span><span class="n">obj</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">        <span class="k">if</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">obj</span><span class="p">,</span> <span class="nb">dict</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">            <span class="k">return</span> <span class="p">{</span><span class="n">k</span><span class="p">:</span> <span class="s2">&#34;***&#34;</span> <span class="k">if</span> <span class="n">k</span><span class="o">.</span><span class="n">lower</span><span class="p">()</span> <span class="ow">in</span> <span class="n">SENSITIVE_KEYS</span> <span class="k">else</span> <span class="n">_sanitize</span><span class="p">(</span><span class="n">v</span><span class="p">)</span> <span class="k">for</span> <span class="n">k</span><span class="p">,</span> <span class="n">v</span> <span class="ow">in</span> <span class="n">obj</span><span class="o">.</span><span class="n">items</span><span class="p">()}</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">        <span class="k">if</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">obj</span><span class="p">,</span> <span class="nb">list</span><span class="p">)</span> <span class="ow">and</span> <span class="nb">len</span><span class="p">(</span><span class="n">obj</span><span class="p">)</span> <span class="o">&gt;</span> <span class="mi">10</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">            <span class="k">return</span> <span class="sa">f</span><span class="s2">&#34;[</span><span class="si">{</span><span class="nb">len</span><span class="p">(</span><span class="n">obj</span><span class="p">)</span><span class="si">}</span><span class="s2"> items]&#34;</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">        <span class="k">return</span> <span class="n">obj</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="k">return</span> <span class="n">_sanitize</span><span class="p">(</span><span class="n">params</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">
</span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="k">class</span> <span class="nc">AWSInterceptor</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">client</span><span class="p">,</span> <span class="n">logger</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">_client</span> <span class="o">=</span> <span class="n">client</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">_logger</span> <span class="o">=</span> <span class="n">logger</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">
</span></span><span class="line"><span class="ln">20</span><span class="cl">    <span class="k">def</span> <span class="fm">__getattr__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">name</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">        <span class="n">method</span> <span class="o">=</span> <span class="nb">getattr</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_client</span><span class="p">,</span> <span class="n">name</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">        <span class="k">if</span> <span class="ow">not</span> <span class="n">callable</span><span class="p">(</span><span class="n">method</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">            <span class="k">return</span> <span class="n">method</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">
</span></span><span class="line"><span class="ln">25</span><span class="cl">        <span class="nd">@functools.wraps</span><span class="p">(</span><span class="n">method</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">        <span class="k">def</span> <span class="nf">wrapped</span><span class="p">(</span><span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">            <span class="n">start</span> <span class="o">=</span> <span class="n">time</span><span class="o">.</span><span class="n">time</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">            <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl">                <span class="n">result</span> <span class="o">=</span> <span class="n">method</span><span class="p">(</span><span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">                <span class="n">duration</span> <span class="o">=</span> <span class="n">time</span><span class="o">.</span><span class="n">time</span><span class="p">()</span> <span class="o">-</span> <span class="n">start</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">                <span class="bp">self</span><span class="o">.</span><span class="n">_logger</span><span class="o">.</span><span class="n">log_call</span><span class="p">(</span><span class="n">name</span><span class="p">,</span> <span class="n">duration</span><span class="p">,</span> <span class="n">success</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span> <span class="n">params</span><span class="o">=</span><span class="n">sanitize</span><span class="p">(</span><span class="n">kwargs</span><span class="p">))</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl">                <span class="k">return</span> <span class="n">result</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl">            <span class="k">except</span> <span class="ne">Exception</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">34</span><span class="cl">                <span class="n">duration</span> <span class="o">=</span> <span class="n">time</span><span class="o">.</span><span class="n">time</span><span class="p">()</span> <span class="o">-</span> <span class="n">start</span>
</span></span><span class="line"><span class="ln">35</span><span class="cl">                <span class="bp">self</span><span class="o">.</span><span class="n">_logger</span><span class="o">.</span><span class="n">log_call</span><span class="p">(</span><span class="n">name</span><span class="p">,</span> <span class="n">duration</span><span class="p">,</span> <span class="n">success</span><span class="o">=</span><span class="kc">False</span><span class="p">,</span> <span class="n">error</span><span class="o">=</span><span class="nb">str</span><span class="p">(</span><span class="n">e</span><span class="p">),</span> <span class="n">params</span><span class="o">=</span><span class="n">sanitize</span><span class="p">(</span><span class="n">kwargs</span><span class="p">))</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl">                <span class="k">raise</span>
</span></span><span class="line"><span class="ln">37</span><span class="cl">
</span></span><span class="line"><span class="ln">38</span><span class="cl">        <span class="k">return</span> <span class="n">wrapped</span></span></span></code></pre></div><p>Notice the weird decorator ?</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl">        <span class="nd">@functools.wraps</span><span class="p">(</span><span class="n">method</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">        <span class="k">def</span> <span class="nf">wrapped</span><span class="p">(</span><span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">):</span></span></span></code></pre></div><p>This is classic decorator technique, but applied dynamically.
It preserves metadata like __name__, __doc__, and signature, so logs and debuggers still refer to the original method, not wrapped.</p>
<p>Now i only had to do</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="n">real_client</span> <span class="o">=</span> <span class="n">boto3</span><span class="o">.</span><span class="n">client</span><span class="p">(</span><span class="s2">&#34;logs&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="n">proxy</span> <span class="o">=</span> <span class="n">AWSInterceptor</span><span class="p">(</span><span class="n">real_client</span><span class="p">,</span> <span class="n">logger</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="n">proxy</span><span class="o">.</span><span class="n">describe_log_groups</span><span class="p">()</span></span></span></code></pre></div><p>That’s it.</p>
]]></content:encoded></item><item><title>Test what you write</title><link>https://mohit-k-s.github.io/blog/2025/test_what_you_write/</link><pubDate>Wed, 30 Jul 2025 00:00:00 +0000</pubDate><guid>https://mohit-k-s.github.io/blog/2025/test_what_you_write/</guid><description>&lt;p&gt;Yesterday , I noticed this weird error in logs.&lt;/p&gt;





&lt;pre tabindex="0"&gt;&lt;code&gt;2025-07-30 10:00:00.000 UTC [1] LOG: operator does not exist: uuid = record at character 50
2025-07-30 10:00:00.000 UTC [1] LOG: statement: SELECT (orders.id) FROM order WHERE orders = ($1, $2)&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;It was immediately clear this was a bug the query attempts to compare a column to a record, which is not valid syntax in Postgres using the &lt;code&gt;=&lt;/code&gt; operator.&lt;/p&gt;
&lt;p&gt;When I brought it up with the developers and QA responsible for the feature, they insisted it had been tested and was working as expected.&lt;/p&gt;</description><content:encoded><![CDATA[<p>Yesterday , I noticed this weird error in logs.</p>





<pre tabindex="0"><code>2025-07-30 10:00:00.000 UTC [1] LOG:  operator does not exist: uuid = record at character 50
2025-07-30 10:00:00.000 UTC [1] LOG:  statement: SELECT (orders.id) FROM order WHERE orders = ($1, $2)</code></pre><p>It was immediately clear this was a bug the query attempts to compare a column to a record, which is not valid syntax in Postgres using the <code>=</code> operator.</p>
<p>When I brought it up with the developers and QA responsible for the feature, they insisted it had been tested and was working as expected.</p>
<p>This prompted me to take a closer look. My assumption was that the intent here was to check whether <code>orders</code> matched <em>any</em> of the values, which would require using the <code>IN</code> operator, like:</p>





<pre tabindex="0"><code>SELECT orders.id FROM order WHERE orders IN ($1, $2)</code></pre><p>Turns out they didn&rsquo;t expect multiple values and the use case only expected one entry in array , which of course will work fine. But the language in code was something that did expect array.</p>
<p>What was not clear to me is who is to blame?</p>
<p>Lessons learned:</p>
<ul>
<li>If your query supports only one value, enforce it.</li>
<li>If your query supports multiple values, use the right operator.</li>
<li>And most importantly: test what you write, especially with different shapes of input.</li>
</ul>
]]></content:encoded></item><item><title>Common Distributed Systems Lingo</title><link>https://mohit-k-s.github.io/blog/2025/distributed_systems_lingo/</link><pubDate>Tue, 29 Jul 2025 00:00:00 +0000</pubDate><guid>https://mohit-k-s.github.io/blog/2025/distributed_systems_lingo/</guid><description>&lt;p&gt;There are a few terms that keep popping up when you work with distributed systems. They&amp;rsquo;re often thrown around casually, and it’s easy to nod along without fully understanding them. I’m collecting those here with simple definitions and examples. This list will evolve as I keep learning.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="fencing-token"&gt;Fencing Token&lt;/h2&gt;
&lt;p&gt;I was reading &lt;a href="https://avi.im/blag/2024/s3-log/#:~:text=a%20concept%20of-,fencing%20tokens.,-I%E2%80%99ve%20left%20this"&gt;V&amp;rsquo;s blog&lt;/a&gt; and he mentioned fencing token. This word was introduced in Martin&amp;rsquo;s blog &lt;a href="https://martin.kleppmann.com/2016/02/08/how-to-do-distributed-locking.html"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;A &lt;strong&gt;fencing token&lt;/strong&gt; is a number (usually monotonically increasing) that helps prevent &lt;strong&gt;split-brain writes&lt;/strong&gt; or &lt;strong&gt;duplicate actions&lt;/strong&gt; in distributed systems.&lt;/p&gt;</description><content:encoded><![CDATA[<p>There are a few terms that keep popping up when you work with distributed systems. They&rsquo;re often thrown around casually, and it’s easy to nod along without fully understanding them. I’m collecting those here with simple definitions and examples. This list will evolve as I keep learning.</p>
<hr>
<h2 id="fencing-token">Fencing Token</h2>
<p>I was reading <a href="https://avi.im/blag/2024/s3-log/#:~:text=a%20concept%20of-,fencing%20tokens.,-I%E2%80%99ve%20left%20this">V&rsquo;s blog</a> and he mentioned fencing token. This word was introduced in Martin&rsquo;s blog <a href="https://martin.kleppmann.com/2016/02/08/how-to-do-distributed-locking.html">here</a>.</p>
<p>A <strong>fencing token</strong> is a number (usually monotonically increasing) that helps prevent <strong>split-brain writes</strong> or <strong>duplicate actions</strong> in distributed systems.</p>
<p>It’s commonly used in <strong>distributed locks</strong>. Here&rsquo;s why:</p>
<p>Let’s say two nodes both believe they hold a lock (due to clock drift or network delays). Without fencing, both might write to a shared resource. With fencing, each action is tagged with a <strong>token</strong> (say, 41 and 42). The system can reject the one with the lower token, assuming it’s stale.</p>
<blockquote>
<p><strong>Only the action with the highest fencing token is considered valid.</strong></p></blockquote>
<p>This way, even if the lock coordination fails, the system can still reject outdated operations.</p>
<hr>
<h2 id="network-partition">Network Partition</h2>
<p>A <strong>network partition</strong> is when a group of nodes in a distributed system can no longer talk to another group - not because the nodes have failed, but because something in the network is blocking communication between them.</p>
<p>Think of it as the system getting split into isolated islands.</p>
<blockquote>
<p><strong>Note:</strong> A partition is about communication failure, not node failure.</p></blockquote>
<h3 id="examples">Examples:</h3>
<ul>
<li>A node can talk to nodes A and B, but not to node C. That’s a network partition.</li>
<li>Someone misconfigures firewall rules between two subnets. That’s a network partition.</li>
<li>NACLs (Network Access Control Lists) are misconfigured to block traffic between nodes.</li>
</ul>
<hr>
]]></content:encoded></item><item><title>Creativity</title><link>https://mohit-k-s.github.io/blog/2025/creativity/</link><pubDate>Sun, 15 Jun 2025 00:00:00 +0000</pubDate><guid>https://mohit-k-s.github.io/blog/2025/creativity/</guid><description>&lt;p&gt;Yesterday, I made my first substack post and I promised myself I’d continue writing, just like I used to back in 10th grade. There’s something deeply fulfilling about writing. I’ve missed that feeling for a long time, and now I’m trying to find my way back to it. So here I am. We’d start with what starts things.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Creativity (noun): the use of imagination or original ideas to create something&lt;/p&gt;</description><content:encoded><![CDATA[<p>Yesterday, I made my first substack post and I promised myself I’d continue writing, just like I used to back in 10th grade. There’s something deeply fulfilling about writing. I’ve missed that feeling for a long time, and now I’m trying to find my way back to it. So here I am. We’d start with what starts things.</p>
<blockquote>
<p>Creativity (noun): the use of imagination or original ideas to create something</p></blockquote>
<p>Creativity, as we all know, is one of the fundamental pillars on which the entire arc of human evolution rests.
If not for original ideas, we’d still be hunting for food and hiding from the cold.</p>
<p>At its core, creativity is the act of combining our mental capacities and taking what’s already available and reshaping it into something new.
This new “thing” doesn’t have to be grand. It can be a poem, a cooling machine, a ziplock bag, or a story whispered by firelight.</p>
<p>Creativity is not limited to the arts. It lives in engineering, cooking, survival, protest, play. It’s how we’ve adapted, improved, and often just endured.
It’s invention born not only from inspiration, but from constraint.</p>
<p>When was the last time you created something ? When was the last time you sat down and just gave your best to do something by yourself , with just what you know. There’s something about that feeling, right?</p>
<p>That quiet intensity. That sense of this is my work.</p>
<p>Of course, people take help and that’s not wrong. We’re not meant to be experts at everything.
But still there’s a certain joy, a deep satisfaction, in being completely immersed in the one thing you love.
To create something original.</p>
<p>I enjoy writing and you might enjoy knitting, that last piece in which you made a cool pattern surely is the word of mouth.</p>
<p>Why ?
Creating is often linked to immediate benefits for society, and for yourself.
Make something useful, and the world might reward you with recognition, money, maybe even status.</p>
<p>That’s fine. There’s no shame in it. But pause for a moment.
Ask yourself: Do you create to live, or live to create?</p>
<p>There’s something quietly romantic almost defiant about calling yourself an original.
About making something not because it’s useful, but because it had to be made.
Not for profit, not for praise. Just for the act. For the joy.</p>
<p>No judgment, though. I respect all creators the practical, the poetic, the ones who ship, and the ones who scribble on napkins and never show a soul.</p>
<p>But if you’re someone who feels that strange pull to make things that don’t quite have a reason yet, keep going.
That’s the kind of creation that changes things.</p>
<p>How ?
The why is simple it’s the spark that gets you going. But it’s not enough. To actually create something, you have to put in the hours. The people you admire weren’t creators from the start they were learners. That’s the part people often skip. You have to be willing to learn, to fail, and to repeat the process over and over again. Obsess over it.</p>
<p>Create with so much intensity that it borders on madness because the truth is, creativity isn’t a lightning strike.</p>
<p>In this era of knowledge, everything is within reach. Imagine Newton the greatest of all time having access to the tools and information we have today. The possibilities would’ve been endless. But here’s the thing: you also live in this era. You, too, can do what you dream of. It’s just not going to happen overnight. Real creation takes time.</p>
<p>And it will demand things from you that most people aren’t willing to give up comfort, distraction, certainty. I know that’s debatable, but it’s often the trade for creating something truly great. History is full of that proof. The people who changed things didn’t always live balanced lives but they lived driven ones. If you’re willing to pay that price, even in small doses, the doors are wide open.</p>
<p>But does that mean you can’t call small wins a win? Not at all. That’s the best part. In the art of creation, everything new is new. Every small step forward counts. A sentence written, a sketch finished, a bug finally fixed they all matter. You don’t need to change the world to call yourself a creator. You just need to keep creating.</p>
<p>What ?
This one’s simple it can be anything. Anything that already exists and just needs a little polishing, or something that doesn’t exist yet at all. It could be a new music piece, a sketch, a blog post, an app , or even your next dish.</p>
<p>The “what” of creation isn’t fixed. That’s the beauty of it. There are a thousand answers, and they’re all valid. What matters is that you decide to make something.
</p>
]]></content:encoded></item></channel></rss>