<?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[Jatin510 — Database Internals, Rust & Systems Programming]]></title><description><![CDATA[Deep dives into database internals, Rust, and systems programming — connection pooling, query engines, Apache DataFusion, and how things really work under the hood.]]></description><link>https://blog.jatin510.dev</link><image><url>https://cdn.hashnode.com/res/hashnode/image/upload/v1752915153759/d97309a0-d733-40e0-b4d1-0f700c088aa8.png</url><title>Jatin510 — Database Internals, Rust &amp; Systems Programming</title><link>https://blog.jatin510.dev</link></image><generator>RSS for Node</generator><lastBuildDate>Thu, 17 Sep 2026 04:50:30 GMT</lastBuildDate><atom:link href="https://blog.jatin510.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[MVCC Explained: Build Postgres-Style Snapshots in 100 Lines of TypeScript
]]></title><description><![CDATA[Part of the Nothing is magic series, where we build database internals from scratch until they stop being magic.

Open two psql sessions.
Session A:
BEGIN;
DELETE FROM users;  -- deletes all 1 million]]></description><link>https://blog.jatin510.dev/mvcc-explained-build-postgres-style-snapshots-in-100-lines-of-typescript</link><guid isPermaLink="true">https://blog.jatin510.dev/mvcc-explained-build-postgres-style-snapshots-in-100-lines-of-typescript</guid><category><![CDATA[PostgreSQL]]></category><category><![CDATA[Databases]]></category><category><![CDATA[TypeScript]]></category><category><![CDATA[System Design]]></category><category><![CDATA[System Architecture]]></category><category><![CDATA[Programming Blogs]]></category><dc:creator><![CDATA[Jagdish Parihar]]></dc:creator><pubDate>Mon, 24 Aug 2026 07:25:38 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/6087c71e6aba67265b7c40cc/b8755d47-3c45-4ff8-8279-43b1af86e34f.svg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><em>Part of the</em> <a href="https://blog.jatin510.dev"><em>Nothing is magic</em></a> <em>series, where we build database internals from scratch until they stop being magic.</em></p>
<hr />
<p>Open two <code>psql</code> sessions.</p>
<p><strong>Session A:</strong></p>
<pre><code class="language-sql">BEGIN;
DELETE FROM users;  -- deletes all 1 million rows
-- don't commit yet
</code></pre>
<p><strong>Session B:</strong></p>
<pre><code class="language-sql">SELECT count(*) FROM users;
-- count: 1000000
</code></pre>
<p>Session A deleted every row. Session B sees every row. Neither session is waiting on the other. No locks, no blocking, no errors.</p>
<p>So... where are the rows? Deleted, or not?</p>
<p>The answer is <em>both</em>, and by the end of this post you'll have built the machinery that makes that answer make sense — in about 100 lines of TypeScript. This machinery is called <strong>MVCC</strong> (Multi-Version Concurrency Control), and it's how Postgres, MySQL/InnoDB, Oracle, and SQLite (in WAL mode) all let readers and writers ignore each other.</p>
<p>As always in this series: no hand-waving. We'll build it one problem at a time, and at each step we'll only add the <em>minimum</em> code needed to fix the problem in front of us. Then we'll point our toy at real Postgres and show that the columns match, one to one.</p>
<hr />
<h2>The wrong mental model</h2>
<p>Most of us carry around a mental model of a database table as a big mutable array. <code>UPDATE</code> overwrites a slot. <code>DELETE</code> removes one.</p>
<p>If that were true, the demo above would be impossible. The moment Session A deletes a row <em>in place</em>, that row is gone — Session B has nothing left to read. The only way to make in-place mutation safe is locks: readers wait for writers, writers wait for readers. Databases worked this way for years, and it was miserable — one long-running report could freeze every write to the table.</p>
<p>So here is the founding decision of MVCC, the thesis of this whole post:</p>
<blockquote>
<p><strong>Nothing is ever updated. Nothing is ever deleted. The database only ever appends.</strong></p>
</blockquote>
<p>Every "problem" we hit from here on is a consequence of this decision, and every piece of MVCC is the minimal fix for one of those problems. Let's start hitting them.</p>
<hr />
<h2>Problem 1: If we never delete, what does DELETE even mean?</h2>
<p>If data is append-only, an <code>UPDATE</code> can't overwrite and a <code>DELETE</code> can't remove. So a "row" can't be one thing. It has to be a <strong>chain of versions</strong>:</p>
<pre><code class="language-plaintext">logical row "alice":
 
  ┌─────────────────────┐    ┌─────────────────────┐
  │ v1: balance = 100   │ →  │ v2: balance = 50    │
  │ (old version)       │    │ (current version)   │
  └─────────────────────┘    └─────────────────────┘
</code></pre>
<ul>
<li><p><code>INSERT</code> appends the first version.</p>
</li>
<li><p><code>UPDATE</code> appends a new version. The old one stays.</p>
</li>
<li><p><code>DELETE</code> appends... nothing. It just <em>marks</em> the latest version as dead. That solves writing. But it immediately creates the next problem: if both <code>v1</code> and <code>v2</code> physically exist, <strong>which one do you see when you read?</strong></p>
</li>
</ul>
<p>We need every version to answer two questions:</p>
<ol>
<li><p><em>Who created me?</em></p>
</li>
<li><p><em>Who killed me?</em> So we stamp every version with two transaction IDs. Postgres calls them <code>xmin</code> and <code>xmax</code>, and so will we:</p>
</li>
</ol>
<pre><code class="language-typescript">type Txid = number;
 
interface Version {
  xmin: Txid;         // birth certificate: which transaction created this version
  xmax: Txid | null;  // death certificate: which transaction deleted it.
                      // null = no one has (yet). Note: a *stamp*, not a removal —
                      // dead versions stay physically present.
  value: unknown;
}
 
interface Row {
  // A "row" is not a value — it's the full history of values,
  // oldest first. Nothing in this array is ever removed or overwritten.
  versions: Version[];
}
</code></pre>
<p>Now the three write operations are almost embarrassingly simple:</p>
<ul>
<li><p><code>INSERT</code> → push <code>{ xmin: me, xmax: null, value }</code></p>
</li>
<li><p><code>DELETE</code> → set <code>xmax = me</code> on the live version</p>
</li>
<li><p><code>UPDATE</code> → <code>DELETE</code> + <code>INSERT</code>. That's it. An update is literally a delete and an insert stapled together. (Run <code>SELECT ctid FROM t</code> before and after an <code>UPDATE</code> in Postgres and watch the row's physical address change — same trick.) Transactions themselves are just an incrementing counter:</p>
</li>
</ul>
<pre><code class="language-typescript">class Database {
  // A "transaction" is nothing more than a number from this counter.
  // Because it only ever increases, txids double as timestamps:
  // smaller txid = started earlier. We'll lean on that ordering constantly.
  private nextTxid: Txid = 1;
  private rows = new Map&lt;string, Row&gt;();
 
  begin(): Txid {
    return this.nextTxid++;
  }
}
</code></pre>
<p><strong>Have we solved MVCC?</strong> Let's check against the demo. Session A (txid 2) deletes everything: it sets <code>xmax = 2</code> on every version. Session B (txid 3) reads: it sees versions with <code>xmax = 2</code> set and... does what, exactly?</p>
<p>We stamped the versions, but we never said what the stamps <em>mean</em> for a reader. Next problem.</p>
<hr />
<h2>Problem 2: Reading uncommitted data (the dirty read)</h2>
<p>Naive rule: "a version is visible if <code>xmax</code> is null."</p>
<p>Watch it fail. Session A has deleted everything but <strong>hasn't committed</strong>. Under the naive rule, Session B sees an empty table. Then Session A runs <code>ROLLBACK</code> — the delete never happened — and Session B has read a state of the world that <em>never existed</em>. This anomaly has a name: the <strong>dirty read</strong>.</p>
<p>The fix: a stamp only counts if the transaction that made it <strong>committed</strong>. Which means we need to track, for every txid, whether it committed, aborted, or is still running. Postgres keeps this in a structure called the commit log — <code>pg_xact</code>, historically <code>clog</code>. Ours is a Map:</p>
<pre><code class="language-typescript">type TxStatus = "in_progress" | "committed" | "aborted";
 
// The commit log: the single source of truth for "did txid N really happen?"
// Postgres calls this pg_xact (historically: clog).
type Clog = Map&lt;Txid, TxStatus&gt;;
 
class Database {
  private clog: Clog = new Map();
 
  begin(): Txid {
    const txid = this.nextTxid++;
    this.clog.set(txid, "in_progress");
    return txid;
  }
 
  // Commit touches ZERO rows. One map write flips the meaning of every
  // xmin/xmax stamp this transaction ever made, all at once, retroactively.
  commit(txid: Txid) { this.clog.set(txid, "committed"); }
 
  // Rollback is the same trick: nothing gets undone, because nothing
  // was done in place. The stamps stay — they just stop counting.
  abort(txid: Txid)  { this.clog.set(txid, "aborted"); }
}
</code></pre>
<p>Notice what <code>commit</code> is: <strong>one map write</strong>. It doesn't touch a single row. Session A deleted a million rows, and committing that is O(1) — we just flip <code>2 → "committed"</code> and instantly, <em>retroactively</em>, all million <code>xmax = 2</code> stamps start counting. Abort is the same trick in reverse: flip one entry and a million stamps become void. This is why <code>ROLLBACK</code> in Postgres is cheap even after a huge write — nothing gets undone, because nothing was done in place.</p>
<p>Improved rule: <em>a version is visible if its</em> <code>xmin</code> <em>committed, and its</em> <code>xmax</code> <em>is null or belongs to a transaction that didn't commit.</em></p>
<p><strong>Have we solved MVCC?</strong> Better — no more dirty reads. But run the demo again with a twist: Session B reads the count (1,000,000), then Session A commits, then Session B — <em>still inside the same transaction</em> — reads again and gets 0. B's world changed under its feet mid-transaction. That's the <strong>non-repeatable read</strong>, and it's the next problem.</p>
<hr />
<h2>Problem 3: The world moves while I'm reading it</h2>
<p>What Session B actually wants is: <em>"show me the database as it was at the moment I started, and keep showing me that, no matter what anyone else does."</em></p>
<p>It wants a <strong>snapshot</strong>.</p>
<p>Here's the moment the magic dissolves, so read this sentence twice: a snapshot is not a copy of the data. Copying a million rows per transaction would be insane. A snapshot is <strong>two numbers and a set</strong>:</p>
<pre><code class="language-typescript">// A frozen view of the world. NOT a copy of any data —
// just the two facts needed to judge any stamp, forever:
interface Snapshot {
  nextTxid: Txid;         // where the counter stood at my begin().
                          // Anything &gt;= this is from my future: invisible.
  activeTxids: Set&lt;Txid&gt;; // who was still running at my begin().
                          // Their fate was undecided in my world — so even if
                          // they commit later, for me they never happened.
}
 
begin(): Txid {
  const txid = this.nextTxid++;
 
  // Photograph the in-flight transactions at this exact instant.
  // This set never changes for the life of the transaction —
  // that immutability IS the "repeatable" in repeatable reads.
  const activeTxids = new Set(
    [...this.clog].filter(([, s]) =&gt; s === "in_progress").map(([id]) =&gt; id)
  );
 
  this.snapshots.set(txid, { nextTxid: this.nextTxid, activeTxids });
  this.clog.set(txid, "in_progress");
  return txid;
}
</code></pre>
<p>That's the entire cost of "freeze the world": record where the txid counter was, and who was in flight. The <em>data</em> doesn't get frozen — the <strong>rules for judging stamps</strong> do.</p>
<p>The first rule the snapshot gives us: <code>xmin &lt; snap.nextTxid</code>. If a version was created by a transaction that started after mine, it doesn't exist in my universe — <em>even if it has already committed</em>. My world ends at the counter value I saw at <code>begin()</code>.</p>
<p><strong>Have we solved MVCC?</strong> Almost — and the last gap is the subtle one that separates people who've read about MVCC from people who've built it.</p>
<hr />
<h2>Problem 4: The transaction from the past that commits in the future</h2>
<p>Suppose transaction 5 is running when I (transaction 8) begin. Transaction 5 has a txid <em>lower</em> than my snapshot's <code>nextTxid</code>, so the rule from Problem 3 — <code>xmin &lt; 9</code> — says its writes are in my past.</p>
<p>But at the moment I took my snapshot, transaction 5 <strong>hadn't committed yet</strong>. If it commits while I'm running, the commit-status check from Problem 2 starts passing, the counter check from Problem 3 already passes, and transaction 5's writes <em>leak into my snapshot mid-flight</em>. The non-repeatable read is back, through the side door.</p>
<p>This is exactly what <code>activeTxids</code> is for. Third rule: a stamp from any transaction that was <strong>in flight when I began</strong> doesn't count for me — no matter what it does later. It can commit five milliseconds after my <code>begin()</code>; as far as I'm concerned, it never happened.</p>
<p>And with that, we have all three conditions for a stamp to "count," and no more problems left to solve. Time to assemble.</p>
<hr />
<h2>The visibility function: 20 lines that are the whole point</h2>
<p>Every question in MVCC reduces to one question: <em>given a version and a snapshot, can this transaction see it?</em> Every rule we derived above becomes one condition:</p>
<pre><code class="language-typescript">// The one question all of MVCC reduces to:
// "Given this version and my snapshot, does it exist — for me?"
// Every line below is load-bearing: delete it, and a named anomaly returns.
function isVisible(v: Version, snap: Snapshot, clog: Clog, myTxid: Txid): boolean {
 
  // ── Part 1: was this version BORN, from my point of view? ──
  const xminVisible =
    // My own writes are always visible to me. Remove this and your own
    // INSERT vanishes from your own SELECT. (Not isolation — amnesia.)
    v.xmin === myTxid ||
 
    // A stranger's write counts only if ALL THREE hold:
    (clog.get(v.xmin) === "committed" &amp;&amp;   // it really happened
                                           //   (remove → DIRTY READS: you see
                                           //    data that later rolls back)
      v.xmin &lt; snap.nextTxid &amp;&amp;            // it happened in my past
                                           //   (remove → transactions from your
                                           //    FUTURE appear in your present)
      !snap.activeTxids.has(v.xmin));      // its author wasn't mid-flight at my begin()
                                           //   (remove → a "past" txid commits late
                                           //    and leaks in: NON-REPEATABLE READS)
 
  if (!xminVisible) return false;   // never born (for me) → nothing to see
 
  // ── Part 2: has this version DIED, from my point of view? ──
 
  // No death certificate at all: alive for everyone.
  if (v.xmax === null) return true;
 
  // I stamped it myself → dead to me, instantly. (My DELETE must take
  // effect for ME before I commit — but for no one else. Same stamp,
  // different verdict per observer.)
  if (v.xmax === myTxid) return false;
 
  // A stranger's delete is judged by the SAME three conditions as a birth:
  // really happened, in my past, author not in flight at my begin().
  // Symmetry is the punchline: death plays by birth's rules.
  const xmaxApplies =
    clog.get(v.xmax) === "committed" &amp;&amp;
    v.xmax &lt; snap.nextTxid &amp;&amp;
    !snap.activeTxids.has(v.xmax);
 
  // A delete that (for me) never happened leaves the version alive (for me).
  return !xmaxApplies;
}
</code></pre>
<p>Read the comments and notice that this function is our whole journey, compressed: the three conditions on <code>xmin</code> are exactly the fixes for Problems 2, 3, and 4 — really happened, in my past, author not in flight. And the punchline is the symmetry: the <code>xmax</code> half is the <em>same three conditions</em>, judging death instead of birth. A delete from an uncommitted transaction, a delete from your future, a delete from a transaction that was mid-flight when you began — none of them happened, for you.</p>
<blockquote>
<p>A version is visible when its birth is inside your snapshot and its death is not.</p>
</blockquote>
<p>That sentence <em>is</em> MVCC. Everything else is bookkeeping.</p>
<p><strong>Break it yourself:</strong> the best way to convince yourself every condition is load-bearing is to delete one and watch its anomaly reappear. The full repo (linked at the end) has a test per condition — comment out a line, run the tests, and see exactly which guarantee falls over.</p>
<hr />
<h2>The operations become trivial</h2>
<p>Here's the payoff for doing the hard thinking in one function — every operation is now a one-liner over version chains:</p>
<pre><code class="language-typescript">class Database {
  // ... nextTxid, rows, clog, snapshots from above ...
 
  // Reading = filtering history through a pure function.
  // No locks taken, no waiting, no coordination with writers —
  // which is exactly WHY readers never block anyone.
  read(txid: Txid, key: string): unknown | undefined {
    const snap = this.snapshots.get(txid)!;
    const row = this.rows.get(key);
    return row?.versions.find(v =&gt; isVisible(v, snap, this.clog, txid))?.value;
  }
 
  // INSERT = append a version stamped with my birth certificate.
  // Invisible to everyone else until my txid flips to "committed" in the clog.
  insert(txid: Txid, key: string, value: unknown) {
    const row = this.rows.get(key) ?? { versions: [] };
    row.versions.push({ xmin: txid, xmax: null, value });
    this.rows.set(key, row);
  }
 
  // DELETE = find the version *I* can see, stamp its death certificate.
  // One integer write. The data itself is untouched — still fully
  // readable by every snapshot that predates me.
  delete(txid: Txid, key: string) {
    const snap = this.snapshots.get(txid)!;
    const v = this.rows.get(key)?.versions
      .find(v =&gt; isVisible(v, snap, this.clog, txid));
    if (v) v.xmax = txid;
  }
 
  // UPDATE doesn't exist. It has never existed. It is a DELETE and an
  // INSERT wearing a trench coat — in our toy AND in Postgres
  // (that's why ctid changes on every UPDATE).
  update(txid: Txid, key: string, value: unknown) {
    this.delete(txid, key);
    this.insert(txid, key, value);
  }
}
</code></pre>
<p><code>read</code> doesn't scan a lock table, doesn't wait, doesn't coordinate with anyone. It filters version chains through a pure function. That's why readers never block writers: <strong>reading is just math over stamps.</strong></p>
<hr />
<h2>Replay the impossible demo — on our database</h2>
<p>Back to the opening mystery, now against our 100 lines:</p>
<pre><code class="language-typescript">const db = new Database();
 
const setup = db.begin();                 // txid 1
db.insert(setup, "alice", { balance: 100 });
db.commit(setup);
 
const sessionA = db.begin();              // txid 2
const sessionB = db.begin();              // txid 3 — snapshot: { nextTxid: 4,
                                          //   activeTxids: {2} }  ← remember this set
 
db.delete(sessionA, "alice");             // alice's version is now:
                                          //   { xmin: 1, xmax: 2, balance: 100 }
                                          // ONE version. Watch it give two answers:
 
db.read(sessionB, "alice");  // → { balance: 100 }
                             //   B judges the death stamp: txid 2 is in B's
                             //   activeTxids → "that delete never happened" → alive
 
db.read(sessionA, "alice");  // → undefined
                             //   A judges the SAME stamp: xmax === myTxid
                             //   → my own delete → dead (to me alone)
 
db.commit(sessionA);         // one clog write: 2 → "committed"
 
db.read(sessionB, "alice");  // → { balance: 100 } — STILL alive for B!
                             //   B's snapshot froze at begin(); txid 2 stays in
                             //   activeTxids forever. A's commit changes the clog,
                             //   not B's snapshot. THIS is repeatable reads.
</code></pre>
<p>Look at the state after the delete: alice's version is <code>{ xmin: 1, xmax: 2, balance: 100 }</code>. One physical version, two different answers:</p>
<ul>
<li><p><strong>For A</strong> (txid 2): <code>xmax === myTxid</code> → rule 5 → dead.</p>
</li>
<li><p><strong>For B</strong> (txid 3): txid 2 was in B's <code>activeTxids</code> at begin → rule 6 fails → the delete never happened → alive. And it <em>stays</em> alive for B even after A commits, because B's snapshot doesn't change. The rows are deleted <em>and</em> not deleted — because "deleted" was never a property of the data. It's a property of the observer. The opening question wasn't a paradox; it was just the wrong question.</p>
</li>
</ul>
<hr />
<h2>Don't take my word for it — ask Postgres</h2>
<p>Our toy isn't "inspired by" Postgres. It's the same design, and Postgres will show you its stamps if you ask:</p>
<pre><code class="language-sql">CREATE TABLE t (id int, val text);
INSERT INTO t VALUES (1, 'hello');
 
SELECT xmin, xmax, ctid, * FROM t;
--  xmin | xmax | ctid  | id | val
--   748 |    0 | (0,1) |  1 | hello
 
