<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Kiran Sabne — Engineering Notes]]></title><description><![CDATA[Databases · Backend Systems · Applied AI · Scaling Architecture]]></description><link>https://kiransabne.dev</link><generator>RSS for Node</generator><lastBuildDate>Wed, 15 Apr 2026 19:00:47 GMT</lastBuildDate><atom:link href="https://kiransabne.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[How do you write efficient pagination for millions of rows?]]></title><description><![CDATA[Efficient pagination is absolutely critical when dealing with millions of rows in PostgreSQL — especially if you care about performance, index usage, and UX for APIs or UI pages.
1. OFFSET/LIMIT Pagination (a.k.a. “Skip-Limit”)
SELECT * FROM orders
O...]]></description><link>https://kiransabne.dev/how-to-write-efficient-pagination-for-millions-of-rows</link><guid isPermaLink="true">https://kiransabne.dev/how-to-write-efficient-pagination-for-millions-of-rows</guid><category><![CDATA[software development]]></category><category><![CDATA[Software Engineering]]></category><category><![CDATA[Databases]]></category><category><![CDATA[Pagination]]></category><dc:creator><![CDATA[kiran sabne]]></dc:creator><pubDate>Fri, 09 Jan 2026 03:03:59 GMT</pubDate><content:encoded><![CDATA[<p>Efficient pagination is <strong>absolutely critical</strong> when dealing with <strong>millions of rows</strong> in PostgreSQL — especially if you care about performance, index usage, and UX for APIs or UI pages.</p>
<h2 id="heading-1-offsetlimit-pagination-aka-skip-limit">1. OFFSET/LIMIT Pagination (a.k.a. “Skip-Limit”)</h2>
<pre><code class="lang-sql"><span class="hljs-keyword">SELECT</span> * <span class="hljs-keyword">FROM</span> orders
<span class="hljs-keyword">ORDER</span> <span class="hljs-keyword">BY</span> created_at
<span class="hljs-keyword">OFFSET</span> <span class="hljs-number">10000</span>
<span class="hljs-keyword">LIMIT</span> <span class="hljs-number">50</span>;
</code></pre>
<h3 id="heading-pros">Pros:</h3>
<ul>
<li><p>Simple to implement</p>
</li>
<li><p>Page numbers are intuitive for UIs</p>
</li>
</ul>
<h3 id="heading-cons">Cons:</h3>
<ul>
<li><p><strong>Slower as OFFSET increases</strong> — PostgreSQL must <strong>scan and discard</strong> the skipped rows</p>
</li>
<li><p><strong>Non-deterministic</strong> under concurrent updates (rows can shift across pages)</p>
</li>
<li><p>Doesn’t scale beyond 100k rows</p>
</li>
</ul>
<h3 id="heading-when-to-use">When to use:</h3>
<ul>
<li><p>Small datasets (or &lt;100 pages)</p>
</li>
<li><p>Admin dashboards or static data</p>
</li>
<li><p>Temporary/internal tooling</p>
</li>
</ul>
<h2 id="heading-2-keyset-pagination-aka-seek-method">2. <strong>Keyset Pagination (a.k.a. Seek Method)</strong></h2>
<h3 id="heading-description">Description:</h3>
<p>Use a <strong>cursor</strong> (like <code>created_at, id</code>) to fetch the next page.</p>
<pre><code class="lang-sql"><span class="hljs-comment">-- First page</span>
<span class="hljs-keyword">SELECT</span> * <span class="hljs-keyword">FROM</span> orders
<span class="hljs-keyword">ORDER</span> <span class="hljs-keyword">BY</span> created_at, <span class="hljs-keyword">id</span>
<span class="hljs-keyword">LIMIT</span> <span class="hljs-number">50</span>;

<span class="hljs-comment">-- Next page (cursor = last row)</span>
<span class="hljs-keyword">SELECT</span> * <span class="hljs-keyword">FROM</span> orders
<span class="hljs-keyword">WHERE</span> (created_at, <span class="hljs-keyword">id</span>) &gt; (<span class="hljs-string">'2024-07-01 10:00:00'</span>, <span class="hljs-string">'uuid-123'</span>)
<span class="hljs-keyword">ORDER</span> <span class="hljs-keyword">BY</span> created_at, <span class="hljs-keyword">id</span>
<span class="hljs-keyword">LIMIT</span> <span class="hljs-number">50</span>;
</code></pre>
<h3 id="heading-pros-1">Pros:</h3>
<ul>
<li><p><strong>O(1)</strong> pagination — always uses index seek</p>
</li>
<li><p>Safe under high concurrency</p>
</li>
<li><p>Deterministic (no skipping/overlaps)</p>
</li>
<li><p>Ideal for <strong>real-time apps</strong></p>
</li>
</ul>
<h3 id="heading-cons-1">Cons:</h3>
<ul>
<li><p>Can't jump to arbitrary page (e.g., page 10)</p>
</li>
<li><p>Cursor management required</p>
</li>
</ul>
<h3 id="heading-index-recommendation">Index recommendation:</h3>
<pre><code class="lang-sql"><span class="hljs-keyword">CREATE</span> <span class="hljs-keyword">INDEX</span> <span class="hljs-keyword">ON</span> orders (created_at, <span class="hljs-keyword">id</span>);
</code></pre>
<h2 id="heading-3-hybrid-keyset-offset-for-jumping-to-specific-pages">3. <strong>Hybrid Keyset + Offset (for Jumping to Specific Pages)</strong></h2>
<h3 id="heading-description-1">Description:</h3>
<p>Use <strong>OFFSET</strong> for initial jump + <strong>keyset</strong> after that.</p>
<blockquote>
<p>E.g., cache the <code>(created_at, id)</code> of the first row on page 10 → use that as keyset.</p>
</blockquote>
<h3 id="heading-pros-2">Pros:</h3>
<ul>
<li><p>Allows page jumping</p>
</li>
<li><p>Still uses index efficiently from that point onward</p>
</li>
</ul>
<h3 id="heading-cons-2">Cons:</h3>
<ul>
<li><p>Adds complexity (requires cursor cache)</p>
</li>
<li><p>Still slow on very high page numbers if not cached</p>
</li>
</ul>
<h3 id="heading-use-case">Use case:</h3>
<ul>
<li>UIs where both page numbers and infinite scroll are required</li>
</ul>
<h2 id="heading-4-cursor-based-pagination-encoded-cursors">4. <strong>Cursor-based Pagination (Encoded Cursors)</strong></h2>
<p>This is <strong>commonly used in APIs</strong> (GraphQL, REST with cursor tokens).</p>
<h3 id="heading-description-2">Description:</h3>
<p>Return a <strong>base64-encoded cursor</strong> like:</p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"next_cursor"</span>: <span class="hljs-string">"created_at=2024-07-01T10:00:00&amp;id=abc-123"</span>
}
</code></pre>
<p>In the next query:</p>
<pre><code class="lang-sql"><span class="hljs-keyword">SELECT</span> * <span class="hljs-keyword">FROM</span> orders
<span class="hljs-keyword">WHERE</span> (created_at, <span class="hljs-keyword">id</span>) &gt; (:created_at, :<span class="hljs-keyword">id</span>)
<span class="hljs-keyword">ORDER</span> <span class="hljs-keyword">BY</span> created_at, <span class="hljs-keyword">id</span>
<span class="hljs-keyword">LIMIT</span> <span class="hljs-number">50</span>;
</code></pre>
<h3 id="heading-pros-3">Pros:</h3>
<ul>
<li><p>API-friendly, stateless pagination</p>
</li>
<li><p>Easy to cache cursors</p>
</li>
<li><p>Works with dynamic filters</p>
</li>
</ul>
<h2 id="heading-how-to-handle-updates-concurrent-changes">How to Handle <strong>UPDATEs / Concurrent Changes</strong></h2>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Challenge</td><td>Problem</td></tr>
</thead>
<tbody>
<tr>
<td>Rows inserted/deleted during pagination</td><td>Causes "row shifting" on OFFSET pagination</td></tr>
<tr>
<td>Rows updated (e.g., created_at changed)</td><td>Might move between pages if paginating on that column</td></tr>
</tbody>
</table>
</div><h2 id="heading-for-large-scale-dataset-pagination">For Large Scale Dataset pagination</h2>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Tip</td><td>Why it Matters</td></tr>
</thead>
<tbody>
<tr>
<td>Use composite indexes</td><td><code>(created_at, id)</code> is better than <code>created_at</code> alone</td></tr>
<tr>
<td>Avoid OFFSET over 10k</td><td>degrades performance fast</td></tr>
<tr>
<td>Consider materialized views or temp tables</td><td>for heavy analytical pagination</td></tr>
<tr>
<td>Expose cursor in your API layer</td><td>makes UX smooth &amp; stateless</td></tr>
<tr>
<td>Cache row cursors by page</td><td>hybrid offset-keyset model</td></tr>
<tr>
<td>Use <code>REPEATABLE READ</code> isolation</td><td>if pagination happens within a transaction to avoid data shifts</td></tr>
</tbody>
</table>
</div><h2 id="heading-when-to-use-each-method">When to Use Each Method</h2>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Use Case</td><td>Recommended Pagination</td></tr>
</thead>
<tbody>
<tr>
<td>Admin dashboard &lt; 10k rows</td><td>OFFSET/LIMIT</td></tr>
<tr>
<td>Real-time web/mobile UI</td><td>Keyset pagination</td></tr>
<tr>
<td>Reporting with stable pages</td><td>Window function with <code>ROW_NUMBER()</code></td></tr>
<tr>
<td>APIs with infinite scroll</td><td>Cursor-based (encoded cursor tokens)</td></tr>
<tr>
<td>UI with both pages &amp; infinite scroll</td><td>Hybrid keyset + cursor caching</td></tr>
</tbody>
</table>
</div>]]></content:encoded></item><item><title><![CDATA[PostgreSQL Parameter Tuning for 100M+ Rows]]></title><description><![CDATA[When you’re running PostgreSQL with 100M+ rows, the difference between “it works” and “it flies” is all about tuning—but not the generic, copy-paste advice you get everywhere. Here’s the advanced, battle-tested checklist I’d give to serious DBAs and ...]]></description><link>https://kiransabne.dev/postgresql-parameter-tuning-for-100m-rows</link><guid isPermaLink="true">https://kiransabne.dev/postgresql-parameter-tuning-for-100m-rows</guid><category><![CDATA[PostgreSQL]]></category><category><![CDATA[Databases]]></category><category><![CDATA[software development]]></category><category><![CDATA[Software Engineering]]></category><category><![CDATA[software architecture]]></category><category><![CDATA[Programming Blogs]]></category><category><![CDATA[query-optimization]]></category><dc:creator><![CDATA[kiran sabne]]></dc:creator><pubDate>Sun, 04 Jan 2026 18:30:39 GMT</pubDate><content:encoded><![CDATA[<p>When you’re running PostgreSQL with 100M+ rows, the difference between “it works” and “it flies” is all about <em>tuning</em>—but not the generic, copy-paste advice you get everywhere. Here’s <strong>the advanced, battle-tested checklist</strong> I’d give to serious DBAs and developers working at scale.</p>
<h2 id="heading-the-hidden-checklist-you-actually-need">The Hidden Checklist You Actually Need</h2>
<h3 id="heading-1-understand-your-workload-first">1. Understand Your Workload First</h3>
<p>Not a setting, but <strong>non-negotiable</strong>.<br />Ask yourself:</p>
<ul>
<li><p>OLTP or OLAP?</p>
</li>
<li><p>Read-heavy or write-heavy?</p>
</li>
<li><p>Bulk inserts? Lots of small transactions?</p>
</li>
<li><p>Real-time queries or batch processing?</p>
</li>
</ul>
<p>Every parameter depends on this.</p>
<h3 id="heading-2-sharedbuffers-but-dont-max-it-out">2. <code>shared_buffers</code> – But Don’t Max It Out</h3>
<p><strong>Common advice</strong>: set <code>shared_buffers = 25% of RAM</code><br /><strong>Better</strong>:<br />1. If your queries are heavy on joins and large scans: go higher (30–40%)<br />2. For many connections and small transactions: stay conservative (15–20%)<br />3. If using a connection pooler: you can afford a bit more here</p>
<p>Rule of Thumb: benchmark at 25%, then test up/down in 5% steps.</p>
<h3 id="heading-3-workmem-the-secret-weapon-for-big-joins">3. <code>work_mem</code> – The Secret Weapon for Big Joins</h3>
<p><strong>This controls sort/hash memory per operation</strong>, not per query.<br />Default is <em>tiny</em>. Too tiny.</p>
<p>Tune it dynamically:</p>
<pre><code class="lang-python">SET work_mem = <span class="hljs-string">'256MB'</span>;  -- per session
</code></pre>
<p>But for config:</p>
<pre><code class="lang-python">work_mem = <span class="hljs-number">64</span>MB   <span class="hljs-comment"># OLAP</span>
work_mem = <span class="hljs-number">4</span><span class="hljs-number">-16</span>MB <span class="hljs-comment"># OLTP</span>
</code></pre>
<p>Use <code>EXPLAIN ANALYZE</code> → look for <strong>“disk”</strong> in sort/hash ops. If you see that? Bump it.</p>
<h3 id="heading-4-maintenanceworkmem-bulk-ops-love-this">4. <code>maintenance_work_mem</code> – Bulk Ops Love This</h3>
<p>Used for <code>VACUUM</code>, <code>CREATE INDEX</code>, etc.<br />1. Default is too low (64MB).<br />2. Crank it up <em>when running vacuums manually</em>:</p>
<pre><code class="lang-python">maintenance_work_mem = <span class="hljs-number">1</span>GB
</code></pre>
<p>If you're indexing 100M rows, go <strong>2–4GB</strong> (if RAM allows).</p>
<h3 id="heading-5-effectivecachesize-inform-the-planner">5. <code>effective_cache_size</code> – Inform the Planner</h3>
<p><strong>Doesn't use memory</strong>, just tells the planner how much OS cache is available.<br />Set it to 50–75% of total RAM:</p>
<pre><code class="lang-python">effective_cache_size = <span class="hljs-number">24</span>GB  <span class="hljs-comment"># on a 32GB machine</span>
</code></pre>
<p>Helps avoid bad nested loop plans on big tables.</p>
<h3 id="heading-6-autovacuum-tuning-the-untold-bottleneck">6. Autovacuum Tuning – The Untold Bottleneck</h3>
<p>Massive tables <strong>require aggressive VACUUM tuning</strong>, otherwise bloat kills you.</p>
<p>In <code>postgresql.conf</code> or via <code>ALTER TABLE</code>:</p>
<pre><code class="lang-python">autovacuum_vacuum_threshold = <span class="hljs-number">1000</span>
autovacuum_vacuum_scale_factor = <span class="hljs-number">0.01</span>
autovacuum_analyze_scale_factor = <span class="hljs-number">0.005</span>
autovacuum_max_workers = <span class="hljs-number">5</span>
autovacuum_naptime = <span class="hljs-number">10</span>s
autovacuum_vacuum_cost_limit = <span class="hljs-number">2000</span>
</code></pre>
<p>For HOT update-heavy workloads, consider <strong>lower thresholds + more workers</strong>.</p>
<h3 id="heading-7-parallelism-parameters-scale-joins-aggregates">7. Parallelism Parameters – Scale Joins + Aggregates</h3>
<p>Enable Postgres to use <strong>parallel query features</strong>.</p>
<pre><code class="lang-python">max_parallel_workers = <span class="hljs-number">8</span>
max_parallel_workers_per_gather = <span class="hljs-number">4</span>
parallel_tuple_cost = <span class="hljs-number">0.1</span>
parallel_setup_cost = <span class="hljs-number">1000</span>
</code></pre>
<p>Lower <code>parallel_tuple_cost</code> and <code>parallel_setup_cost</code> to encourage parallelism.</p>
<h3 id="heading-8-randompagecost-amp-seqpagecost-for-ssd-optimization">8. <code>random_page_cost</code> &amp; <code>seq_page_cost</code> – For SSD Optimization</h3>
<p>If on SSD (you should be), reduce these to reflect reality.</p>
<pre><code class="lang-python">random_page_cost = <span class="hljs-number">1.1</span>
seq_page_cost = <span class="hljs-number">1.0</span>
</code></pre>
<p>Default assumes spinning disks. SSDs have less cost difference between random vs sequential access.</p>
<h3 id="heading-9-connection-limits-dont-overload">9. Connection Limits – Don’t Overload</h3>
<p>Too many active connections will kill performance.</p>
<p>Use a <strong>connection pooler</strong> like <strong>PgBouncer</strong>:</p>
<pre><code class="lang-python">max_connections = <span class="hljs-number">100</span>  <span class="hljs-comment"># keep it low</span>
</code></pre>
<p>Let PgBouncer manage thousands of app connections.</p>
<h3 id="heading-10-logging-for-insightful-tuning">10. Logging for Insightful Tuning</h3>
<p>Turn on query logging to find bad queries:</p>
<pre><code class="lang-python">log_min_duration_statement = <span class="hljs-number">1000</span>  <span class="hljs-comment"># ms</span>
log_checkpoints = on
log_autovacuum_min_duration = <span class="hljs-number">0</span>
log_temp_files = <span class="hljs-number">0</span>
</code></pre>
<p>Then mine the logs and use <code>pg_stat_statements</code> for real performance work.</p>
<h2 id="heading-bonus-tableindex-level-tricks">Bonus: Table/Index-Level Tricks</h2>
<ul>
<li><p>Use <strong>BRIN indexes</strong> for append-only, timestamped data</p>
</li>
<li><p>Use <strong>partial indexes</strong> if only part of the data is queried often</p>
</li>
<li><p>Consider <strong>UNLOGGED tables</strong> for transient data (faster inserts, no WAL)</p>
</li>
<li><p>Use <code>pg_repack</code> to reclaim bloat without locking tables</p>
</li>
<li><p>Implement Partitioning &amp; Data Archival</p>
</li>
<li><p>Smart Indexing &amp; Monitoring index bloat regularly with pgstattuple</p>
</li>
<li><p>Tablespaces, Compression &amp; Storage</p>
</li>
<li><p>Logical/Native Replication to scale out</p>
</li>
<li><p>Aggressive Monitoring, Debugging &amp; Benchmarking</p>
</li>
</ul>
<h2 id="heading-tldr-the-advanced-tune-checklist">TL;DR – The Advanced Tune Checklist</h2>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Parameter</td><td>Suggested Value</td><td>Notes</td></tr>
</thead>
<tbody>
<tr>
<td>shared_buffers</td><td>25–40% RAM</td><td>Don’t go too high</td></tr>
<tr>
<td>work_mem</td><td>8MB–256MB</td><td>Depends on queries</td></tr>
<tr>
<td>maintenance_work_mem</td><td>1–4GB</td><td>For index/vacuum</td></tr>
<tr>
<td>effective_cache_size</td><td>~75% RAM</td><td>Informs planner</td></tr>
<tr>
<td>autovacuum_*</td><td>Aggressive</td><td>Keep bloat down</td></tr>
<tr>
<td>max_connections</td><td>≤ 100</td><td>Use PgBouncer</td></tr>
<tr>
<td>parallel_workers_*</td><td>4–8</td><td>Enable parallel queries</td></tr>
<tr>
<td>random_page_cost</td><td>1.1 (SSD)</td><td>Lower for SSDs</td></tr>
</tbody>
</table>
</div><p>If you're managing 100M+ rows, this is not optional anymore. It’s <strong>engineering</strong>. And it works. Add your scaling experience in the comments below.</p>
]]></content:encoded></item><item><title><![CDATA[Understanding and Solving Cache Stampede: The Invisible Threat to Databases]]></title><description><![CDATA[Imagine this:
Your high-traffic app is humming along smoothly. Most data requests are being served instantly from Redis or other, blazing-fast cache layer. But then — a cache key expires, and within milliseconds, 10,000 clients hit your backend at on...]]></description><link>https://kiransabne.dev/understanding-and-solving-cache-stampede-the-invisible-threat-to-databases</link><guid isPermaLink="true">https://kiransabne.dev/understanding-and-solving-cache-stampede-the-invisible-threat-to-databases</guid><category><![CDATA[PostgreSQL]]></category><category><![CDATA[Redis]]></category><category><![CDATA[caching]]></category><category><![CDATA[Software Engineering]]></category><category><![CDATA[Databases]]></category><category><![CDATA[performance]]></category><category><![CDATA[scalability]]></category><category><![CDATA[software architecture]]></category><dc:creator><![CDATA[kiran sabne]]></dc:creator><pubDate>Sat, 03 Jan 2026 09:25:17 GMT</pubDate><content:encoded><![CDATA[<p>Imagine this:</p>
<p>Your high-traffic app is humming along smoothly. Most data requests are being served instantly from Redis or other, blazing-fast cache layer. But then — <strong>a cache key expires</strong>, and within milliseconds, <strong>10,000 clients</strong> hit your backend at once trying to fetch the same data from the <strong>database</strong>.</p>
<p><strong>Boom</strong> — your production database is slammed, response times skyrocket, and the system becomes unresponsive.</p>
<p>You’ve just been trampled by a <strong>cache stampede</strong>.</p>
<h2 id="heading-what-is-a-cache-stampede">What is a Cache Stampede?</h2>
<p>A <strong>cache stampede</strong> (also called a <strong>cache miss storm</strong>) occurs when:</p>
<ol>
<li><p>A popular cache key expires</p>
</li>
<li><p>Many clients attempt to read the same key</p>
</li>
<li><p>All of them get a <strong>cache miss</strong></p>
</li>
<li><p>All of them <strong>bypass the cache simultaneously</strong></p>
</li>
<li><p>They hit the <strong>backend/database at once</strong></p>
</li>
<li><p>Overload happens — especially in high-concurrency environments</p>
</li>
</ol>
<blockquote>
<p>Even if your cache hit rate is 99%, <strong>a stampede on 1% of traffic can collapse the system</strong>.</p>
</blockquote>
<h2 id="heading-why-cache-stampedes-happen">Why Cache Stampedes Happen</h2>
<p>Caches are typically built with a <strong>cache-aside pattern</strong>, where:</p>
<ul>
<li><p>You <strong>check the cache</strong> first</p>
</li>
<li><p>If the key is missing, you <strong>recompute or fetch from the DB</strong></p>
</li>
<li><p>Then <strong>store it back into the cache</strong></p>
</li>
</ul>
<p>This works great for individual requests… but under heavy concurrency, when <strong>many requests miss the same key</strong>, all of them go through this pattern <strong>at the same time</strong>:</p>
<pre><code class="lang-python"><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">get_data</span>(<span class="hljs-params">key</span>):</span>
    data = redis.get(key)
    <span class="hljs-keyword">if</span> <span class="hljs-keyword">not</span> data:
        data = query_postgres()
        redis.set(key, data, ex=<span class="hljs-number">60</span>)
    <span class="hljs-keyword">return</span> data
