<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>blog.qnt.moe</title>
  <subtitle>Welcome to my personal blog. My opinions are of my own.</subtitle>
  <link href="https://blog.qnt.moe/feed.xml" rel="self"/>
  <link href="https://blog.qnt.moe/"/>
  <id>https://blog.qnt.moe/</id>
  <updated>2026-10-01T00:00:00+00:00</updated>
  <author><name>Abhijeet</name></author>

  <entry>
    <title>Tomato is a vegetable and Oxygen is a metal</title>
    <link href="https://blog.qnt.moe/p/tomato/"/>
    <id>https://blog.qnt.moe/p/tomato/</id>
    <published>2026-10-01T00:00:00+00:00</published>
    <updated>2026-10-01T00:00:00+00:00</updated>
    <content type="html"><p>I have a problem with a specific class of <em>pop facts</em> that get thrown around.
It goes like this: someone says, for example, &quot;I love tomatoes, it's a juicy
vegetable&quot;, only to be responded with something like &quot;well, you know, it's
technically a fruit&quot;.</p>
<p>I think there's a greater issue to resolve before we assign truth values to
things stated by both of these people, and I think the underlying lesson is
much more valuable than having a clean resolution on what category tomato
belongs to.</p>
<h2>Domain specific terminology</h2>
<p>It's incorrect to assume terminology from one domain can't mean something very
different in another domain.</p>
<p>It's instantly obvious when you look at an example:</p>
<blockquote>
<p><strong><a href="https://en.wiktionary.org/wiki/bug">Bug</a></strong> (n.)</p>
<p>(entomology) An insect of the order Hemiptera (the “true bugs”).</p>
<p>(informal, proscribed) Any motile small terrestrial invertebrate, especially one that is seen as a pest.</p>
</blockquote>
<p>In computing, the same word is used for something completely unreleated:</p>
<blockquote>
<p>(computing, engineering jargon) A problem that needs fixing.</p>
</blockquote>
<p>However, things can get <em>much</em> dirtier. Sometimes the terminologies overalap
(there is not a rule that says they can't). And such terms can lead to
confusion.</p>
<blockquote>
<p><strong><a href="https://en.wiktionary.org/wiki/metal">Metal</a></strong> (n.)</p>
<p>Any of a number of chemical elements in the periodic table that form a
metallic bond with other metal atoms; generally shiny, somewhat malleable and
hard, often a conductor of heat and electricity.</p>
</blockquote>
<p>but:</p>
<blockquote>
<p>(astronomy) An element which was not directly created after the Big Bang but
instead formed through nuclear reactions; any element other than hydrogen and
helium.</p>
</blockquote>
<p>(I wasn't aware of this unusual usage of the term <em>metal</em> until I looked for
good examples for this article. Thanks to my friend Pranav Mahapatra from
<a href="https://evincism.com/">evincism.com</a> for validating the term is actually in
common use in this sense.)</p>
<h2>The meaning of a word lies in its use [0]</h2>
<p>Words don't exist in isolation, context around them can change what they mean.</p>
<p>I would go as far as saying that different people may use and understand a word
slightly differently in the same discussion. A lot of times, this results in
disagreements: Is <a href="https://en.wikipedia.org/wiki/Hijra_(South_Asia)"><em>hijra</em></a> a
gender? Yes and no:</p>
<blockquote>
<p><strong><a href="https://en.wiktionary.org/wiki/gender">Gender</a></strong> (n.)</p>
<p>[..]</p>
<ol start="2">
<li>
<p>Sex (a category, either male or female)</p>
</li>
<li>
<p>Identification as a man, a woman, or something else, and association with a
(social) role or set of behavioral and cultural traits, clothing, etc; a
category to which a person belongs on this basis.</p>
</li>
</ol>
</blockquote>
<p>Neither of the answer is incorrect, they are simply using the word &quot;gender&quot;
differently. And neither of these usages of the term is incorrect either. You
cannot deny that for most of the history gender simply meant <em>sex</em> and
continues to do so. You also cannot refuse, at the same time, that people (and
acadamia) don't use it in the second sense.</p>
<p>When having discourse, I think it's imporant that you clarify in what sense
your opponents are using a word. This video comes to mind: <a href="https://www.youtube.com/watch?v=gujcpG4g9Cw">Jordan Peterson:
&quot;What do you mean by believe&quot;</a>. He
was memed, a lot, by atheists, because he asked &quot;What do you mean by
believe&quot;--I think he was doing something, however much I disagree with his
definition of what it means to &quot;believe&quot;, it's how he chooses to use the word.
It's an important clarification however, becuase now the discussion can proceed
with his understanding of what it means to believe in mind.</p>
<p>I think we should do this more often.</p>
<h2>Mutual exclusion</h2>
<p>Two categories may not necessarily be mutually exclusive, that is, a thing can
belong to two different categories, <em>A</em> and <em>B</em>, unless their definitions
necessarily require them to be disjoint.</p>
<p>This seems obvious, but we do think of some categories as mutually exclusive
when they are not. Consider: water, it's both natual (as in naturally
occuring) and a chemical substance. A lot of people seem to use the term
<em>chemical</em> to mean <em>synthesised, harmful substances</em>, which is of course in
mutual exclusion with <em>natuarally occuring</em>.</p>
<blockquote>
<p><strong><a href="https://en.wiktionary.org/wiki/chemical">Chemical</a></strong> (n.)</p>
<ol>
<li>
<p>(chemistry, sciences) Any specific chemical element or chemical compound
or alloy.</p>
</li>
<li>
<p>(colloquial) An artificial chemical compound.
&quot;I color my hair with henna, not chemicals.&quot;</p>
</li>
<li>
<p>(slang) An addictive drug.</p>
</li>
</ol>
</blockquote>
<p>Again, I am not arguing against such usage, but academia uses it in a way that
is not mutually exclusive with <em>natural</em>, therefore, as far as chemists are
concerned, water is both natural and a chemical.</p>
<p>Imagine having a conversation where one states &quot;water is a chemical&quot; and the
other person states, with an intention to refute, that water is natural. They
would be right, but it's not the argument the other person think they are
making. Being a natural substance does not imply that water is not chemical. Of
course, their statement is true, but it's not in conflict with the initial
statement the other person made.</p>
<h2>Tomato</h2>
<p>I started with tomatoes, but I sidelined them for the rest of the article.</p>
<p>The pop fact that &quot;tomato is a fruit&quot; has it's origins in the botanical definition of a fruit:</p>
<blockquote>
<p><strong><a href="https://en.wiktionary.org/wiki/fruit">fruit</a></strong> (n.)</p>
<ol>
<li>(botany) A product of fertilization in a plant, specifically:
<ul>
<li>
<p>The seed-bearing part of a plant; often edible, colourful, fragrant, and
sweet or sour; produced from a floral ovary after fertilization.</p>
</li>
<li>
<p>The spores of cryptogams and their accessory organs.</p>
</li>
</ul>
</li>
</ol>
</blockquote>
<p>Tomatoes fit.</p>
<p>However, the same pop fact has now taken the shape of &quot;tomato is a fruit, not
vegetable&quot;.</p>
<p>This stems from the fact that in culinary, fruits and vegetables are disjoint
categories.</p>
<p><a href="https://www.crimemuseum.org/crime-library/terrorism/ted-kaczynski-the-unabomber/">You can't eat your cake and have it too.</a>.</p>
<p>Botanical concept of a &quot;fruit&quot; is about classifying parts a plant. The parts of
plants that are <em>not</em> fruit are: stem, leaf, root.</p>
<p>The category of <em>vegetable</em> is simply not something botany concerns itself
with. Why should it? Whether something is vegetable is of little importance to
a botanist.</p>
<p>To me, saying &quot;tomato is a fruit, not a vegetable&quot; is an idiotic statement to
make. Not because it is false (which it actually is [1]), but because it misses
the point of both, botany and culinary classification of fruits and vegtables.</p>
<p>It reads like the person making the claim does not actually know what a
botanical fruit is--one would never contrast that with a vegetable otherwise.</p>
<p>This &quot;pop fact&quot; is &quot;pop&quot; becuase it spread by people stating it to others
verbatim without actually getting into the nature of those words in botanical
and culinary disciplines, and providing no argument for why something that's a
fruit can't be a vegetable (spoiler: it can).</p>
<p>Furthermore, the culinary classification is culture based and there is no clear
line between fruits and vegetables.</p>
<p>Quoting Wiktionary:</p>
<blockquote>
<ol start="2">
<li>
<p>(culinary) Any sweet or sour, edible part of a plant that resembles
seed-bearing fruit (see former sense) even if it does not develop from a
floral ovary.</p>
<ul>
<li>A sweet or sweetish vegetable, such as the petioles of rhubarb, that
resembles a true fruit or is used in cookery as if it was a fruit.</li>
</ul>
</li>
</ol>
</blockquote>
<p>But the way <em>I personally</em> see people use the term as:</p>
<blockquote>
<p>A part of the plant, shaped like true-fruit, which is eaten without
cooking.</p>
</blockquote>
<p>By this definition, something can be a (culinary) fruit or vegetable depending
on where you live and the culinary norms.</p>
<p>In parts of India, jackfruit is cooked while in others it's eaten raw. That
makes jackfruit a:</p>
<ul>
<li>A <em>botanical</em> fruit</li>
<li>A <em>culinary</em> fruit</li>
<li>A <em>culinary</em> vegetable</li>
</ul>
<p>Even at an individual level, for a person who eats tomatos raw (yes I do), it
is a (culinary) fruit.</p>
<h2>See it</h2>
<p>Below is an interactive diagram that shows relationship between each of these
terms. Intersections are clickable.</p>
<p><svg xmlns="http://www.w3.org/2000/svg" id="venn" viewBox="229 -14 651 454" width="100%">
<style>
  #venn { display: block; max-width: 100%; height: auto; font-family: inherit; cursor: pointer; }
  #venn .circle { fill-opacity: .25; }
  #venn text { font-size: 15px; font-weight: 600; pointer-events: none; }
  #venn .hint { font-size: 11px; font-weight: 400; fill: var(--muted, #866a72); }
  #venn .hl { fill: var(--ink, #2a1a1f); fill-opacity: .1; pointer-events: none; }
  #venn #scene { transition: transform .45s ease; transform-box: view-box; }
  #venn #dim { fill: var(--bg, #fbe9ee); opacity: 0; transition: opacity .45s ease; pointer-events: none; }
  #venn #dim.on { opacity: .6; }
  #venn .outline { fill: none; stroke: var(--ink, #2a1a1f); stroke-width: 2; stroke-opacity: .75; stroke-dasharray: 4 3; pointer-events: none; }
  .venn-def { margin: .5em 0 1.5em; padding: .8em 1em; border-radius: 6px; background: var(--code-bg, #f6dde3); font-size: .92em; }
  .venn-def[hidden] { display: none; }
  .venn-def b { display: block; margin-bottom: .3em; }
  .venn-def p { margin: 0 0 .5em; }
  .venn-def p:last-child { margin: 0; }
  .venn-def ul { margin: 0; padding-left: 1.2em; }
</style>
<defs>
  <circle id="cB" cx="453.19" cy="206" r="204"/>
  <circle id="cV" cx="694" cy="206" r="170"/>
  <circle id="cC" cx="654.46" cy="146.04" r="98.15"/>
  <!-- The five atomic regions. T=(600.00,64.35) top meeting point, Bo=(600.00,347.65) bottom, P=(653.59,244.19) where B and C cross. -->
  <path id="a-Bo"  d="M 600.00 64.35 A 204 204 0 1 0 600.00 347.65 A 170 170 0 0 1 600.00 64.35 Z"/>
  <path id="a-Vo"  d="M 600.00 64.35 A 170 170 0 1 1 600.00 347.65 A 204 204 0 0 0 653.59 244.19 A 98.15 98.15 0 1 0 600.00 64.35 Z"/>
  <path id="a-BV"  d="M 600.00 64.35 A 170 170 0 0 0 600.00 347.65 A 204 204 0 0 0 653.59 244.19 A 98.15 98.15 0 0 1 600.00 64.35 Z"/>
  <path id="a-Co"  d="M 600.00 64.35 A 98.15 98.15 0 1 1 653.59 244.19 A 204 204 0 0 0 600.00 64.35 Z"/>
  <path id="a-BVC" d="M 600.00 64.35 A 204 204 0 0 1 653.59 244.19 A 98.15 98.15 0 0 1 600.00 64.35 Z"/>
  <path id="a-Out" fill-rule="evenodd" d="M 0 -100 H 1200 V 500 H 0 Z M 600.00 64.35 A 204 204 0 1 0 600.00 347.65 A 170 170 0 1 0 600.00 64.35 Z"/>
  <clipPath id="clipB"></clipPath>
  <clipPath id="clipV"></clipPath>
  <clipPath id="clipC"></clipPath>
  <mask id="dimMask"><rect x="0" y="-100" width="1200" height="600" fill="#fff"/><g id="dimCut" fill="#000"></g></mask>
</defs>

<g id="scene">
<use href="#cB" class="circle" fill="rgb(31,119,180)"/>
<use href="#cV" class="circle" fill="rgb(44,160,44)"/>
<use href="#cC" class="circle" fill="rgb(255,127,14)"/>

<text text-anchor="middle" x="413" y="210" fill="rgb(31,119,180)">Botanic fruits</text>
<text text-anchor="middle" x="770" y="260" fill="rgb(44,160,44)">Vegetables</text>
<text text-anchor="middle" x="700" y="120" fill="rgb(255,127,14)"><tspan x="700" dy="0">Culinary</tspan><tspan x="700" dy="1.1em">fruits</tspan></text>
<rect id="dim" x="0" y="-100" width="1200" height="600" mask="url(#dimMask)"/>
<g id="hl"></g>
<use href="#cB" class="outline" clip-path="url(#clipB)" display="none"/>
<use href="#cV" class="outline" clip-path="url(#clipV)" display="none"/>
<use href="#cC" class="outline" clip-path="url(#clipC)" display="none"/>

</g>
<text class="hint" text-anchor="middle" x="554" y="430">Click a circle, then keep clicking inside the selection to narrow it</text>

<script><![CDATA[
(function () {
  var NS = "http://www.w3.org/2000/svg", XL = "http://www.w3.org/1999/xlink";
  var svg = document.getElementById("venn");
  var circles = { B: [453.19, 206, 204], V: [694, 206, 170], C: [654.46, 146.04, 98.15] };
  // Atomic regions and which circles they lie in. C sits inside V, so these five are all there is. Out is everything else.
  var atoms = { Bo: "B", Vo: "V", BV: "BV", Co: "VC", BVC: "BVC", Out: "" };
  var names = { Bo: "Botanic fruit, not eaten", Vo: "Vegetable, not a fruit", BV: "Botanic fruit eaten as vegetable", Co: "Culinary fruit, not botanic", BVC: "Botanic and culinary fruit" };
  // The shapes a selection may take: the three circles, B+V, B+V-C, B+C, B only, V only, C only. (C+V is the C circle itself.)
  var allowed = ["Bo,BV,BVC", "Vo,BV,Co,BVC", "Co,BVC", "BV,BVC", "BV", "BVC", "Bo", "Vo", "Co"];
  // Definitions, keyed by the atomic regions a selection covers.
  var defs = {
    "Bo,BV,BVC": "<b>Botanic fruit</b><p>A product of fertilization in a plant, specifically:</p><ul><li>The seed-bearing part of a plant; often edible, colourful, fragrant, and sweet or sour; produced from a floral ovary after fertilization.</li><li>The spores of cryptogams and their accessory organs.</li></ul>",
    "Vo,BV,Co,BVC": "<b>Vegetables</b><p>Parts of plants humans eat.</p>",
    "Co,BVC": "<b>Culinary fruit</b><p>Any sweet or sour, edible part of a plant that resembles seed-bearing fruit even if it does not develop from a floral ovary.</p><ul><li>A sweet or sweetish vegetable, such as the petioles of rhubarb, that resembles a true fruit or is used in cookery as if it was a fruit.</li></ul>",
    "BV,BVC": "<b>Botanic fruit that is eaten</b><p>Seed-bearing parts of plants that humans eat, raw or cooked.</p>",
    "Bo": "<b>Botanic fruit that nobody eats</b><p>Seed-bearing parts of plants that are not food: cotton bolls, maple keys, poisonous berries.</p>",
    "BVC": "<b>Botanic fruit eaten as fruit</b><p>Seed-bearing parts of plants that we eat as fruit: apple, mango, and tomato for the people who eat it raw.</p>",
    "Bo,BV": "<b>Botanic fruit that is not a culinary fruit</b><p>Seed-bearing parts of plants that are either not eaten or are cooked</p>",
    "BV": "<b>Botanic fruit eaten as a vegetable</b><p>Seed-bearing parts of plants that we cook: tomato, pumpkin, eggplant</p>",
    "Vo,Co": "<b>Vegetables that are not botanic fruit</b><p>Edible roots, stems, and leaves, including the few eaten as fruit, like rhubarb.</p>",
    "Vo,BV": "<b>Vegetables not eaten as fruit</b><p>The savoury plant parts: potato, spinach, pumpkin, eggplant.</p>",
    "Co": "<b>Culinary fruit that is not a botanic fruit</b><p>Eaten as fruit, but not a seed-bearing part of the plant: rhubarb.</p>",
    "Vo": "<b>Vegetable that is not a fruit in any sense</b><p>Edible parts of plants that are not seed-bearing and not fruit-like: potato, spinach, carrot, onion.</p>"
  };
  var box = document.createElement("div");
  box.className = "venn-def";
  box.hidden = true;
  svg.parentNode.insertBefore(box, svg.nextSibling);
  var sel = null;   // { inc: "BV", exc: "C" }: circles the selection must be in / must not be in

  function regionsOf(inc, exc) {
    return Object.keys(names).filter(function (a) {
      var m = atoms[a];
      return inc.split("").every(function (k) { return m.indexOf(k) >= 0; }) &&
             exc.split("").every(function (k) { return m.indexOf(k) < 0; });
    });
  }
  // The atom on the other side of circle k's boundary from atom a, or null if that boundary never touches a.
  function mirror(a, k) {
    var m = atoms[a].indexOf(k) >= 0 ? atoms[a].replace(k, "") : "BVC".split("").filter(function (c) { return c === k || atoms[a].indexOf(c) >= 0; }).join("");
    for (var b in atoms) if (atoms[b] === m) return b;
    return null;
  }
  function fill(el, regions, cls) {
    el.innerHTML = "";
    regions.forEach(function (a) {
      var u = document.createElementNS(NS, "use");
      u.setAttribute("href", "#a-" + a); u.setAttributeNS(XL, "xlink:href", "#a-" + a);
      if (cls) u.setAttribute("class", cls);
      el.appendChild(u);
    });
  }
  function draw() {
    var regs = sel ? regionsOf(sel.inc, sel.exc) : [];
    fill(document.getElementById("hl"), regs, "hl");
    fill(document.getElementById("dimCut"), regs);
    document.getElementById("dim").setAttribute("class", regs.length ? "on" : "");
    zoom(regs);
    ["B", "V", "C"].forEach(function (k) {
      // Outline of circle k: the stretches where exactly one side is selected. Clip the stroked circle to those atoms (both sides).
      var clip = {};
      regs.forEach(function (a) { var m = mirror(a, k); if (m && regs.indexOf(m) < 0) { clip[a] = clip[m] = true; } });
      var out = svg.querySelector('.outline[clip-path="url(#clip' + k + ')"]');
      out.setAttribute("display", Object.keys(clip).length ? "inline" : "none");
      fill(document.getElementById("clip" + k), Object.keys(clip));
    });
    box.hidden = !regs.length;
    if (regs.length) box.innerHTML = defs[regs.join(",")] || "<b>" + regs.map(function (a) { return names[a]; }).join(" + ") + "</b>";
  }
  // Zoom the scene onto the selection's bounding box; back to the full view when nothing is selected.
  function zoom(regs) {
    var scene = document.getElementById("scene"), vb = svg.viewBox.baseVal;
    if (!regs.length) return scene.style.transform = "";
    var x0 = Infinity, y0 = Infinity, x1 = -Infinity, y1 = -Infinity;
    svg.querySelectorAll("#hl use").forEach(function (u) {
      var b = u.getBBox();
      x0 = Math.min(x0, b.x); y0 = Math.min(y0, b.y); x1 = Math.max(x1, b.x + b.width); y1 = Math.max(y1, b.y + b.height);
    });
    var pad = 30, s = Math.min(vb.width / (x1 - x0 + pad), vb.height / (y1 - y0 + pad), 2.2);
    var cx = vb.x + vb.width / 2, cy = vb.y + vb.height / 2, bx = (x0 + x1) / 2, by = (y0 + y1) / 2;
    scene.style.transform = "translate(" + (cx - bx * s) + "px, " + (cy - by * s) + "px) scale(" + s + ")";
  }
  function locate(x, y) {
    var inside = "", owner = null, best = Infinity;
    for (var k in circles) {
      var c = circles[k], d = Math.hypot(x - c[0], y - c[1]) / c[2], score = c[2] * 10 + d;
      if (d <= 1) { inside += k; if (score < best) { best = score; owner = k; } }
    }
    for (var a in atoms) if (atoms[a] === inside) return { atom: a, inside: inside, owner: owner };
    return { atom: "Out", inside: "", owner: null };
  }
  // One narrowing step: the first unconstrained circle (includes before excludes) that changes the shape into an allowed one.
  function narrow(inside) {
    var cur = regionsOf(sel.inc, sel.exc).join(",");
    var order = "BVC".split("").filter(function (k) { return inside.indexOf(k) >= 0; })
      .concat("BVC".split("").filter(function (k) { return inside.indexOf(k) < 0; }));
    for (var i = 0; i < order.length; i++) {
      var k = order[i];
      if ((sel.inc + sel.exc).indexOf(k) >= 0) continue;
      var next = inside.indexOf(k) >= 0 ? { inc: sel.inc + k, exc: sel.exc } : { inc: sel.inc, exc: sel.exc + k };
      var shape = regionsOf(next.inc, next.exc).join(",");
      if (shape !== cur && allowed.indexOf(shape) >= 0) return next;
    }
    // No single step allowed: jump to the exact region under the cursor if that one is.
    var rest = "BVC".split("").filter(function (k) { return inside.indexOf(k) < 0; }).join("");
    var exact = regionsOf(inside, rest).join(",");
    if (exact !== cur && allowed.indexOf(exact) >= 0) return { inc: inside, exc: rest };
    return null;   // nowhere allowed to go: clear
  }
  svg.addEventListener("click", function (e) {
    var pt = new DOMPoint(e.clientX, e.clientY).matrixTransform(document.getElementById("scene").getScreenCTM().inverse());
    var at = locate(pt.x, pt.y);
    if (!at.owner) sel = null;
    else if (sel && regionsOf(sel.inc, sel.exc).indexOf(at.atom) >= 0) sel = narrow(at.inside);
    else sel = { inc: at.owner, exc: "" };
    draw();
  });
})();
]]></script>
</svg>
</p>
<h2>Fin</h2>
<p>Do you know more such terms that have caused similar class of confusion? My
e-mail inbox is open.</p>
<p>Also: how do <em>you</em> define a vegetable?</p>
<hr />
<ol start="0">
<li>
<p><a href="https://www.wittgensteinproject.org/w/index.php/Philosophische_Untersuchungen">Philosophische Untersuchungen</a></p>
</li>
<li>
<p>When you talk about fruits and vegetables in the same sentence, I am
automatically going to assume you mean &quot;culinary fruit&quot; since the entire
category of a &quot;botanical vegetable&quot; simply does not exist.</p>
</li>
</ol>
</content>
  </entry>

  <entry>
    <title>Why I refuse to use Linkedin</title>
    <link href="https://blog.qnt.moe/p/linkedin/"/>
    <id>https://blog.qnt.moe/p/linkedin/</id>
    <published>2026-04-02T23:31:32+05:30</published>
    <updated>2026-04-02T23:31:32+05:30</updated>
    <content type="html"><p>People in the tech industry assume everyone has a LinkedIn account with an
up-to-date profile. I do have an account, but I refuse to update or use it; for various
reasons listed here.</p>
<h2>LinkedIn is invasive</h2>
<p>Read:</p>
<ul>
<li><a href="https://browsergate.eu/">LinkedIn illegally scans your browser for things it shouldn't know.</a></li>
<li><a href="https://wccftech.com/linkedin-sued-for-spying-on-clipboard-data-after-ios-14-exposes-its-app/">LinkedIn was sued for spying on user clipboard data.</a></li>
</ul>
<h2>Employment histroy is private information</h2>
<p>I don't want my employment histroy to be public. Simple as that.</p>
<p>I do not understand why employers and everyone in corpo expect everyone to
put their employment histroy and education background out open in public, for
everyone to see.</p>
<p>If you want to know where I have worked for the entirity of my career, you
should have to request that information from me; in form of my resume for example. I do not
believe this information should be publicly available for viewing without my
permission/consent.</p>
<h2>The content I don't wish to consume</h2>
<p>Most, if not all LinkedIn posts on my LinkedIn feed are one of either:</p>
<ul>
<li>AI generated posts which say a lot yet don't say anything.</li>
<li>Stupid illustrations that claim to explain a very basic concept but the
analogies are bad and misrepresent the thing they are explaining.</li>
<li>The &quot;Insperational&quot; posts.</li>
<li>Marketing.</li>
</ul>
<p>I do not wish to see any of these.</p>
<h2>To you, the reader</h2>
<p>If you are my employer, asking me to update my LinkedIn profile with your
organisation's logo or post something on behalf of you:
I refuse. And I will quit my job over it if this is a deal-breaker for you.</p>
<p>If you are a co-worker/peer/friend: now you know why.</p>
</content>
  </entry>

  <entry>
    <title>SandDB: An immutable, persistent key-value store</title>
    <link href="https://blog.qnt.moe/p/sand-db/"/>
    <id>https://blog.qnt.moe/p/sand-db/</id>
    <published>2025-08-20T00:00:00+00:00</published>
    <updated>2025-08-20T00:00:00+00:00</updated>
    <summary>Cooking a key-value database in Rust.</summary>
    <content type="html"><h2>Motivation</h2>
<p>I had just began to cook my <em>super secret</em> project for which I needed an
embedded data store to back my data.</p>
<p>Now, I <em>could</em>, use a well known and well supported solution. But hey, that's
not very fun, is it?</p>
<p>I decided I will write my own. Not because this was an &quot;unsolved&quot; problem, but
just because I thought &quot;how hard could it be to roll by own key-value
database?&quot;.</p>
<p>I had recently read up some literature on building simple databases from various
sources and really wanted to give it a try. The whole process has been very
rewarding. I might even give building a relational database a shot in future.</p>
<h2>Goals</h2>
<p>My goals for the store (from the orignal use-case) are:</p>
<ul>
<li>Very fast writes single and batch writes.</li>
<li>Reasonably fast point reads.</li>
<li>Reasonably fast range scans.</li>
</ul>
<h2>Non goals</h2>
<ul>
<li>Deletion: I wanted an append-only, immutable store. I did not need a delete
operation.</li>
<li>Foreign keys: I only wanted to read and write a bunch of values quickly. I did
not need a way to reference one datum from another. Well, at least not in a
way the store should be aware of.</li>
</ul>
<h2>Inspiration</h2>
<h3>Git's reftable</h3>
<p>Git has traditionally used files under <code>.git/refs</code> to store refs (&quot;loose refs&quot;).
This is simple and worked, however some large repositories such as android and
rails had way too many refs and lookup was noticeably slow.</p>
<p>Other problems with loose refs include:</p>
<ul>
<li>They occupy a large number of disk blocks</li>
<li>Batch reads are penalized by large number of syscalls.</li>
</ul>
<p>It should be noted that git also had the &quot;packed ref&quot; format as an alternative
but it had its own problems:</p>
<ul>
<li>Single lookup requires linearly scanning the file.</li>
<li>Atomic writes required copying the entire packed ref file for even small
transactions.</li>
</ul>
<p>This lead to the development of the new reftable format which employs an
LSM-tree-like storage engine. The performance gains are massive in terms of both
query time and storage.</p>
<p>For more information, see <a href="https://git-scm.com/docs/reftable">reftable page</a>
in git's documentation.</p>
<p>This facinated me and I dived a little bit into the design and implemenation
details of this new format.</p>
<h3>RocksDB</h3>
<p>While looking around the ideas and techniques behind the reftable format, I
found a few more key-value databases. The one that perticularly stood out to be
was RocksDB. Reading about it, I discovered the concept of Log Structured Merge
trees (LSM trees).</p>
<p>I also named this project SandDB because I was inspired from RocksDB.</p>
<h2>Log-structured-merge-trees</h2>
<p>The core and heart of SandDB is LSM tree. A data structure designed for very
fast writes.</p>
<p>LSM trees have a tiered storage model where each tier/level is more &quot;permanent&quot;.</p>
<h3>Memtable</h3>
<p>The first level is (usually) backed by an in-memory data structure. This is
called a &quot;memtable&quot;. SandDB uses Rust's <code>BTreeMap</code> (for no other reason than
it is readily available for use in the language).</p>
<p>All the new writes are added to the memtable and no files are touched.</p>
<p>If you can't tell, this is <em>very</em> fast since we inherit <code>BTreeMap</code>'s insertion
time complexity i.e. O(log n) where n is the height of B+Tree.</p>
<h3>SSTables</h3>
<p>Of course, entries in the memtable will eventually have to be written to files
in order for the store to be persistent.</p>
<p>Once the memtable reaches a certain threshold, it is flushed to an SSTable file.
This is an immutable file i.o.w, once created it is never modified.</p>
<p>We must also flush the memtable to an SSTable when the database exits so that we
don't lose any entries.</p>
<p>SSTables are binary files. I have documented it's format <a href="https://github.com/quanta-kt/SandDB/blob/master/docs/sst-file-spec.md">here</a>
if you are interested.</p>
<h3>Compaction</h3>
<p>Now since we create a new SSTable on each flush, we will inevitably end up with
several such files over time. This can be a problem for querying as we might end
up having to scan a lot of different files. Opening files also add to our
number of syscalls per query.</p>
<p>We must therefore occasionally <em>compact</em> our SSTables, i.e. merge smaller
SSTables into a larger ones.</p>
<p>SSTables are tiered. When a memtable is flushed, a level 0 SSTable is created.
When we have &quot;way too many&quot; SSTables, we initiate a compaction: gather all level
0 SSTables and merge them into a single level 1 SSTable.</p>
<p>When we have enough level 1 SSTables, we merge them to level 2. At the time of
writing, SandDB only merges upto level 2. We also merge several level 2 SSTables
into one if there are too many of them.</p>
<p>We only pay the cost of compaction once in a while.</p>
<h3>Manifest</h3>
<p>An additional file called manifest is kept around to track the SSTables and
their metadata. It includes:</p>
<ul>
<li>ID of the manifest file, using which we can derive the filename.</li>
<li>It's level.</li>
<li>The key range.</li>
</ul>
<p>Each time an SSTable is added or removed, the manifest is updated to reflect
the same.</p>
<p>When querying, the reader looks at the manifest and figures out which SSTables
might contain the key it is looking for and reads those in the order of their
(ID, level). The ordering matters because we want to ensure we look in the most
recently written SSTables first.</p>
<p>We only open the SSTables which the manifest tells us might contain our key.
This way we don't have to open every single SSTable and we save reads and
syscalls.</p>
<p>We can also go beyond having a key range and add per-SSTable
<a href="https://en.wikipedia.org/wiki/Bloom_filter">bloom filters</a>
in the manifest. This would further reduce the number of files we would have to
open and read. I plan to implement this sometime later in future.</p>
<p>The manifest file is a WAL-style file. When a new SSTable is added, an entry
saying &quot;added sst x&quot; is appended to the file. Similarly, when an SSTable is
deleted, an entry saying &quot;deleted sst x&quot; is inserted.</p>
<p>Why do this instead of rewriting the manifest? For atomicity (and possibly for
isolation, in future).</p>
<p>If we replace the entire contents of manifest instead of appending, a reader in
the middle of reading manifest might end up reading first half of the file
before the sst was added and the later half after.</p>
<p>I will admit that SandDB does not yet allow concurrent read/write, however I do
see myself sitting down to implement it in future. Soon (TM).</p>
<p>I have documented manifest file's format here
<a href="https://github.com/quanta-kt/SandDB/blob/master/docs/manifest-file-spec.md">here</a>
if you are interested.</p>
</content>
  </entry>

</feed>