UPDATE t SET val = 'world';
 
SELECT xmin, xmax, ctid, * FROM t;
--  xmin | xmax | ctid  | id | val
--   749 |    0 | (0,2) |  1 | world
</code></pre>
<p>Every column maps to something we built:</p>
<table>
<thead>
<tr>
<th>Postgres</th>
<th>Our toy</th>
<th>Meaning</th>
</tr>
</thead>
<tbody><tr>
<td><code>xmin</code></td>
<td><code>Version.xmin</code></td>
<td>txid that created the version</td>
</tr>
<tr>
<td><code>xmax</code></td>
<td><code>Version.xmax</code></td>
<td>txid that deleted it (0 = our <code>null</code>)</td>
</tr>
<tr>
<td><code>ctid</code></td>
<td>position in <code>versions[]</code></td>
<td>physical location — note it <em>changed</em> on UPDATE: new version, new address</td>
</tr>
<tr>
<td><code>pg_xact</code></td>
<td>our <code>clog</code> Map</td>
<td>commit status per txid</td>
</tr>
<tr>
<td><code>pg_current_snapshot()</code></td>
<td>our <code>Snapshot</code></td>
<td>literally prints <code>xmin:xmax:xip_list</code> — the two numbers and the set</td>
</tr>
</tbody></table>
<p>Run <code>SELECT pg_current_snapshot();</code> inside a transaction and you'll see something like <code>748:752:749,750</code> — "everything before 748 is my past, everything from 752 on is my future, and 749/750 were in flight when I began." Two numbers and a set. Nothing is magic.</p>
<p>Now the fun one. After the UPDATE above, where did version <code>(0,1)</code> — the <code>'hello'</code> version — go?</p>
<pre><code class="language-sql">VACUUM VERBOSE t;
-- INFO:  vacuuming "public.t"
-- INFO:  table "t": removed 1 dead item identifiers in 1 pages
</code></pre>
<p>It was still there. Dead, invisible to every current snapshot — but physically present until <code>VACUUM</code> reaped it.</p>
<hr />
<h2>What we deliberately punted</h2>
<p>An honest accounting of the gap between our 100 lines and production Postgres:</p>
<ul>
<li><p><strong>Write-write conflicts.</strong> Two transactions updating the same row: our toy lets the second one clobber the first's <code>xmax</code>. Postgres blocks the second writer until the first commits/aborts, then applies first-updater-wins. It's the one place MVCC <em>does</em> lock — writers against writers, never against readers.</p>
</li>
<li><p><strong>Command IDs (</strong><code>cmin</code><strong>/</strong><code>cmax</code><strong>).</strong> Postgres tracks visibility per <em>statement</em> within a transaction, not just per transaction, so a <code>DELETE</code> doesn't hide rows from itself mid-scan.</p>
</li>
<li><p><strong>SERIALIZABLE.</strong> Snapshot isolation — what we built — still permits an anomaly called write skew. Postgres's fix (SSI) is a genuinely great paper (<a href="https://drkp.net/papers/ssi-vldb12.pdf">Cahill et al.</a>) and out of scope here.</p>
</li>
<li><p><strong>Hint bits and performance.</strong> Real Postgres doesn't consult the clog on every visibility check; it caches commit status in bits on the tuple itself. Same logic, heavy optimization.</p>
</li>
<li><p><strong>Garbage.</strong> The big one. Our founding decision — <em>never update, never delete, only append</em> — has a bill attached: dead versions pile up forever. Every UPDATE leaves a corpse. Postgres's answer is autovacuum, easily its most feared subsystem, and the source of half the Postgres war stories you've read. That bill, and how it gets paid, is the next post: <strong>MVCC's dirty secret — garbage.</strong></p>
</li>
</ul>
<hr />
<h2>The whole architecture, in one breath</h2>
<p>Look back at what happened. We made one decision — append-only — and then fixed the problems it created, one at a time, adding only what each problem demanded:</p>
<ol>
<li><p>Can't overwrite → <strong>version chains</strong> with <code>xmin</code>/<code>xmax</code> stamps</p>
</li>
<li><p>Stamps from uncommitted transactions → the <strong>clog</strong></p>
</li>
<li><p>The world moving mid-read → <strong>snapshots</strong> (two numbers and a set)</p>
</li>
<li><p>In-flight transactions committing late → <strong>activeTxids</strong></p>
</li>
<li><p>All four rules composed → the <strong>visibility function</strong></p>
</li>
<li><p>Every operation → a trivial wrapper around it At no point did we design "MVCC." We just refused to update data in place and then dealt with the consequences honestly — and the entire architecture of Postgres's concurrency control fell out, in 100 lines.</p>
</li>
</ol>
<p>Nothing is magic.</p>
<hr />
<p><em>Previously in this series:</em> <a href="https://blog.jatin510.dev"><em>Write-Ahead Log Explained: Build One in 30 Lines</em></a> <em>— where these versions would actually get persisted.</em></p>
]]></content:encoded></item><item><title><![CDATA[Write-Ahead Log Explained: Build Crash-Safe Durability in 30 Lines]]></title><description><![CDATA[Last time we built a connection pool from scratch. This time: how does a database not lose your data when the power dies?

You write a row. The database says OK. A millisecond later, someone trips ove]]></description><link>https://blog.jatin510.dev/write-ahead-log-explained-build-crash-safe-durability-in-30-lines</link><guid isPermaLink="true">https://blog.jatin510.dev/write-ahead-log-explained-build-crash-safe-durability-in-30-lines</guid><category><![CDATA[Databases]]></category><category><![CDATA[wal]]></category><category><![CDATA[storage]]></category><category><![CDATA[postgres]]></category><category><![CDATA[TypeScript]]></category><dc:creator><![CDATA[Jagdish Parihar]]></dc:creator><pubDate>Sun, 26 Jul 2026 14:25:44 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/6087c71e6aba67265b7c40cc/4d5f9d61-fe12-41f7-aa76-ec93473695a8.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><em>Last time we built a</em> <a href="https://blog.jatin510.dev/connection-pooling-explained"><em>connection pool from scratch</em></a><em>. This time: how does a database not lose your data when the power dies?</em></p>
<hr />
<p>You write a row. The database says <code>OK</code>. A millisecond later, someone trips over the power cable.</p>
<p>When the machine boots back up, is your row still there?</p>
<p>If the answer is "yes," then some code ran <em>before</em> that <code>OK</code> was sent that made your write survive a crash that hadn't happened yet. That code is the write-ahead log. It sounds like the deepest, scariest part of a database. It's an append to a file and one system call.</p>
<p>We're going to derive it. One problem at a time, starting from a <code>Map</code>.</p>
<h2>The store that forgets</h2>
<p>Here's the simplest key-value store there is:</p>
<pre><code class="language-js">const store = new Map();
store.set("balance", 100);
</code></pre>
<p>Fast. Correct. Completely fake — it lives in RAM.</p>
<pre><code class="language-bash">$ kill -9 &lt;pid&gt;
</code></pre>
<p>Every write since startup is gone. <code>SIGKILL</code> can't be caught, so there's no shutdown hook, no flush-on-exit, no goodbye. The process is simply deleted mid-run.</p>
<p>So, the problem, stated plainly:</p>
<blockquote>
<p><strong>How does a single write survive a crash?</strong></p>
</blockquote>
<p>Everything below is that one question, chased until it's answered properly.</p>
<p>The first step is obvious enough: put the data somewhere that isn't RAM.</p>
<h2>Step 1: write it to a file</h2>
<p>"Durable" just means "it's in a file," right? So after every change, save the map:</p>
<pre><code class="language-js">const fs = require("fs");
const store = new Map();

function set(key, value) {
  store.set(key, value);
  fs.writeFileSync("data.json", JSON.stringify([...store]));  // save everything
}

function load() {
  if (!fs.existsSync("data.json")) return;
  for (const [k, v] of JSON.parse(fs.readFileSync("data.json", "utf8"))) store.set(k, v);
}
</code></pre>
<p><strong>And this works.</strong> Not "sort of" — kill the process, restart, call <code>load()</code>, and every write you made is there. As a statement of intent, "save everything after every change" is exactly right. (It has a nasty hole we'll come back to in Step 8, and another one below.)</p>
<p>It fails on cost, and it fails badly.</p>
<p>But first, the other hole, because it's worse than it looks: <code>writeFileSync</code> opens with <code>"w"</code>, which <strong>truncates the file to zero before writing a single byte.</strong> Crash mid-snapshot and you haven't lost one write — you've lost <em>the entire dataset</em>. The real fix is to write a temp file and <code>rename()</code> it over the original, since <code>rename</code> is atomic. Keep that in mind: our very first attempt is the least crash-safe design in this whole article.</p>
<p>Say the store holds 2 GB and one <code>set()</code> changes a 20-byte value. To persist those 20 bytes, you write out all 2 GB. The ratio of bytes-shipped-to-disk versus bytes-actually-changed is called <strong>write amplification</strong>, and here it's:</p>
<pre><code class="language-plaintext">2,000,000,000 / 20  ≈  100,000,000 : 1
</code></pre>
<p>A hundred million units of work for one unit of change. On an SSD sustaining 500 MB/s, serializing 2 GB takes <strong>~4 seconds</strong> — so a single key update blocks for four seconds before it can return <code>OK</code>.</p>
<p>Worse, the cost is O(N) in your <em>total</em> data, not in the size of the change. Grow to 20 GB and every tiny write takes 40 seconds. You've built a store that punishes you for putting data in it.</p>
<p>The fix has to be: stop writing things that didn't change.</p>
<h2>Step 2: only write what changed</h2>
<p>If just 20 bytes changed, write 20 bytes. Seek to wherever that key lives on disk and overwrite it in place:</p>
<pre><code class="language-js">function set(key, value) {
  store.set(key, value);
  const offset = offsets.get(key);              // where this key lives on disk
  fs.writeSync(fd, serialize(value), 0, len, offset);   // overwrite it
}
</code></pre>
<p>The write is tiny again, and it stays tiny no matter how big the dataset gets. Problem solved — except we just created two new ones.</p>
<p><strong>Random I/O.</strong> Seeking to an arbitrary offset for every write is the slowest thing storage does. On a spinning disk, seek latency caps you around a hundred writes per second. Even on an SSD, a 20-byte write forces the drive to read-modify-write an entire flash page underneath you. We traded one big sequential write for an endless stream of small random ones.</p>
<p><strong>Corruption.</strong> This is the fatal one. You are mutating the <em>only</em> copy of data you already committed. If the machine dies halfway through overwriting that record, you haven't lost a pending write — you've destroyed a finished one, and there's no clean version to fall back to. Instead of losing the newest write, you lose an old write that was already safe.</p>
<p>In-place updates and crash safety are fundamentally at war.</p>
<h2>The mistake both attempts share</h2>
<p>Two attempts, two dead ends. Step back and they have the same root cause:</p>
<blockquote>
<p>Both attempts try to keep an up-to-date copy of the <strong>data</strong> on disk.</p>
</blockquote>
<p>Maintaining that copy is what forces the bad choice. Rewrite it wholesale and you pay O(N) per write. Edit it in place and you risk destroying committed data.</p>
<p>So stop storing the data.</p>
<h2>Step 3: store the changes instead</h2>
<p>Don't maintain the map on disk. Instead, append a one-line description of <em>what happened</em> to a log file — and never touch bytes you've already written:</p>
<pre><code class="language-js">const fd = fs.openSync("data.wal", "a");    // "a" = append only

function append(record) {
  fs.writeSync(fd, JSON.stringify(record) + "\n");
}

append({ op: "set", key: "balance", value: 100 });
</code></pre>
<p>The file now looks like a diary rather than a database:</p>
<pre><code class="language-plaintext">{"op":"set","key":"balance","value":100}
{"op":"set","key":"balance","value":250}
{"op":"del","key":"temp"}
</code></pre>
<p>Look at what that bought us, against both failures at once:</p>
<ul>
<li><p><strong>We only write the change.</strong> O(change), never O(N). A 20-byte update writes ~40 bytes of record, whether the store holds 2 MB or 2 TB.</p>
</li>
<li><p><strong>Every write is a sequential append.</strong> No seeking — the fastest access pattern a disk has, on spinning rust and SSDs alike.</p>
</li>
<li><p><strong>We never overwrite anything.</strong> Old records are immutable. A crash can only damage the record currently in flight — it can't reach back and corrupt a write that already succeeded.</p>
</li>
</ul>
<p>That third point is the one that actually resolves Step 2's war. Append-only isn't just faster; it's <em>structurally</em> safer, because committed bytes are never in the line of fire.</p>
<p>One problem: this file is a list of changes, not a store. To read <code>balance</code> you'd have to scan the whole log every time.</p>
<h2>Step 4: reads need memory</h2>
<p>So keep the <code>Map</code>. It never stopped being useful — it was only ever the <em>durability</em> that was missing. Write to both: the log for survival, the map for speed.</p>
<pre><code class="language-js">function set(key, value) {
  append({ op: "set", key, value });   // durability
  store.set(key, value);               // speed
}

function get(key) {
  return store.get(key);               // pure memory, O(1)
}
</code></pre>
<p>Reads never touch the disk. Writes are one small sequential append. This is the shape almost every real storage engine has: an in-memory structure for serving, an append-only log for surviving.</p>
<p>Which leaves one hole. Restart the process and the <code>Map</code> is empty again.</p>
<h2>Step 5: replay to rebuild</h2>
<p>The log is a complete, ordered history of every change ever made. So on startup, read it top to bottom and re-apply each record. Whatever state you ended with, you get back:</p>
<pre><code class="language-js">function replay() {
  if (!fs.existsSync(path)) return;
  for (const line of fs.readFileSync(path, "utf8").split("\n")) {
    if (!line) continue;
    const r = JSON.parse(line);
    if (r.op === "set") store.set(r.key, r.value);
    if (r.op === "del") store.delete(r.key);
  }
}
</code></pre>
<p>This is why every record carries an <code>op</code>. A log of values alone can't express <em>absence</em> — "delete <code>temp</code>" is an event, and the only way to record an event is to name it. Replay is just re-running history.</p>
<p>Put it together and the store works — it's not finished, but it runs:</p>
<pre><code class="language-js">// wal.js
const fs = require("fs");

class WAL {
  constructor(path) {
    this.path = path;
    this.store = new Map();
    this.#replay();                       // rebuild memory from history
    this.fd = fs.openSync(path, "a");     // append handle for new writes
  }

  set(key, value) { this.#append({ op: "set", key, value }); this.store.set(key, value); }
  del(key)        { this.#append({ op: "del", key });        this.store.delete(key); }
  get(key)        { return this.store.get(key); }

  #append(record) {
    fs.writeSync(this.fd, JSON.stringify(record) + "\n");
  }

  #replay() { /* as above */ }
}

module.exports = WAL;
</code></pre>
<p>Now look closely at <code>set()</code>. It touches two places — the log and the map. Before worrying about their order, we need to pin down what we actually owe the caller.</p>
<h2>Step 6: what exactly did we promise?</h2>
<p>When <code>set()</code> returns, we've said <code>OK</code>. That's a promise: <em>this write will survive a crash.</em> Anything before the return is ours to lose freely. Anything after is a lie if we lose it. So the rule is:</p>
<blockquote>
<p>The log record for an operation must be durable <strong>before</strong> the operation is acknowledged.</p>
</blockquote>
<p>Walk every crash point against <code>set()</code>:</p>
<table>
<thead>
<tr>
<th>Crash point</th>
<th>Log has it?</th>
<th>Caller got <code>OK</code>?</th>
<th>Outcome</th>
</tr>
</thead>
<tbody><tr>
<td>Mid-<code>#append</code></td>
<td>Partially</td>
<td>No</td>
<td>Nothing was promised — safe to drop (Step 9 makes replay handle it)</td>
</tr>
<tr>
<td>After append, before <code>store.set</code></td>
<td>Yes</td>
<td>No</td>
<td>Replay applies it. State correct</td>
</tr>
<tr>
<td>After <code>store.set</code>, before return</td>
<td>Yes</td>
<td>No</td>
<td>Replay applies it. State correct</td>
</tr>
<tr>
<td>After return</td>
<td>Yes</td>
<td>Yes</td>
<td>Promise kept</td>
</tr>
</tbody></table>
<p>The log always contains <strong>at least</strong> every acknowledged write. It might contain one extra — a record we logged but hadn't returned from yet — and that's harmless, since replaying it just leaves state slightly <em>newer</em> than promised. What it can never do is lack a write we already confirmed. That would be a lie.</p>
<p>Now, the ordering. Look at the table again and notice something uncomfortable: <strong>swapping the two lines inside</strong> <code>set()</code> <strong>wouldn't break a single row.</strong> Our map is volatile — it's gone on crash either way — and the guarantee is anchored to the <em>return</em>, not to the map. So why insist on log-first?</p>
<p>Because that's an accident of our map living in RAM. The moment the structure you're updating is <em>also</em> on disk — real data pages, a B-tree, an SSTable — the order becomes everything. Mutate the page first, crash, and you're left with a half-modified persistent structure and no log record to redo or undo it: unrecoverable corruption. Log first, and recovery can always repair the page from the record.</p>
<p>That's what "write-ahead" means — the log lands ahead of the acknowledgment, <em>and</em> ahead of the data it describes. We'll keep log-first from here on, because that's the invariant that survives contact with a real storage engine.</p>
<h2>Step 7: does it survive? kill -9</h2>
<p>Enough theory. Let's kill it.</p>
<pre><code class="language-js">// writer.js
const WAL = require("./wal");
const db = new WAL("data.wal");
let n = 0;
setInterval(() =&gt; {
  db.set(`key:${n}`, { n, at: Date.now() });
  console.log("wrote", `key:${n++}`);
}, 500);
</code></pre>
<pre><code class="language-bash">$ node writer.js
wrote key:0
wrote key:1
wrote key:2
wrote key:3
# in another terminal:
$ kill -9 $(pgrep -f writer.js)
</code></pre>
<p>No cleanup ran. No handler fired. Now read it back:</p>
<pre><code class="language-js">// reader.js
const WAL = require("./wal");
const db = new WAL("data.wal");
console.log([...db.store.entries()]);
</code></pre>
<pre><code class="language-bash">$ node reader.js
[ [ 'key:0', {…} ], [ 'key:1', {…} ], [ 'key:2', {…} ], [ 'key:3', {…} ] ]
</code></pre>
<p>Every acknowledged write came back. We murdered the process and lost nothing.</p>
<p>Notice something, though: there is no <code>fsync</code> anywhere in this code. If durability supposedly requires forcing bytes to disk, why did this just work? And if it works without it — what exactly are we still exposed to?</p>
<h2>Step 8: surviving the power cut</h2>
<p>Here's the part most WAL tutorials get wrong.</p>
<p><code>fs.writeSync()</code> does <strong>not</strong> put your bytes on the disk. It copies them into the operating system's <strong>page cache</strong> — kernel memory — and returns. The kernel flushes to the physical device later, on its own schedule.</p>
<p>That explains the test above. Once the bytes are in the page cache, they belong to the kernel, not your process. Your process can die any way it likes — <code>kill -9</code>, segfault, OOM kill, uncaught throw — and the kernel still writes them out. <strong>A process crash cannot lose data that's already in the page cache.</strong></p>
<p>So <code>write()</code> alone buys real durability against <em>your program</em> crashing. That's the majority of crashes, and it's genuinely why the demo in Step 7 passed.</p>
<p>What <code>write()</code> does not survive is the <strong>operating system</strong> dying with those bytes still in its cache: a power cut, a kernel panic, a yanked plug. The page cache is RAM, and RAM doesn't survive power loss. Anything not yet flushed evaporates — including writes you already answered <code>OK</code> to. That breaks the invariant.</p>
<p><code>fsync()</code> is the syscall that says <em>do not return until these bytes are physically on the storage device</em>:</p>
<pre><code class="language-js">  #append(record) {
    fs.writeSync(this.fd, JSON.stringify(record) + "\n");  // → page cache
    fs.fsyncSync(this.fd);                                 // → physical disk
  }
</code></pre>
<p>That one line is the border between "safe if my program crashes" and "safe if the building loses power."</p>
<p>It's also, by far, the expensive part. <code>writeSync</code> is a memcpy into kernel memory — nanoseconds. <code>fsyncSync</code> blocks on physical hardware — often a millisecond or more, and it's the reason every serious database gives you a knob to trade durability against throughput. We'll see three of those knobs shortly.</p>
<p>One more thing our 30 lines quietly assume: <code>fs.writeSync</code> returns the number of bytes it actually wrote, and it's allowed to write fewer than you asked for. Production code loops until the buffer is drained. For small appends to a regular file you'll effectively never see it — but "effectively never" is doing real work in that sentence.</p>
<h2>Step 9: the half-written record</h2>
<p>One crash case from Step 6's table deserves its own section: crashing <em>mid-append</em>.</p>
<p><code>writeSync</code> is not atomic. Lose power partway through and the last line in the file is a fragment:</p>
<pre><code class="language-plaintext">{"op":"set","key":"key:40","value":{"n":40,"at":1753500000000}}
{"op":"set","key":"key:41","value":{"n":41,"at":17
</code></pre>
<p>That's a <strong>torn record</strong>, and <code>JSON.parse</code> throws on it. Which means our replay loop crashes on startup — the durability feature has become a boot failure. Impressive own goal.</p>
<p>The fix is one line:</p>
<pre><code class="language-js">    let r;
    try { r = JSON.parse(line); } catch { break; }   // torn tail — stop replaying
</code></pre>
<p>Stop at the first line that won't parse, and keep everything before it.</p>
<p>It's worth checking that this heuristic actually holds rather than assuming it. Take one full record and try to parse every possible truncation of it: of the 57 cut points in <code>{"op":"set","key":"a","value":{"n":41,"at":1753500000000}}</code>, <strong>zero</strong> produce valid JSON. Truncation always removes a closing brace or quote, so a torn record can't masquerade as a complete one. For this format, the crude fix is genuinely sound — not by luck.</p>
<p>This is safe, and it's safe <em>because of the invariant</em> — not by luck:</p>
<ol>
<li><p>A torn record can only ever be the <strong>last</strong> record. Nothing gets appended after a crash, so there's no valid data hiding behind the damage.</p>
</li>
<li><p>A torn record was never acknowledged. The <code>fsync</code> inside <code>#append</code> hadn't returned, so <code>set()</code> hadn't returned, so the caller was never told <code>OK</code>. Discarding it breaks no promise.</p>
</li>
</ol>
<p>Truncating at the tear leaves the log as a clean prefix of history — exactly the guarantee we defined in Step 6.</p>
<p>Catching a <code>JSON.parse</code> exception is a crude way to detect corruption, but the <em>idea</em> is exactly right. So let's go see how the grown-ups implement it.</p>
<h2>The finished thing</h2>
<p>Every patch folded back in:</p>
<pre><code class="language-js">// wal.js — a crash-safe key-value store
const fs = require("fs");

class WAL {
  constructor(path) {
    this.path = path;
    this.store = new Map();
    this.#replay();                     // rebuild memory from history
    this.fd = fs.openSync(path, "a");   // append-only handle
  }

  set(key, value) { this.#append({ op: "set", key, value }); this.store.set(key, value); }
  del(key)        { this.#append({ op: "del", key });        this.store.delete(key); }
  get(key)        { return this.store.get(key); }

  #append(record) {
    fs.writeSync(this.fd, JSON.stringify(record) + "\n");  // → page cache
    fs.fsyncSync(this.fd);                                 // → physical disk
  }

  #replay() {
    if (!fs.existsSync(this.path)) return;
    for (const line of fs.readFileSync(this.path, "utf8").split("\n")) {
      if (!line) continue;
      let r;
      try { r = JSON.parse(line); } catch { break; }   // torn tail — stop
      if (r.op === "set") this.store.set(r.key, r.value);
      if (r.op === "del") this.store.delete(r.key);
    }
  }
}

module.exports = WAL;
</code></pre>
<p>Thirty lines. Append-only for cheap writes, <code>fsync</code> for power loss, replay for boot, <code>break</code> for the torn tail. Every line is there because a specific failure put it there.</p>
<h2>Proof: the real thing</h2>
<p>Nothing is magic, so let's check our 30 lines against production source.</p>
<p><strong>LevelDB</strong> (<code>db/log_writer.cc</code>, <code>db/log_reader.cc</code>) is our toy, hardened. Where we write newline-delimited JSON, it frames each record with a 7-byte header — 4-byte CRC-32C, 2-byte length, 1-byte type — inside fixed 32 KB blocks. On recovery it recomputes the CRC and drops a bad or short final record, exactly where our <code>break</code> gives up: a checksum instead of a thrown exception, same decision. And it's literally write-ahead — <code>DBImpl::Write</code> appends to the log <em>first</em>, then applies to the memtable, which is an in-memory map just like ours. <code>WriteOptions.sync</code> is what turns on the <code>fsync</code>.</p>
<p><strong>PostgreSQL</strong> exposes the knobs. <code>synchronous_commit</code> decides whether <code>COMMIT</code> waits for the WAL flush — turn it off and commits return before they're durable, which is faster and means a crash can lose the last few transactions without corrupting anything. <code>wal_sync_method</code> picks the actual syscall (<code>fdatasync</code>, <code>fsync</code>, …). And our torn-record problem gets solved at the page level by <code>full_page_writes</code>: the first change to a page after a checkpoint logs the entire page, so a torn page can be rebuilt from the WAL. (If you've been reading the Postgres 18 <code>io_uring</code> work — this is the write path it feeds.)</p>
<p><strong>SQLite</strong> makes the tradeoff a single config line: <code>PRAGMA synchronous = OFF | NORMAL | FULL</code>. That's our <code>fsyncSync</code>, promoted to a user-facing dial.</p>
<p>Same primitive underneath all three: append a record, choose how hard to <code>fsync</code>, replay on boot, tolerate a torn tail.</p>
<h2>The one problem we didn't solve</h2>
<p>Two loose ends, one small and one large.</p>
<p>The small one: <code>fsync</code> on a file isn't always enough. When you <em>create</em> a new file, the filename itself lives in the parent directory, and that directory entry needs its own <code>fsync</code> to survive a crash — otherwise a power cut can leave you with durable bytes and no name pointing at them. And we do hit this: on first run, <code>fs.openSync(path, "a")</code> creates <code>data.wal</code>. Every append after that is safe; the very first one needs an <code>fsync</code> on the parent directory too. Ours doesn't do it.</p>
<p>The large one: <strong>the log grows forever.</strong> Every <code>set</code> appends, even overwriting the same key a thousand times. Nothing is ever removed. Replay gets slower every day the process runs, and the file eventually eats the disk.</p>
<p>Which means the log needs a way to say: <em>I've folded everything up to here into the main data structure, so the old records can go.</em> That's a <strong>checkpoint</strong> — and when the main data structure is a sorted tree on disk, checkpointing becomes <strong>compaction</strong>, the beating heart of every LSM engine.</p>
<p>That's the next brick. See you there.</p>
<hr />
<p><em>If this was useful, the</em> <a href="https://blog.jatin510.dev/connection-pooling-explained"><em>connection pooling post</em></a> <em>is its older sibling.</em></p>
]]></content:encoded></item><item><title><![CDATA[Connection Pooling Explained: Build One From Scratch in 30 Lines]]></title><description><![CDATA[Nothing is magic — part of a series on building infrastructure primitives from scratch.
I used to think request queuing and connection pooling were deep infrastructure magic — something libraries did ]]></description><link>https://blog.jatin510.dev/connection-pooling-explained</link><guid isPermaLink="true">https://blog.jatin510.dev/connection-pooling-explained</guid><category><![CDATA[PG ]]></category><category><![CDATA[connection]]></category><category><![CDATA[connection pooling]]></category><category><![CDATA[database]]></category><category><![CDATA[networking]]></category><dc:creator><![CDATA[Jagdish Parihar]]></dc:creator><pubDate>Sun, 05 Jul 2026 07:42:09 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/6087c71e6aba67265b7c40cc/851ee9ca-d55d-458a-870c-c17a9b6cb806.svg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><em>Nothing is magic — part of a series on building infrastructure primitives from scratch.</em></p>
<p>I used to think request queuing and connection pooling were deep infrastructure magic — something libraries did that I could never see. Then I built each piece myself in ~30 lines of TypeScript, and the whole thing turned out to be arrays, counters, and one key insight about what a "connection" actually is.</p>
<p>This post builds the idea bottom-up. By the end, connection pooling should feel obvious — not memorized, but inevitable.</p>
<h2>Building Block 1: A connection is just a held reference</h2>
<p>When a client calls <code>fetch()</code>, the kernel opens a TCP connection identified by a unique 4-tuple: <code>(client IP, client port, server IP, server port)</code>. Ten simultaneous requests from the same laptop use up to ten ephemeral client ports — ten distinct sockets. (Browsers cap this at ~6 per origin and queue the rest — yet another queue. Sequential requests reuse one connection via keep-alive, and HTTP/2 multiplexes everything over a single socket.)</p>
<p>When Node accepts one, it hands your callback a <code>(req, res)</code> pair. Here's the insight that unlocked everything for me:</p>
<blockquote>
<p><code>res</code> is not "response data". It is a handle to one specific open socket.</p>
</blockquote>
<p>There's no ID matching, no "which user does this response belong to" lookup. The correlation is structural — <code>res</code> <em>is</em> the write-end of that exact connection. As long as you hold the reference, you can reply later — even minutes later — and the bytes go down the right socket. (Not indefinitely: Node kills requests after a timeout, 5 minutes by default, and the client may give up first.)</p>
<p>Which means: <strong>a connection is something you can store.</strong> Put <code>res</code> in an array and answer when you're ready. That single trick is what queuing is.</p>
<h2>Building Block 2: A queue is an array and a counter</h2>
<p>Say I only want to process 2 requests at a time, and allow at most 5 to wait in line. I need two limits, a counter, and an array:</p>
<pre><code class="language-typescript">const MAX_CONCURRENT = 2;      // how many requests we process at once
const MAX_QUEUE = 5;           // how many can wait before we start rejecting