</code></pre>
<p>Without protection, this causes 1000s of concurrent <code>query_postgres()</code> calls.</p>
<h2 id="heading-real-world-impact">Real-world Impact</h2>
<p>A cache stampede causes:</p>
<ul>
<li><p><strong>Sudden spikes in DB traffic</strong> (often 10x to 100x)</p>
</li>
<li><p><strong>Connection pool exhaustion</strong>s.</p>
</li>
<li><p><strong>Slow queries, timeouts, or even crashes</strong></p>
</li>
<li><p><strong>Denial of service</strong> for downstream services</p>
</li>
<li><p><strong>Resource contention across your stack</strong></p>
</li>
</ul>
<h2 id="heading-when-does-this-happen">When Does This Happen?</h2>
<ul>
<li><p>After a <strong>cache TTL expires</strong></p>
</li>
<li><p>After a <strong>cache eviction</strong> (due to memory pressure)</p>
</li>
<li><p>During <strong>cold starts</strong> or <strong>deployment rollouts</strong></p>
</li>
<li><p>When there’s <strong>only one shared cache key</strong> for a popular item (e.g., homepage data)</p>
</li>
</ul>
<h2 id="heading-how-to-mitigate-cache-stampedes">How to Mitigate Cache Stampedes</h2>
<p>There are few ways to mitigate Cache Stampedes.</p>
<h3 id="heading-1-request-coalescing-single-flight-pattern">1. <strong>Request Coalescing / Single-flight Pattern</strong></h3>
<p>Let only <strong>one request rebuild the cache</strong>, while others <strong>wait for it</strong> to finish.</p>
<p><strong>Concept</strong>:</p>
<ul>
<li><p>First request takes a lock and fetches the data from DB</p>
</li>
<li><p>Others wait for that request to populate the cache</p>
</li>
</ul>
<h3 id="heading-2-add-jitter-randomized-expiry">2. <strong>Add Jitter (Randomized Expiry)</strong></h3>
<p>Avoid simultaneous cache expiry by <strong>randomizing TTLs</strong>.</p>
<pre><code class="lang-json">ttl = random.randint(<span class="hljs-number">50</span>, <span class="hljs-number">70</span>)
redis.set(key, data, ex=ttl)
</code></pre>
<p>Prevents a "herd" of keys expiring together<br />Works best in high-concurrency apps with shared TTLs</p>
<h3 id="heading-3-serve-stale-data-while-rebuilding">3. <strong>Serve Stale Data While Rebuilding</strong></h3>
<p>Don’t block the user when the cache expires.</p>
<p>Instead:</p>
<ul>
<li><p>Return the <strong>stale value</strong></p>
</li>
<li><p>Trigger a <strong>background refresh</strong></p>
</li>
</ul>
<p>This is known as <strong>“stale-while-revalidate”</strong>.</p>
<p><strong>Implementation Approach</strong>:</p>
<ul>
<li><p>Store TTL metadata separately</p>
</li>
<li><p>If TTL is expired, serve stale data and refresh in background thread</p>
</li>
</ul>
<pre><code class="lang-json">if redis.ttl(key) &lt;= <span class="hljs-number">0</span>:
    async_refresh(key)
return redis.get(key)
</code></pre>
<h3 id="heading-4-proactive-cache-warming-preload">4. <strong>Proactive Cache Warming / Preload</strong></h3>
<p>Use background jobs to <strong>preload popular cache keys</strong> before they expire.</p>
<p>Example:</p>
<pre><code class="lang-json">hot_keys = ['homepage', 'top_products', 'user_analytics']

for key in hot_keys:
    data = query_postgres()
    redis.set(key, data, ex=<span class="hljs-number">60</span>)
