<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Pgm on MongrelDB</title><link>https://www.mongreldb.com/articles/tags/pgm/</link><description>Recent content in Pgm on MongrelDB</description><image><title>MongrelDB</title><url>https://www.mongreldb.com/assets/og-mongreldb.png</url><link>https://www.mongreldb.com/assets/og-mongreldb.png</link></image><generator>Hugo</generator><language>en-US</language><lastBuildDate>Sun, 02 Aug 2026 12:00:00 -0500</lastBuildDate><atom:link href="https://www.mongreldb.com/articles/tags/pgm/index.xml" rel="self" type="application/rss+xml"/><item><title>Why MongrelDB Uses a PGM Learned-Range Index</title><link>https://www.mongreldb.com/articles/2026/07/why-we-shipped-learned-range-indexes-before-we-shipped-b-trees/</link><pubDate>Sat, 11 Jul 2026 11:22:00 -0500</pubDate><guid>https://www.mongreldb.com/articles/2026/07/why-we-shipped-learned-range-indexes-before-we-shipped-b-trees/</guid><description>MongrelDB&amp;#39;s PGM learned-range index uses an epsilon-bounded piecewise linear model to predict sorted-key positions, then verifies the bounded range exactly.</description><content:encoded><![CDATA[<p>A learned index sounds more mysterious than it is, partly because the early papers used the language of models and training while the practical implementation often looks like a compact set of line segments over sorted keys; MongrelDB&rsquo;s range index is the practical version, a shrinking-cone, epsilon-bounded piecewise linear model that predicts where an ordered value belongs, followed by bounded correction and exact verification against the stored data.</p>
<p>There is no neural network hiding in the storage directory. There is no recency-weighted loss function learning from customer traffic. The public index kind is <code>LearnedRange</code>, the implementation is a PGM-style model, and the reason to use it is that sorted numeric and time keys can often be described with much less metadata than a general-purpose tree.</p>
<h2 id="the-problem-is-locating-a-range">The problem is locating a range</h2>
<p>A query such as <code>created_at BETWEEN a AND b</code> needs two positions: where qualifying keys begin and where they end. If the column is sorted in a run, a model can approximate the mapping from key to position, then search a bounded window around each prediction and verify the actual values.</p>
<p>The epsilon parameter controls that bound. Smaller epsilon means more line segments and a tighter correction window; larger epsilon means a smaller model and more local scanning. That is the useful trade, because it is explicit and because correctness does not depend on the prediction being perfect.</p>
<p>An index prediction is only a starting position. The engine checks the candidate range against the real values, so model error changes work, not query truth.</p>
<h2 id="how-the-shrinking-cone-model-is-built">How the shrinking-cone model is built</h2>
<p>Imagine walking sorted <code>(key, position)</code> points from left to right. A line segment is valid while one slope can keep every observed point within the configured positional error. Each new point narrows the set of slopes that still satisfy that bound, like a cone shrinking around the valid lines; when the cone becomes empty, the current segment closes and a new one begins.</p>
<p>The stored result is a sequence of segment anchors and slopes. Lookup chooses the segment covering the key, predicts a position, then searches within the bounded correction interval. Monotonic, smooth distributions need few segments. Irregular distributions need more. The index adapts its size to the key distribution without changing query correctness.</p>
<h2 id="why-this-belongs-beside-page-pruning">Why this belongs beside page pruning</h2>
<p>MongrelDB sorted runs already store per-page minimum and maximum statistics. A range predicate can skip pages whose bounds do not overlap the query, and encrypted columns keep those statistics inside an authenticated envelope so the same pruning remains available after open.</p>
<p>The PGM index narrows where the relevant key region should begin; page statistics eliminate unrelated pages; MVCC visibility and exact predicate checks remove versions that do not belong in the snapshot. None of those layers has to carry the whole optimization alone.</p>
<p>A native condition looks like this:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-rust" data-lang="rust"><span style="display:flex;"><span>Condition::RangeF64 {
</span></span><span style="display:flex;"><span>    column_id: <span style="color:#ae81ff">3</span>,
</span></span><span style="display:flex;"><span>    lo: <span style="color:#ae81ff">50.0</span>,
</span></span><span style="display:flex;"><span>    lo_inclusive: <span style="color:#a6e22e">true</span>,
</span></span><span style="display:flex;"><span>    hi: <span style="color:#ae81ff">200.0</span>,
</span></span><span style="display:flex;"><span>    hi_inclusive: <span style="color:#a6e22e">false</span>,
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>SQL comparisons and <code>BETWEEN</code> can push down to the same range path when the planner recognizes the predicate.</p>
<h2 id="the-measurement-we-can-defend">The measurement we can defend</h2>
<p>On the current published one-million-row benchmark fixture, integer range lookup measured 8.8231 milliseconds and a Bitmap plus range intersection measured 14.224 milliseconds on the documented Intel Core Ultra 9 386H host. Those are whole query-path measurements for one data distribution and one machine, not proof that a learned index beats every B-tree by a fixed multiplier.</p>
<p>The repository does not publish a current cross-engine PGM-versus-B-tree claim, so neither should this article. A uniform integer fixture, a timestamp stream, a clustered price distribution, and a pathological sparse keyspace produce different model sizes and correction work; the only useful comparison holds data, projection, cache state, durability, and hardware constant.</p>
<h2 id="where-pgm-is-the-wrong-index">Where PGM is the wrong index</h2>
<p>Equality on a low-cardinality status belongs in a Roaring Bitmap, not a range model. Exact substring containment belongs in the FM-index. Primary-key lookup uses the implicit primary-key surface, whose current in-memory implementation is an ordered-map stand-in rather than a completed HOT trie. Dense similarity belongs in ANN.</p>
<p>A range model also becomes less attractive when keys have no useful order, when the distribution creates too many short segments, or when a mature tree implementation and its ecosystem matter more than saving model space. MongrelDB does not expose a general secondary B-tree today, and pretending otherwise would make the schema promise a path the engine does not have.</p>
<h2 id="why-the-shared-rowid-matters">Why the shared RowId matters</h2>
<p>The range result is not an isolated row stream. It is a RowId candidate set that can intersect with a tenant Bitmap, an FM substring, or another hard condition before row decoding. That is where a specialized range index earns its place in a mixed engine: it does one job, returns the same identity type as every other index, and lets the query combine strengths instead of forcing every predicate through one universal tree.</p>
<p>Learned indexing is useful here because the model has a bounded, verifiable job. It predicts position in ordered data, the engine corrects and checks the answer, and if a workload does not benefit, the right response is to benchmark a different access path rather than write another paragraph about artificial intelligence.</p>
<p>The implementation and options are documented in <a href="https://github.com/visorcraft/MongrelDB/blob/master/docs/06-indexes.md"><code>docs/06-indexes.md</code></a>, while the current range measurements and commands live in <a href="https://github.com/visorcraft/MongrelDB/blob/master/BENCHMARKS.md"><code>BENCHMARKS.md</code></a>.</p>
]]></content:encoded></item></channel></rss>