let active = 0;                // how many are being processed right now
const waiting: Job[] = [];     // everyone else — this array IS the queue

http.createServer((req, res) =&gt; {
  const job = { res, enqueuedAt: Date.now() };

  if (active &lt; MAX_CONCURRENT) {
    handle(job);               // free slot → run now
  } else if (waiting.length &lt; MAX_QUEUE) {
    waiting.push(job);         // ENQUEUE: res sits in memory, client keeps waiting
  } else {
    res.writeHead(503).end();  // queue full → shed load
  }
});
</code></pre>
<p>And when a job finishes, pull the next one:</p>
<pre><code class="language-typescript">async function handle(job: Job) {
  active++;
  await doWork();              // the slow part — say each job takes 2 seconds
  active--;
  job.res.end("done");         // reply NOW — the socket was open this whole time
  drain();
}

function drain() {
  while (active &lt; MAX_CONCURRENT &amp;&amp; waiting.length &gt; 0) {
    handle(waiting.shift()!);  // DEQUEUE: FIFO, next in line gets the slot
  }
}
</code></pre>
<p>Now run this and fire 10 concurrent requests. Assume each job takes 2 seconds of work: with only 2 slots, the server drains the queue in waves of 2, every 2 seconds:</p>
<ul>
<li><p><strong>t=0s</strong> — requests 1–2 grab the free slots and start immediately. Requests 3–7 land in <code>waiting[]</code>. Requests 8–10 find the queue full and get instant 503s.</p>
</li>
<li><p><strong>t=2s</strong> — requests 1–2 finish, freeing both slots. <code>drain()</code> pulls requests 3–4 off the queue. They waited 2s.</p>
</li>
<li><p><strong>t=4s</strong> — requests 3–4 finish. Requests 5–6 start. They waited 4s.</p>
</li>
<li><p><strong>t=6s</strong> — requests 5–6 finish. Request 7 finally starts. It waited 6s.</p>
</li>
</ul>
<p>That's where the climbing wait times come from: every request ahead of you in the queue costs you a share of a work-cycle. In general, the request at queue position <em>p</em> waits about <code>ceil(p / MAX_CONCURRENT) × WORK_MS</code>. This is the arithmetic behind every "slow server" you've ever hit — your wait grows with how deep in the queue you land, in steps of (work time ÷ worker count). It's also why "add more instances" fixes latency under load: double the workers and the same queue position waits half as long.</p>
<p>While a request "queues", nothing mystical happens — its <code>res</code> object sits in an array, its TCP socket stays open, and the client's <code>fetch()</code> promise simply hasn't resolved yet.</p>
<p>(Below this array there are more queues you don't manage: the kernel's accept queue, socket buffers, Node's event loop. Same concept at every layer — bytes parked in a buffer, waiting for capacity. A request is never "floating"; it's always parked somewhere specific.)</p>
<h2>Building Block 3: Connections are expensive, requests are cheap</h2>
<p>One more fact before the payoff. Opening a database connection costs real time:</p>
<ol>
<li><p>TCP handshake (a network round trip)</p>
</li>
<li><p>TLS negotiation (another round trip — two on older TLS 1.2)</p>
</li>
<li><p>Authentication (password/SCRAM exchange)</p>
</li>
<li><p>The DB allocating resources</p>
</li>
</ol>
<p>That last step is the killer, and it's a design decision from the 1980s we still live with: <strong>Postgres forks an entire OS process per connection.</strong> Process isolation was the robust concurrency primitive of that era — but it means every connection costs the database real memory, and a Postgres server comfortably holds hundreds of connections, not tens of thousands.</p>
<p>So connections are slow to create (easily 5–50ms, often more than the query itself) <em>and</em> expensive for the server to hold in bulk. Creating one per query would be like hiring and firing an employee for every task. The obvious move: pay the setup cost once, keep the connection alive, and share it.</p>
<p>But "keep it alive" — where? Same answer as Building Block 1. <strong>In an array.</strong> An idle DB connection is just an authenticated, open socket wrapped in a JS object, sitting in your process memory doing nothing, ready to be handed out.</p>
<h2>The Payoff: A pool is the queue, inverted</h2>
<p>Look at what we've built:</p>
<ul>
<li><p><strong>Request queue:</strong> work waits in an array for a free <em>slot</em>.</p>
</li>
<li><p><strong>Connection pool:</strong> <em>connections</em> wait in an array for incoming work.</p>
</li>
</ul>
<p>Same machinery, mirrored. And when the pool runs dry, it flips back into our request queue — callers wait in line for a connection:</p>
<pre><code class="language-typescript">const MAX_POOL_SIZE = 10;      // most connections we'll ever open

class Pool {
  private idle: Connection[] = [];                     // connections waiting for work
  private waiters: ((c: Connection) =&gt; void)[] = [];   // work waiting for connections
  private total = 0;

  async acquire(): Promise&lt;Connection&gt; {
    if (this.idle.length &gt; 0)
      return this.idle.pop()!;              // reuse: no handshake, ~0ms

    if (this.total &lt; MAX_POOL_SIZE) {
      this.total++;
      return await createConnection();      // expensive path, done rarely
    }

    // pool exhausted → the caller queues (Building Block 2 again!)
    return new Promise(resolve =&gt; this.waiters.push(resolve));
  }

  release(conn: Connection) {
    const waiter = this.waiters.shift();
    if (waiter) waiter(conn);               // hand straight to next in line
    else this.idle.push(conn);              // nobody waiting → back on the shelf
  }
}
</code></pre>
<p>Usage:</p>
<pre><code class="language-typescript">const conn = await pool.acquire();
try {
  await conn.query("SELECT ...");
} finally {
  pool.release(conn);   // forget this and you have a "connection leak"
}
</code></pre>
<p>That's it. That's connection pooling. Two arrays and a counter.</p>
<p>Everything you've heard about pools now decodes for free:</p>
<ul>
<li><p><strong>"Pool exhaustion"</strong> — all connections checked out, the <code>waiters</code> array is growing. Usually a missing <code>release()</code> or slow queries hogging connections.</p>
</li>
<li><p><strong>"Acquire timeout"</strong> — a waiter that gives up (rejects its promise) after N ms instead of queuing forever.</p>
</li>
<li><p><strong>"Idle timeout"</strong> — a janitor that closes sockets sitting unused in <code>idle</code> for too long, so the DB isn't holding a process for nothing.</p>
</li>
<li><p><strong>"Max pool size"</strong> — the bound on <code>total</code>, playing the same role <code>MAX_CONCURRENT</code> did in our request queue. (Some pools also take a <em>min</em> size: connections pre-created and kept warm — our toy has no equivalent.)</p>
</li>
</ul>
<h2>Proof: the real thing is the same two arrays</h2>
<p>Don't take my word for it — open <a href="https://github.com/brianc/node-postgres/blob/master/packages/pg-pool/index.js"><code>pg-pool/index.js</code></a>, the pool used by node-postgres. Its constructor has:</p>
<pre><code class="language-javascript">this._clients = []        // every connection that exists
this._idle = []           // connections sitting alive, waiting for work
this._pendingQueue = []   // callers waiting because the pool is full
</code></pre>
<p>Our toy pool, with underscores. "Holding a connection alive" is literally an object pushed onto <code>_idle</code>:</p>
<pre><code class="language-javascript">class IdleItem {
  constructor(client, idleListener, timeoutId) {
    this.client = client              // wraps a live net.Socket to Postgres
    this.idleListener = idleListener  // if the server dies while parked, evict
    this.timeoutId = timeoutId        // idle-timeout janitor
  }
}
</code></pre>
<p>The <code>client</code> holds the underlying socket, the array holds the <code>IdleItem</code>, so nothing closes or garbage-collects the connection. A live socket, held by an object, held by an array — the same trick as parking <code>res</code> in our HTTP queue.</p>
<p>Our <code>drain()</code> is their <code>_pulseQueue()</code>: <code>_pendingQueue.shift()</code> to dequeue the next waiter (FIFO), <code>_idle.pop()</code> to grab a parked connection, <code>newClient()</code> if there's room to grow. Even the production metrics are just array lengths:</p>
<pre><code class="language-javascript">get waitingCount() { return this._pendingQueue.length }
get idleCount()    { return this._idle.length }
get totalCount()   { return this._clients.length }
</code></pre>
<p>The remaining ~400 lines are exactly the hardening we predicted: idle timeouts, acquire timeouts, <code>maxUses</code>/<code>maxLifetimeSeconds</code> rotation, error listeners on parked clients, double-release protection.</p>
<p>One elegant quirk: reuse is <code>_idle.pop()</code> — LIFO, not FIFO. That looks unfair until you see why: handing out the most-recently-used connection keeps a small hot set busy, while rarely-used connections quietly age toward their idle timeout and close themselves. The pool shrinks to fit actual load. A FIFO pool would keep every connection just-barely-alive forever.</p>
<h2>The bigger lesson</h2>
<p>Your typical Node service is queues all the way down: HTTP requests queue for the event loop, handlers queue for pool connections, queries queue inside the database, and TCP backpressure queues bytes all the way back to the client when anything fills up. None of it is magic. At every layer it's the same primitive:</p>
<blockquote>
<p>Hold a reference in an array. Hand it out when there's capacity.</p>
</blockquote>
<p>Whenever infrastructure feels magical, build the naive 30-line version. The real thing is almost always the naive version plus error handling — and the next time someone says "we're seeing pool exhaustion," you won't picture magic. You'll picture an array named <code>waiters</code>, and it's growing.</p>
]]></content:encoded></item><item><title><![CDATA[Building PostgreSQL Extensions with Rust: A Complete Guide Using pgrx]]></title><description><![CDATA[Building PostgreSQL Extensions with Rust: A Complete Guide Using pgrx
PostgreSQL is one of the most extensible databases ever built. Its extension system lets you add custom data types, functions, operators, and even entirely new storage engines. Tra...]]></description><link>https://blog.jatin510.dev/building-postgresql-extensions-with-rust-a-complete-guide-using-pgrx</link><guid isPermaLink="true">https://blog.jatin510.dev/building-postgresql-extensions-with-rust-a-complete-guide-using-pgrx</guid><category><![CDATA[Rust]]></category><category><![CDATA[rust lang]]></category><category><![CDATA[PostgreSQL]]></category><category><![CDATA[SQL]]></category><category><![CDATA[Databases]]></category><category><![CDATA[Tutorial]]></category><dc:creator><![CDATA[Jagdish Parihar]]></dc:creator><pubDate>Wed, 14 Jan 2026 04:27:40 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1768339824274/978da7fa-8846-4013-b73c-e5015f5b6589.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h1 id="heading-building-postgresql-extensions-with-rust-a-complete-guide-using-pgrx">Building PostgreSQL Extensions with Rust: A Complete Guide Using pgrx</h1>
<p>PostgreSQL is one of the most extensible databases ever built. Its extension system lets you add custom data types, functions, operators, and even entirely new storage engines. Traditionally, this power came with a cost: you had to write C code.</p>
<p>C is fast, but it's also unforgiving. A single buffer overflow or null pointer dereference can crash your entire database server—taking all your connections and transactions down with it. For a production database, that's terrifying.</p>
<p>Enter Rust.</p>
<p>In this guide, I'll show you how to build PostgreSQL extensions using Rust and the <strong>pgrx</strong> framework. We'll use a real extension—<strong>pg_mask</strong>—as our working example: a set of data masking functions for GDPR and privacy compliance.</p>
<h2 id="heading-why-rust-over-c">Why Rust Over C?</h2>
<p>If you've written C for PostgreSQL before, you know the pain:</p>
<ul>
<li><p><strong>Memory management is manual.</strong> Forget to <code>pfree()</code> something? Memory leak. Free it twice? Crash.</p>
</li>
<li><p><strong>Error handling is tedious.</strong> PostgreSQL's <code>ereport()</code> system works, but it's easy to get wrong.</p>
</li>
<li><p><strong>Tooling is antiquated.</strong> No package manager, no standardized testing, dependency management via copy-paste.</p>
</li>
</ul>
<p>Rust solves all of this:</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Problem</td><td>C</td><td>Rust</td></tr>
</thead>
<tbody>
<tr>
<td>Memory safety</td><td>Manual, error-prone</td><td>Compiler-enforced</td></tr>
<tr>
<td>Null pointer bugs</td><td>Runtime crashes</td><td>Compile-time prevention</td></tr>
<tr>
<td>Package management</td><td>Copy-paste code</td><td>Cargo + <a target="_blank" href="http://crates.io">crates.io</a></td></tr>
<tr>
<td>Testing</td><td>Roll your own</td><td>Built-in test framework</td></tr>
<tr>
<td>Error handling</td><td>Macros + longjmp</td><td>Result types + <code>?</code> operator</td></tr>
</tbody>
</table>
</div><p>The key insight: <strong>Rust gives you C's performance without C's footguns.</strong> Your PostgreSQL server won't crash because of a segfault in your extension code.</p>
<h2 id="heading-the-pgrx-framework">The pgrx Framework</h2>
<p><a target="_blank" href="https://github.com/pgcentralfoundation/pgrx">pgrx</a> is the bridge between Rust and PostgreSQL. It handles:</p>
<ul>
<li><p><strong>FFI bindings</strong>: All the unsafe C interop is wrapped in safe Rust APIs</p>
</li>
<li><p><strong>Memory contexts</strong>: Automatically integrates with PostgreSQL's memory management</p>
</li>
<li><p><strong>Error handling</strong>: Rust panics become PostgreSQL errors (no crashes!)</p>
</li>
<li><p><strong>SQL generation</strong>: Your Rust function signatures automatically become SQL function definitions</p>
</li>
<li><p><strong>Development server</strong>: Spin up a PostgreSQL instance with your extension pre-loaded</p>
</li>
</ul>
<p>The magic is that pgrx lets you write idiomatic Rust while producing extensions that are indistinguishable from C extensions to PostgreSQL.</p>
<h2 id="heading-project-structure-anatomy-of-a-pgrx-extension">Project Structure: Anatomy of a pgrx Extension</h2>
<p>Let's look at <strong>pg_mask</strong>, a data masking extension. Here's the complete project structure:</p>
<pre><code class="lang-rust">pg_mask/
├── .cargo/
│   └── config.toml         # Platform-specific linker settings
├── src/
│   ├── lib.rs              # Your extension code lives here
│   └── bin/
│       └── pgrx_embed.rs   # Required <span class="hljs-keyword">for</span> SQL generation
├── Cargo.toml              # Rust project configuration
├── pg_mask.control         # PostgreSQL extension metadata
└── SETUP.md                # Documentation
</code></pre>
<p>That's it. Five files to create a production-ready PostgreSQL extension.</p>
<p>Let's examine each file.</p>
<h3 id="heading-cargotoml-project-configuration">Cargo.toml: Project Configuration</h3>
<pre><code class="lang-toml"><span class="hljs-section">[package]</span>
<span class="hljs-attr">name</span> = <span class="hljs-string">"pg_mask"</span>
<span class="hljs-attr">version</span> = <span class="hljs-string">"0.1.0"</span>
<span class="hljs-attr">edition</span> = <span class="hljs-string">"2024"</span>