</code></pre>
<p>Keeps your most important data hot<br />Ideal for dashboards, trending items, etc.</p>
<p>Use schedulers like <code>cron</code>, <code>Celery beat</code>, <code>Sidekiq</code>, etc.</p>
<h3 id="heading-5-use-multi-level-cache-l1-l2">5. <strong>Use Multi-Level Cache (L1 + L2)</strong></h3>
<p>Layered cache architecture:</p>
<ul>
<li><p><strong>L1 cache</strong>: In-process or in-memory (e.g. Python <code>LRUCache</code>)</p>
</li>
<li><p><strong>L2 cache</strong>: Redis / Memcached</p>
</li>
<li><p><strong>L3</strong>: Database</p>
</li>
</ul>
<p>Each layer reduces pressure on the next.</p>
<h3 id="heading-6-batch-writes-if-cache-miss-triggers-db-writes">6. <strong>Batch Writes (if cache miss triggers DB writes)</strong></h3>
<p>If the cache miss causes <strong>multiple writes</strong>, use <strong>queues</strong> like Kafka or Redis Streams to <strong>batch</strong> and smooth load.</p>
<h2 id="heading-postgresql-tips-for-surviving-stampedes">PostgreSQL Tips for Surviving Stampedes</h2>
<p>If it still happens:</p>
<ol>
<li><p><strong>Use PgBouncer</strong>: Reduces connection overhead</p>
</li>
<li><p><strong>Add read replicas</strong>: Distribute SELECT load</p>
</li>
<li><p><strong>Materialized Views</strong>: Precompute expensive queries</p>
</li>
<li><p><strong>Analyze slow queries</strong>: Use <code>EXPLAIN ANALYZE</code> + indexes</p>
</li>
<li><p><strong>Use rate-limiting middleware</strong>: Prevent DoS</p>
</li>
</ol>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Cache stampedes are <strong>easy to miss in staging</strong>, but devastating in production. You don’t need a DDoS to bring down your system — just a single cache key expiring under high load.</p>
<p>The fix isn’t just “increase cache TTL” — it’s about <strong>smart architecture</strong>:</p>
<ul>
<li><p>Let only one request rebuild</p>
</li>
<li><p>Serve stale when you can</p>
</li>
<li><p>Add randomness to TTL</p>
</li>
<li><p>Use background warming.</p>
</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[Create a Semantic Search API with FastAPI, Sentence-BERT, and PostgreSQL pgvector]]></title><description><![CDATA[In this post, I’ll walk you through how I built a semantic similarity search API using FastAPI, Sentence-BERT (SBERT), and PostgreSQL with the pgvector extension.This project began as a proof of concept (POC) to explore how seamlessly we can integrat...]]></description><link>https://kiransabne.dev/create-a-semantic-search-api-with-fastapi-sentence-bert-and-postgresql-pgvector</link><guid isPermaLink="true">https://kiransabne.dev/create-a-semantic-search-api-with-fastapi-sentence-bert-and-postgresql-pgvector</guid><category><![CDATA[PostgreSQL]]></category><category><![CDATA[postgres]]></category><category><![CDATA[software development]]></category><category><![CDATA[Software Engineering]]></category><category><![CDATA[software architecture]]></category><category><![CDATA[SQL]]></category><dc:creator><![CDATA[kiran sabne]]></dc:creator><pubDate>Tue, 14 Oct 2025 18:10:15 GMT</pubDate><content:encoded><![CDATA[<p>In this post, I’ll walk you through how I built a <strong>semantic similarity search API</strong> using <strong>FastAPI</strong>, <strong>Sentence-BERT (SBERT)</strong>, and <strong>PostgreSQL</strong> with the <strong>pgvector</strong> extension.<br />This project began as a <strong>proof of concept (POC)</strong> to explore how seamlessly we can integrate <strong>deep learning–based text embeddings</strong> into a <strong>traditional relational database</strong> for fast, contextual search — without introducing an external vector database or adding unnecessary complexity to the existing tech stack. Link to Repo - <a target="_blank" href="https://github.com/kiransabne04/fastapi-sbert-pgvector-similarity">kiransabne04/fastapi-sbert-pgvector-similarity: A FastAPI-based text similarity and semantic search API using Sentence-BERT (all-MiniLM-L6-v2) with PostgreSQL + pgvector for vector storage and similarity matching.</a></p>
<h2 id="heading-why-semantic-search">Why Semantic Search?</h2>
<p>Traditional keyword search only matches <strong>exact terms</strong>.<br />Semantic search, on the other hand, understands <strong>context</strong> — for example, the phrases:</p>
<blockquote>
<p>“How do I reset my password?”<br />and<br />“Forgot my login credentials.”</p>
</blockquote>
<p>mean the same thing, even though they use completely different words.</p>
<p>That’s what <strong>Sentence-BERT (SBERT)</strong> enables — it transforms sentences into numerical <strong>embeddings</strong> that capture semantic meaning.<br />Once we have those embeddings, we can use <strong>vector similarity</strong> (like cosine similarity) to find text with similar meaning.</p>
<h2 id="heading-high-overview">High Overview</h2>
<p>Here’s the high-level flow of the system we built:</p>
<ol>
<li><p><strong>FastAPI</strong> serves REST endpoints for inserting and searching text.</p>
</li>
<li><p><strong>Sentence-BERT</strong> generates a 384-dimensional vector for each text.</p>
</li>
<li><p><strong>PostgreSQL with pgvector</strong> stores these embeddings and performs fast similarity queries using vector math.</p>
</li>
</ol>
<h2 id="heading-tech-stack">Tech Stack</h2>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Component</td><td>Description</td></tr>
</thead>
<tbody>
<tr>
<td><strong>FastAPI</strong></td><td>Web framework for the API</td></tr>
<tr>
<td><strong>Sentence-Transformers</strong></td><td>Generates text embeddings (SBERT)</td></tr>
<tr>
<td><strong>PostgreSQL 15 + pgvector</strong></td><td>Stores embeddings and runs similarity search</td></tr>
<tr>
<td><strong>Uvicorn</strong></td><td>ASGI server for FastAPI</td></tr>
<tr>
<td><strong>Docker Compose</strong></td><td>Spins up Postgres with vector support</td></tr>
</tbody>
</table>
</div><p>Model used:</p>
<blockquote>
<p><code>all-MiniLM-L6-v2</code> — lightweight, accurate, and great for quick experimentation.</p>
</blockquote>
<p>Here’s how the key pieces fit together.</p>
<h3 id="heading-sentence-bert-embeddings">Sentence-BERT Embeddings</h3>
<p>Using Hugging Face’s <code>sentence-transformers</code> library, each text is transformed into a 384-dimensional embedding vector:</p>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> sentence_transformers <span class="hljs-keyword">import</span> SentenceTransformer

model = SentenceTransformer(<span class="hljs-string">"all-MiniLM-L6-v2"</span>)

text = <span class="hljs-string">"A forgotten attic filled with old memories"</span>
embedding = model.encode(text)
print(len(embedding))  <span class="hljs-comment"># 384</span>
</code></pre>
<h3 id="heading-postgresql-pgvector">PostgreSQL + pgvector</h3>
<p>To store and search these embeddings efficiently, we enable the <code>pgvector</code> extension.</p>
<pre><code class="lang-sql"><span class="hljs-keyword">CREATE</span> EXTENSION <span class="hljs-keyword">IF</span> <span class="hljs-keyword">NOT</span> <span class="hljs-keyword">EXISTS</span> vector;

<span class="hljs-keyword">CREATE</span> <span class="hljs-keyword">TABLE</span> items (
    <span class="hljs-keyword">id</span> <span class="hljs-built_in">SERIAL</span> PRIMARY <span class="hljs-keyword">KEY</span>,
    title <span class="hljs-built_in">TEXT</span> <span class="hljs-keyword">NOT</span> <span class="hljs-literal">NULL</span>,
    description <span class="hljs-built_in">TEXT</span> <span class="hljs-keyword">NOT</span> <span class="hljs-literal">NULL</span>,
    embedding VECTOR(<span class="hljs-number">384</span>),
    created_at <span class="hljs-built_in">TIMESTAMP</span> <span class="hljs-keyword">DEFAULT</span> <span class="hljs-keyword">NOW</span>()
);
</code></pre>
<p>We then create a vector index to speed up similarity queries:</p>
<pre><code class="lang-sql"><span class="hljs-keyword">CREATE</span> <span class="hljs-keyword">INDEX</span> items_embedding_idx
<span class="hljs-keyword">ON</span> items
<span class="hljs-keyword">USING</span> ivfflat (embedding vector_cosine_ops)
<span class="hljs-keyword">WITH</span> (lists = <span class="hljs-number">100</span>);
</code></pre>
<h3 id="heading-fastapi-endpoints">FastAPI Endpoints</h3>
<h4 id="heading-insertitem"><code>/insert_item</code></h4>
<p>Accepts multiple text items and inserts their embeddings into PostgreSQL.</p>
<pre><code class="lang-sql">@app.post('/insert_item')
def insert_items(request: ItemRequest):
    embeddings = [embedder.encode(i.description) for i in request.item_requests]
    <span class="hljs-comment"># Store in DB as vector</span>