<span class="hljs-section">[lib]</span>
<span class="hljs-attr">crate-type</span> = [<span class="hljs-string">"cdylib"</span>, <span class="hljs-string">"lib"</span>]

<span class="hljs-section">[[bin]]</span>
<span class="hljs-attr">name</span> = <span class="hljs-string">"pgrx_embed_pg_mask"</span>
<span class="hljs-attr">path</span> = <span class="hljs-string">"src/bin/pgrx_embed.rs"</span>

<span class="hljs-section">[features]</span>
<span class="hljs-attr">default</span> = [<span class="hljs-string">"pg18"</span>]
<span class="hljs-attr">pg18</span> = [<span class="hljs-string">"pgrx/pg18"</span>, <span class="hljs-string">"pgrx-tests/pg18"</span>]
<span class="hljs-attr">pg_test</span> = []

<span class="hljs-section">[dependencies]</span>
<span class="hljs-attr">pgrx</span> = <span class="hljs-string">"0.16"</span>

<span class="hljs-section">[dev-dependencies]</span>
<span class="hljs-attr">pgrx-tests</span> = <span class="hljs-string">"0.16"</span>
</code></pre>
<p>Key points:</p>
<ul>
<li><p><code>crate-type = ["cdylib", "lib"]</code>: Builds both a C-compatible shared library (for PostgreSQL to load) and a Rust library (for testing)</p>
</li>
<li><p><strong>Feature flags for PostgreSQL versions</strong>: pgrx supports multiple PostgreSQL versions. Pick yours with a feature flag.</p>
</li>
<li><p><strong>Single dependency</strong>: Just <code>pgrx</code>. That's all you need.</p>
</li>
</ul>
<h3 id="heading-pgmaskcontrol-extension-metadata">pg_mask.control: Extension Metadata</h3>
<p>Every PostgreSQL extension needs a <code>.control</code> file:</p>
<pre><code class="lang-plaintext">comment = 'pg_mask: Data masking functions for GDPR/privacy compliance'
default_version = '@CARGO_VERSION@'
module_pathname = '$libdir/pg_mask'
relocatable = false
superuser = false
</code></pre>
<ul>
<li><p><code>comment</code>: Shows up in <code>\dx</code> listings</p>
</li>
<li><p><code>default_version</code>: pgrx replaces <code>@CARGO_VERSION@</code> with your Cargo.toml version</p>
</li>
<li><p><code>module_pathname</code>: Where PostgreSQL looks for your shared library</p>
</li>
<li><p><code>superuser = false</code>: Regular users can use these functions (appropriate for data masking)</p>
</li>
</ul>
<p><strong>Important</strong>: The control file name must match your <strong>extension name</strong> (not necessarily the crate name). Extension name <code>pg_mask</code> → <code>pg_mask.control</code>.</p>
<h3 id="heading-the-embedding-binary">The Embedding Binary</h3>
<pre><code class="lang-rust"><span class="hljs-comment">// src/bin/pgrx_embed.rs</span>
::pgrx::pgrx_embed!();
</code></pre>
<p>This one-liner is required by pgrx for SQL generation. It generates the PostgreSQL-compatible module initialization code and the SQL schema from your Rust function definitions. Without it, <code>cargo pgrx</code> commands will fail.</p>
<h3 id="heading-macos-linker-configuration">macOS Linker Configuration</h3>
<p>If you're developing on macOS, you need this in <code>.cargo/config.toml</code>:</p>
<pre><code class="lang-toml"><span class="hljs-section">[target.aarch64-apple-darwin]</span>
<span class="hljs-attr">rustflags</span> = [<span class="hljs-string">"-C"</span>, <span class="hljs-string">"link-arg=-undefined"</span>, <span class="hljs-string">"-C"</span>, <span class="hljs-string">"link-arg=dynamic_lookup"</span>]

<span class="hljs-section">[target.x86_64-apple-darwin]</span>
<span class="hljs-attr">rustflags</span> = [<span class="hljs-string">"-C"</span>, <span class="hljs-string">"link-arg=-undefined"</span>, <span class="hljs-string">"-C"</span>, <span class="hljs-string">"link-arg=dynamic_lookup"</span>]
</code></pre>
<p><strong>Why?</strong> PostgreSQL extensions are shared libraries that reference symbols from the PostgreSQL server binary. On macOS, the linker normally requires all symbols to be resolved at link time. The <code>-undefined dynamic_lookup</code> flags tell the linker "trust me, these symbols will exist at runtime."</p>
<p>Without this, your build will fail with undefined symbol errors for PostgreSQL internal functions.</p>
<h2 id="heading-writing-extension-functions">Writing Extension Functions</h2>
<p>Now the fun part. Here's the complete <code>src/</code><a target="_blank" href="http://lib.rs"><code>lib.rs</code></a>:</p>
<pre><code class="lang-rust"><span class="hljs-keyword">use</span> pgrx::prelude::*;

pgrx::pg_module_magic!();

<span class="hljs-comment">/// Masks an email address, showing only first char and domain</span>
<span class="hljs-comment">/// Example: "john.doe@example.com" -&gt; "j*******@example.com"</span>
<span class="hljs-meta">#[pg_extern]</span>
<span class="hljs-function"><span class="hljs-keyword">fn</span> <span class="hljs-title">mask_email</span></span>(email: &amp;<span class="hljs-built_in">str</span>) -&gt; <span class="hljs-built_in">String</span> {
    <span class="hljs-keyword">match</span> email.split_once(<span class="hljs-string">'@'</span>) {
        <span class="hljs-literal">Some</span>((local, domain)) =&gt; {
            <span class="hljs-keyword">if</span> local.is_empty() {
                <span class="hljs-built_in">format!</span>(<span class="hljs-string">"@{}"</span>, domain)
            } <span class="hljs-keyword">else</span> {
                <span class="hljs-keyword">let</span> first_char = local.chars().next().unwrap();
                <span class="hljs-keyword">let</span> mask_len = local.len().saturating_sub(<span class="hljs-number">1</span>);
                <span class="hljs-built_in">format!</span>(<span class="hljs-string">"{}{}@{}"</span>, first_char, <span class="hljs-string">"*"</span>.repeat(mask_len), domain)
            }
        }
        <span class="hljs-literal">None</span> =&gt; <span class="hljs-string">"*"</span>.repeat(email.len()),
    }
}

<span class="hljs-comment">/// Masks a credit card number, showing only last 4 digits</span>
<span class="hljs-comment">/// Example: "4111111111111111" -&gt; "****-****-****-1111"</span>
<span class="hljs-meta">#[pg_extern]</span>
<span class="hljs-function"><span class="hljs-keyword">fn</span> <span class="hljs-title">mask_card</span></span>(card: &amp;<span class="hljs-built_in">str</span>) -&gt; <span class="hljs-built_in">String</span> {
    <span class="hljs-keyword">let</span> digits: <span class="hljs-built_in">String</span> = card.chars().filter(|c| c.is_ascii_digit()).collect();
    <span class="hljs-keyword">if</span> digits.len() &gt;= <span class="hljs-number">4</span> {
        <span class="hljs-keyword">let</span> last_four = &amp;digits[digits.len() - <span class="hljs-number">4</span>..];
        <span class="hljs-built_in">format!</span>(<span class="hljs-string">"****-****-****-{}"</span>, last_four)
    } <span class="hljs-keyword">else</span> {
        <span class="hljs-string">"*"</span>.repeat(digits.len())
    }
}

<span class="hljs-comment">/// Masks a phone number, showing only last 4 digits</span>
<span class="hljs-comment">/// Example: "+1 (555) 123-4567" -&gt; "***-***-4567"</span>
<span class="hljs-meta">#[pg_extern]</span>
<span class="hljs-function"><span class="hljs-keyword">fn</span> <span class="hljs-title">mask_phone</span></span>(phone: &amp;<span class="hljs-built_in">str</span>) -&gt; <span class="hljs-built_in">String</span> {
    <span class="hljs-keyword">let</span> digits: <span class="hljs-built_in">String</span> = phone.chars().filter(|c| c.is_ascii_digit()).collect();
    <span class="hljs-keyword">if</span> digits.len() &gt;= <span class="hljs-number">4</span> {
        <span class="hljs-keyword">let</span> last_four = &amp;digits[digits.len() - <span class="hljs-number">4</span>..];
        <span class="hljs-built_in">format!</span>(<span class="hljs-string">"***-***-{}"</span>, last_four)
    } <span class="hljs-keyword">else</span> {
        <span class="hljs-string">"*"</span>.repeat(digits.len())
    }
}

<span class="hljs-comment">/// Masks a SSN, showing only last 4 digits</span>
<span class="hljs-comment">/// Example: "123-45-6789" -&gt; "***-**-6789"</span>
<span class="hljs-meta">#[pg_extern]</span>
<span class="hljs-function"><span class="hljs-keyword">fn</span> <span class="hljs-title">mask_ssn</span></span>(ssn: &amp;<span class="hljs-built_in">str</span>) -&gt; <span class="hljs-built_in">String</span> {
    <span class="hljs-keyword">let</span> digits: <span class="hljs-built_in">String</span> = ssn.chars().filter(|c| c.is_ascii_digit()).collect();
    <span class="hljs-keyword">if</span> digits.len() &gt;= <span class="hljs-number">4</span> {
        <span class="hljs-keyword">let</span> last_four = &amp;digits[digits.len() - <span class="hljs-number">4</span>..];
        <span class="hljs-built_in">format!</span>(<span class="hljs-string">"***-**-{}"</span>, last_four)
    } <span class="hljs-keyword">else</span> {
        <span class="hljs-string">"*"</span>.repeat(digits.len())
    }
}

<span class="hljs-comment">/// Generic masking: shows first N and last M characters</span>
<span class="hljs-comment">/// Example: mask_text("sensitive data", 2, 2) -&gt; "se**********ta"</span>
<span class="hljs-meta">#[pg_extern]</span>
<span class="hljs-function"><span class="hljs-keyword">fn</span> <span class="hljs-title">mask_text</span></span>(text: &amp;<span class="hljs-built_in">str</span>, show_first: <span class="hljs-built_in">i32</span>, show_last: <span class="hljs-built_in">i32</span>) -&gt; <span class="hljs-built_in">String</span> {
    <span class="hljs-keyword">let</span> show_first = show_first.max(<span class="hljs-number">0</span>) <span class="hljs-keyword">as</span> <span class="hljs-built_in">usize</span>;
    <span class="hljs-keyword">let</span> show_last = show_last.max(<span class="hljs-number">0</span>) <span class="hljs-keyword">as</span> <span class="hljs-built_in">usize</span>;
    <span class="hljs-keyword">let</span> len = text.chars().count();  <span class="hljs-comment">// Use chars().count() for Unicode safety</span>

    <span class="hljs-keyword">if</span> show_first + show_last &gt;= len {
        <span class="hljs-keyword">return</span> text.to_string();
    }

    <span class="hljs-keyword">let</span> first: <span class="hljs-built_in">String</span> = text.chars().take(show_first).collect();
    <span class="hljs-keyword">let</span> last: <span class="hljs-built_in">String</span> = text.chars().skip(len - show_last).collect();
    <span class="hljs-keyword">let</span> mask_len = len - show_first - show_last;

    <span class="hljs-built_in">format!</span>(<span class="hljs-string">"{}{}{}"</span>, first, <span class="hljs-string">"*"</span>.repeat(mask_len), last)
}

<span class="hljs-comment">/// Masks an IP address</span>
<span class="hljs-comment">/// IPv4: "192.168.1.100" -&gt; "192.168.xxx.xxx"</span>
<span class="hljs-comment">/// IPv6: Shows first segment only</span>
<span class="hljs-meta">#[pg_extern]</span>
<span class="hljs-function"><span class="hljs-keyword">fn</span> <span class="hljs-title">mask_ip</span></span>(ip: &amp;<span class="hljs-built_in">str</span>) -&gt; <span class="hljs-built_in">String</span> {
    <span class="hljs-keyword">if</span> ip.contains(<span class="hljs-string">':'</span>) {
        <span class="hljs-comment">// IPv6</span>
        <span class="hljs-keyword">let</span> parts: <span class="hljs-built_in">Vec</span>&lt;&amp;<span class="hljs-built_in">str</span>&gt; = ip.split(<span class="hljs-string">':'</span>).collect();
        <span class="hljs-keyword">if</span> parts.is_empty() {
            <span class="hljs-keyword">return</span> <span class="hljs-string">"xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx"</span>.to_string();
        }
        <span class="hljs-keyword">let</span> first = parts[<span class="hljs-number">0</span>];
        <span class="hljs-keyword">let</span> masked: <span class="hljs-built_in">Vec</span>&lt;&amp;<span class="hljs-built_in">str</span>&gt; = std::iter::once(first)
            .chain(std::iter::repeat(<span class="hljs-string">"xxxx"</span>).take(parts.len() - <span class="hljs-number">1</span>))
            .collect();
        masked.join(<span class="hljs-string">":"</span>)
    } <span class="hljs-keyword">else</span> {
        <span class="hljs-comment">// IPv4</span>
        <span class="hljs-keyword">let</span> parts: <span class="hljs-built_in">Vec</span>&lt;&amp;<span class="hljs-built_in">str</span>&gt; = ip.split(<span class="hljs-string">'.'</span>).collect();
        <span class="hljs-keyword">if</span> parts.len() &gt;= <span class="hljs-number">2</span> {
            <span class="hljs-built_in">format!</span>(<span class="hljs-string">"{}.{}.xxx.xxx"</span>, parts[<span class="hljs-number">0</span>], parts[<span class="hljs-number">1</span>])
        } <span class="hljs-keyword">else</span> {
            <span class="hljs-string">"xxx.xxx.xxx.xxx"</span>.to_string()
        }
    }
}
</code></pre>
<p>Let's break down the key concepts.</p>
<h3 id="heading-pgmodulemagic">pg_module_magic!()</h3>
<pre><code class="lang-rust">pgrx::pg_module_magic!();
</code></pre>
<p>This macro generates the PostgreSQL module magic block—a special struct that PostgreSQL checks to verify the extension was compiled for the right PostgreSQL version. In C, you'd write this manually. In Rust, it's one line.</p>
<h3 id="heading-pgextern-exposing-functions-to-sql">#[pg_extern]: Exposing Functions to SQL</h3>
<p>The <code>#[pg_extern]</code> attribute is where the magic happens:</p>
<pre><code class="lang-rust"><span class="hljs-meta">#[pg_extern]</span>
<span class="hljs-function"><span class="hljs-keyword">fn</span> <span class="hljs-title">mask_email</span></span>(email: &amp;<span class="hljs-built_in">str</span>) -&gt; <span class="hljs-built_in">String</span> {
    <span class="hljs-comment">// ...</span>
}
</code></pre>
<p>This single annotation:</p>
<ol>
<li><p>Makes the function callable from SQL</p>
</li>
<li><p>Generates the SQL <code>CREATE FUNCTION</code> statement</p>
</li>
<li><p>Handles type conversions between PostgreSQL and Rust</p>
</li>
<li><p>Wraps panics in proper PostgreSQL error handling</p>
</li>
</ol>
<p>The function signature <code>fn mask_email(email: &amp;str) -&gt; String</code> automatically becomes:</p>
<pre><code class="lang-sql"><span class="hljs-keyword">CREATE</span> <span class="hljs-keyword">FUNCTION</span> mask_email(email <span class="hljs-built_in">TEXT</span>) <span class="hljs-keyword">RETURNS</span> <span class="hljs-built_in">TEXT</span>
</code></pre>
<p>pgrx handles the mapping:</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Rust Type</td><td>PostgreSQL Type</td></tr>
</thead>
<tbody>
<tr>
<td><code>&amp;str</code></td><td><code>TEXT</code></td></tr>
<tr>
<td><code>String</code></td><td><code>TEXT</code></td></tr>
<tr>
<td><code>i32</code></td><td><code>INTEGER</code></td></tr>
<tr>
<td><code>i64</code></td><td><code>BIGINT</code></td></tr>
<tr>
<td><code>f64</code></td><td><code>DOUBLE PRECISION</code></td></tr>
<tr>
<td><code>bool</code></td><td><code>BOOLEAN</code></td></tr>
<tr>
<td><code>Option&lt;T&gt;</code></td><td>Nullable T</td></tr>
</tbody>
</table>
</div><h3 id="heading-writing-safe-code">Writing Safe Code</h3>
<p>Notice how the code handles edge cases:</p>
<pre><code class="lang-rust"><span class="hljs-keyword">match</span> email.split_once(<span class="hljs-string">'@'</span>) {
    <span class="hljs-literal">Some</span>((local, domain)) =&gt; {
        <span class="hljs-comment">// Valid email with @</span>
    }
    <span class="hljs-literal">None</span> =&gt; <span class="hljs-string">"*"</span>.repeat(email.len()),  <span class="hljs-comment">// No @ found</span>
}
</code></pre>
<p>In C, you'd be doing pointer arithmetic and praying you don't overflow. In Rust, the compiler ensures you handle both cases. If you forget the <code>None</code> branch, your code won't compile.</p>
<h2 id="heading-building-and-running">Building and Running</h2>
<h3 id="heading-initial-setup">Initial Setup</h3>
<pre><code class="lang-bash"><span class="hljs-comment"># Install the pgrx CLI tool</span>
cargo install cargo-pgrx --version 0.16.1 --locked