</code></pre>
<h4 id="heading-findsimilar"><code>/find_similar</code></h4>
<p>Finds top-N most similar descriptions by comparing embeddings using pgvector’s cosine similarity operator <code>&lt;=&gt;</code>. There are other similarity operators as well for you to explore.</p>
<pre><code class="lang-sql"><span class="hljs-keyword">SELECT</span> title, description, <span class="hljs-number">1</span> - (embedding &lt;=&gt; %s::vector) <span class="hljs-keyword">AS</span> similarity
<span class="hljs-keyword">FROM</span> items
<span class="hljs-keyword">ORDER</span> <span class="hljs-keyword">BY</span> similarity <span class="hljs-keyword">DESC</span>
<span class="hljs-keyword">LIMIT</span> <span class="hljs-number">5</span>;
</code></pre>
<p>The result: a list of text items with <strong>semantic similarity scores</strong>.</p>
<h3 id="heading-example-in-action">Example in Action</h3>
<h3 id="heading-input-text">Input Text</h3>
<blockquote>
<p>“The smell of aged paper and leather in a quiet bookstore.”</p>
</blockquote>
<h3 id="heading-output">Output</h3>
<pre><code class="lang-sql">{
  "similar_description": [
    {
      "title": "A vintage bookstore",
      "description_text": "The bookstore smelled of aged paper and leather...",
      "similarity": 0.86,
      "similarity_percent": 86.42
    },
    {
      "title": "A forgotten attic",
      "description_text": "The air in the attic hung heavy <span class="hljs-keyword">with</span> the scent <span class="hljs-keyword">of</span> forgotten things...<span class="hljs-string">",
      "</span>similarity<span class="hljs-string">": 0.74,
      "</span>similarity_percent<span class="hljs-string">": 74.10
    }
  ]
}</span>
</code></pre>
<p>That’s semantic similarity in action. You can further improve/optimize it based on requirement and other pre-steps.</p>
<h2 id="heading-alternatives-to-sbert">Alternatives to SBERT</h2>
<p>Few other alternatives are</p>
<ol>
<li><p>intfloat/e5-large-v2</p>
</li>
<li><p>nomic-ai/nomic-embed-text-v1</p>
</li>
<li><p>OpenAI Embeddings (<code>text-embedding-3-large</code>)</p>
</li>
<li><p>Cohere Embeddings (<code>embed-multilingual-v3.0</code>)</p>
</li>
<li><p>Sentence-T5 or Universal Sentence Encoder (USE)</p>
</li>
</ol>
<h2 id="heading-thoughts">Thoughts:</h2>
<p>This little POC with proper architecture &amp; implementation has worked wonder for one of use cases. With just a few hundred lines of Python and SQL, you can build a real semantic search engine — no external AI infrastructure required.</p>
<p>If you’re exploring <strong>NLP, information retrieval, or vector databases</strong>, this is one of the best starting points you can build on.</p>
]]></content:encoded></item><item><title><![CDATA[PostgreSQL Indexing: When BRIN Is a Better Choice Than B-Tree]]></title><description><![CDATA[If you’re indexing huge, append-only tables with naturally ordered data — BRIN can give you massive performance and space benefits compared to B-Tree.
What is a BRIN Index?
BRIN = Block Range Index
A BRIN index doesn't index every row, unlike B-Tree....]]></description><link>https://kiransabne.dev/postgresql-indexing-when-brin-is-a-better-choice-than-b-tree</link><guid isPermaLink="true">https://kiransabne.dev/postgresql-indexing-when-brin-is-a-better-choice-than-b-tree</guid><category><![CDATA[software development]]></category><category><![CDATA[Software Engineering]]></category><category><![CDATA[software architecture]]></category><category><![CDATA[Databases]]></category><category><![CDATA[PostgreSQL]]></category><category><![CDATA[Programming Blogs]]></category><dc:creator><![CDATA[kiran sabne]]></dc:creator><pubDate>Sat, 04 Oct 2025 09:42:21 GMT</pubDate><content:encoded><![CDATA[<p>If you’re indexing <em>huge, append-only</em> tables with <em>naturally ordered</em> data — <strong>BRIN</strong> can give you massive performance and space benefits compared to <strong>B-Tree</strong>.</p>
<h2 id="heading-what-is-a-brin-index">What is a BRIN Index?</h2>
<p><strong>BRIN</strong> = <strong>Block Range Index</strong></p>
<p>A BRIN index <strong>doesn't index every row</strong>, unlike B-Tree. Instead, it stores <strong>summary data (min, max)</strong> about <em>physical blocks</em> of rows.</p>
<p>Think of BRIN as a <strong>metadata guide</strong>: it narrows down <strong>where</strong> to search, not <strong>what</strong> to return.</p>
<h2 id="heading-how-brin-works-internally">How BRIN Works Internally</h2>
<p>When you create a <strong>BRIN index</strong> on a column, PostgreSQL organizes the index <strong>by summarizing values over physical block ranges</strong> in the table.</p>
<p>For <strong>each block range</strong>, the BRIN index stores <strong>summary metadata</strong>, including:</p>
<ul>
<li><p><strong>Minimum value</strong> in the range</p>
</li>
<li><p><strong>Maximum value</strong> in the range</p>
</li>
<li><p>And other summary data, depending on the BRIN operator class</p>
</li>
</ul>
<h3 id="heading-during-index-creation">During Index Creation</h3>
<ul>
<li><p>PostgreSQL <strong>scans the table</strong> in fixed-size block ranges.</p>
</li>
<li><p>For each range, it <strong>stores a tuple</strong> in the BRIN index with the min/max values (and possibly other stats).</p>
</li>
</ul>
<h3 id="heading-during-query-execution">During Query Execution</h3>
<ol>
<li><p>When a query has a <strong>search condition</strong> (e.g., <code>WHERE column = 42</code>):</p>
</li>
<li><p>PostgreSQL consults the BRIN index to <strong>check the min/max values for each range</strong>.</p>
</li>
<li><p>If the search value <strong>falls outside</strong> a block range’s min/max → <strong>the range is skipped</strong>.</p>
</li>
<li><p>If it <strong>falls inside</strong> the range → PostgreSQL <strong>performs a heap scan</strong> on that range (similar to a sequential scan but limited to that range).</p>
</li>
</ol>
<blockquote>
<p>BRIN does <strong>not</strong> point to individual rows—just ranges. It’s extremely space-efficient, but depends heavily on <strong>data correlation</strong> (e.g., time-ordered inserts).</p>
</blockquote>
<h2 id="heading-brin-vs-b-tree-feature-comparison">BRIN vs. B-Tree: Feature Comparison</h2>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Feature</td><td>B-Tree</td><td>BRIN</td></tr>
</thead>
<tbody>
<tr>
<td>Index size</td><td>Large (grows with rows)</td><td>Tiny (constant per block)</td></tr>
<tr>
<td>Query performance</td><td>Fast, consistent</td><td>Depends on data distribution</td></tr>
<tr>
<td>Best for</td><td>Random access</td><td>Sequential, append-only data</td></tr>
<tr>
<td>Build time</td><td>Slow on big tables</td><td>Very fast</td></tr>
<tr>
<td>Maintenance</td><td>Higher</td><td>Minimal</td></tr>
<tr>
<td>Can be used for =, &gt;, &lt;</td><td>Yes</td><td>only helps if the data is ordered</td></tr>
</tbody>
</table>
</div><h2 id="heading-benchmark-brin-vs-b-tree-with-real-data">Benchmark: BRIN vs. B-Tree with Real Data</h2>
<h3 id="heading-dataset-100-million-records">Dataset: 100 million records</h3>
<pre><code class="lang-sql"><span class="hljs-keyword">CREATE</span> <span class="hljs-keyword">TABLE</span> sensor_data (
    <span class="hljs-keyword">id</span> BIGSERIAL PRIMARY <span class="hljs-keyword">KEY</span>,
    device_id <span class="hljs-built_in">INT</span>,
    temperature <span class="hljs-built_in">NUMERIC</span>,
    event_time <span class="hljs-built_in">TIMESTAMP</span>
);
</code></pre>
<p>Populate with realistic data:</p>
<pre><code class="lang-sql"><span class="hljs-keyword">INSERT</span> <span class="hljs-keyword">INTO</span> sensor_data (device_id, temperature, event_time)
<span class="hljs-keyword">SELECT</span>
  (random()*<span class="hljs-number">1000</span>)::<span class="hljs-built_in">int</span>,
  <span class="hljs-keyword">round</span>(random()*<span class="hljs-number">50</span>, <span class="hljs-number">2</span>),
  <span class="hljs-keyword">now</span>() - (random() * <span class="hljs-built_in">interval</span> <span class="hljs-string">'365 days'</span>)
<span class="hljs-keyword">FROM</span> generate_series(<span class="hljs-number">1</span>, <span class="hljs-number">100000000</span>);
</code></pre>
<h3 id="heading-create-indexes">Create Indexes</h3>
<pre><code class="lang-sql"><span class="hljs-comment">-- BRIN</span>
<span class="hljs-keyword">CREATE</span> <span class="hljs-keyword">INDEX</span> brin_sensor_event_time <span class="hljs-keyword">ON</span> sensor_data <span class="hljs-keyword">USING</span> brin (event_time);

<span class="hljs-comment">-- B-Tree</span>
<span class="hljs-keyword">CREATE</span> <span class="hljs-keyword">INDEX</span> btree_sensor_event_time <span class="hljs-keyword">ON</span> sensor_data <span class="hljs-keyword">USING</span> btree (event_time);
</code></pre>
<h3 id="heading-performance-test">Performance Test</h3>
<p>Query:</p>
<pre><code class="lang-sql"><span class="hljs-keyword">SELECT</span> * <span class="hljs-keyword">FROM</span> sensor_data
<span class="hljs-keyword">WHERE</span> event_time <span class="hljs-keyword">BETWEEN</span> <span class="hljs-string">'2023-01-01'</span> <span class="hljs-keyword">AND</span> <span class="hljs-string">'2023-01-10'</span>;
</code></pre>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Index Type</td><td>Index Size</td><td>Query Time</td></tr>
</thead>
<tbody>
<tr>
<td>B-Tree</td><td>~15 GB</td><td>80ms</td></tr>
<tr>
<td>BRIN</td><td>~60 MB</td><td>500ms–1.5s</td></tr>
</tbody>
</table>
</div><blockquote>
<p>BRIN is <strong>~250x smaller</strong>, but <strong>slower for highly selective queries</strong></p>
</blockquote>
<h2 id="heading-when-to-use-brin-best-use-cases">When to Use BRIN (Best Use-Cases)</h2>
<ul>
<li><p><strong>Append-only</strong> or <strong>time-series data</strong></p>
</li>
<li><p>Large tables: 100M+ rows</p>
</li>
<li><p>Columns with <strong>monotonic</strong> or <strong>naturally ordered</strong> data:</p>
<ul>
<li><p>Timestamps</p>
</li>
<li><p>IDs</p>
</li>
<li><p>Dates</p>
</li>
</ul>
</li>
<li><p>Use with <strong>partitioned tables</strong> for cheap indexing</p>
</li>
<li><p>Excellent for <strong>archival</strong> or <strong>rarely queried</strong> historical data</p>
</li>
</ul>
<h2 id="heading-when-not-to-use-brin">When <strong>Not</strong> to Use BRIN</h2>
<ul>
<li><p>Highly <strong>randomized</strong> or <strong>non-sequential</strong> data</p>
</li>
<li><p>High <strong>update/delete</strong> activity (BRIN won’t self-tune)</p>
</li>
<li><p>You need <strong>fast, pinpoint lookups</strong> (use B-Tree instead)</p>
</li>
</ul>
<h2 id="heading-creating-amp-tuning-brin">Creating &amp; Tuning BRIN</h2>
<h3 id="heading-basic-syntax">Basic Syntax</h3>
<pre><code class="lang-sql"><span class="hljs-keyword">CREATE</span> <span class="hljs-keyword">INDEX</span> idx_brin_column
<span class="hljs-keyword">ON</span> your_table <span class="hljs-keyword">USING</span> brin(your_column);
</code></pre>
<h3 id="heading-tuning-pagesperrange">Tuning <code>pages_per_range</code></h3>
<pre><code class="lang-sql"><span class="hljs-comment">-- Lower = finer granularity (but larger index)</span>
<span class="hljs-keyword">CREATE</span> <span class="hljs-keyword">INDEX</span> idx_brin_custom
<span class="hljs-keyword">ON</span> your_table <span class="hljs-keyword">USING</span> brin(your_column)
<span class="hljs-keyword">WITH</span> (pages_per_range = <span class="hljs-number">32</span>);
</code></pre>
<p>Default = 128; use lower for sparse data, higher for dense data</p>
<h2 id="heading-measuring-brin-effectiveness">Measuring BRIN Effectiveness</h2>
<h3 id="heading-check-index-size">Check index size</h3>
<pre><code class="lang-sql"><span class="hljs-keyword">SELECT</span> pg_size_pretty(pg_relation_size(<span class="hljs-string">'idx_brin_column'</span>));
</code></pre>
<h3 id="heading-view-brin-summary-info">View BRIN summary info</h3>
<pre><code class="lang-sql"><span class="hljs-keyword">SELECT</span> * <span class="hljs-keyword">FROM</span> pg_brin_bloom_summary;
<span class="hljs-comment">-- Or use pg_visibility module for deeper inspection</span>
</code></pre>
<h3 id="heading-reindex-or-vacuum">REINDEX or VACUUM</h3>
<p>If data distribution changed a lot:</p>
<pre><code class="lang-sql">REINDEX INDEX idx_brin_column;
VACUUM <span class="hljs-keyword">ANALYZE</span> your_table;
</code></pre>
<h2 id="heading-pairing-brin-with-other-indexes">Pairing BRIN with Other Indexes</h2>
<p>Yes! Combine strategies:</p>
<ul>
<li><p>BRIN on <code>event_time</code></p>
</li>
<li><p>B-Tree on <code>device_id</code></p>
</li>
</ul>
<p>Allows PostgreSQL to <strong>combine filters</strong> effectively.</p>
<pre><code class="lang-sql"><span class="hljs-keyword">SELECT</span> * <span class="hljs-keyword">FROM</span> sensor_data
<span class="hljs-keyword">WHERE</span> event_time &gt; <span class="hljs-keyword">now</span>() - <span class="hljs-built_in">interval</span> <span class="hljs-string">'30 days'</span>
<span class="hljs-keyword">AND</span> device_id = <span class="hljs-number">42</span>;
</code></pre>
<p>Use <strong>multicolumn BRIN</strong> only if both columns are sequentially increasing.</p>
<h2 id="heading-other-brin-index-types">Other BRIN Index Types</h2>
<p>Available operator classes:</p>
<ul>
<li><p><code>brin_minmax</code> (default)</p>
</li>
<li><p><code>brin_bloom</code> (Postgres 14+): bitset-based</p>
</li>
<li><p><code>brin_inclusion</code> (Postgres 15+): for ranges</p>
</li>
<li><p>Custom extensions like <code>brin_lov</code> (local dictionary)</p>
</li>
</ul>
<h2 id="heading-pros-and-cons">Pros and Cons</h2>
<h3 id="heading-pros">Pros</h3>
<ul>
<li><p><strong>Tiny disk footprint</strong></p>
</li>
<li><p><strong>Fast to build</strong></p>
</li>
<li><p>Great for <strong>bulk time-based ingestion</strong></p>
</li>
<li><p>Easy maintenance</p>
</li>
</ul>
<h3 id="heading-cons">Cons</h3>
<ul>
<li><p>Slower than B-Tree for <strong>pinpoint</strong> queries</p>
</li>
<li><p>Sensitive to data <strong>distribution</strong></p>
</li>
<li><p>No enforcement of uniqueness</p>
</li>
<li><p>Doesn't help much on <strong>UPDATE-heavy</strong> workloads</p>
</li>
</ul>
<h2 id="heading-final-thoughts">Final Thoughts</h2>
<p><strong>BRIN indexes are an absolute gem</strong> for large-scale, time-based, or append-only tables where:</p>
<ul>
<li><p>You care about index <strong>size and creation time</strong></p>
</li>
<li><p>Queries are <strong>range-based</strong> (e.g., logs, sensor data)</p>
</li>
<li><p>You don’t need millisecond response times for every query</p>
</li>
</ul>
<p>For more details refer the official doc at <a target="_blank" href="https://www.postgresql.org/docs/current/brin.html">PostgreSQL: Documentation: 18: 65.5. BRIN Indexes</a></p>
]]></content:encoded></item><item><title><![CDATA[MongoDB Locking Explained: Summary of Concurrency and Locking Strategies]]></title><description><![CDATA[When working with high-concurrency workloads in MongoDB, understanding how locking and concurrency control work is essential to maintain performance and data integrity. Here’s a quick overview of the core ideas:
Key Highlights:

Document-Level Lockin...]]></description><link>https://kiransabne.dev/mongodb-locking-explained-summary-of-concurrency-and-locking-strategies</link><guid isPermaLink="true">https://kiransabne.dev/mongodb-locking-explained-summary-of-concurrency-and-locking-strategies</guid><category><![CDATA[Software Engineering]]></category><category><![CDATA[software development]]></category><category><![CDATA[software architecture]]></category><dc:creator><![CDATA[kiran sabne]]></dc:creator><pubDate>Sat, 19 Jul 2025 18:58:41 GMT</pubDate><content:encoded><![CDATA[<p>When working with high-concurrency workloads in MongoDB, understanding how locking and concurrency control work is essential to maintain performance and data integrity. Here’s a quick overview of the core ideas:</p>
<h3 id="heading-key-highlights">Key Highlights:</h3>
<ul>
<li><p><strong>Document-Level Locking</strong>: With the WiredTiger engine, MongoDB supports fine-grained locks at the document level.</p>
</li>
<li><p><strong>Lock Types</strong>: Internally uses Shared (S), Exclusive (X), Intent Shared (IS), and Intent Exclusive (IX) for coordination.</p>
</li>
<li><p><strong>Optimistic Locking</strong>: Uses a version field to prevent overwrites in high-read environments.</p>
</li>
<li><p><strong>Pessimistic Locking</strong>: Simulated via a <code>lock</code> field to prevent concurrent access during critical updates.</p>
</li>
<li><p><strong>Deadlock Prevention</strong>: Implement retry logic, consistent update order, and use <code>$maxTimeMS</code> to avoid transaction stalls.</p>
</li>
<li><p><strong>Monitoring Tools</strong>: Use <code>serverStatus().locks</code> and <code>currentOp({ waitingForLock: true })</code> to track lock contention.</p>
</li>
</ul>
<h3 id="heading-use-cases-covered">Use Cases Covered:</h3>
<ul>
<li><p>Real-world optimistic &amp; pessimistic lock patterns in MongoDB</p>
</li>
<li><p>Deadlock detection and prevention strategies</p>
</li>
<li><p>Lock optimization techniques and when to use which approach</p>
</li>
</ul>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Scenario</td><td>Best Strategy</td></tr>
</thead>
<tbody>
<tr>
<td>High Reads, Low Conflict</td><td>Optimistic Locking</td></tr>
<tr>
<td>High Write Contention</td><td>Pessimistic Locking</td></tr>
<tr>
<td>Multi-doc Transactions</td><td>Retry on Deadlocks</td></tr>
</tbody>
</table>
</div><p>More details are posted <a target="_blank" href="https://databasedeveloper.dev/mastering-mongodb-locking-concurrency-and-performance-optimization-a-deep-dive">here at another post</a></p>
]]></content:encoded></item><item><title><![CDATA[Cracking the SQL Interview: Real Questions and PostgreSQL Internals You Should Know]]></title><description><![CDATA[Over the past few months since the start of new year, I’ve been actively interviewing for technical roles, and each conversation proved to be both challenging and rewarding. In this post, I’ll share some of the SQL questions I was asked, along with d...]]></description><link>https://kiransabne.dev/cracking-the-sql-interview-real-questions-and-postgresql-internals-you-should-know</link><guid isPermaLink="true">https://kiransabne.dev/cracking-the-sql-interview-real-questions-and-postgresql-internals-you-should-know</guid><category><![CDATA[software development]]></category><category><![CDATA[Software Engineering]]></category><category><![CDATA[PostgreSQL]]></category><category><![CDATA[Databases]]></category><category><![CDATA[data-engineering]]></category><category><![CDATA[backend]]></category><category><![CDATA[SQL]]></category><dc:creator><![CDATA[kiran sabne]]></dc:creator><pubDate>Sat, 12 Jul 2025 16:02:34 GMT</pubDate><content:encoded><![CDATA[<p>Over the past few months since the start of new year, I’ve been actively interviewing for technical roles, and each conversation proved to be both challenging and rewarding. In this post, I’ll share some of the <strong>SQL questions</strong> I was asked, along with deep-dive <strong>PostgreSQL internals topics</strong> that stood out in interviews.</p>
<p>If you’re preparing for data engineering, backend, or database-focused roles, this guide is for you.</p>
<h3 id="heading-real-sql-questions-i-was-asked-in-interviews">Real SQL Questions I Was Asked in Interviews</h3>
<p>Here are some real SQL questions I encountered across multiple interviews. These test your ability to work with complex queries and understand database structures:</p>
<ul>
<li><p>Recursive Hierarchy with Levels - Return employee ID, name, manager name, and depth in the org chart (Recursive CTE)</p>
</li>
<li><p>Find Managers with 2 or more employees</p>
</li>
<li><p>Departmental Top 3 Salaries</p>
</li>
<li><p>Customers who bought all products</p>
</li>
<li><p>Rolling sums - Calculate a <strong>7-day rolling sum</strong> of sales for each product, ordered by date.</p>
</li>
<li><p>Find continuous date ranges when there was sales activity every day without gaps.</p>
</li>
</ul>
<p>The below are advanced topics came up in several interview rounds and often led to deep discussions.</p>
<h3 id="heading-1-postgresql-indexing-internals-use-cases-and-performance">1. PostgreSQL Indexing: Internals, Use Cases, and Performance</h3>
<h4 id="heading-had-low-level-discussions-on-indexes-along-with-their-use-cases">Had low level discussions on indexes along with their use cases.</h4>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Index Type</td><td>Use Case</td></tr>
</thead>
<tbody>
<tr>
<td>B-Tree</td><td>Equality, Range queries</td></tr>
<tr>
<td>Hash</td><td>Equality only</td></tr>
<tr>
<td>GIN</td><td>Full-text, JSONB, array overlap</td></tr>
<tr>
<td>GiST</td><td>Geometric search, similarity search</td></tr>
<tr>
<td>BRIN</td><td>Large, naturally sorted tables (e.g., logs)</td></tr>
<tr>
<td>Partial</td><td>Index on filtered rows</td></tr>
<tr>
<td>Expression</td><td>Index on function output (e.g., <code>lower(name)</code>)</td></tr>
</tbody>
</table>
</div><ul>
<li><p><strong>Covering Indexes</strong>: <code>CREATE INDEX ON emp (deptid) INCLUDE (salary)</code></p>
</li>
<li><p><strong>Multicolumn Indexes</strong>: Consider column order (leading column matters!)</p>
</li>
<li><p><strong>Bloom Index</strong>: Used in specialized fuzzy match cases</p>
</li>
<li><p><strong>Index Usage</strong>: How planner choose Indexes, Analyze &amp; Statistics effects on Index Usage.</p>
</li>
<li><p>Index Maintenance: Need for vacuum &amp;/ reindex, index Bloat, Fill factor &amp; tuning.</p>
</li>
<li><p>Other: Index Impact on Write Performance, OLTP &amp; OLAP workloads, Finding unused index, Index usage metrics, Indexes and concurrent writes, etc.</p>
</li>
</ul>
<h3 id="heading-2-reading-postgresql-execution-plans">2. Reading PostgreSQL Execution Plans</h3>
<p>How to interpret &amp; understand execution plans in turn understanding query performance. What are the things to look into the plans.</p>
<pre><code class="lang-sql"><span class="hljs-keyword">EXPLAIN</span> (<span class="hljs-keyword">ANALYZE</span>, BUFFERS, COSTS, VERBOSE)
<span class="hljs-keyword">SELECT</span> * <span class="hljs-keyword">FROM</span> employee <span class="hljs-keyword">WHERE</span> salary &gt; <span class="hljs-number">100000</span>;
</code></pre>
<h4 id="heading-things-to-know">Things to know:</h4>
<ul>
<li><p><strong>Seq Scan</strong>: No useful index found</p>
</li>
<li><p><strong>Index Scan</strong>: Ideal for high-selectivity queries</p>
</li>
<li><p><strong>Bitmap Heap Scan</strong>: Used for broader matches, combines index results</p>
</li>
<li><p><strong>Rows Removed by Filter</strong>: Wasted work — suggests filter isn’t pushed down</p>
</li>
<li><p>Joins &amp; Join Methods (Nested Join, Merge Join &amp; Hash Join): Use case scenarios, Memory Impacts, how sorting effects the join, hash table spills etc.</p>
</li>
<li><p>Other Topics: Parallel Query, CTEs &amp; subplans, cost estimates, watch for <strong>loops &gt; 1</strong> in nested loops, monitor <strong>buffers read/hit</strong> to spot I/O inefficiencies, etc.</p>
</li>
</ul>
<h3 id="heading-3-partitioning">3. Partitioning</h3>
<blockquote>
<p>I was asked: “How would you partition a 5B-row logs table by region and time?”</p>
</blockquote>
<h4 id="heading-few-of-the-topics-discussed-during-interviews-were">Few of the topics discussed during interviews were,</h4>
<ul>
<li><p>Partitioning Methods - Range Partition, List Partition, Hash Partition, Composite Partition</p>
</li>
<li><p>Partitioning Work - Declarative partitioning vs table inheritance, Planner routes inserts/update, default partitions.</p>
</li>
<li><p>Performance Implications - Partition Pruning, indexing on partitions vs global index (Postgres doesn’t support native global index)</p>
</li>
<li><p>Impact on Query Plans &amp; when pruning fails.</p>
</li>
<li><p>Maintenance - Adding / Removing partitions, Detaching / Attaching partitions, Archiving, Table bloat, constraints behavior with partitioned tables etc.</p>
</li>
</ul>
<h3 id="heading-4-sharding-postgresql-horizontal-scale">4. Sharding PostgreSQL (Horizontal Scale)</h3>
<blockquote>
<p>Interviewer: “What happens when a single node isn’t enough?”</p>
</blockquote>
<p><strong>Sharding</strong> = <strong>horizontal partitioning</strong> → splitting data across multiple <em>physical</em> databases/servers, not just partitions in one DB.</p>
<ul>
<li><p>Each <strong>shard</strong> holds a subset of rows.</p>
</li>
<li><p>Common shard keys: customer ID, tenant ID, geographic region, time.</p>
</li>
<li><p>Goal: keep each shard small &amp; fast to query independently.</p>
</li>
</ul>
<h4 id="heading-manual-sharding-approaches">Manual Sharding Approaches:</h4>
<ul>
<li><p>Application-controlled sharding (based on user_id, region)</p>
</li>
<li><p>Foreign Data Wrappers (<code>postgres_fdw</code>)</p>
</li>
<li><p>Tools like <strong>Citus</strong></p>
</li>
</ul>
<h4 id="heading-example-shard-strategy-customer-db">Example Shard Strategy (Customer DB):</h4>
<ul>
<li><p>Users A–M → shard_1</p>
</li>
<li><p>Users N–Z → shard_2</p>
</li>
<li><p>Metadata service maps user → shard</p>
</li>
</ul>
<blockquote>
<p>You must handle picking a good shard key, <strong>cross-shard joins</strong>, <strong>global transactions</strong>, Distributed transactions, Data rebalancing, Consistency &amp; failover, <strong>replication lag</strong> etc.</p>
</blockquote>
<h3 id="heading-5-change-data-capture-cdc">5. Change Data Capture (CDC)</h3>
<p><strong>Change Data Capture</strong> means continuously capturing <strong>row-level data changes</strong> (INSERT, UPDATE, DELETE) from a source database and delivering them to downstream systems.</p>
<blockquote>
<p>“How do you build a real-time pipeline to stream changes?”</p>
</blockquote>
<h4 id="heading-postgresql-cdc-techniques">✅ PostgreSQL CDC Techniques:</h4>
<div class="hn-table">
<table>
<thead>
<tr>
<td><strong>Technique</strong></td><td><strong>Latency</strong></td><td><strong>Setup Effort</strong></td><td><strong>Pros</strong></td><td><strong>Cons</strong></td><td><strong>When to Use</strong></td></tr>
</thead>
<tbody>
<tr>
<td>🔹 <strong>Triggers + Audit Tables</strong></td><td>Low</td><td>Easy</td><td>Simple to implement for small DBs</td><td>Performance overhead at high TPS, hard to scale</td><td>When you just need an audit trail (e.g. small B2B SaaS)</td></tr>
<tr>
<td>🔹 <strong>Logical Replication</strong></td><td>Medium–Low</td><td>Native</td><td>Supports pub/sub; only committed changes; easy failover</td><td>Cannot replicate DDL; table must have PK</td><td>Multi-region read replicas, selective replication</td></tr>
<tr>
<td>🔹 <strong>WAL Stream Decoding</strong></td><td>Low</td><td>Medium–Hard</td><td>True real-time streaming; works well with Kafka, Debezium</td><td>Harder to manage slots, risk of WAL bloat</td><td>Enterprise streaming pipelines</td></tr>
<tr>
<td>🔹 <strong>pgoutput</strong> (logical decoding plugin)</td><td>Efficient</td><td>Native, preferred</td><td>Default plugin for logical replication; Debezium uses it</td><td>Limited to logical replication; same WAL slot constraints</td><td>Best practice for Kafka CDC</td></tr>
</tbody>
</table>
</div><p><strong>Logical Replication (Built-in)</strong></p>
<ul>
<li><p>PostgreSQL 10+ supports <em>logical replication</em>.</p>
</li>
<li><p>Publishes <strong>row-level changes</strong> as a stream.</p>
</li>
<li><p>Use: <code>CREATE PUBLICATION</code> and <code>CREATE SUBSCRIPTION</code>.</p>
</li>
<li><p>Works via WAL (Write-Ahead Log): changes are encoded as logical changes, not raw blocks.</p>
</li>
<li><p>Good for replica clusters or feeding Kafka connectors.</p>
</li>
</ul>
<p><strong>Logical Decoding</strong></p>
<ul>
<li><p>The foundation for logical replication.</p>
</li>
<li><p><code>pgoutput</code> is the default plugin.</p>
</li>
<li><p>Or use plugins like <code>wal2json</code> or <code>decoderbufs</code>:</p>
<ul>
<li><p><code>wal2json</code>: output changes as JSON — easy for Kafka, Debezium.</p>
</li>
<li><p><code>decoderbufs</code>: protobuf format.</p>
</li>
</ul>
</li>
<li><p>Tools read the WAL stream via a <strong>replication slot</strong>.</p>
</li>
</ul>
<p><strong>Triggers-Based CDC</strong></p>
<ul>
<li><p>Implemented with <code>AFTER INSERT/UPDATE/DELETE</code> triggers.</p>
</li>
<li><p>Simpler for small systems, but high overhead under heavy writes.</p>
</li>
</ul>
<h3 id="heading-6-other-concepts">6. Other concepts:</h3>
<p>Few other things which I think it’s important to go over are,</p>
<ul>
<li><p>When to use <strong>BRIN</strong> over B-tree indexes</p>
</li>
<li><p>Index design tradeoffs in high-write OLTP systems</p>
</li>
<li><p>Join types and when to apply each</p>
</li>
<li><p>Timeseries partitioning and pruning validation</p>
</li>
<li><p>Postgres internals: MVCC, WAL, autovacuum</p>
</li>
<li><p>Locking, concurrency control, and deadlocks</p>
</li>
<li><p>Designing a hybrid search (full-text + semantic)</p>
</li>
<li><p>Data ingestion pipelines with Kafka and Postgres</p>
</li>
<li><p>Handling <strong>SCDs</strong>, surrogate vs natural keys</p>
</li>
<li><p>Real-time event guarantees: exactly-once vs at-least-once delivery</p>
</li>
<li><p>What indexing strategies do you use for analytical vs transactional workloads?</p>
</li>
</ul>
<p>If you’re preparing for roles that involve SQL, data engineering, or backend systems, mastering both query skills and PostgreSQL internals can really set you apart. The questions span from hands-on SQL to system-level architecture — and each was an opportunity to demonstrate practical depth.</p>
<p>Let me know in the comments if you'd like to add more questions or topics to this list!</p>
]]></content:encoded></item><item><title><![CDATA[Using Go's Context Package: Managing Concurrency and Lifecycle in Applications"]]></title><description><![CDATA[In Go programming, the context package is widely used to manage deadlines, cancellations, and other request-scoped values across API boundaries and go-routines. It provides a standardised way to pass contextual information and control the lifecycle o...]]></description><link>https://kiransabne.dev/using-gos-context-package-managing-concurrency-and-lifecycle-in-golang-applications</link><guid isPermaLink="true">https://kiransabne.dev/using-gos-context-package-managing-concurrency-and-lifecycle-in-golang-applications</guid><category><![CDATA[Programming Blogs]]></category><category><![CDATA[software development]]></category><dc:creator><![CDATA[kiran sabne]]></dc:creator><pubDate>Fri, 22 Nov 2024 12:49:05 GMT</pubDate><content:encoded><![CDATA[<p>In Go programming, the <code>context</code> package is widely used to manage deadlines, cancellations, and other request-scoped values across API boundaries and go-routines. It provides a standardised way to pass contextual information and control the lifecycle of operations, especially when dealing with concurrency and long-running tasks.</p>
<h3 id="heading-overview-of-the-context-package"><strong>Overview of the</strong> <code>context</code> Package</h3>
<p>The context package helps you control the lifecycle of operations and control, from managing HTTP requests to orchestrating database calls:</p>
<ul>
<li><p><strong>Request cancellation</strong>: Propagating cancellation signals to stop long-running tasks.</p>
</li>
<li><p><strong>Deadlines and timeouts</strong>: Automatically cancel operations if they exceed a given timeout.</p>
</li>
<li><p><strong>Passing request-scoped values</strong>: Sharing request-scoped data (e.g., user information, tracing IDs) across different program layers.</p>
</li>
</ul>
<h3 id="heading-basic-types-in-context"><strong>Basic Types in</strong> <code>context</code></h3>
<ol>
<li><p><code>context.Context</code>:</p>
<ul>
<li>This is the main type used to carry deadlines, cancellations, and values across API boundaries. It is immutable and provides methods to access its state.</li>
</ul>
</li>
<li><p><strong>Context Creation Functions</strong>:</p>
<ul>
<li><p><code>context.Background()</code>: Returns an empty context. This is often used as a root context in long-running background tasks.</p>
</li>
<li><p><code>context.TODO()</code>: Similar to <code>Background()</code>, but used when you’re unsure what context to use. It signals "I haven’t figured out what context to use here yet."</p>
</li>
</ul>
</li>
<li><p><strong>Context with Deadline, Timeout, or Cancellation</strong>:</p>
<ul>
<li><p><code>context.WithCancel(parent)</code>: Creates a context that can be explicitly canceled.</p>
</li>
<li><p><code>context.WithDeadline(parent, deadline)</code>: Creates a context that will be canceled automatically at a specific time (deadline).</p>
</li>
<li><p><code>context.WithTimeout(parent, timeout)</code>: Creates a context that will be canceled after a specified timeout.</p>
</li>
</ul>
</li>
<li><p><strong>Value Context</strong>:</p>
<ul>
<li><code>context.WithValue(parent, key, value)</code>: Attaches key-value pairs to a context. Useful for passing request-scoped data.</li>
</ul>
</li>
</ol>
<hr />
<h3 id="heading-why-use-the-context-package"><strong>Why Use the</strong> <code>context</code> Package?</h3>
<h4 id="heading-1-cancellation-propagation"><strong>1. Cancellation Propagation</strong></h4>
<ul>
<li><p><strong>Problem</strong>: In a concurrent system, if one part of the operation fails or is no longer needed, you want to propagate the cancellation to other goroutines to avoid wasted resources.</p>
</li>
<li><p><strong>Solution</strong>: <code>context.WithCancel()</code> allows you to cancel all child goroutines when the parent context is canceled.</p>
</li>
</ul>
<h4 id="heading-2-timeouts-and-deadlines"><strong>2. Timeouts and Deadlines</strong></h4>
<ul>
<li><p><strong>Problem</strong>: Long-running operations can consume system resources if they hang or fail to return within a reasonable time.</p>
</li>
<li><p><strong>Solution</strong>: <code>context.WithTimeout()</code> and <code>context.WithDeadline()</code> help ensure that operations do not run indefinitely by enforcing timeouts.</p>
</li>
</ul>
<h4 id="heading-3-passing-request-scoped-values"><strong>3. Passing Request-Scoped Values</strong></h4>
<ul>
<li><p><strong>Problem</strong>: When processing a request, you may need to pass values (e.g., user info, tracing IDs, or other metadata) across function calls without modifying function signatures excessively.</p>
</li>
<li><p><strong>Solution</strong>: <code>context.WithValue()</code> allows you to pass values between layers of the program without cluttering function signatures.</p>
</li>
</ul>
<hr />
<h3 id="heading-common-use-cases"><strong>Common Use Cases</strong></h3>
<h4 id="heading-1-http-servers-request-cancellation-and-timeouts"><strong>1. HTTP Servers (Request Cancellation and Timeouts)</strong></h4>
<ul>
<li><p>In web servers, <code>context</code> is commonly used to manage request timeouts and propagate cancellations.</p>
</li>
<li><p>For example, in an HTTP server, if a client disconnects or a request times out, the server should stop processing the request immediately. The <code>context</code> package allows the cancellation signal to propagate to all goroutines working on that request.</p>
</li>
</ul>
<pre><code class="lang-go"><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">handler</span><span class="hljs-params">(w http.ResponseWriter, r *http.Request)</span></span> {
    ctx := r.Context()

    <span class="hljs-keyword">select</span> {
    <span class="hljs-keyword">case</span> &lt;-time.After(<span class="hljs-number">5</span> * time.Second): <span class="hljs-comment">// Simulating a long-running operation</span>
        fmt.Fprintln(w, <span class="hljs-string">"Finished processing"</span>)
    <span class="hljs-keyword">case</span> &lt;-ctx.Done(): <span class="hljs-comment">// If the request is cancelled or times out</span>
        fmt.Fprintln(w, <span class="hljs-string">"Request cancelled"</span>)
    }
}
</code></pre>
<h4 id="heading-2-database-operations-timeouts-and-cancellations"><strong>2. Database Operations (Timeouts and Cancellations)</strong></h4>
<ul>
<li>In database operations, contexts are used to set timeouts and ensure that if a query takes too long, it is canceled.</li>
</ul>
<pre><code class="lang-go"><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">queryDB</span><span class="hljs-params">(ctx context.Context, db *sql.DB)</span> <span class="hljs-title">error</span></span> {
    queryCtx, cancel := context.WithTimeout(ctx, <span class="hljs-number">2</span>*time.Second)
    <span class="hljs-keyword">defer</span> cancel()

    rows, err := db.QueryContext(queryCtx, <span class="hljs-string">"SELECT * FROM users"</span>)
    <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
        <span class="hljs-keyword">return</span> err <span class="hljs-comment">// This will return if the query exceeds 2 seconds</span>
    }
    <span class="hljs-keyword">defer</span> rows.Close()

    <span class="hljs-comment">// Process rows...</span>
    <span class="hljs-keyword">return</span> <span class="hljs-literal">nil</span>
}
</code></pre>
<h4 id="heading-3-goroutines-and-background-tasks-graceful-shutdown"><strong>3. Goroutines and Background Tasks (Graceful Shutdown)</strong></h4>
<ul>
<li>In systems with multiple goroutines, you often need a mechanism to stop all related goroutines when a parent operation is canceled (e.g., during a graceful shutdown).</li>
</ul>
<pre><code class="lang-go"><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">process</span><span class="hljs-params">(ctx context.Context)</span></span> {
    <span class="hljs-keyword">for</span> {
        <span class="hljs-keyword">select</span> {
        <span class="hljs-keyword">case</span> &lt;-ctx.Done():
            fmt.Println(<span class="hljs-string">"Context cancelled, shutting down"</span>)
            <span class="hljs-keyword">return</span>
        <span class="hljs-keyword">default</span>:
            <span class="hljs-comment">// Do some work...</span>
        }
    }
}

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">main</span><span class="hljs-params">()</span></span> {
    ctx, cancel := context.WithCancel(context.Background())
    <span class="hljs-keyword">go</span> process(ctx)
    time.Sleep(<span class="hljs-number">2</span> * time.Second)
    cancel() <span class="hljs-comment">// Gracefully cancel the process</span>
}
</code></pre>
<hr />
<h3 id="heading-advanced-use-cases"><strong>Advanced Use Cases</strong></h3>
<h4 id="heading-1-distributed-tracing"><strong>1. Distributed Tracing</strong></h4>
<ul>
<li>Context is heavily used in distributed systems to propagate tracing information (e.g., trace IDs) across services. Middleware can extract tracing data from incoming requests, store it in the context, and propagate it across calls.</li>
</ul>
<pre><code class="lang-go"><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">traceMiddleware</span><span class="hljs-params">(next http.Handler)</span> <span class="hljs-title">http</span>.<span class="hljs-title">Handler</span></span> {
    <span class="hljs-keyword">return</span> http.HandlerFunc(<span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">(w http.ResponseWriter, r *http.Request)</span></span> {
        traceID := r.Header.Get(<span class="hljs-string">"X-Trace-ID"</span>)
        ctx := context.WithValue(r.Context(), <span class="hljs-string">"traceID"</span>, traceID)
        next.ServeHTTP(w, r.WithContext(ctx))
    })
}

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">handler</span><span class="hljs-params">(w http.ResponseWriter, r *http.Request)</span></span> {
    traceID := r.Context().Value(<span class="hljs-string">"traceID"</span>)
    fmt.Fprintf(w, <span class="hljs-string">"Trace ID: %v"</span>, traceID)
}
</code></pre>
<h4 id="heading-2-propagating-deadline-across-microservices"><strong>2. Propagating Deadline Across Microservices</strong></h4>
<ul>
<li>In microservices, a client making an external request can propagate the deadline using <code>context.Context</code>, so the downstream service knows when the request times out and can handle it accordingly.</li>
</ul>
<pre><code class="lang-go"><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">makeExternalCall</span><span class="hljs-params">(ctx context.Context)</span> <span class="hljs-title">error</span></span> {
    req, err := http.NewRequestWithContext(ctx, <span class="hljs-string">"GET"</span>, <span class="hljs-string">"http://example.com"</span>, <span class="hljs-literal">nil</span>)
    <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
        <span class="hljs-keyword">return</span> err
    }

    resp, err := http.DefaultClient.Do(req)
    <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
        <span class="hljs-keyword">return</span> err
    }
    <span class="hljs-keyword">defer</span> resp.Body.Close()

    <span class="hljs-keyword">return</span> <span class="hljs-literal">nil</span>
}
</code></pre>
<h4 id="heading-3-graceful-shutdown-in-servers"><strong>3. Graceful Shutdown in Servers</strong></h4>
<ul>
<li>Using <code>context</code> with <code>signal.NotifyContext</code> allows for graceful shutdowns in servers. This ensures that the server stops accepting new requests while allowing ongoing requests to finish.</li>
</ul>
<pre><code class="lang-go"><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">main</span><span class="hljs-params">()</span></span> {
    srv := &amp;http.Server{Addr: <span class="hljs-string">":8080"</span>, Handler: http.DefaultServeMux}

    <span class="hljs-comment">// Listen for system interrupt signals (e.g., Ctrl+C)</span>
    ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
    <span class="hljs-keyword">defer</span> stop()

    <span class="hljs-keyword">go</span> <span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">()</span></span> {
        <span class="hljs-keyword">if</span> err := srv.ListenAndServe(); err != <span class="hljs-literal">nil</span> &amp;&amp; err != http.ErrServerClosed {
            fmt.Printf(<span class="hljs-string">"Server failed: %v\n"</span>, err)
        }
    }()

    <span class="hljs-comment">// Wait for interrupt signal</span>
    &lt;-ctx.Done()

    fmt.Println(<span class="hljs-string">"Shutting down gracefully..."</span>)
    ctxShutDown, cancel := context.WithTimeout(context.Background(), <span class="hljs-number">5</span>*time.Second)
    <span class="hljs-keyword">defer</span> cancel()

    <span class="hljs-keyword">if</span> err := srv.Shutdown(ctxShutDown); err != <span class="hljs-literal">nil</span> {
        fmt.Printf(<span class="hljs-string">"Server Shutdown Failed: %v\n"</span>, err)
    }
}
</code></pre>
<hr />
<h3 id="heading-industry-standards-and-best-practices"><strong>Industry Standards and Best Practices</strong></h3>
<ol>
<li><p><strong>Always Pass Context as the First Argument</strong>:</p>
<ul>
<li>Functions that take a <code>context.Context</code> should accept it as the first argument. This is the convention in the Go standard library and ensures consistency across APIs.</li>
</ul>
</li>
</ol>
<pre><code class="lang-go">    <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">DoSomething</span><span class="hljs-params">(ctx context.Context, arg <span class="hljs-keyword">int</span>)</span></span> {
        <span class="hljs-comment">// Implementation...</span>
    }