<span class="hljs-comment"># Initialize pgrx (required before first build!)</span>
cargo pgrx init --pg18=$(<span class="hljs-built_in">which</span> pg_config)
</code></pre>
<p>The init step downloads PostgreSQL headers and sets up the build environment. <strong>You must run this before your first build.</strong></p>
<h3 id="heading-development-workflow">Development Workflow</h3>
<pre><code class="lang-bash"><span class="hljs-comment"># Start a PostgreSQL instance with your extension loaded</span>
cargo pgrx run pg18
</code></pre>
<p>This command:</p>
<ol>
<li><p>Compiles your extension</p>
</li>
<li><p>Starts a temporary PostgreSQL server</p>
</li>
<li><p>Connects you to a <code>psql</code> session</p>
</li>
</ol>
<p>From there:</p>
<pre><code class="lang-sql"><span class="hljs-comment">-- Load the extension</span>
<span class="hljs-keyword">CREATE</span> EXTENSION pg_mask;

<span class="hljs-comment">-- Test your functions</span>
<span class="hljs-keyword">SELECT</span> mask_email(<span class="hljs-string">'john.doe@example.com'</span>);
<span class="hljs-comment">-- Returns: j*******@example.com</span>

<span class="hljs-keyword">SELECT</span> mask_card(<span class="hljs-string">'4111111111111111'</span>);
<span class="hljs-comment">-- Returns: ****-****-****-1111</span>

<span class="hljs-keyword">SELECT</span> mask_phone(<span class="hljs-string">'+1 (555) 123-4567'</span>);
<span class="hljs-comment">-- Returns: ***-***-4567</span>

<span class="hljs-keyword">SELECT</span> mask_ssn(<span class="hljs-string">'123-45-6789'</span>);
<span class="hljs-comment">-- Returns: ***-**-6789</span>

<span class="hljs-keyword">SELECT</span> mask_text(<span class="hljs-string">'sensitive data'</span>, <span class="hljs-number">2</span>, <span class="hljs-number">2</span>);
<span class="hljs-comment">-- Returns: se**********ta</span>

<span class="hljs-keyword">SELECT</span> mask_ip(<span class="hljs-string">'192.168.1.100'</span>);
<span class="hljs-comment">-- Returns: 192.168.xxx.xxx</span>
</code></pre>
<h3 id="heading-running-tests">Running Tests</h3>
<p>pgrx includes a test framework that runs tests inside a real PostgreSQL instance:</p>
<pre><code class="lang-rust"><span class="hljs-meta">#[cfg(any(test, feature = <span class="hljs-meta-string">"pg_test"</span>))]</span>
<span class="hljs-meta">#[pg_schema]</span>
<span class="hljs-keyword">mod</span> tests {
    <span class="hljs-keyword">use</span> super::*;

    <span class="hljs-meta">#[pg_test]</span>
    <span class="hljs-function"><span class="hljs-keyword">fn</span> <span class="hljs-title">test_mask_email</span></span>() {
        <span class="hljs-built_in">assert_eq!</span>(
            mask_email(<span class="hljs-string">"john.doe@example.com"</span>),
            <span class="hljs-string">"j*******@example.com"</span>
        );
    }

    <span class="hljs-meta">#[pg_test]</span>
    <span class="hljs-function"><span class="hljs-keyword">fn</span> <span class="hljs-title">test_mask_card</span></span>() {
        <span class="hljs-built_in">assert_eq!</span>(
            mask_card(<span class="hljs-string">"4111111111111111"</span>),
            <span class="hljs-string">"****-****-****-1111"</span>
        );
    }
}
</code></pre>
<p>Run tests with:</p>
<pre><code class="lang-bash">cargo pgrx <span class="hljs-built_in">test</span> pg18
</code></pre>
<h3 id="heading-packaging-for-production">Packaging for Production</h3>
<pre><code class="lang-bash">cargo pgrx package
</code></pre>
<p>This creates a distributable package in <code>target/release/pg_mask-pg18/</code> containing:</p>
<ul>
<li><p>The compiled shared library</p>
</li>
<li><p>SQL installation scripts</p>
</li>
<li><p>The control file</p>
</li>
</ul>
<h2 id="heading-common-gotchas">Common Gotchas</h2>
<h3 id="heading-1-control-file-name-extension-name">1. Control File Name = Extension Name</h3>
<p>The <code>.control</code> file must match your <strong>extension name</strong> (not necessarily the crate name):</p>
<ul>
<li><p>Extension name: <code>pg_mask</code></p>
</li>
<li><p>Control file: <code>pg_mask.control</code></p>
</li>
</ul>
<h3 id="heading-2-the-pgrxembed-binary-is-required">2. The pgrx_embed Binary is Required</h3>
<p>The embedding binary is required for SQL generation. Without it, <code>cargo pgrx</code> commands will fail:</p>
<pre><code class="lang-rust"><span class="hljs-comment">// src/bin/pgrx_embed.rs</span>
::pgrx::pgrx_embed!();
</code></pre>
<p>And referenced in <code>Cargo.toml</code>:</p>
<pre><code class="lang-toml"><span class="hljs-section">[[bin]]</span>
<span class="hljs-attr">name</span> = <span class="hljs-string">"pgrx_embed_pg_mask"</span>  <span class="hljs-comment"># Must be pgrx_embed_&lt;extension_name&gt;</span>
<span class="hljs-attr">path</span> = <span class="hljs-string">"src/bin/pgrx_embed.rs"</span>
</code></pre>
<h3 id="heading-3-macos-needs-special-linker-flags">3. macOS Needs Special Linker Flags</h3>
<p>On macOS, you must add <code>-undefined dynamic_lookup</code> linker flags in <code>.cargo/config.toml</code>. Without these, linking fails with undefined symbol errors for PostgreSQL functions.</p>
<h3 id="heading-4-must-run-cargo-pgrx-init-before-first-build">4. Must Run cargo pgrx init Before First Build</h3>
<p>Before your first build, initialize pgrx:</p>
<pre><code class="lang-bash">cargo pgrx init --pg18=$(<span class="hljs-built_in">which</span> pg_config)
</code></pre>
<p>This sets up PostgreSQL headers and the build environment. Skip this and you'll get cryptic build errors.</p>
<h3 id="heading-5-postgresql-version-must-match-feature-flag">5. PostgreSQL Version Must Match Feature Flag</h3>
<p>Your feature flag must match your target PostgreSQL version:</p>
<pre><code class="lang-toml"><span class="hljs-section">[features]</span>
<span class="hljs-attr">default</span> = [<span class="hljs-string">"pg18"</span>]  <span class="hljs-comment"># Must match your PostgreSQL installation</span>
<span class="hljs-attr">pg15</span> = [<span class="hljs-string">"pgrx/pg15"</span>, <span class="hljs-string">"pgrx-tests/pg15"</span>]
<span class="hljs-attr">pg16</span> = [<span class="hljs-string">"pgrx/pg16"</span>, <span class="hljs-string">"pgrx-tests/pg16"</span>]
<span class="hljs-attr">pg17</span> = [<span class="hljs-string">"pgrx/pg17"</span>, <span class="hljs-string">"pgrx-tests/pg17"</span>]
<span class="hljs-attr">pg18</span> = [<span class="hljs-string">"pgrx/pg18"</span>, <span class="hljs-string">"pgrx-tests/pg18"</span>]
</code></pre>
<p>If you're targeting PostgreSQL 16 but have <code>default = ["pg18"]</code>, you'll get version mismatch errors at runtime.</p>
<h2 id="heading-beyond-simple-functions">Beyond Simple Functions</h2>
<p>pg_mask demonstrates simple scalar functions, but pgrx supports much more:</p>
<h3 id="heading-custom-types">Custom Types</h3>
<pre><code class="lang-rust"><span class="hljs-meta">#[derive(PostgresType, Serialize, Deserialize)]</span>
<span class="hljs-keyword">pub</span> <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">Point</span></span> {
    x: <span class="hljs-built_in">f64</span>,
    y: <span class="hljs-built_in">f64</span>,
}
</code></pre>
<h3 id="heading-aggregate-functions">Aggregate Functions</h3>
<pre><code class="lang-rust"><span class="hljs-meta">#[pg_aggregate]</span>
<span class="hljs-keyword">impl</span> Aggregate <span class="hljs-keyword">for</span> MySum {
    <span class="hljs-class"><span class="hljs-keyword">type</span> <span class="hljs-title">State</span></span> = <span class="hljs-built_in">i64</span>;
    <span class="hljs-class"><span class="hljs-keyword">type</span> <span class="hljs-title">Args</span></span> = <span class="hljs-built_in">i32</span>;
    <span class="hljs-comment">// ...</span>
}
</code></pre>
<h3 id="heading-background-workers">Background Workers</h3>
<pre><code class="lang-rust"><span class="hljs-meta">#[pg_guard]</span>
<span class="hljs-keyword">pub</span> <span class="hljs-keyword">extern</span> <span class="hljs-string">"C"</span> <span class="hljs-function"><span class="hljs-keyword">fn</span> <span class="hljs-title">my_worker_main</span></span>(_arg: pg_sys::Datum) {
    <span class="hljs-comment">// Runs in background</span>
}
</code></pre>
<h3 id="heading-server-programming-interface-spi">Server Programming Interface (SPI)</h3>
<pre><code class="lang-rust"><span class="hljs-meta">#[pg_extern]</span>
<span class="hljs-function"><span class="hljs-keyword">fn</span> <span class="hljs-title">count_rows</span></span>(table_name: &amp;<span class="hljs-built_in">str</span>) -&gt; <span class="hljs-built_in">i64</span> {
    Spi::connect(|client| {
        <span class="hljs-keyword">let</span> query = <span class="hljs-built_in">format!</span>(<span class="hljs-string">"SELECT COUNT(*) FROM {}"</span>, table_name);
        client.select(&amp;query, <span class="hljs-literal">None</span>, <span class="hljs-literal">None</span>)?
            .first()
            .get_one::&lt;<span class="hljs-built_in">i64</span>&gt;()?
            .unwrap_or(<span class="hljs-number">0</span>)
    })
}
</code></pre>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Building PostgreSQL extensions with Rust and pgrx is genuinely enjoyable. You get:</p>
<ul>
<li><p><strong>Memory safety</strong>: No more debugging segfaults at 3 AM</p>
</li>
<li><p><strong>Modern tooling</strong>: Cargo, <a target="_blank" href="http://crates.io">crates.io</a>, rust-analyzer</p>
</li>
<li><p><strong>Type safety</strong>: The compiler catches mistakes before they hit production</p>
</li>
<li><p><strong>Performance</strong>: Rust compiles to native code, matching C speed</p>
</li>
</ul>
<p>The pg_mask extension we walked through is ~100 lines of Rust. A C equivalent would be 300+ lines, riddled with manual memory management and error handling boilerplate.</p>
<p>If you've been hesitant to write PostgreSQL extensions because of C's complexity, give pgrx a try. The learning curve is gentler than you might expect, especially if you already know Rust.</p>
<h2 id="heading-source-code">Source Code</h2>
<p>Full source code for pg_mask: <a target="_blank" href="http://github.com/jatin510/postgres-ext-exp">github.com/jatin510/postgres-ext-exp</a></p>
<h2 id="heading-resources">Resources</h2>
<ul>
<li><p><a target="_blank" href="https://github.com/pgcentralfoundation/pgrx">pgrx GitHub Repository</a></p>
</li>
<li><p><a target="_blank" href="https://docs.rs/pgrx">pgrx Documentation</a></p>
</li>
<li><p><a target="_blank" href="https://www.postgresql.org/docs/current/extend-extensions.html">PostgreSQL Extension Documentation</a></p>
</li>
</ul>
<h2 id="heading-ideas-for-your-first-extension">Ideas for Your First Extension</h2>
<ul>
<li><p><strong>Data validation functions</strong>: Email formats, phone numbers, URLs</p>
</li>
<li><p><strong>Text processing</strong>: Fuzzy matching, phonetic encoding, slugification</p>
</li>
<li><p><strong>Encryption wrappers</strong>: Field-level encryption with key management</p>
</li>
<li><p><strong>External API integrations</strong>: HTTP clients, queue publishers</p>
</li>
<li><p><strong>Custom index types</strong>: Specialized data structures for your domain</p>
</li>
</ul>
<p>The PostgreSQL extension ecosystem needs more Rust. Go build something.</p>
]]></content:encoded></item><item><title><![CDATA[I Tried Corrode’s “Prototyping in Rust” — And It Changed How I Build Things]]></title><description><![CDATA[When I came across Corrode’s article on Prototyping in Rust, it immediately clicked with something I’d been struggling with for a while.
As someone who builds Rust-based systems every day — from high-throughput analytics pipelines to Web Crawler — I ...]]></description><link>https://blog.jatin510.dev/i-tried-corrodes-prototyping-in-rust-and-it-changed-how-i-build-things</link><guid isPermaLink="true">https://blog.jatin510.dev/i-tried-corrodes-prototyping-in-rust-and-it-changed-how-i-build-things</guid><category><![CDATA[Rust]]></category><category><![CDATA[Programming Blogs]]></category><category><![CDATA[Programming Tips]]></category><category><![CDATA[Rust programming]]></category><category><![CDATA[Tips for Developers]]></category><dc:creator><![CDATA[Jagdish Parihar]]></dc:creator><pubDate>Sun, 26 Oct 2025 05:32:26 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1761456630881/e193a2d9-b7d3-4107-8328-36b51afeaecc.webp" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>When I came across <a target="_blank" href="https://corrode.dev/blog/prototyping/">Corrode’s article on <em>Prototyping in Rust</em></a>, it immediately clicked with something I’d been struggling with for a while.</p>
<p>As someone who builds Rust-based systems every day — from <strong>high-throughput analytics pipelines</strong> to <strong>Web Crawler</strong> — I often find myself walking the fine line between <em>speed</em> and <em>safety</em>.</p>
<p>Rust gives me production-grade reliability, but I used to believe it wasn’t meant for <em>rapid prototyping</em>. Corrode challenged that idea — and they were right.</p>
<hr />
<h2 id="heading-reading-corrodes-take-on-prototyping"><strong>Reading Corrode’s Take on Prototyping</strong></h2>
<p>The post made a simple but powerful claim:</p>
<blockquote>
<p><em>“You can prototype effectively in Rust — if you allow yourself to write imperfect code first.”</em></p>
<p>(<a target="_blank" href="https://corrode.dev/blog/prototyping/">Source: Corrode.dev</a>)</p>
</blockquote>
<p>It laid out a refreshing way to approach Rust in the early stages of an idea. Some of the lessons that stood out to me:</p>
<ul>
<li><p><strong>Start messy, refine later.</strong> Don’t build abstractions too soon.</p>
</li>
<li><p><strong>Use simple types.</strong> Stick to owned values (String, Vec&lt;T&gt;) — references can wait.</p>
</li>
<li><p><strong>Lean on inference.</strong> Let the compiler infer types for you.</p>
</li>
<li><p><strong>Don’t fear unwrap().</strong> It’s fine in a prototype; clarity is more important than polish.</p>
</li>
<li><p><strong>Keep it flat.</strong> One main.rs file is enough when you’re exploring.</p>
</li>
<li><p><strong>Avoid early optimization.</strong> Let the design breathe before tuning performance.</p>
</li>
</ul>
<p>The tone of the article was liberating — it felt like permission to <em>just build</em>, not over-engineer.</p>
<hr />
<h2 id="heading-applying-it-to-my-own-rust-projects"><strong>Applying It to My Own Rust Projects</strong></h2>
<p>I decided to apply Corrode’s mindset to one of my ongoing practice projects — a <strong>Trie-based autocomplete system</strong>.</p>
<p>Normally, I would architect everything first: multiple modules, traits, and generic abstractions. This time, I didn’t.</p>
<p>Here’s what changed:</p>
<ol>
<li><p><strong>Flat structure first</strong></p>
<p> Instead of setting up a full module hierarchy, I began with a single file to keep my focus on the logic and flow. Once the core idea was validated, I refactored it into modules. That early simplicity made iteration much faster.</p>
</li>
<li><p><strong>Concrete types everywhere</strong></p>
<p> Instead of juggling lifetimes and generics, I used owned types like String and Vec&lt;Node&gt;. When the structure became stable, I started refactoring.</p>
</li>
<li><p><strong>Fearless use of unwrap() and todo!()</strong></p>
<p> They became markers of progress. Every unwrap() told me, <em>“this part works for now — fix it later.”</em></p>
</li>
<li><p><strong>Delayed optimization</strong></p>
<p> My first goal was correctness and clarity. Only later did I switch my lookups from a Vec to a HashMap, and profiling confirmed that’s all I needed.</p>
</li>
</ol>
<p>Surprisingly, I had a working prototype in a single evening — clean, testable, and fast enough to ship internally.</p>
<hr />
<h2 id="heading-why-i-love-rust"><strong>Why I Love Rust</strong></h2>
<p>Corrode’s article didn’t just change how I prototype — it deepened my love for the language itself.</p>
<p>Here’s why Rust feels <em>right</em> for me:</p>
<ul>
<li><p><strong>Compiler as a collaborator, not a barrier</strong></p>
<p>  The compiler’s strictness isn’t punishment — it’s mentorship. When it complains, it’s teaching you something real about ownership, lifetimes, or concurrency.</p>
</li>
<li><p><strong>Zero-cost confidence</strong></p>
<p>  Every cargo build gives me production-grade guarantees. I don’t need a separate rewrite phase; prototypes <em>become</em> products.</p>
</li>
<li><p><strong>Performance without paranoia</strong></p>
<p>  I can write readable, safe code and still hit C-level performance. It’s freedom without fear.</p>
</li>
<li><p><strong>Ecosystem that grows with you</strong></p>
<p>  Whether I’m using tokio, reqwest, or DataFusion, the crates ecosystem feels mature and practical — not bloated.</p>
</li>
<li><p><strong>It rewards thoughtfulness</strong></p>
<p>  Rust doesn’t make things <em>easy</em>; it makes them <em>clear</em>. Once you internalize the model, it feels like your brain and the compiler are working on the same problem.</p>
</li>
</ul>
<p>That’s why I love Rust — it gives me <em>flow</em> and <em>discipline</em> at the same time.</p>
<hr />
<h2 id="heading-what-changed-after-that-experiment"><strong>What Changed After That Experiment</strong></h2>
<p>After adopting Corrode’s philosophy, my workflow shifted:</p>
<ul>
<li><p>I no longer switch to Javascript or Python for quick experiments.</p>
</li>
<li><p>My “prototypes” evolve seamlessly into production code.</p>
</li>
<li><p>My iteration loop became faster — because the compiler and I now trust each other.</p>
</li>
</ul>
<p>Rust stopped being the language I feared to prototype in — it became the one I rely on <em>to think clearly</em>.</p>
<hr />
<h2 id="heading-closing-thoughts"><strong>Closing Thoughts</strong></h2>
<p>If you’ve ever felt Rust was too heavy for quick experimentation, you owe yourself 10 minutes to read <a target="_blank" href="https://corrode.dev/blog/prototyping/">Corrode’s <em>Prototyping in Rust</em></a>.</p>
<p>It’s not just about writing Rust differently — it’s about thinking differently.</p>
<p>Prototype fast. Iterate safely. Refactor confidently.</p>
<p>And when you do, you’ll realize — <strong>Rust doesn’t slow you down; it slows down your mistakes.</strong></p>
]]></content:encoded></item><item><title><![CDATA[My Research Journey into Rust & Performance: Solving the 1BRC Challenge ⚡️]]></title><description><![CDATA[A little over a year ago, I got curious about the 1 Billion Row Challenge (1BRC). It seemed like the perfect playground to test Rust’s performance chops — 1 billion weather station measurements, aggregate per-city statistics (min, max, average), and ...]]></description><link>https://blog.jatin510.dev/my-research-journey-into-rust-and-performance-solving-the-1brc-challenge</link><guid isPermaLink="true">https://blog.jatin510.dev/my-research-journey-into-rust-and-performance-solving-the-1brc-challenge</guid><category><![CDATA[Rust]]></category><category><![CDATA[performance]]></category><category><![CDATA[1brc]]></category><category><![CDATA[Performance Optimization]]></category><category><![CDATA[low level design]]></category><dc:creator><![CDATA[Jagdish Parihar]]></dc:creator><pubDate>Thu, 28 Aug 2025 17:34:34 GMT</pubDate><content:encoded><![CDATA[<p>A little over a year ago, I got curious about the <strong>1 Billion Row Challenge (1BRC)</strong>. It seemed like the perfect playground to test Rust’s performance chops — 1 billion weather station measurements, aggregate per-city statistics (min, max, average), and do it as fast as possible.</p>
<p>At that time, I went down a rabbit hole of <strong>Rust performance research</strong>, experimenting with naïve approaches, multithreading, and low-level optimizations. I never wrote about it back then, but looking back, the lessons are worth sharing. So here’s my journey — from <strong>12 minutes → 2 mins → 10 seconds</strong>.</p>
<hr />
<h2 id="heading-stage-1-the-naive-rust-approach-12-minutes">Stage 1: The Naïve Rust Approach — 12 Minutes ⏳</h2>
<p>I began with a straightforward solution:</p>
<ul>
<li><p>Load the file into a string.</p>
</li>
<li><p>Split by newline.</p>
</li>
<li><p>Parse each line into <code>city;temperature</code>.</p>
</li>
<li><p>Aggregate results in a <code>HashMap&lt;String, CityStats&gt;</code>.</p>
</li>
</ul>
<p>It was <strong>idiomatic Rust</strong>, safe, and simple. But it took <strong>12 minutes</strong> to finish.</p>
<p>This stage gave me a baseline, but it was clear that high-level string parsing was eating performance alive.</p>
<hr />
<h2 id="heading-stage-2-embracing-concurrency-15-seconds">Stage 2: Embracing Concurrency — 15 Seconds 🚀</h2>
<p>My next line of research was <strong>parallelism</strong>. Rust provides great abstractions like <code>std::thread::scope</code> and <code>Arc&lt;Mutex&lt;T&gt;&gt;</code>, so I divided the file into <strong>thread-safe chunks</strong> aligned on newline boundaries. Each thread processed its own slice of the file and then merged results into a global <code>HashMap</code>.</p>
<p>The speedup was dramatic — <strong>down to ~2 mins</strong>.</p>
<p>This was my first “wow” moment: Rust’s <strong>fearless concurrency</strong> makes scaling across CPU cores approachable and safe. But something was still bothering me — parsing overhead.</p>
<hr />
<h2 id="heading-stage-3-researching-parsing-costs-working-with-bytes-10-seconds">Stage 3: Researching Parsing Costs → Working with Bytes — 10 Seconds ⚡️</h2>
<p>I dug deeper into how Rust handles strings and UTF-8. My research led me to an important insight:</p>
<blockquote>
<p><strong>Strings are expensive. Bytes are cheap.</strong></p>
</blockquote>
<p>Every conversion to <code>String</code> or <code>&amp;str</code> was adding overhead. So I restructured my code to work directly on <strong>raw</strong> <code>u8</code> arrays. Instead of treating the file as text, I processed <strong>byte slices</strong> and converted only when strictly necessary.</p>
<p>This optimization cut execution time almost in half — <strong>from 2 mins to ~10s</strong>.</p>
<p>At this point, profiling showed something surprising:</p>
<ul>
<li><p><strong>~4s</strong> = actual computation.</p>
</li>
<li><p><strong>~6s</strong> = just loading data from the SSD.</p>
</li>
</ul>
<p>That meant I had reached the <strong>I/O limit of my hardware</strong>. Any further improvement would require tricks like memory-mapped files (<code>mmap</code>), SIMD parsing, or asynchronous I/O.</p>
<hr />
<h2 id="heading-lessons-learned">Lessons Learned 📚</h2>
<p>This wasn’t just about solving a coding challenge — it was a <strong>research journey</strong> into Rust’s performance model.</p>
<ol>
<li><p><strong>Naïve is necessary.</strong> My 12-min baseline gave me something to measure against.</p>
</li>
<li><p><strong>Concurrency matters, but parsing dominates.</strong> Threads gave me my first big win, but eliminating string parsing was the real breakthrough.</p>
</li>
<li><p><strong>I/O is king.</strong> Once your code is fast enough, the bottleneck shifts from CPU to hardware.</p>
</li>
<li><p><strong>Rust shines in performance-critical paths.</strong> Working with raw bytes in a safe way is exactly where Rust feels both low-level and empowering.</p>
</li>
</ol>
<hr />
<h2 id="heading-code-snapshot-processing-data-with-bytes">Code Snapshot: Processing Data with Bytes</h2>
<p>Here’s the core of my final approach:</p>
<pre><code class="lang-rust"><span class="hljs-function"><span class="hljs-keyword">fn</span> <span class="hljs-title">process_data</span></span>(data: &amp;[<span class="hljs-built_in">u8</span>]) -&gt; HashMap&lt;<span class="hljs-built_in">String</span>, CityStats&gt; {
    <span class="hljs-keyword">let</span> <span class="hljs-keyword">mut</span> map: HashMap&lt;<span class="hljs-built_in">String</span>, CityStats&gt; = HashMap::new();

    <span class="hljs-keyword">for</span> segment <span class="hljs-keyword">in</span> data.split(|&amp;byte| byte == <span class="hljs-string">b'\n'</span>) {
        <span class="hljs-keyword">let</span> <span class="hljs-keyword">mut</span> parts = std::<span class="hljs-built_in">str</span>::from_utf8(segment).unwrap().split(<span class="hljs-string">';'</span>);

        <span class="hljs-keyword">if</span> <span class="hljs-keyword">let</span> (<span class="hljs-literal">Some</span>(city), <span class="hljs-literal">Some</span>(value)) = (parts.next(), parts.next()) {
            <span class="hljs-keyword">let</span> val = value.parse::&lt;<span class="hljs-built_in">f32</span>&gt;().unwrap();
            <span class="hljs-keyword">match</span> map.entry(city.to_string()) {
                Entry::Occupied(<span class="hljs-keyword">mut</span> e) =&gt; {
                    <span class="hljs-keyword">let</span> s = e.get_mut();
                    s.count += <span class="hljs-number">1.0</span>;
                    s.sum += val;
                    s.min = s.min.min(val);
                    s.max = s.max.max(val);
                }
                Entry::Vacant(e) =&gt; {
                    e.insert(CityStats { min: val, max: val, count: <span class="hljs-number">1.0</span>, sum: val });
                }
            }
        }
    }

    map
}
</code></pre>
<hr />
<h2 id="heading-closing-thoughts">Closing Thoughts 💡</h2>
<p>This project was less about “solving 1BRC” and more about <strong>understanding Rust at the performance frontier</strong>.</p>
<p>I started with high-level Rust (strings, safe iteration) and ended up optimizing down to raw bytes. Along the way, I learned how <strong>multithreading, memory access patterns, and I/O limits</strong> interact in real-world workloads.</p>
<p>Right now, my solution runs in <strong>10 seconds</strong>, where <strong>6 seconds are I/O bound</strong>. That means the core algorithm is blazing fast — and any further speedup requires going beyond CPU optimizations into <strong>system-level tricks</strong>.</p>
<p>This experience has convinced me: <strong>Rust isn’t just about safety. It’s about giving you the tools to write code that’s as fast as your hardware will allow.</strong></p>
]]></content:encoded></item><item><title><![CDATA[🪶 Apache Arrow: The Modern Memory Format Powering Analytical Engines]]></title><description><![CDATA[Apache Arrow is an in-memory columnar data format optimized for analytical workloads. It enables fast data access, zero-copy reads, and efficient interoperability between systems like Pandas, DuckDB, Polars, and query engines like Apache DataFusion. ...]]></description><link>https://blog.jatin510.dev/apache-arrow-the-modern-memory-format-powering-analytical-engines</link><guid isPermaLink="true">https://blog.jatin510.dev/apache-arrow-the-modern-memory-format-powering-analytical-engines</guid><category><![CDATA[Rust]]></category><category><![CDATA[Databases]]></category><category><![CDATA[data processing]]></category><category><![CDATA[datafusion]]></category><category><![CDATA[duckDB]]></category><dc:creator><![CDATA[Jagdish Parihar]]></dc:creator><pubDate>Tue, 05 Aug 2025 18:49:32 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1754419841038/8081cf2c-3eab-49e4-92ae-707d96791500.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Apache Arrow is an in-memory columnar data format optimized for analytical workloads. It enables fast data access, zero-copy reads, and efficient interoperability between systems like Pandas, DuckDB, Polars, and query engines like Apache DataFusion. If you’ve used PyArrow, Polars, or Arrow arrays in Rust, you’ve already felt its power.</p>
<hr />
<h3 id="heading-the-problem-bottlenecks-in-data-analytics">🚨 The Problem: Bottlenecks in Data Analytics</h3>
<p>For years, analytical engines have faced a fundamental challenge: <strong>how to process massive datasets in memory efficiently</strong>.</p>
<p>Traditional formats like CSV or even JSON:</p>
<ul>
<li><p>Are row-oriented (bad for analytics)</p>
</li>
<li><p>Require parsing + decoding before processing</p>
</li>
<li><p>Don't support vectorized execution or SIMD</p>
</li>
</ul>
<p>Even columnar formats like Parquet are designed for <strong>storage</strong>, not <strong>runtime execution</strong>.</p>
<p>What we needed was a <strong>standard, fast, language-agnostic format for in-memory columnar data</strong>.</p>
<hr />
<h3 id="heading-enter-apache-arrow">🚀 Enter Apache Arrow</h3>
<p>Apache Arrow was born to solve this. It's a language-independent specification and implementation for:</p>
<ul>
<li><p><strong>In-memory columnar data layout</strong></p>
</li>
<li><p><strong>Zero-copy reads and writes</strong></p>
</li>
<li><p><strong>Interoperability between systems and languages</strong></p>
</li>
<li><p><strong>Support for modern CPU hardware (SIMD, caches)</strong></p>
</li>
</ul>
<p>Arrow is not a database, and not a query engine—it's the <strong>foundation</strong> those tools build on.</p>
<hr />
<h3 id="heading-core-concepts-in-arrow">🧠 Core Concepts in Arrow</h3>
<h4 id="heading-1-columnar-format">🧱 1. Columnar Format</h4>
<p>Arrow stores data by column, not by row. This means:</p>
<ul>
<li><p>Better cache locality</p>
</li>
<li><p>Vectorized execution (e.g., compute on whole columns at once)</p>
</li>
<li><p>Efficient compression</p>
</li>
</ul>
<h4 id="heading-2-recordbatch">🪵 2. RecordBatch</h4>
<p>A <code>RecordBatch</code> in Arrow is like a table in memory:</p>
<ul>
<li><p>It contains a schema (field names and types)</p>
</li>
<li><p>And a set of column arrays<br />  Each column is an <code>ArrowArray</code>, backed by contiguous memory buffers.</p>
</li>
</ul>
<h4 id="heading-3-buffers">🧩 3. Buffers</h4>
<p>Each column has:</p>
<ul>
<li><p>A <strong>data buffer</strong> (the actual values)</p>
</li>
<li><p>A <strong>null bitmap</strong> buffer (to track missing values)</p>
</li>
<li><p>An optional <strong>offset buffer</strong> (for variable-width types like strings)</p>
</li>
</ul>
<h4 id="heading-4-language-bindings">📚 4. Language Bindings</h4>
<p>Arrow is implemented in:</p>
<ul>
<li><p>C++</p>
</li>
<li><p>Rust</p>
</li>
<li><p>Python (via PyArrow)</p>
</li>
<li><p>Go, Java, and more</p>
</li>
</ul>
<p>This means a dataset generated in <strong>Rust</strong> can be <strong>read directly in Python or Go</strong> without copying or converting.</p>
<hr />
<h3 id="heading-real-world-use-cases">🔥 Real-World Use Cases</h3>
<h4 id="heading-pandas-pyarrow">📊 Pandas + PyArrow</h4>
<p>PyArrow allows Arrow arrays to be passed to/from Pandas and NumPy without copying, speeding up IO and interoperability.</p>
<h4 id="heading-duckdb">🦆 DuckDB</h4>
<p>DuckDB uses Arrow to interface with Python, R, and even web clients. When you call <code>.arrow()</code> on a DuckDB result, you get a zero-copy view.</p>
<h4 id="heading-apache-datafusion">⚙️ Apache DataFusion</h4>
<p>DataFusion is a Rust-based SQL engine that processes <code>RecordBatch</code> Arrow data. Its entire physical execution plan is Arrow-native.</p>
<h4 id="heading-polars">🐻‍❄️ Polars</h4>
<p>Polars uses Arrow arrays under the hood for lightning-fast, multi-threaded, Rust-native DataFrame processing.</p>
<hr />
<p><strong>TL;DR</strong>: Use <strong>Parquet</strong> to store data, use <strong>Arrow</strong> to process it.</p>
<hr />
<h3 id="heading-example-arrow-in-rust">🧪 Example: Arrow in Rust</h3>
<pre><code class="lang-rust"><span class="hljs-keyword">use</span> arrow::array::{Int32Array, Array};
<span class="hljs-keyword">use</span> arrow::record_batch::RecordBatch;
<span class="hljs-keyword">use</span> arrow::datatypes::{DataType, Field, Schema};
<span class="hljs-keyword">use</span> std::sync::Arc;