</code></pre>
<ol start="2">
<li><p><strong>Avoid Storing Contexts in Structs</strong>:</p>
<ul>
<li>Contexts are meant to be short-lived and should not be stored in global or long-lived variables. They are request-scoped or operation-scoped and should be passed explicitly through function calls.</li>
</ul>
</li>
<li><p><strong>Respect</strong> <code>ctx.Done()</code>:</p>
<ul>
<li>Always check <code>ctx.Done()</code> to ensure that long-running operations can gracefully terminate if the context is canceled or if a deadline is exceeded.</li>
</ul>
</li>
<li><p><strong>Use</strong> <code>context.WithValue()</code> Sparingly:</p>
<ul>
<li>While <code>context.WithValue()</code> is useful for passing request-scoped values, avoid overusing it. If you need to pass many values, it's better to create a dedicated struct to hold those values and pass that struct around instead of storing them in the context.</li>
</ul>
</li>
</ol>
<hr />
<h3 id="heading-conclusion"><strong>Conclusion</strong></h3>
<p>The <code>context</code> package is a powerful tool for controlling the lifecycle of concurrent operations, managing deadlines, propagating cancellations, and passing request-scoped values across layers of a program. It is essential for writing clean, efficient, and manageable concurrent code, especially in large-scale systems or services. By following industry best practices, you can use the <code>context</code> package to build robust, production-grade applications and services.</p>
]]></content:encoded></item><item><title><![CDATA[Utilize Table Inheritance in PostgreSQL for a More Efficient Database Design]]></title><description><![CDATA[In every application development process, we often encounter scenarios where entities share common attributes while also possessing unique characteristics. We manage these parent-child-like relationships between entities using inheritance logic in ou...]]></description><link>https://kiransabne.dev/utilize-table-inheritance-in-postgresql-for-a-more-efficient-database-design</link><guid isPermaLink="true">https://kiransabne.dev/utilize-table-inheritance-in-postgresql-for-a-more-efficient-database-design</guid><category><![CDATA[PostgreSQL]]></category><category><![CDATA[software development]]></category><category><![CDATA[Software Engineering]]></category><category><![CDATA[Databases]]></category><dc:creator><![CDATA[kiran sabne]]></dc:creator><pubDate>Mon, 29 May 2023 07:11:04 GMT</pubDate><content:encoded><![CDATA[<p>In every application development process, we often encounter scenarios where entities share common attributes while also possessing unique characteristics. We manage these parent-child-like relationships between entities using inheritance logic in our programs. Similarly, the PostgreSQL RDBMS provides a solution for addressing these cases through table inheritance. In this blog post, we will delve into the concept of table inheritance in PostgreSQL, discussing its advantages, disadvantages, and practical use cases.</p>
<h3 id="heading-introduction-amp-definition">Introduction &amp; Definition</h3>
<p>Table inheritance is a feature in PostgreSQL that allows you to create a hierarchy of tables based on a parent-child relationship. The child tables inherit the structure, constraints, and attributes of the parent table, while also having its definitions and additional attributes. This approach provides a convenient way to manage related data and simplify your database schema. To understand, let's see a simple and widely used scenario.</p>
<p>Suppose we have an application like HRMS or something wherein we have to manage employee data, and the organization has different types/categories of employees like full-time, part-time, contracts, special consultants etc. Each of these employees has some common attributes like first name, last name, email, joining date, date of birth etc but also specific attributes unique to those employee types. Below DDL script will help understand more,<br />Create the parent table called "employees" with common attributes:</p>
<pre><code class="lang-sql">
<span class="hljs-comment">-- Create the parent table called "employees" with common attributes:</span>
<span class="hljs-keyword">CREATE</span> <span class="hljs-keyword">TABLE</span> employees (
    <span class="hljs-keyword">id</span> <span class="hljs-built_in">SERIAL</span> PRIMARY <span class="hljs-keyword">KEY</span>,
    <span class="hljs-keyword">name</span> <span class="hljs-built_in">VARCHAR</span>(<span class="hljs-number">100</span>),
    email <span class="hljs-built_in">VARCHAR</span>(<span class="hljs-number">100</span>),
    hire_date <span class="hljs-built_in">DATE</span>
);
<span class="hljs-comment">-- Create child tables for each type of employee, inheriting from the "employees" table:</span>