<span class="hljs-function"><span class="hljs-keyword">fn</span> <span class="hljs-title">main</span></span>() {
    <span class="hljs-keyword">let</span> data = Int32Array::from(<span class="hljs-built_in">vec!</span>[<span class="hljs-literal">Some</span>(<span class="hljs-number">1</span>), <span class="hljs-literal">None</span>, <span class="hljs-literal">Some</span>(<span class="hljs-number">3</span>)]);
    <span class="hljs-keyword">let</span> field = Field::new(<span class="hljs-string">"numbers"</span>, DataType::Int32, <span class="hljs-literal">true</span>);
    <span class="hljs-keyword">let</span> schema = Arc::new(Schema::new(<span class="hljs-built_in">vec!</span>[field]));
    <span class="hljs-keyword">let</span> batch = RecordBatch::try_new(schema, <span class="hljs-built_in">vec!</span>[Arc::new(data)]).unwrap();

    <span class="hljs-built_in">println!</span>(<span class="hljs-string">"Rows: {}"</span>, batch.num_rows());
    <span class="hljs-built_in">println!</span>(<span class="hljs-string">"Columns: {}"</span>, batch.num_columns());
}
</code></pre>
<hr />
<h3 id="heading-summary">🧵 Summary</h3>
<p>Apache Arrow is the backbone of modern data systems:</p>
<ul>
<li><p>Columnar + cache-efficient layout</p>
</li>
<li><p>Language-agnostic zero-copy interoperability</p>
</li>
<li><p>Powering analytical engines from Polars to DuckDB</p>
</li>
</ul>
<p>It's not just a data format—it's a <strong>standard for high-performance analytics</strong>.</p>
<hr />
<h3 id="heading-references">🔗 References</h3>
<ul>
<li><p><a target="_blank" href="https://arrow.apache.org/overview/">Apache Arrow Official Site</a></p>
</li>
<li><p><a target="_blank" href="https://docs.rs/arrow/latest/arrow/">Arrow Rust Docs</a></p>
</li>
<li><p><a target="_blank" href="https://github.com/apache/datafusion">Apache DataFusion</a></p>
</li>
<li><p><a target="_blank" href="https://duckdb.org/2021/12/03/duck-arrow.html">https://duckdb.org/2021/12/03/duck-arrow.html</a></p>
<h2 id="heading-about-the-author">✨ About the Author</h2>
<p>  I'm <strong>Jagdish Parihar</strong>, a backend engineer passionate about high-performance systems, distributed databases, and query engines.</p>
<p>  I've contributed to <a target="_blank" href="https://github.com/apache/datafusion/pulls?q=is%3Apr+author%3Ajatin510">Apache DataFusion</a>, focusing on SQL engine internals like custom aggregate functions and optimizer rule enhancements. I'm also exploring Apache Arrow in Rust as part of building scalable analytical systems.</p>
<p>  You can find me here:</p>
<ul>
<li><p>🌐 <a target="_blank" href="https://www.linkedin.com/in/jatin510/">LinkedIn</a></p>
</li>
<li><p>🧑‍💻 <a target="_blank" href="https://github.com/jatin510">GitHub</a></p>
</li>
<li><p>📬 <a target="_blank" href="mailto:jatin6972@gmail.com">jatin6972@gmail.com</a></p>
</li>
</ul>
</li>
</ul>
<p>    If you’re building something cool with Arrow, DataFusion, or Rust — let’s connect!</p>
]]></content:encoded></item><item><title><![CDATA[Understanding Parquet: An Efficient Columnar File Format]]></title><description><![CDATA[Introduction
Parquet has quickly become one of the most popular file formats for storing large-scale analytics data. Parquet is now a top choice due to its efficiency, compression, and seamless integration with big data frameworks. My experience cont...]]></description><link>https://blog.jatin510.dev/understanding-parquet-an-efficient-columnar-file-format</link><guid isPermaLink="true">https://blog.jatin510.dev/understanding-parquet-an-efficient-columnar-file-format</guid><category><![CDATA[Rust]]></category><category><![CDATA[database]]></category><category><![CDATA[query-optimization]]></category><category><![CDATA[Parquet]]></category><category><![CDATA[storage]]></category><category><![CDATA[AWS]]></category><dc:creator><![CDATA[Jagdish Parihar]]></dc:creator><pubDate>Thu, 24 Jul 2025 06:53:41 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1753340669311/11e66ecb-bf39-4997-9003-0f61ee2f41bb.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-introduction">Introduction</h2>
<p>Parquet has quickly become one of the most popular file formats for storing large-scale analytics data. Parquet is now a top choice due to its efficiency, compression, and seamless integration with big data frameworks. My experience contributing to Apache DataFusion, a query engine that extensively uses Parquet, has deepened my understanding and appreciation of this format.</p>
<h2 id="heading-what-is-parquet">What is Parquet?</h2>
<p>Parquet is an open-source, columnar storage file format optimized for large-scale data processing and analysis. Unlike traditional row-oriented formats like CSV or JSON, Parquet stores data column-wise, offering significant performance improvements for analytical queries.</p>
<h2 id="heading-why-columnar-storage-matters">Why Columnar Storage Matters</h2>
<p>In row-oriented formats, accessing a single column requires scanning entire rows, including unnecessary data. Columnar storage like Parquet solves this by:</p>
<ul>
<li><p><strong>Efficient Querying</strong>: Columns can be read independently, dramatically speeding up analytical queries.</p>
</li>
<li><p><strong>Better Compression</strong>: Columnar data tends to have similar values, making compression techniques like RLE and dictionary encoding highly effective.</p>
</li>
<li><p><strong>Reduced I/O</strong>: Less disk access as queries often target specific columns.</p>
</li>
</ul>
<p>While contributing to DataFusion, I realized how crucial predicate pushdown and efficient column pruning are, especially for performance-critical queries.</p>
<h2 id="heading-parquet-file-structure">Parquet File Structure</h2>
<p>A Parquet file consists of:</p>
<ul>
<li><p><strong>Row Groups</strong>: Logical partitions of data within a file, each containing column chunks.</p>
</li>
<li><p><strong>Column Chunks</strong>: Segments within row groups storing individual columns.</p>
</li>
<li><p><strong>Page Headers and Pages</strong>: Within column chunks, data is divided into pages containing actual values.</p>
</li>
<li><p><strong>Metadata</strong>: Contains schema information and statistics like min/max values that help query optimization.</p>
</li>
</ul>
<p>Understanding Parquet’s metadata handling significantly improved my contributions to DataFusion’s query optimizer, particularly in filtering and skipping irrelevant data.</p>
<h2 id="heading-advantages-of-parquet">Advantages of Parquet</h2>
<h3 id="heading-performance">Performance</h3>
<ul>
<li><p>Faster query execution.</p>
</li>
<li><p>Supports predicate pushdown (skips irrelevant data based on query predicates), a critical aspect I optimized while working on DataFusion.</p>
</li>
</ul>
<h3 id="heading-compression-efficiency">Compression Efficiency</h3>
<ul>
<li>Built-in compression codecs like Snappy, GZIP, LZO, Brotli, and Zstd.</li>
</ul>
<h3 id="heading-schema-evolution">Schema Evolution</h3>
<ul>
<li>Flexible schema management allows you to add or remove columns over time without breaking compatibility.</li>
</ul>
<h3 id="heading-integration">Integration</h3>
<ul>
<li>Seamlessly integrates with frameworks like Apache Spark, Hadoop, AWS Athena, Apache Drill, Apache Impala, and Apache DataFusion.</li>
</ul>
<h2 id="heading-use-cases">Use Cases</h2>
<p>Parquet is ideal for:</p>
<ul>
<li><p>Big Data analytics</p>
</li>
<li><p>Data warehousing</p>
</li>
<li><p>Machine learning pipelines</p>
</li>
<li><p>Ad-hoc querying and BI tools</p>
</li>
</ul>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Parquet's columnar structure, efficient compression, and strong ecosystem support make it indispensable for modern data engineering. Contributing to Apache DataFusion has shown me firsthand the value of efficient column pruning, predicate pushdown, and metadata utilization, making Parquet an exceptional format for scalable and performant data workflows.</p>
<p>Happy querying!</p>
]]></content:encoded></item><item><title><![CDATA[My Journey into Query Engines, Databases, and Rust]]></title><description><![CDATA[🚀 Introduction
My journey into query engines, databases, and Rust began with curiosity and a passion for systems-level performance. Rust's promise of safety and efficiency drew me in, while databases provided the perfect playground to test its capab...]]></description><link>https://blog.jatin510.dev/my-journey-into-query-engines-databases-and-rust</link><guid isPermaLink="true">https://blog.jatin510.dev/my-journey-into-query-engines-databases-and-rust</guid><category><![CDATA[Rust]]></category><category><![CDATA[Rust programming]]></category><category><![CDATA[query-optimization]]></category><category><![CDATA[Databases]]></category><category><![CDATA[datafusion]]></category><dc:creator><![CDATA[Jagdish Parihar]]></dc:creator><pubDate>Sat, 19 Jul 2025 09:41:54 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1752918045084/7424f0f8-4a15-4159-aff8-d9c7c654ab9d.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-introduction">🚀 Introduction</h2>
<p>My journey into query engines, databases, and Rust began with curiosity and a passion for systems-level performance. Rust's promise of safety and efficiency drew me in, while databases provided the perfect playground to test its capabilities. In this post, I'll share my path, with a particular focus on my contributions to Apache DataFusion, and I hope to inspire others to explore this exciting intersection.</p>
<h2 id="heading-discovering-rust-amp-datafusion">Discovering Rust &amp; DataFusion</h2>
<p>I first encountered Rust while exploring modern languages offering memory safety without sacrificing performance. The zero-cost abstractions, excellent concurrency model, and powerful tooling like Cargo hooked me instantly.</p>
<p>Soon after, I discovered Apache DataFusion—a powerful, query engine written in Rust. Its integration with Apache Arrow, Parquet, and extensibility through custom SQL extensions and optimizer rules made it a compelling choice for deepening my skills.</p>
<h2 id="heading-diving-into-the-internals">Diving into the Internals</h2>
<p>Apache DataFusion offered me insights into the inner workings of query planners and optimizers. Its architecture revolves around parsing SQL into logical plans, then optimizing and executing queries efficiently with Rust's concurrency and safety guarantees.</p>
<p>Rust proved essential, enabling DataFusion to leverage Arrow's columnar data structures and ensuring safe, concurrent query execution without traditional overheads.</p>
<h2 id="heading-my-contributions">My Contributions</h2>
<p>Contributing to DataFusion gave me practical experience and deepened my understanding of query engines. Some of my notable contributions include:</p>
<ul>
<li><p><strong>SQL Engine Enhancements:</strong> Implementing custom aggregate functions and fixing optimizer rules, focusing on correctness and extensibility.</p>
</li>
<li><p><strong>Performance Tuning:</strong> Optimizing SQL expressions and query execution paths for improved performance and reliability.</p>
</li>
<li><p><strong>Community Collaboration:</strong> Actively reviewing PRs, collaborating on feature discussions, and enhancing documentation for clearer guidance.</p>
</li>
</ul>
<p>You can explore all my contributions here: <a target="_blank" href="https://github.com/apache/datafusion/pulls?q=is%3Apr+author%3Ajatin510">Apache DataFusion PRs</a>.</p>
<h2 id="heading-lessons-learned">Lessons Learned</h2>
<h3 id="heading-technical-growth">Technical Growth</h3>
<ul>
<li><p>Gained a deep understanding of query optimization strategies and SQL parsing mechanisms.</p>
</li>
<li><p>Learned Rust best practices around ownership, lifetimes, concurrency, and efficient memory management.</p>
</li>
</ul>
<h3 id="heading-community-and-open-source">Community and Open Source</h3>
<ul>
<li><p>Experienced firsthand the power of community-driven development through PR reviews and collaborative discussions.</p>
</li>
<li><p>Improved skills in communication, debugging, and writing maintainable code.</p>
</li>
</ul>
<h2 id="heading-the-bigger-picture">The Bigger Picture</h2>
<p>Apache DataFusion represents a broader trend toward modular, embeddable query engines, shaping the future of analytics and data-driven systems. Its ecosystem, connecting Arrow, Parquet, Spark, and other systems like Ballista and Lance, is continuously growing, offering exciting opportunities for developers.</p>
<h2 id="heading-whats-next">What's Next</h2>
<p>Moving forward, I aim to continue contributing to Apache DataFusion, focusing on deeper query optimization, performance benchmarks, and expanding the extensibility of its SQL APIs. I also encourage anyone interested in databases or Rust to explore and contribute—there’s a vibrant community ready to help.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>My journey with Rust and Apache DataFusion has been rewarding, teaching me not only technical skills but also the value of community collaboration. I'm excited for what's ahead and hope my story inspires your own exploration into query engines, databases, and Rust.</p>
<p>Happy coding!</p>
<hr />
<p>Feel free to connect and follow my ongoing journey:</p>
<ul>
<li><p><a target="_blank" href="https://github.com/jatin510">GitHub</a></p>
</li>
<li><p><a target="_blank" href="https://linkedin.com/in/jatin510">LinkedIn</a></p>
</li>
<li><p><a target="_blank" href="https://x.com/jatin6972">X</a></p>
</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[Implementing `simplify` for `starts_with` in Apache DataFusion]]></title><description><![CDATA[Intro about Datafusion
Apache DataFusion is a Rust-native query engine with a powerful optimizer. One key component of its optimizer is expression simplification, often referred to as the simplify function. In this post, we'll walk through how we imp...]]></description><link>https://blog.jatin510.dev/implementing-simplify-for-startswith-in-apache-datafusion</link><guid isPermaLink="true">https://blog.jatin510.dev/implementing-simplify-for-startswith-in-apache-datafusion</guid><category><![CDATA[datafusion]]></category><category><![CDATA[query engine]]></category><category><![CDATA[Databases]]></category><category><![CDATA[apache]]></category><category><![CDATA[query-optimization]]></category><dc:creator><![CDATA[Jagdish Parihar]]></dc:creator><pubDate>Sat, 01 Mar 2025 13:58:49 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1752916460599/c93c3211-08f6-437e-b22d-1338c4a1334c.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h1 id="heading-intro-about-datafusion">Intro about Datafusion</h1>
<p>Apache DataFusion is a Rust-native query engine with a powerful optimizer. One key component of its optimizer is <strong>expression simplification</strong>, often referred to as the <code>simplify</code> function. In this post, we'll walk through how we <strong>implemented a</strong> <code>simplify</code> rule for the <code>starts_with</code> string function in DataFusion, turning it into an equivalent <code>LIKE</code> pattern. We'll cover a high-level overview of DataFusion's simplify mechanism, why this change was needed, how we implemented it step by step (including basic regex handling and tests), and the benefits it brings to both the DataFusion project and Rust developers.</p>
<p><strong>Note:</strong> Portions of this article were drafted with the help of an AI writing assistant, then heavily edited by me for clarity and accuracy.</p>
<h2 id="heading-what-is-datafusions-simplify-function">What is DataFusion's <code>simplify</code> Function?</h2>
<p>In DataFusion, <code>simplify</code> is an optimization mechanism that rewrites expressions into simpler or more efficient forms during query planning. This includes things like constant folding (e.g. replacing <code>1 + 2</code> with <code>3</code>) and algebraic rewrites. Each built-in function or expression can provide its own <code>simplify</code> logic. Thanks to DataFusion's well-structured design, these simplification rules are <strong>modular</strong> – typically implemented as a method on the function's definition – making it easy to add new rules without touching the rest of the codebase. For example, the <code>starts_with</code> function in DataFusion has a <code>fn simplify(...)</code> method where we can plug in custom rewrite logic​. The optimizer automatically applies these rules, so if a function's arguments allow a simplification, DataFusion will use the simpler form in the query plan.</p>
<h2 id="heading-why-simplify-startswith">Why Simplify <code>starts_with</code>?</h2>
<p>The <code>starts_with(string, prefix)</code> function returns true if <code>string</code> begins with the given <code>prefix</code>. Without simplification, <code>starts_with</code> would be treated as a black-box scalar function in the query plan. By introducing a simplification rule, we can rewrite <code>starts_with(col, 'prefix')</code> into a standard SQL pattern match <code>col LIKE 'prefix%'</code>.</p>
<p>This has <strong>big benefits</strong>:</p>
<p>SQL engines (and DataFusion) know how to optimize <code>LIKE 'prefix%'</code> patterns for string columns. In particular, converting to <code>LIKE</code> enables <strong>predicate pruning</strong> based on prefix filters​. In other words, DataFusion can use file metadata (like min/max column values) to skip reading data that can't match the prefix, significantly improving performance for queries that filter on string prefixes.</p>
<p>Thanks to the PR that implements predicate pruning: <a target="_blank" href="https://github.com/apache/datafusion/pull/12978">https://github.com/apache/datafusion/pull/12978</a></p>
<p>Another benefit is that the logical plan becomes more transparent. A <code>LIKE 'prefix%'</code> is a recognizable operation for developers and other optimizations, whereas a custom function call might be harder to leverage. Overall, simplifying <code>starts_with</code> makes the query plan more efficient and easier to reason about.</p>
<h2 id="heading-implementing-the-simplification-for-startswith">Implementing the Simplification for <code>starts_with</code></h2>
<p><strong>Thanks to DataFusion's modular architecture, adding this optimization was straightforward.</strong> All the logic for <code>starts_with</code> is encapsulated in its own module, so we only needed to modify that and add tests. Here are the steps we took to implement the <code>simplify</code> rule for <code>starts_with</code>:</p>
<ol>
<li><p><strong>Introducing the</strong> <code>simplify</code> logic: We added a new <code>simplify</code> method in the <code>starts_with</code> function implementation. This method inspects the function's arguments. If the second argument (the prefix) is a <strong>literal string</strong>, we can simplify. DataFusion provides an <code>ExprSimplifyResult</code> type to indicate whether an expression was simplified or left unchanged. In our case, when we detect a literal prefix, we'll return a <code>Simplified</code> result with a new expression.</p>
</li>
<li><p><strong>Converting to a</strong> <code>LIKE</code> expression (basic regex handling): If the prefix is literal, we construct a new pattern for a SQL <code>LIKE</code>. Essentially, we take the prefix and append a <code>%</code> wildcard to match any suffix. For example, <code>starts_with(name, "Ja")</code> becomes <code>name LIKE "Ja%"</code>. We also had to handle any special characters in the prefix. In SQL <code>LIKE</code> patterns, the <code>%</code> and <code>_</code> characters have special meaning (any sequence of characters and any single character, respectively). We made sure to <strong>escape</strong> these in the prefix before appending <code>%</code> so that, for instance, a literal prefix <code>"ja%"</code> is treated as <code>"ja\%"</code> in the pattern (meaning the string "ja%") and then becomes <code>"ja\%%"</code> after adding the wildcard​. This way, the semantics of <code>starts_with</code> (which treats the prefix as literal text) are preserved in the <code>LIKE</code> expression.</p>
</li>
</ol>
<p>    The core snippet of the implementation looked like this:</p>
<pre><code class="lang-rust">    <span class="hljs-keyword">if</span> <span class="hljs-keyword">let</span> Expr::Literal(ScalarValue::Utf8(<span class="hljs-literal">Some</span>(prefix))) = &amp;args[<span class="hljs-number">1</span>] {
        <span class="hljs-comment">// Found constant prefix, convert to LIKE pattern</span>
        <span class="hljs-keyword">let</span> escaped = prefix.replace(<span class="hljs-string">"%"</span>, <span class="hljs-string">"\\%"</span>).replace(<span class="hljs-string">"_"</span>, <span class="hljs-string">"\\_"</span>);
        <span class="hljs-keyword">let</span> pattern = <span class="hljs-built_in">format!</span>(<span class="hljs-string">"{}%"</span>, escaped);
        <span class="hljs-keyword">let</span> like_expr = Expr::Literal(ScalarValue::Utf8(<span class="hljs-literal">Some</span>(pattern)));
        <span class="hljs-keyword">return</span> <span class="hljs-literal">Ok</span>(ExprSimplifyResult::Simplified(Expr::Like(Like {
            negated: <span class="hljs-literal">false</span>,
            expr: <span class="hljs-built_in">Box</span>::new(args[<span class="hljs-number">0</span>].clone()),    <span class="hljs-comment">// the string column</span>
            pattern: <span class="hljs-built_in">Box</span>::new(like_expr),       <span class="hljs-comment">// the 'prefix%' pattern</span>
            escape_char: <span class="hljs-literal">None</span>,
            case_insensitive: <span class="hljs-literal">false</span>,
        }))));
    }
    <span class="hljs-comment">// If not a literal prefix, no change</span>
    <span class="hljs-keyword">return</span> <span class="hljs-literal">Ok</span>(ExprSimplifyResult::Original(args));
</code></pre>
<p>    In practice, we wrote similar logic for all relevant string types (<code>Utf8</code>, <code>LargeUtf8</code>, and the newer <code>Utf8View</code>) since DataFusion supports multiple string array types. The idea is the same: detect literal prefix, build the <code>'prefix%'</code> pattern, wrap it in a <code>LIKE</code> expression node, and mark the expression as simplified.</p>
<ol start="3">
<li><strong>Updating tests:</strong> With the implementation in place, we updated and added tests to ensure everything works as expected. We wrote unit tests for <code>starts_with</code> to confirm that the function still returns correct boolean results for various cases (e.g. <code>"alphabet", "alph" -&gt; true</code>, <code>"alphabet", "bet" -&gt; false</code>, etc.)​. We paid special attention to edge cases like an <strong>empty prefix</strong> (which should always yield true as long as the string is not NULL, since every string starts with <code>""</code>). We also added an integration test using DataFusion's SQL engine to verify the optimization kicks in. For example, using an <code>EXPLAIN</code> query on a filter with <code>starts_with</code>, we should see the plan has been rewritten to use <code>LIKE</code>. After our change, an <code>EXPLAIN SELECT * FROM my_table WHERE starts_with(col, 'f')</code> showed a filter of <code>col LIKE 'f%'</code> in the logical plan, and the physical plan included a <strong>pruning predicate</strong> based on the prefix​. This confirmed that predicate pushdown was working: DataFusion was able to determine, for instance, that only values between <code>"f"</code> and <code>"g"</code> (non-inclusive of <code>"g"</code>) could match <code>f%</code>, and use that to skip irrelevant data. All new tests passed, demonstrating that the <code>simplify</code> rule behaves correctly.</li>
</ol>
<h2 id="heading-why-datafusions-design-made-this-easy">Why DataFusion's Design Made This Easy</h2>
<p>Implementing this feature highlighted how well-structured DataFusion is. The codebase cleanly separates concerns: the logic for each function (including execution and optimization rules) lives in one place. We didn't have to tinker with the core optimizer loop or planner; we just implemented the <code>simplify</code> method for <code>starts_with</code> and the existing optimization framework took care of invoking it. DataFusion already had patterns for similar rewrites (for instance, simplifying certain regex patterns to equality checks in the past), so we were able to follow an established approach. This modular design meant less risk of breaking unrelated parts of the system and made the code review process smoother. It’s a testament to DataFusion’s extensibility that a contributor could add such an optimization in a relatively small, focused PR.</p>
<h2 id="heading-benefits-and-impact">Benefits and Impact</h2>
<p>This enhancement brings several benefits:</p>
<ul>
<li><p><strong>Performance Boost:</strong> Queries filtering on string prefixes can see significant speed-ups. By converting <code>starts_with(col, "prefix")</code> into <code>col LIKE 'prefix%'</code>, DataFusion can apply <strong>predicate pruning</strong> at the data source level​. In practical terms, if you're querying a large dataset stored in Parquet files, DataFusion will only read the files (or row groups) that could possibly contain values with the given prefix. This reduces I/O and speeds up query execution.</p>
</li>
<li><p><strong>Optimized Plans:</strong> The logical and physical plans become more optimized and easier to understand. Using a native <code>LIKE</code> operator in plans means other optimizations (like combining with other conditions) work seamlessly. It also aligns with SQL standards, which can be helpful for developers debugging query plans. The plan shows a clear intention ("column starts with X") via a <code>LIKE 'X%'</code> clause, which is intuitive.</p>
</li>
<li><p><strong>Encouraging Extensibility:</strong> For Rust developers and contributors, this is a great example of how to extend DataFusion. If you have a custom scalar function or see an opportunity for a rewrite rule, DataFusion provides the hooks (like the <code>simplify</code> trait method) to implement it. You can add specialized knowledge (such as domain-specific optimizations) in the same way we did for <code>starts_with</code>. The fact that we accomplished this with a small change localized to one module underscores how approachable DataFusion's codebase is for newcomers and seasoned devs alike.</p>
</li>
<li><p><strong>Maintaining Correctness:</strong> Importantly, all these optimizations maintain the exact semantics of the original expression. By escaping special characters and carefully constructing the new expression, we ensure that <code>starts_with</code> and the equivalent <code>LIKE</code> behave identically for all inputs. The robust test suite gives confidence in the correctness of this rewrite.</p>
</li>
</ul>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Implementing the <code>simplify</code> rule for <code>starts_with</code> was a satisfying journey that improved DataFusion's optimizer and demonstrated the strength of its design. In just a few lines of code, we unlocked an optimization that can benefit many real-world queries. This change makes DataFusion smarter about string predicates, allowing it to skip unnecessary work and respond faster. For DataFusion users, it means you can write queries with <code>starts_with</code> and get performance similar to writing a <code>LIKE</code> by hand – the engine does it for you. For Rust developers, it's an example of how contributing to an open-source project like DataFusion can be straightforward thanks to clear abstraction boundaries. We hope this encourages more contributions and explorations into DataFusion's optimization capabilities. After all, as we've seen, a <strong>simple</strong> change (pun intended) can go a long way in making your queries run <em>simply</em> faster!</p>
]]></content:encoded></item><item><title><![CDATA[Trie: K-ary search tree]]></title><description><![CDATA[Trie is a typeof k-ary search tree used for storing and searching a specific key from a set.
Trie helps us to find the key in O(length of the key) time.
Check out the code example: Github: Trie implementation link
The trie will be made of trie nodes,...]]></description><link>https://blog.jatin510.dev/trie-search-tree</link><guid isPermaLink="true">https://blog.jatin510.dev/trie-search-tree</guid><category><![CDATA[Trie]]></category><category><![CDATA[TypeScript]]></category><category><![CDATA[datastructure]]></category><category><![CDATA[algorithms]]></category><category><![CDATA[search]]></category><dc:creator><![CDATA[Jagdish Parihar]]></dc:creator><pubDate>Mon, 12 Dec 2022 11:32:05 GMT</pubDate><content:encoded><![CDATA[<p>Trie is a typeof k-ary search tree used for storing and searching a specific key from a set.</p>
<p>Trie helps us to find the key in <strong>O(length of the key)</strong> time.</p>
<p>Check out the code example: <a target="_blank" href="https://gist.github.com/jatin510/3bbda3d1cd0aa06ef0dc3bb56d55f05f">Github: Trie implementation link</a></p>
<p>The trie will be made of trie nodes, which will have three fields. <code>Key, children and isWord</code></p>
<ul>
<li><code>key</code> will store the character.</li>
<li><code>children</code> will be the map that will keep track of the child trie node on the basis of a key character.</li>
<li><code>isWord</code> will be a boolean field. which will mark if the node is the word or not.</li>
</ul>
<h2 id="heading-structure-of-the-trie-node">Structure of the Trie Node:</h2>
<pre><code class="lang-Typescript"><span class="hljs-comment">// TrieNode.ts</span>