<span class="hljs-keyword">CREATE</span> <span class="hljs-keyword">TABLE</span> full_time_employees () INHERITS (employees);
<span class="hljs-keyword">CREATE</span> <span class="hljs-keyword">TABLE</span> part_time_employees () INHERITS (employees);
<span class="hljs-keyword">CREATE</span> <span class="hljs-keyword">TABLE</span> contractors () INHERITS (employees);

<span class="hljs-comment">-- Add specific attributes to each child table:</span>

<span class="hljs-comment">-- Adding columns to the full-time employees table</span>
<span class="hljs-keyword">ALTER</span> <span class="hljs-keyword">TABLE</span> full_time_employees <span class="hljs-keyword">ADD</span> <span class="hljs-keyword">COLUMN</span> salary <span class="hljs-built_in">NUMERIC</span>(<span class="hljs-number">10</span>, <span class="hljs-number">2</span>);
<span class="hljs-keyword">ALTER</span> <span class="hljs-keyword">TABLE</span> full_time_employees <span class="hljs-keyword">ADD</span> <span class="hljs-keyword">COLUMN</span> vacation_days <span class="hljs-built_in">INTEGER</span>;

<span class="hljs-comment">-- Adding columns to the part-time employees table</span>
<span class="hljs-keyword">ALTER</span> <span class="hljs-keyword">TABLE</span> part_time_employees <span class="hljs-keyword">ADD</span> <span class="hljs-keyword">COLUMN</span> hourly_rate <span class="hljs-built_in">NUMERIC</span>(<span class="hljs-number">10</span>, <span class="hljs-number">2</span>);
<span class="hljs-keyword">ALTER</span> <span class="hljs-keyword">TABLE</span> part_time_employees <span class="hljs-keyword">ADD</span> <span class="hljs-keyword">COLUMN</span> hours_worked <span class="hljs-built_in">INTEGER</span>;

<span class="hljs-comment">-- Adding columns to the contractors table</span>
<span class="hljs-keyword">ALTER</span> <span class="hljs-keyword">TABLE</span> contractors <span class="hljs-keyword">ADD</span> <span class="hljs-keyword">COLUMN</span> contract_rate <span class="hljs-built_in">NUMERIC</span>(<span class="hljs-number">10</span>, <span class="hljs-number">2</span>);
<span class="hljs-keyword">ALTER</span> <span class="hljs-keyword">TABLE</span> contractors <span class="hljs-keyword">ADD</span> <span class="hljs-keyword">COLUMN</span> contract_duration <span class="hljs-built_in">INTEGER</span>;
</code></pre>
<p>In this example, the parent table "employees" contains common attributes shared by all employees. The child tables, such as "full_time_employees", "part_time_employees", and "contractors", inherit these common attributes and allow for the addition of specific attributes related to each employee type.</p>
<p>With the use of table inheritance, we have done,</p>
<ul>
<li><p>Maintained a centralized employee table for common attributes and shared functionality.</p>
</li>
<li><p>For unique attributes of each employee type, we created separate child tables for types with those attributes, ensuring data integrity and clarity.</p>
</li>
<li><p>Retrieval and Filtering of data while performing queries specific to each employee type using the child tables.</p>
</li>
</ul>
<p>This approach provides flexibility in managing different types of employees while maintaining a consistent structure and enabling specific attributes for each employee type.  </p>
<p>Similarly, it can be implemented for various product categories in an e-commerce database. Each product category has its unique attributes other than the common attributes. The parent table will be a product table having common attributes like product name, price, etc and child tables having attributes specific to each product type. This design can also be used in designing content management systems, where types of content are different.</p>
<h3 id="heading-pros">Pros:</h3>
<ul>
<li><p>Table inheritance allows you to organize your data with the parent table containing common attributes shared by all child tables, while each child table can have its specific attributes. This logical organization makes it easier to manage and query data.</p>
</li>
<li><p>By centralizing common attributes in the parent table, you avoid duplicating columns across multiple tables.</p>
</li>
<li><p>Constraints defined on the parent table are automatically enforced on all child tables. This ensures data integrity and consistency throughout the inheritance hierarchy.</p>
</li>
<li><p>With table inheritance, you can perform queries on specific child tables to retrieve data relevant to a particular entity type. This allows for efficient filtering and retrieval of data based on specific attributes.</p>
</li>
</ul>
<h3 id="heading-cons">Cons:</h3>
<ul>
<li><p>The query execution planner will have to consider the structure and constraints defined for both parent and child tables, in turn adding complexity to query planning and can result in slightly longer planning time.</p>
</li>
<li><p>PostgreSQL uses indexes defined on the specific table, if any, for querying that table. This means that you may need to create separate indexes for each child table to optimize query performance.</p>
</li>
<li><p>Table inheritance is often used for dividing or partitioning large tables into more manageable chunks, which is also known as data segmentation. But it introduces additional complexity in making complex execution plans and may involve querying multiple child tables, causing a performance drop.</p>
</li>
<li><p>When performing maintenance operations like VACUUM or ANALYZE on a parent table, PostgreSQL will also process the child tables. This can increase the time required for these operations, especially if the inherited tables contain a significant amount of data.</p>
</li>
</ul>
<h3 id="heading-summary">Summary:</h3>
<p>Table inheritance in PostgreSQL offers a robust method for organizing related data, enhancing code reusability, and preserving data integrity, which can streamline your database schema, minimize redundancy, and boost query adaptability. When devising your database, assess the entities, their shared attributes, and unique traits to establish if table inheritance is an appropriate strategy. Keep in mind the importance of meticulously designing your database schema and taking into account the particular requirements and access patterns of your application when employing table inheritance.</p>
]]></content:encoded></item><item><title><![CDATA[Handling Exceptions in Postgres]]></title><description><![CDATA[In PostgreSQL, exception handling is implemented using the PL/pgSQL procedural language, which extends the capabilities of SQL with additional programming constructs, such as variables, loops, and conditionals. PL/pgSQL provides a comprehensive excep...]]></description><link>https://kiransabne.dev/handling-exceptions-in-postgres</link><guid isPermaLink="true">https://kiransabne.dev/handling-exceptions-in-postgres</guid><category><![CDATA[PostgreSQL]]></category><category><![CDATA[software development]]></category><category><![CDATA[SQL]]></category><category><![CDATA[postgres]]></category><dc:creator><![CDATA[kiran sabne]]></dc:creator><pubDate>Tue, 11 Apr 2023 09:36:43 GMT</pubDate><content:encoded><![CDATA[<p>In PostgreSQL, exception handling is implemented using the PL/pgSQL procedural language, which extends the capabilities of SQL with additional programming constructs, such as variables, loops, and conditionals. PL/pgSQL provides a comprehensive exception-handling mechanism that enables developers to catch and handle a wide range of errors that may occur during the execution of database functions and procedures.</p>
<p>PostgreSQL stops the execution of the block and the related transaction when a block contains an error. A block is a collection of statements that are contained within a BEGIN and END block structure in PL/pgSQL. In PostgreSQL, blocks are used to define unique procedures, triggers, and functions. The beginning and end of the block are indicated by the terms BEGIN and END, respectively.</p>
<h3 id="heading-syntax">Syntax</h3>
<pre><code class="lang-sql"><span class="hljs-keyword">BEGIN</span>
    <span class="hljs-comment">-- Code goes here</span>
<span class="hljs-keyword">EXCEPTION</span>
    <span class="hljs-keyword">WHEN</span> exception_type <span class="hljs-keyword">THEN</span>
        <span class="hljs-comment">-- Exception handling code goes here</span>
<span class="hljs-keyword">END</span>;
</code></pre>
<p>Besides Begin and End block, the EXCEPTION keyword indicates the start of the exception handling section, which is executed if an exception is thrown, inside which the WHEN clause specifies the type of exception that the exception handler will handle. The THEN keyword indicates the start of the code block that handles the exception.</p>
<p>PostgreSQL has a wide range of built-in exception types such as SQLSTATE, SQLERRM, NO_DATA_FOUND, and TOO_MANY_ROWS. Moreover, users can also create custom exceptions using the RAISE statement. For a complete list of condition names on the PostgreSQL website. To illustrate how PostgreSQL handles exceptions, let's take the basic example of a divide-by-zero exception. The PL/pgSQL block below demonstrates how to handle this exception:</p>
<pre><code class="lang-sql"><span class="hljs-keyword">do</span>
$$
<span class="hljs-keyword">declare</span> 
    <span class="hljs-keyword">result</span> <span class="hljs-built_in">int</span>;