<span class="hljs-keyword">type</span> childrenType = {
  [key: <span class="hljs-built_in">string</span>]: <span class="hljs-built_in">any</span>
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> TrieNode {
  key: <span class="hljs-built_in">string</span> | <span class="hljs-literal">null</span>;
  children: chilrenType;
  isWord: <span class="hljs-built_in">boolean</span>;

  <span class="hljs-keyword">constructor</span>(<span class="hljs-params">key: <span class="hljs-built_in">string</span> | <span class="hljs-literal">null</span></span>) {
    <span class="hljs-built_in">this</span>.key = key;
    <span class="hljs-built_in">this</span>.children = {};
    <span class="hljs-built_in">this</span>.isWord = <span class="hljs-literal">false</span>;
  }
}
</code></pre>
<h2 id="heading-structure-of-the-trie">Structure of the Trie :</h2>
<h3 id="heading-root-node">Root Node</h3>
<ul>
<li>The Trie is made up of Trie Node</li>
<li>The root TrieNode will have the <code>key</code> value of <code>null</code> and every Trie Node will have two properties of children: an empty object and isWord as a false boolean field.</li>
<li>In Trie constructor, pass the value of <code>null</code> while instantiating the TrieNode for the <code>root</code> node. </li>
</ul>
<pre><code class="lang-Typescript"><span class="hljs-comment">// Trie.ts</span>
<span class="hljs-keyword">import</span> { TrieNode } <span class="hljs-keyword">from</span> <span class="hljs-string">"./TrieNode"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> Trie {
  root: TrieNode;
  <span class="hljs-keyword">constructor</span>(<span class="hljs-params"></span>) {
    <span class="hljs-built_in">this</span>.root = <span class="hljs-keyword">new</span> TrieNode(<span class="hljs-literal">null</span>);
  }
...
}
</code></pre>
<h3 id="heading-inserting-word-in-trie">Inserting word in Trie</h3>
<p>The <code>insertWord</code> function will be responsible for inserting the word in the trie.</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// Trie.ts</span>
...
 insertWord(word: <span class="hljs-built_in">string</span>) {
    <span class="hljs-keyword">let</span> node = <span class="hljs-built_in">this</span>.root;
    <span class="hljs-keyword">const</span> n = word.length;

    <span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> i = <span class="hljs-number">0</span>; i &lt; n; i++) {
      <span class="hljs-keyword">const</span> char = word[i];

      <span class="hljs-comment">// set children</span>
      <span class="hljs-keyword">if</span> (!node.children[char]) {
        node.children[char] = <span class="hljs-keyword">new</span> TrieNode(char);
      }

      <span class="hljs-comment">// move node forward</span>
      node = node.children[char];

      <span class="hljs-comment">// set last char as word</span>
      <span class="hljs-keyword">if</span> (i === n - <span class="hljs-number">1</span>) {
        node.isWord = <span class="hljs-literal">true</span>;
      }
    }
  }