<span class="hljs-keyword">begin</span>
    <span class="hljs-keyword">SELECT</span> <span class="hljs-number">1</span>/<span class="hljs-number">0</span> <span class="hljs-keyword">INTO</span> <span class="hljs-keyword">result</span>;

    <span class="hljs-comment">-- exception example based on SQL ERROR CODE</span>
    exception
        WHEN division_by_zero THEN
            result := NULL;
<span class="hljs-keyword">end</span>;
$$
language plpgsql;
</code></pre>
<p>In the above block, if the SELECT statement attempts to divide by zero, a division_by_zero exception is raised. The exception handling code, specified in the WHEN clause, sets the result variable to NULL in this case.</p>
<p>When an error occurs within the BEGIN...EXCEPTION block in PL/pgSQL, the execution is stopped and the control is transferred to the exception list. Then, PL/pgSQL scans the exception list to find the first match for the error that occurred. If a match is found, the statements inside the corresponding EXCEPTION block execute, and the control passes to the statement after the END keyword. If no match is found, the error propagates outwards and can be caught by the EXCEPTION clause of the enclosing block. In case there is no enclosing block with the EXCEPTION clause, PL/pgSQL aborts processing.</p>
<h3 id="heading-example-syntax-code-block-for-multiple-exceptions">Example Syntax Code block for Multiple Exceptions:</h3>
<pre><code class="lang-sql"><span class="hljs-keyword">do</span>
$$
<span class="hljs-keyword">declare</span>
    rec <span class="hljs-built_in">record</span>;
    emp_name varchar = 'abc';
<span class="hljs-keyword">begin</span>
    <span class="hljs-keyword">select</span> 
        &lt;column_names&gt;
    <span class="hljs-keyword">into</span> <span class="hljs-keyword">strict</span> rec
    <span class="hljs-keyword">from</span> &lt;table_name&gt;
    <span class="hljs-keyword">where</span> employee_name = emp_name; <span class="hljs-comment">-- example where clause.</span>

    <span class="hljs-comment">-- exception example based on SQL ERROR CODE</span>
    exception
        when sqlstate 'P0002' then
            raise exception 'employee <span class="hljs-keyword">with</span> <span class="hljs-keyword">name</span> % <span class="hljs-keyword">not</span> <span class="hljs-keyword">found</span><span class="hljs-string">', emp_name;
        when sqlstate '</span>P0003<span class="hljs-string">' then
            raise exception '</span>employee <span class="hljs-keyword">with</span> <span class="hljs-keyword">name</span> % <span class="hljs-keyword">is</span> already <span class="hljs-keyword">present</span><span class="hljs-string">', emp_name;

    -- exception example based on Expection Condition
    exception
        when too_many_rows then
            raise exception '</span><span class="hljs-keyword">Search</span> <span class="hljs-keyword">query</span> <span class="hljs-keyword">returns</span> more <span class="hljs-keyword">than</span> one <span class="hljs-keyword">rows</span><span class="hljs-string">';
end;
$$
language plpgsql;</span>
</code></pre>
<p>This exception-handling mechanism is crucial for handling errors gracefully and preventing application crashes or data corruption. It allows developers to provide better user experiences by responding appropriately to errors, rather than simply halting execution.</p>
]]></content:encoded></item><item><title><![CDATA[Traveling Salesman Problem Path Finder in SQL]]></title><description><![CDATA[I came across a situation to find the path for a salesman based on city locations and roads connecting these cities. The base version of the scenario was the same problem as Traveling Salesman Problem. I wanted to try solving the problem with SQL.
Be...]]></description><link>https://kiransabne.dev/traveling-salesman-problem-path-finder-in-sql</link><guid isPermaLink="true">https://kiransabne.dev/traveling-salesman-problem-path-finder-in-sql</guid><category><![CDATA[SQL]]></category><category><![CDATA[SQL Server]]></category><category><![CDATA[PostgreSQL]]></category><category><![CDATA[software development]]></category><category><![CDATA[Software Engineering]]></category><dc:creator><![CDATA[kiran sabne]]></dc:creator><pubDate>Tue, 04 Apr 2023 12:06:40 GMT</pubDate><content:encoded><![CDATA[<p>I came across a situation to find the path for a salesman based on city locations and roads connecting these cities. The base version of the scenario was the same problem as Traveling Salesman Problem. I wanted to try solving the problem with SQL.</p>
<p>Below is the problem statement, I got on internet for Traveling Salesman Problem, try reading through the inputs and outputs required. And I also posted solutions I made in both T-SQL and PostgreSQL variants.</p>
<p>First, is the City table containing two columns, Id and Name. This time, we're traveling between cities in Germany and we've been given a second table called Road that has the columns CityFrom, CityTo, and Time which contain average trip durations on a given route from one city to another. Below is create statement and data to populate rows.</p>
<pre><code class="lang-sql"><span class="hljs-keyword">create</span> <span class="hljs-keyword">schema</span> trip
<span class="hljs-keyword">create</span> <span class="hljs-keyword">table</span> trip.city(
    <span class="hljs-keyword">id</span> <span class="hljs-built_in">int</span> <span class="hljs-keyword">identity</span>(<span class="hljs-number">1</span>,<span class="hljs-number">1</span>), <span class="hljs-comment">-- in postgresl use serial datatype instead</span>
    <span class="hljs-keyword">name</span> <span class="hljs-built_in">varchar</span>(<span class="hljs-number">100</span>)
);

<span class="hljs-keyword">insert</span> <span class="hljs-keyword">into</span> trip.city(<span class="hljs-keyword">name</span>) <span class="hljs-keyword">values</span> (<span class="hljs-string">'Berlin'</span>), (<span class="hljs-string">'Hamburg'</span>), (<span class="hljs-string">'Muinch'</span>), (<span class="hljs-string">'Amsterdam'</span>);
<span class="hljs-keyword">select</span> * <span class="hljs-keyword">from</span> trip.city;

<span class="hljs-keyword">create</span> <span class="hljs-keyword">table</span> trip.road(
    city_from <span class="hljs-built_in">int</span>,
    city_to <span class="hljs-built_in">int</span>,
    <span class="hljs-built_in">time</span> <span class="hljs-built_in">int</span>
);
<span class="hljs-keyword">insert</span> <span class="hljs-keyword">into</span> trip.road(city_from, city_to, <span class="hljs-built_in">time</span>) <span class="hljs-keyword">values</span> 
(<span class="hljs-number">1</span>, <span class="hljs-number">2</span>, <span class="hljs-number">180</span>), (<span class="hljs-number">1</span>, <span class="hljs-number">3</span>, <span class="hljs-number">365</span>), (<span class="hljs-number">1</span>, <span class="hljs-number">4</span>, <span class="hljs-number">390</span>), (<span class="hljs-number">2</span>, <span class="hljs-number">3</span>, <span class="hljs-number">490</span>), (<span class="hljs-number">2</span>, <span class="hljs-number">4</span>, <span class="hljs-number">270</span>), (<span class="hljs-number">3</span>, <span class="hljs-number">2</span>, <span class="hljs-number">420</span>), (<span class="hljs-number">3</span>, <span class="hljs-number">4</span>, <span class="hljs-number">370</span>), (<span class="hljs-number">4</span>, <span class="hljs-number">2</span>, <span class="hljs-number">245</span>), (<span class="hljs-number">4</span>, <span class="hljs-number">3</span>, <span class="hljs-number">385</span>);
<span class="hljs-keyword">select</span> *  <span class="hljs-keyword">from</span> trip.road;
</code></pre>
<p>The trip path should cover all cities, starting from Berlin. The main task is to return travel paths starting from Berlin and covering all cities in descending order by the total time taken.</p>
<p>The output should contain the following columns</p>
<ol>
<li><p>path – the city names, separated by N' -&gt; ',</p>
</li>
<li><p>last_city_id – the ID of the last city visited,</p>
</li>
<li><p>total_time – the total time spent driving,</p>
</li>
<li><p>places_count – the number of places visited; it should equal 4.</p>
</li>
</ol>
<p>The below solution is T-SQL(SQL Server) based variant:</p>
<pre><code class="lang-sql">;<span class="hljs-keyword">with</span> travel (<span class="hljs-keyword">path</span>, last_city_id, total_time, places_count )
<span class="hljs-keyword">as</span>
(
    <span class="hljs-keyword">select</span>
        <span class="hljs-keyword">cast</span>(<span class="hljs-keyword">name</span> <span class="hljs-keyword">as</span> <span class="hljs-keyword">nvarchar</span>(<span class="hljs-keyword">max</span>)),
        <span class="hljs-keyword">id</span>,
        <span class="hljs-number">0</span>,
        <span class="hljs-number">1</span>
    <span class="hljs-keyword">from</span> trip.city c
    <span class="hljs-keyword">where</span> c.name = <span class="hljs-string">'Berlin'</span> <span class="hljs-comment">-- since starting from berlin, then we initially return the anchor point</span>

    <span class="hljs-keyword">union</span> <span class="hljs-keyword">all</span> 

    <span class="hljs-keyword">select</span>
        t.path + N<span class="hljs-string">' -&gt; '</span> + c.name, <span class="hljs-comment">-- concate the city name with existing travel path name </span>
        c.id, 
        t.total_time + r.time,
        t.places_count + <span class="hljs-number">1</span>
    <span class="hljs-keyword">from</span> travel t
    <span class="hljs-keyword">join</span> trip.road r
        <span class="hljs-keyword">on</span> t.last_city_id= r.city_from
    <span class="hljs-keyword">join</span> trip.city c
        <span class="hljs-keyword">on</span> c.id = r.city_to
    <span class="hljs-keyword">where</span> <span class="hljs-keyword">charindex</span>(c.name, t.path) = <span class="hljs-number">0</span> <span class="hljs-comment">-- where part was to prevent revisiting the same city twice in a path</span>

)

<span class="hljs-keyword">select</span> * <span class="hljs-keyword">from</span> travel <span class="hljs-keyword">where</span> places_count = <span class="hljs-number">4</span> <span class="hljs-keyword">order</span> <span class="hljs-keyword">by</span> total_time <span class="hljs-keyword">desc</span>;
</code></pre>
<p>And below is PostgreSQL varient</p>
<pre><code class="lang-sql"><span class="hljs-keyword">WITH</span> <span class="hljs-keyword">RECURSIVE</span> travel (<span class="hljs-keyword">path</span>, last_city_id, total_time, places_count) <span class="hljs-keyword">AS</span> (
  <span class="hljs-keyword">SELECT</span>
    <span class="hljs-keyword">CAST</span>(<span class="hljs-keyword">name</span> <span class="hljs-keyword">AS</span> <span class="hljs-built_in">text</span>),
    <span class="hljs-keyword">id</span>,
    <span class="hljs-number">0</span>,
    <span class="hljs-number">1</span>
  <span class="hljs-keyword">FROM</span> trip.city c
  <span class="hljs-keyword">WHERE</span> c.name = <span class="hljs-string">'Berlin'</span>

  <span class="hljs-keyword">UNION</span> <span class="hljs-keyword">ALL</span>

  <span class="hljs-keyword">SELECT</span>
    t.path || <span class="hljs-string">' -&gt; '</span> || c.name,
    c.id,
    t.total_time + r.time,
    t.places_count + <span class="hljs-number">1</span>
  <span class="hljs-keyword">FROM</span> travel t
  <span class="hljs-keyword">JOIN</span> trip.road r
    <span class="hljs-keyword">ON</span> t.last_city_id = r.city_from
  <span class="hljs-keyword">JOIN</span> trip.city c
    <span class="hljs-keyword">ON</span> c.id = r.city_to
  <span class="hljs-keyword">WHERE</span> <span class="hljs-keyword">position</span>(c.name <span class="hljs-keyword">IN</span> t.path) = <span class="hljs-number">0</span>
)

<span class="hljs-keyword">SELECT</span> *
<span class="hljs-keyword">FROM</span> travel
<span class="hljs-keyword">WHERE</span> places_count = <span class="hljs-number">4</span>
<span class="hljs-keyword">ORDER</span> <span class="hljs-keyword">BY</span> total_time <span class="hljs-keyword">DESC</span>;
</code></pre>
<p>In the codes above, we used common table expression (CTE) to recursively build up a list of travel paths between cities. The <code>travel</code> CTE contains four columns: <code>path</code> (the travel path so far), <code>last_city_id</code> (the ID of the last city visited), <code>total_time</code> (the total travel time so far), and <code>places_count</code> (the number of places visited so far).</p>
<p>The first part of the CTE returns to the starting point in Berlin. Then, the <code>union all</code> clause is used to recursively build up the travel paths. In the second part of the CTE, you're joining the <code>travel</code> CTE with the <code>road</code> and <code>city</code> tables to find the next city to visit. The <code>charindex</code> function (in T-SQL) or <code>position</code> function (in PostgreSQL) is used to make sure you don't revisit any cities on the same path. You can also use the <code>substring</code> function in PostgreSQL as an alternative.</p>
<p>Finally, the <code>SELECT</code> statement is used to return all travel paths that visit all four cities and are ordered in descending order by total travel time.</p>
]]></content:encoded></item></channel></rss>