...
</code></pre>
<p>The <code>insertWord</code> function adds the word, character by character, in the trie. If the node with the key is not found in the trie at the particular position, then we create the new trie node.
In the end, when we reach the last character, we mark that node, <code>isWord</code> field as true.</p>
<h3 id="heading-searching-in-trie">Searching in Trie</h3>
<p>The <code>contains</code> function is responsible for checking if the word exists the trie. It will search for the word which we want to check.</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// Trie.ts</span>
...
contains(word: <span class="hljs-built_in">string</span>): <span class="hljs-built_in">boolean</span> {
    <span class="hljs-keyword">let</span> node = <span class="hljs-built_in">this</span>.root;
    <span class="hljs-keyword">let</span> n = word.length;

    <span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> i = <span class="hljs-number">0</span>; i &lt; n; i++) {
      <span class="hljs-keyword">const</span> char = word.charAt(i);

      <span class="hljs-keyword">if</span> (node.children[char]) {
        node = node.children[char];
      } <span class="hljs-keyword">else</span> {
        <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>;
      }
    }

    <span class="hljs-keyword">return</span> node.isWord;
  }
...
</code></pre>
<h2 id="heading-final-code">Final Code :</h2>
<pre><code class="lang-typescript"><span class="hljs-keyword">type</span> childrenType = {
  [key: <span class="hljs-built_in">string</span>]: <span class="hljs-built_in">any</span>
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> TrieNode {
  key: <span class="hljs-built_in">string</span> | <span class="hljs-literal">null</span>;
  children: chilrenType;
  isWord: <span class="hljs-built_in">boolean</span>;

  <span class="hljs-keyword">constructor</span>(<span class="hljs-params">key: <span class="hljs-built_in">string</span> | <span class="hljs-literal">null</span></span>) {
    <span class="hljs-built_in">this</span>.key = key;
    <span class="hljs-built_in">this</span>.children = {};
    <span class="hljs-built_in">this</span>.isWord = <span class="hljs-literal">false</span>;
  }
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> Trie {
  root: TrieNode;

  <span class="hljs-keyword">constructor</span>(<span class="hljs-params"></span>) {
    <span class="hljs-built_in">this</span>.root = <span class="hljs-keyword">new</span> TrieNode(<span class="hljs-literal">null</span>);
  }

  insertWord(word: <span class="hljs-built_in">string</span>) {
    <span class="hljs-keyword">let</span> node = <span class="hljs-built_in">this</span>.root;
    <span class="hljs-keyword">const</span> n = word.length;

    <span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> i = <span class="hljs-number">0</span>; i &lt; n; i++) {
      <span class="hljs-keyword">const</span> char = word[i];

      <span class="hljs-comment">// set children</span>
      <span class="hljs-keyword">if</span> (!node.children[char]) {
        node.children[char] = <span class="hljs-keyword">new</span> TrieNode(char);
      }

      <span class="hljs-comment">// move node forward</span>
      node = node.children[char];

      <span class="hljs-comment">// set last char as word</span>
      <span class="hljs-keyword">if</span> (i === n - <span class="hljs-number">1</span>) {
        node.isWord = <span class="hljs-literal">true</span>;
      }
    }
  }

  contains(word: <span class="hljs-built_in">string</span>): <span class="hljs-built_in">boolean</span> {
    <span class="hljs-keyword">let</span> node = <span class="hljs-built_in">this</span>.root;
    <span class="hljs-keyword">let</span> n = word.length;

    <span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> i = <span class="hljs-number">0</span>; i &lt; n; i++) {
      <span class="hljs-keyword">const</span> char = word.charAt(i);

      <span class="hljs-keyword">if</span> (node.children[char]) {
        node = node.children[char];
      } <span class="hljs-keyword">else</span> {
        <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>;
      }
    }

    <span class="hljs-keyword">return</span> node.isWord;
  }
}
</code></pre>
<p>Check out the code example: <a target="_blank" href="https://gist.github.com/jatin510/3bbda3d1cd0aa06ef0dc3bb56d55f05f">Github: Trie implementation link</a></p>
]]></content:encoded></item><item><title><![CDATA[Implementing Custom API timeout using the `Promise.race()` method.]]></title><description><![CDATA[This tutorial is about creating a custom API timeout for your API calls using Promise.race() method.
What is Promise.race() method.
The Promise.race() the method returns a promise that fulfills or rejects as soon as one of the promises in an iterable...]]></description><link>https://blog.jatin510.dev/implementing-custom-api-timeout-using-the-promiserace-method</link><guid isPermaLink="true">https://blog.jatin510.dev/implementing-custom-api-timeout-using-the-promiserace-method</guid><category><![CDATA[promises]]></category><category><![CDATA[JavaScript]]></category><category><![CDATA[coding]]></category><category><![CDATA[development]]></category><category><![CDATA[asynchronous]]></category><dc:creator><![CDATA[Jagdish Parihar]]></dc:creator><pubDate>Wed, 14 Sep 2022 07:52:26 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1671079758154/sg-Zculv-.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>This tutorial is about creating a custom API timeout for your API calls using <code>Promise.race()</code> method.</p>
<h2 id="heading-what-is-promiserace-method">What is Promise.race() method.</h2>
<p>The <code>Promise.race()</code> the method returns a promise that fulfills or rejects as soon as one of the promises in an iterable fulfills or rejects, with the value or reason from that promise.</p>
<h2 id="heading-implementation">Implementation</h2>
<p>So, we can provide two inputs inside the race method, i.e:</p>
<ol>
<li><p>API call promise.</p>
</li>
<li><p>Delay promise with our custom timeout logic.</p>
</li>
</ol>
<pre><code class="lang-javascript">
    <span class="hljs-built_in">Promise</span>.race([apiCall, timeoutFunc])
</code></pre>
<p>Here, The <code>apiCall</code> method will represent our API call promise.</p>
<pre><code class="lang-javascript">    <span class="hljs-comment">// your url </span>
    <span class="hljs-keyword">const</span> apiCall = fetch(<span class="hljs-string">"https://api.github.com/"</span>);
</code></pre>
<p>and the timeout function will contain the delay logic which will be responsible for throwing a timeout error.</p>
<pre><code class="lang-javascript">
    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">delay</span>(<span class="hljs-params">timeOutPeriod</span>) </span>{
    <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Promise</span>(<span class="hljs-function">(<span class="hljs-params">_resolve, reject</span>) =&gt;</span> {
        <span class="hljs-built_in">setTimeout</span>(<span class="hljs-function">() =&gt;</span> reject(<span class="hljs-string">"Request timeout"</span>), timeOutPeriod);
    });
    }

    <span class="hljs-keyword">const</span> timePeriod =  <span class="hljs-number">5000</span>;
    <span class="hljs-keyword">const</span> timeoutFunc = delay(timePeriod);
</code></pre>
<p>So, when we will run the Promise.race method, whichever promise is completed first, will return the output.</p>
<pre><code class="lang-javascript">
    <span class="hljs-built_in">Promise</span>.race([apiCall, timeoutFunc])
    .then(<span class="hljs-function">(<span class="hljs-params">data</span>) =&gt;</span> data.json())
    .then(<span class="hljs-function">(<span class="hljs-params">data</span>) =&gt;</span> <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"Data : "</span>, data))
    .catch(<span class="hljs-function">(<span class="hljs-params">error</span>) =&gt;</span> <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"Error : "</span>error));
</code></pre>
<p>Here, in this particular example, we have provided the custom timeout logic of 5000 milliseconds. So, either the API will give a response with 5 seconds or our network request will get timed out after the 5 seconds.</p>
<p>To check out the code, visit my github link <a target="_blank" href="https://github.com/jatin510/custom-api-timeout-promise.race">github</a>.</p>
]]></content:encoded></item><item><title><![CDATA[Queue Data Structure in Javascript]]></title><description><![CDATA[Implement Queue data structure in Javascript
The Queue is the data structure that works on FIFO Principle, which is the abbreviation for "First In First Out".
Queues can have various applications:
Let's suppose you are making a service that requires ...]]></description><link>https://blog.jatin510.dev/queue-data-structure-in-javascript</link><guid isPermaLink="true">https://blog.jatin510.dev/queue-data-structure-in-javascript</guid><category><![CDATA[JavaScript]]></category><category><![CDATA[queue]]></category><category><![CDATA[data structures]]></category><category><![CDATA[software implementation]]></category><category><![CDATA[FIFO]]></category><dc:creator><![CDATA[Jagdish Parihar]]></dc:creator><pubDate>Sat, 03 Sep 2022 14:10:31 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1671079469316/GDDxsotKH.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h1 id="heading-implement-queue-data-structure-in-javascript">Implement Queue data structure in Javascript</h1>
<p>The <code>Queue</code> is the data structure that works on FIFO Principle, which is the abbreviation for "First In First Out".</p>
<h2 id="heading-queues-can-have-various-applications">Queues can have various applications:</h2>
<p>Let's suppose you are making a service that requires you to send the email, but you don't want your program to wait for mailing to finish.
Then you can send that particular task to the mailing queue and it will take control of sending all the mails in FIFO manner.</p>
<h3 id="heading-now-lets-implement-some-functionality-of-the-queue">Now let's implement some functionality of the queue</h3>
<p>The queue has some operations such as:</p>
<ol>
<li><p><code>enqueue</code> which will add the elements at the end of the queue.</p>
</li>
<li><p><code>Dequeue</code> removes the element from the start of the queue.</p>
</li>
<li><p><code>Front</code> To find out the element at the beginning of the queue. </p>
</li>
<li><p><code>Size</code> function will give you the size of the queue.</p>
</li>
<li><p><code>Empty</code> function will check if the queue is empty or not, and return us the boolean value.</p>
</li>
<li><p><code>Print</code> function will print the whole queue for us.</p>
</li>
</ol>
<pre><code class="lang-Javascript">
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Queue</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">let</span> items = [];


  <span class="hljs-built_in">this</span>.enqueue = <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">element</span>) </span>{
    items.push(element);
  };

  <span class="hljs-built_in">this</span>.dequeue = <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params"></span>) </span>{
    <span class="hljs-keyword">return</span> items.shift();
  };

  <span class="hljs-built_in">this</span>.front = <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params"></span>) </span>{
    <span class="hljs-keyword">return</span> items[<span class="hljs-number">0</span>];
  };

   <span class="hljs-built_in">this</span>.size = <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params"></span>) </span>{
    <span class="hljs-keyword">return</span> items.length;
  };

  <span class="hljs-built_in">this</span>.isEmpty = <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params"></span>) </span>{
    <span class="hljs-keyword">return</span> items.length === <span class="hljs-number">0</span>;
  };

    <span class="hljs-built_in">this</span>.print = <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params"></span>) </span>{
    <span class="hljs-built_in">console</span>.log(items);
  };

}
</code></pre>
<p>Result :</p>
<pre><code class="lang-Javascript"><span class="hljs-keyword">const</span> q = <span class="hljs-keyword">new</span> Queue();

q.enqueue(<span class="hljs-number">10</span>); <span class="hljs-comment">// [10]      : enqueue from end</span>
q.enqueue(<span class="hljs-number">20</span>); <span class="hljs-comment">// [ 10, 20] : enqueue from end</span>
q.dequeue();   <span class="hljs-comment">// [ 20]     : dequeue from start</span>
q.print();     <span class="hljs-comment">// prints the whole queue</span>
<span class="hljs-built_in">console</span>.log(q.size()); <span class="hljs-comment">// return the size : i.e 1</span>
</code></pre>
]]></content:encoded></item><item><title><![CDATA[Clean Code In Javascript]]></title><description><![CDATA[Hi, 
I am a software developer, primarily working on the javascript language. While working on the lots of code. I realized the importance of the clean code.
So, I want to share my experience with everyone.
So, while writing javascript code. Keep the...]]></description><link>https://blog.jatin510.dev/clean-code-in-javascript</link><guid isPermaLink="true">https://blog.jatin510.dev/clean-code-in-javascript</guid><category><![CDATA[js]]></category><category><![CDATA[clean code]]></category><category><![CDATA[coding]]></category><category><![CDATA[learn coding]]></category><dc:creator><![CDATA[Jagdish Parihar]]></dc:creator><pubDate>Fri, 07 May 2021 17:31:16 GMT</pubDate><content:encoded><![CDATA[<p>Hi, </p>
<p>I am a software developer, primarily working on the javascript language. While working on the lots of code. I realized the importance of the clean code.</p>
<p>So, I want to share my experience with everyone.</p>
<p>So, while writing javascript code. Keep these things in mind.</p>
<h1 id="code-editor-and-tools">Code Editor &amp; Tools</h1>
<p>Most developers use Visual Studio Code these days. You can use any code editor that you want. However, tools like Prettier can be integrated into most modern code editors. Prettier can also be run via command line to automatically format files as per provided specification. A good read is available at <a target="_blank" href="https://prettier.io/docs/en/options.html">prettier.io</a>.</p>
<h4 id="recommended-creating-prettierrcjson-file-in-the-root-folder-of-the-project">Recommended creating <code>.prettierrc.json</code> file in the root folder of the project.</h4>
<h4 id="example-prettierrcjson-file">Example <code>.prettierrc.json</code> file.</h4>
<pre><code class="lang-JSON">
{
    <span class="hljs-attr">"tabWidth"</span>: <span class="hljs-number">4</span>,      <span class="hljs-comment">// make tab width equal to 4</span>
    <span class="hljs-attr">"semi"</span>: <span class="hljs-literal">true</span>,       <span class="hljs-comment">// semicolon after the statement ends</span>
    <span class="hljs-attr">"singleQuote"</span>: <span class="hljs-literal">true</span> <span class="hljs-comment">// all the string will be using single quote</span>
}
</code></pre>
<h2 id="var-vs-letconst">var vs let/const</h2>
<p>Never use <code>var</code> to define variables. Always use <code>let</code> or <code>const</code>. I follow a golden rule; if there's no need to reassign another value to a variable after it's first assigned something, then it should be a <code>const</code>. If you need to reassign something else to the same variable, it should be a <code>let</code>.</p>
<p>An alternative is to always define a variable with <code>const</code>. If in the code somewhere, you are reassigning it, then the code will throw an easily recognizable error and you just change <code>const</code> to <code>let</code>.</p>
<h2 id="variable-names">Variable names</h2>
<p>Avoid using generic variable names such as <code>const i = 'jatin'</code>. 
Use a proper name that tells what the variable is about. 
For example, if the variable represents, a user name then, it should be <code>const user = 'jatin'</code></p>
<h2 id="comparison-vs">Comparison == vs ===</h2>
<p>Never compare two variables/values with double equal. It is not reliable, because  <code>1 == true</code> is correct with double equals, despite the fact that <code>1</code> is a number and <code>true</code> is a boolean. </p>
<p>The <code>==</code> just compares value, the <code>===</code> compares the value as well as the type.</p>
<pre><code class="lang-JS">
-&gt; <span class="hljs-number">1</span> == <span class="hljs-string">'1'</span>  <span class="hljs-comment">// true : values are same</span>
-&gt; <span class="hljs-number">1</span> === <span class="hljs-string">'1'</span> <span class="hljs-comment">// false : values are same but type is different</span>
-&gt; <span class="hljs-number">1</span> === <span class="hljs-number">1</span>   <span class="hljs-comment">// true : both value and types are equal</span>
</code></pre>
<p>Always compare with triple equal.</p>
<h2 id="set-maximum-line-width-in-code-editor-settings">Set maximum line width in code editor settings</h2>
<p>A line should not be more than 80 spaces/print-width longs. If it is, then the code shall be split into multiple lines.</p>
<h2 id="always-use-the-brackets-when-using-conditional-statements">Always use the brackets when using conditional statements</h2>
<p>It makes the code more readable.</p>
<pre><code class="lang-JS"><span class="hljs-comment">// Bad code</span>
<span class="hljs-keyword">if</span> (isCurrent)<span class="hljs-keyword">return</span> <span class="hljs-string">'foo'</span>;

<span class="hljs-comment">// Good code</span>
<span class="hljs-keyword">if</span> (isCurrent) {
    <span class="hljs-keyword">return</span> <span class="hljs-string">'foo'</span>;
}
</code></pre>
<h2 id="use-proper-bracket-spacing-for-the-good-readability">Use proper bracket spacing for the good readability</h2>
<p>Have proper bracket spacing as well.</p>
<pre><code class="lang-JS"><span class="hljs-comment">// Bad code</span>
<span class="hljs-keyword">const</span> array = [<span class="hljs-number">1</span>,<span class="hljs-number">2</span>,<span class="hljs-number">3</span>];
<span class="hljs-keyword">const</span> person = {<span class="hljs-attr">name</span>:<span class="hljs-string">'John'</span>,<span class="hljs-attr">age</span>:<span class="hljs-number">25</span>};

<span class="hljs-comment">// Good code</span>
<span class="hljs-keyword">const</span> array = [<span class="hljs-number">1</span>, <span class="hljs-number">2</span>, <span class="hljs-number">3</span>];
<span class="hljs-keyword">const</span> person = { <span class="hljs-attr">name</span>: <span class="hljs-string">'John'</span>, <span class="hljs-attr">age</span>: <span class="hljs-number">25</span> };
</code></pre>
<h1 id="destructuring">Destructuring</h1>
<p>Always try to de-structure when possible. I am going to give two examples here.</p>
<p>Default method</p>
<pre><code class="lang-JS"><span class="hljs-keyword">const</span> user = getUser();
<span class="hljs-built_in">console</span>.log(user.name);
<span class="hljs-built_in">console</span>.log(user.age);
</code></pre>
<p>Recommended method</p>
<pre><code class="lang-JS"><span class="hljs-keyword">const</span> { name, age } = getUser();
<span class="hljs-built_in">console</span>.log(name);
<span class="hljs-built_in">console</span>.log(age);
</code></pre>
]]></content:encoded></item></channel></rss>