<?xml version="1.0" encoding="utf-8"?><?xml-stylesheet type="text/xsl" href="rss.xsl"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/">
    <channel>
        <title>Duckstring Blog</title>
        <link>https://duckstring.com/blog</link>
        <description>Duckstring Blog</description>
        <lastBuildDate>Thu, 08 Oct 2026 00:00:00 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <language>en</language>
        <copyright>Copyright © 2026 Duckstring.</copyright>
        <item>
            <title><![CDATA[Rank-Based Clustering for Improved Pipeline Performance]]></title>
            <link>https://duckstring.com/blog/clustering-for-pipelines</link>
            <guid>https://duckstring.com/blog/clustering-for-pipelines</guid>
            <pubDate>Thu, 08 Oct 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[How to order Parquet files for efficient pipelines using multiple columns for downstream queries.]]></description>
            <content:encoded><![CDATA[<p>The DuckDB team wrote a great post on why you might worry about the write order in tables prepared for analytical purposes:
<a href="https://duckdb.org/2025/06/06/advanced-sorting-for-fast-selective-queries" target="_blank" rel="noopener noreferrer" class="">Faster Dashboards with Multi-Column Approximate Sorting</a>.
It's a great introduction to space-filling curves and why they're
relevant for optimizing queries that could target multiple columns - well worth a read. The gist is that
the multiple columns used for some downstream purpose (e.g. querying or joins) form a multi-dimensional space, but
the row order - imperative for skipping row groups - is inherently one-dimensional. The task for optimizing for
multi-column queries becomes one of effectively reducing this multi-dimensional space to a one-dimensional order.</p>
<p>Getting this right is especially important for multi-step data transformation pipelines, where the same table might
be used for multiple purposes downstream.</p>
<p>The key insight of this article is that space-filling the <em>potential</em> values in the dataset is sub-optimal for an uneven
distribution of values. By first <em>ranking</em> the values from each column, and interleaving those <em>ranks</em>, much better performance can be achieved.
This is especially valuable where it's fine for a row's key to change as the table does, such as when the whole table is rewritten.
Here I present a variety of alternative methods for comparison on the TPC-DS dataset.</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="why-order-matters-for-query-performance">Why order matters for query performance<a href="https://duckstring.com/blog/clustering-for-pipelines#why-order-matters-for-query-performance" class="hash-link" aria-label="Direct link to Why order matters for query performance" title="Direct link to Why order matters for query performance" translate="no">​</a></h2>
<p>In a Parquet file, rows are grouped into chunks called row groups (DuckDB writes 122,880 rows
per group by default), and within each group every column is stored and compressed separately. Alongside the
data, the file's footer records statistics for each column in each row group, including its smallest and
largest value. If you ask for <code>WHERE price BETWEEN 400 AND 430</code>, DuckDB reads the
footer first, and any row group whose <code>price</code> range is, say, 2 to 380 can't possibly contain a match. DuckDB
skips it without decompressing a byte. These per-group ranges are often called zone maps.</p>
<p>If rows are ordered randomly (the worst case), every row group contains a similar distribution of values. Every
footer's min/max values are nearly identical, spanning the full set of values, so no query can avoid reading
everything.</p>
<p>If only one column is required downstream, then ordering by that column does a fantastic job (theoretical best) at splitting the
row groups - for a unique column, any target value could only exist in exactly one row group. For multiple columns though,
there is a trade off - ordering first by the first 'most important' column might mean the second column is barely
ordered at all (depending on the first's cardinality). Typical lexicographic ordering like this can help very little if
the columns are not selected carefully.</p>
<p><strong>Row group skipping under multiple orders</strong></p>
<p><img decoding="async" loading="lazy" alt="Four small tables, each 32 by 32 cells, showing the min/max box of every row group under random order, a sort, Z-order and Hilbert order, with a horizontal band for a filter on the second column" src="https://duckstring.com/assets/images/row-groups-31b7510e469bcdb72d2a606aca27607e.svg" width="738" height="222" class="img_ev3q"></p>
<p><em>Shaded band is the filter, blue boxes are candidate row groups, unshaded boxes are skipped row groups.</em></p>
<p>The figure above shows the problem on a toy table: every cell of a 32 by 32 grid, written in four different orders
and split into row groups of 48 rows. Each box is one row group's min/max range, and the shaded band is a filter on
the second column. In random order every box spans the whole grid, so all 22 row groups are read. Sorting by the
first column and then the second gives tall, thin stripes - ideal for a filter on the first column, but a filter on
the second still reads 21 of the 22.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="space-filling-curves-for-dimension-reduction">Space-filling curves for dimension reduction<a href="https://duckstring.com/blog/clustering-for-pipelines#space-filling-curves-for-dimension-reduction" class="hash-link" aria-label="Direct link to Space-filling curves for dimension reduction" title="Direct link to Space-filling curves for dimension reduction" translate="no">​</a></h2>
<p>Space-filling curves are a mathematical construction that threads a one-dimensional path (curve) through a higher-dimensional
space. They are self-similar at various scales, and as their fidelity increases, they enable these higher-dimensional spaces
to be mapped entirely by the path.
This is very useful in multiple fields, due to these mappings approximately preserving proximity in the higher-dimensional
space when condensed down to the single dimension path.</p>
<p>The Hilbert curve is a particularly beautiful variant, where each step is adjacent to the previous - a property that
also makes it more effective for the purpose of dimension reduction. The Morton 'Z-order' however is used
more commonly, as its construction is very straightforward: the first bit of the first column is taken, then the first of the
second, and so on, then the second bit of the first column, until some bit depth is reached or the data is exhausted - a process
called 'bit-interleaving'. This has the neat effect of drawing a series of 'Z' shapes, themselves ordered into 'Z' shapes, snaking
their way through the multi-dimensional space.</p>
<p><strong>Curve visualization</strong></p>
<p><img decoding="async" loading="lazy" alt="Z-order and Hilbert curves drawn over an 8 by 8 grid" src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMjQgNjAwIDMwNiIgd2lkdGg9IjYwMCIgaGVpZ2h0PSIzMDYiIHJvbGU9ImltZyIgYXJpYS1sYWJlbD0iWi1vcmRlciBhbmQgSGlsYmVydCBjdXJ2ZXMgb24gYW4gOCBieSA4IGdyaWQiIGZvbnQtZmFtaWx5PSJJbnRlciwgc3lzdGVtLXVpLCAtYXBwbGUtc3lzdGVtLCAnU2Vnb2UgVUknLCBzYW5zLXNlcmlmIj4KPHRpdGxlPlotb3JkZXIgYW5kIEhpbGJlcnQgY3VydmVzIG9uIGFuIDggYnkgOCBncmlkPC90aXRsZT4KPHJlY3QgeT0iMjQiIHdpZHRoPSI2MDAiIGhlaWdodD0iMzA2IiByeD0iOCIgZmlsbD0iIzFhMWExOSIvPgo8dGV4dCB4PSIxNjAuMCIgeT0iNDYuMCIgZm9udC1zaXplPSIxMyIgZmlsbD0iI2ZmZmZmZiIgdGV4dC1hbmNob3I9Im1pZGRsZSIgZm9udC13ZWlnaHQ9IjYwMCI+Wi1vcmRlciAoTW9ydG9uKTwvdGV4dD4KPGxpbmUgeDE9IjQwIiB5MT0iNTIiIHgyPSIyODAiIHkyPSI1MiIgc3Ryb2tlPSIjMzgzODM1Ii8+CjxsaW5lIHgxPSI0MCIgeTE9IjUyIiB4Mj0iNDAiIHkyPSIyOTIiIHN0cm9rZT0iIzM4MzgzNSIvPgo8bGluZSB4MT0iNDAiIHkxPSI4MiIgeDI9IjI4MCIgeTI9IjgyIiBzdHJva2U9IiMzODM4MzUiLz4KPGxpbmUgeDE9IjcwIiB5MT0iNTIiIHgyPSI3MCIgeTI9IjI5MiIgc3Ryb2tlPSIjMzgzODM1Ii8+CjxsaW5lIHgxPSI0MCIgeTE9IjExMiIgeDI9IjI4MCIgeTI9IjExMiIgc3Ryb2tlPSIjMzgzODM1Ii8+CjxsaW5lIHgxPSIxMDAiIHkxPSI1MiIgeDI9IjEwMCIgeTI9IjI5MiIgc3Ryb2tlPSIjMzgzODM1Ii8+CjxsaW5lIHgxPSI0MCIgeTE9IjE0MiIgeDI9IjI4MCIgeTI9IjE0MiIgc3Ryb2tlPSIjMzgzODM1Ii8+CjxsaW5lIHgxPSIxMzAiIHkxPSI1MiIgeDI9IjEzMCIgeTI9IjI5MiIgc3Ryb2tlPSIjMzgzODM1Ii8+CjxsaW5lIHgxPSI0MCIgeTE9IjE3MiIgeDI9IjI4MCIgeTI9IjE3MiIgc3Ryb2tlPSIjMzgzODM1Ii8+CjxsaW5lIHgxPSIxNjAiIHkxPSI1MiIgeDI9IjE2MCIgeTI9IjI5MiIgc3Ryb2tlPSIjMzgzODM1Ii8+CjxsaW5lIHgxPSI0MCIgeTE9IjIwMiIgeDI9IjI4MCIgeTI9IjIwMiIgc3Ryb2tlPSIjMzgzODM1Ii8+CjxsaW5lIHgxPSIxOTAiIHkxPSI1MiIgeDI9IjE5MCIgeTI9IjI5MiIgc3Ryb2tlPSIjMzgzODM1Ii8+CjxsaW5lIHgxPSI0MCIgeTE9IjIzMiIgeDI9IjI4MCIgeTI9IjIzMiIgc3Ryb2tlPSIjMzgzODM1Ii8+CjxsaW5lIHgxPSIyMjAiIHkxPSI1MiIgeDI9IjIyMCIgeTI9IjI5MiIgc3Ryb2tlPSIjMzgzODM1Ii8+CjxsaW5lIHgxPSI0MCIgeTE9IjI2MiIgeDI9IjI4MCIgeTI9IjI2MiIgc3Ryb2tlPSIjMzgzODM1Ii8+CjxsaW5lIHgxPSIyNTAiIHkxPSI1MiIgeDI9IjI1MCIgeTI9IjI5MiIgc3Ryb2tlPSIjMzgzODM1Ii8+CjxsaW5lIHgxPSI0MCIgeTE9IjI5MiIgeDI9IjI4MCIgeTI9IjI5MiIgc3Ryb2tlPSIjMzgzODM1Ii8+CjxsaW5lIHgxPSIyODAiIHkxPSI1MiIgeDI9IjI4MCIgeTI9IjI5MiIgc3Ryb2tlPSIjMzgzODM1Ii8+Cjxwb2x5bGluZSBwb2ludHM9IjU1LjAsMjc3LjAgNTUuMCwyNDcuMCA4NS4wLDI3Ny4wIDg1LjAsMjQ3LjAgNTUuMCwyMTcuMCA1NS4wLDE4Ny4wIDg1LjAsMjE3LjAgODUuMCwxODcuMCAxMTUuMCwyNzcuMCAxMTUuMCwyNDcuMCAxNDUuMCwyNzcuMCAxNDUuMCwyNDcuMCAxMTUuMCwyMTcuMCAxMTUuMCwxODcuMCAxNDUuMCwyMTcuMCAxNDUuMCwxODcuMCA1NS4wLDE1Ny4wIDU1LjAsMTI3LjAgODUuMCwxNTcuMCA4NS4wLDEyNy4wIDU1LjAsOTcuMCA1NS4wLDY3LjAgODUuMCw5Ny4wIDg1LjAsNjcuMCAxMTUuMCwxNTcuMCAxMTUuMCwxMjcuMCAxNDUuMCwxNTcuMCAxNDUuMCwxMjcuMCAxMTUuMCw5Ny4wIDExNS4wLDY3LjAgMTQ1LjAsOTcuMCAxNDUuMCw2Ny4wIDE3NS4wLDI3Ny4wIDE3NS4wLDI0Ny4wIDIwNS4wLDI3Ny4wIDIwNS4wLDI0Ny4wIDE3NS4wLDIxNy4wIDE3NS4wLDE4Ny4wIDIwNS4wLDIxNy4wIDIwNS4wLDE4Ny4wIDIzNS4wLDI3Ny4wIDIzNS4wLDI0Ny4wIDI2NS4wLDI3Ny4wIDI2NS4wLDI0Ny4wIDIzNS4wLDIxNy4wIDIzNS4wLDE4Ny4wIDI2NS4wLDIxNy4wIDI2NS4wLDE4Ny4wIDE3NS4wLDE1Ny4wIDE3NS4wLDEyNy4wIDIwNS4wLDE1Ny4wIDIwNS4wLDEyNy4wIDE3NS4wLDk3LjAgMTc1LjAsNjcuMCAyMDUuMCw5Ny4wIDIwNS4wLDY3LjAgMjM1LjAsMTU3LjAgMjM1LjAsMTI3LjAgMjY1LjAsMTU3LjAgMjY1LjAsMTI3LjAgMjM1LjAsOTcuMCAyMzUuMCw2Ny4wIDI2NS4wLDk3LjAgMjY1LjAsNjcuMCIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjZDk1OTI2IiBzdHJva2Utd2lkdGg9IjIiIHN0cm9rZS1saW5lam9pbj0icm91bmQiLz4KPGNpcmNsZSBjeD0iNTUuMCIgY3k9IjI3Ny4wIiByPSI0IiBmaWxsPSIjZDk1OTI2IiBzdHJva2U9IiMxYTFhMTkiIHN0cm9rZS13aWR0aD0iMiIvPgo8dGV4dCB4PSIxNjAuMCIgeT0iMzE0LjAiIGZvbnQtc2l6ZT0iMTEiIGZpbGw9IiM4YTg5N2YiIHRleHQtYW5jaG9yPSJtaWRkbGUiIGZvbnQtd2VpZ2h0PSI0MDAiPmZpcnN0IGNvbHVtbiDihpI8L3RleHQ+Cjx0ZXh0IHg9IjMwIiB5PSIxNzIuMCIgZm9udC1zaXplPSIxMSIgZmlsbD0iIzhhODk3ZiIgdGV4dC1hbmNob3I9Im1pZGRsZSIgdHJhbnNmb3JtPSJyb3RhdGUoLTkwIDMwIDE3Mi4wKSI+c2Vjb25kIGNvbHVtbiDihpI8L3RleHQ+Cjx0ZXh0IHg9IjQ0MC4wIiB5PSI0Ni4wIiBmb250LXNpemU9IjEzIiBmaWxsPSIjZmZmZmZmIiB0ZXh0LWFuY2hvcj0ibWlkZGxlIiBmb250LXdlaWdodD0iNjAwIj5IaWxiZXJ0PC90ZXh0Pgo8bGluZSB4MT0iMzIwIiB5MT0iNTIiIHgyPSI1NjAiIHkyPSI1MiIgc3Ryb2tlPSIjMzgzODM1Ii8+CjxsaW5lIHgxPSIzMjAiIHkxPSI1MiIgeDI9IjMyMCIgeTI9IjI5MiIgc3Ryb2tlPSIjMzgzODM1Ii8+CjxsaW5lIHgxPSIzMjAiIHkxPSI4MiIgeDI9IjU2MCIgeTI9IjgyIiBzdHJva2U9IiMzODM4MzUiLz4KPGxpbmUgeDE9IjM1MCIgeTE9IjUyIiB4Mj0iMzUwIiB5Mj0iMjkyIiBzdHJva2U9IiMzODM4MzUiLz4KPGxpbmUgeDE9IjMyMCIgeTE9IjExMiIgeDI9IjU2MCIgeTI9IjExMiIgc3Ryb2tlPSIjMzgzODM1Ii8+CjxsaW5lIHgxPSIzODAiIHkxPSI1MiIgeDI9IjM4MCIgeTI9IjI5MiIgc3Ryb2tlPSIjMzgzODM1Ii8+CjxsaW5lIHgxPSIzMjAiIHkxPSIxNDIiIHgyPSI1NjAiIHkyPSIxNDIiIHN0cm9rZT0iIzM4MzgzNSIvPgo8bGluZSB4MT0iNDEwIiB5MT0iNTIiIHgyPSI0MTAiIHkyPSIyOTIiIHN0cm9rZT0iIzM4MzgzNSIvPgo8bGluZSB4MT0iMzIwIiB5MT0iMTcyIiB4Mj0iNTYwIiB5Mj0iMTcyIiBzdHJva2U9IiMzODM4MzUiLz4KPGxpbmUgeDE9IjQ0MCIgeTE9IjUyIiB4Mj0iNDQwIiB5Mj0iMjkyIiBzdHJva2U9IiMzODM4MzUiLz4KPGxpbmUgeDE9IjMyMCIgeTE9IjIwMiIgeDI9IjU2MCIgeTI9IjIwMiIgc3Ryb2tlPSIjMzgzODM1Ii8+CjxsaW5lIHgxPSI0NzAiIHkxPSI1MiIgeDI9IjQ3MCIgeTI9IjI5MiIgc3Ryb2tlPSIjMzgzODM1Ii8+CjxsaW5lIHgxPSIzMjAiIHkxPSIyMzIiIHgyPSI1NjAiIHkyPSIyMzIiIHN0cm9rZT0iIzM4MzgzNSIvPgo8bGluZSB4MT0iNTAwIiB5MT0iNTIiIHgyPSI1MDAiIHkyPSIyOTIiIHN0cm9rZT0iIzM4MzgzNSIvPgo8bGluZSB4MT0iMzIwIiB5MT0iMjYyIiB4Mj0iNTYwIiB5Mj0iMjYyIiBzdHJva2U9IiMzODM4MzUiLz4KPGxpbmUgeDE9IjUzMCIgeTE9IjUyIiB4Mj0iNTMwIiB5Mj0iMjkyIiBzdHJva2U9IiMzODM4MzUiLz4KPGxpbmUgeDE9IjMyMCIgeTE9IjI5MiIgeDI9IjU2MCIgeTI9IjI5MiIgc3Ryb2tlPSIjMzgzODM1Ii8+CjxsaW5lIHgxPSI1NjAiIHkxPSI1MiIgeDI9IjU2MCIgeTI9IjI5MiIgc3Ryb2tlPSIjMzgzODM1Ii8+Cjxwb2x5bGluZSBwb2ludHM9IjMzNS4wLDI3Ny4wIDMzNS4wLDI0Ny4wIDM2NS4wLDI0Ny4wIDM2NS4wLDI3Ny4wIDM5NS4wLDI3Ny4wIDQyNS4wLDI3Ny4wIDQyNS4wLDI0Ny4wIDM5NS4wLDI0Ny4wIDM5NS4wLDIxNy4wIDQyNS4wLDIxNy4wIDQyNS4wLDE4Ny4wIDM5NS4wLDE4Ny4wIDM2NS4wLDE4Ny4wIDM2NS4wLDIxNy4wIDMzNS4wLDIxNy4wIDMzNS4wLDE4Ny4wIDMzNS4wLDE1Ny4wIDM2NS4wLDE1Ny4wIDM2NS4wLDEyNy4wIDMzNS4wLDEyNy4wIDMzNS4wLDk3LjAgMzM1LjAsNjcuMCAzNjUuMCw2Ny4wIDM2NS4wLDk3LjAgMzk1LjAsOTcuMCAzOTUuMCw2Ny4wIDQyNS4wLDY3LjAgNDI1LjAsOTcuMCA0MjUuMCwxMjcuMCAzOTUuMCwxMjcuMCAzOTUuMCwxNTcuMCA0MjUuMCwxNTcuMCA0NTUuMCwxNTcuMCA0ODUuMCwxNTcuMCA0ODUuMCwxMjcuMCA0NTUuMCwxMjcuMCA0NTUuMCw5Ny4wIDQ1NS4wLDY3LjAgNDg1LjAsNjcuMCA0ODUuMCw5Ny4wIDUxNS4wLDk3LjAgNTE1LjAsNjcuMCA1NDUuMCw2Ny4wIDU0NS4wLDk3LjAgNTQ1LjAsMTI3LjAgNTE1LjAsMTI3LjAgNTE1LjAsMTU3LjAgNTQ1LjAsMTU3LjAgNTQ1LjAsMTg3LjAgNTQ1LjAsMjE3LjAgNTE1LjAsMjE3LjAgNTE1LjAsMTg3LjAgNDg1LjAsMTg3LjAgNDU1LjAsMTg3LjAgNDU1LjAsMjE3LjAgNDg1LjAsMjE3LjAgNDg1LjAsMjQ3LjAgNDU1LjAsMjQ3LjAgNDU1LjAsMjc3LjAgNDg1LjAsMjc3LjAgNTE1LjAsMjc3LjAgNTE1LjAsMjQ3LjAgNTQ1LjAsMjQ3LjAgNTQ1LjAsMjc3LjAiIGZpbGw9Im5vbmUiIHN0cm9rZT0iIzM5ODdlNSIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWpvaW49InJvdW5kIi8+CjxjaXJjbGUgY3g9IjMzNS4wIiBjeT0iMjc3LjAiIHI9IjQiIGZpbGw9IiMzOTg3ZTUiIHN0cm9rZT0iIzFhMWExOSIgc3Ryb2tlLXdpZHRoPSIyIi8+Cjx0ZXh0IHg9IjQ0MC4wIiB5PSIzMTQuMCIgZm9udC1zaXplPSIxMSIgZmlsbD0iIzhhODk3ZiIgdGV4dC1hbmNob3I9Im1pZGRsZSIgZm9udC13ZWlnaHQ9IjQwMCI+Zmlyc3QgY29sdW1uIOKGkjwvdGV4dD4KPHRleHQgeD0iMzEwIiB5PSIxNzIuMCIgZm9udC1zaXplPSIxMSIgZmlsbD0iIzhhODk3ZiIgdGV4dC1hbmNob3I9Im1pZGRsZSIgdHJhbnNmb3JtPSJyb3RhdGUoLTkwIDMxMCAxNzIuMCkiPnNlY29uZCBjb2x1bW4g4oaSPC90ZXh0Pgo8L3N2Zz4K" width="600" height="306" class="img_ev3q"></p>
<p><em>Morton and Hilbert curves in two dimensions.</em></p>
<p>Both visit every cell only once, but the Z-order makes long jumps at the edge of each quadrant - row groups that cover either end
of these extremes have very poor separation. The Hilbert curve, comparatively, only ever steps to a neighbouring cell.
The question for which to use comes down to whether the extra complexity of evaluating the Hilbert curve is justified by
performance improvements.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="rank-space-vs-value-space">Rank-space vs value-space<a href="https://duckstring.com/blog/clustering-for-pipelines#rank-space-vs-value-space" class="hash-link" aria-label="Direct link to Rank-space vs value-space" title="Direct link to Rank-space vs value-space" translate="no">​</a></h2>
<p>Consider two columns of 4-bit values. Each value may be one of 16 possibilities, and bit-interleaving the pair gives
<span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msup><mn>2</mn><mrow><mn>4</mn><mo>+</mo><mn>4</mn></mrow></msup><mo>=</mo><mn>256</mn></mrow><annotation encoding="application/x-tex">2^{4+4} = 256</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8141em"></span><span class="mord"><span class="mord">2</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.8141em"><span style="top:-3.063em;margin-right:0.05em"><span class="pstrut" style="height:2.7em"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">4</span><span class="mbin mtight">+</span><span class="mord mtight">4</span></span></span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2778em"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em"></span></span><span class="base"><span class="strut" style="height:0.6444em"></span><span class="mord">256</span></span></span></span> possible ordering values. This might be a perfectly good structure, if the true values in the data
<strong>span the entire range of possibilities</strong>. In practice, values are rarely spread evenly
across this potential <em>value-space</em>, and are instead clustered around some set of common values. If, say, only the
first and last quarters of the possible values are used in each column (0 to 3, 12 to 15), the order curve
will assume values 3 and 12 are distant, despite being adjacent in the true data.</p>
<p>Consider instead taking any two columns, ordering each, and breaking each into 16 equal-sized chunks, where each
chunk is assigned a 4-bit value. This creates a <em>rank-space</em>, which necessarily better approximates the distribution
of real data. The <em>labels</em> for these groups may then be interleaved to create the order key.</p>
<p>This is particularly valuable where there is real skew to the data. Here's <code>ss_net_paid</code> from TPC-DS, the amount paid
per sale line, split into 16 buckets both ways:</p>
<p><strong>Rows per bucket for <code>ss_net_paid</code>, 16 buckets</strong></p>
<p><img decoding="async" loading="lazy" alt="Bar charts of rows per bucket for ss_net_paid: scaled buckets put 58% of rows in the first bucket; ranked buckets hold 6.25% each" src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMzIgNzIwIDIxNCIgd2lkdGg9IjcyMCIgaGVpZ2h0PSIyMTQiIHJvbGU9ImltZyIgYXJpYS1sYWJlbD0iQnVja2V0IHBvcHVsYXRpb25zIGZvciBhIHNrZXdlZCBjb2x1bW46IHNjYWxlZCBhZ2FpbnN0IHJhbmtlZCIgZm9udC1mYW1pbHk9IkludGVyLCBzeXN0ZW0tdWksIC1hcHBsZS1zeXN0ZW0sICdTZWdvZSBVSScsIHNhbnMtc2VyaWYiPgo8dGl0bGU+QnVja2V0IHBvcHVsYXRpb25zIGZvciBhIHNrZXdlZCBjb2x1bW46IHNjYWxlZCBhZ2FpbnN0IHJhbmtlZDwvdGl0bGU+CjxyZWN0IHk9IjMyIiB3aWR0aD0iNzIwIiBoZWlnaHQ9IjIxNCIgcng9IjgiIGZpbGw9IiMxYTFhMTkiLz4KPHRleHQgeD0iNTAuMCIgeT0iNTIuMCIgZm9udC1zaXplPSIxMyIgZmlsbD0iI2ZmZmZmZiIgdGV4dC1hbmNob3I9InN0YXJ0IiBmb250LXdlaWdodD0iNjAwIj5TY2FsZWQgYmV0d2VlbiBtaW4gYW5kIG1heDwvdGV4dD4KPGxpbmUgeDE9IjUwLjAiIHkxPSIyMTAuMCIgeDI9IjMzNS4wIiB5Mj0iMjEwLjAiIHN0cm9rZT0iIzM4MzgzNSIvPgo8dGV4dCB4PSI0Mi4wIiB5PSIyMTQuMCIgZm9udC1zaXplPSIxMSIgZmlsbD0iIzhhODk3ZiIgdGV4dC1hbmNob3I9ImVuZCIgZm9udC13ZWlnaHQ9IjQwMCI+MCU8L3RleHQ+CjxsaW5lIHgxPSI1MC4wIiB5MT0iMTYwLjAiIHgyPSIzMzUuMCIgeTI9IjE2MC4wIiBzdHJva2U9IiMzODM4MzUiLz4KPHRleHQgeD0iNDIuMCIgeT0iMTY0LjAiIGZvbnQtc2l6ZT0iMTEiIGZpbGw9IiM4YTg5N2YiIHRleHQtYW5jaG9yPSJlbmQiIGZvbnQtd2VpZ2h0PSI0MDAiPjIwJTwvdGV4dD4KPGxpbmUgeDE9IjUwLjAiIHkxPSIxMTAuMCIgeDI9IjMzNS4wIiB5Mj0iMTEwLjAiIHN0cm9rZT0iIzM4MzgzNSIvPgo8dGV4dCB4PSI0Mi4wIiB5PSIxMTQuMCIgZm9udC1zaXplPSIxMSIgZmlsbD0iIzhhODk3ZiIgdGV4dC1hbmNob3I9ImVuZCIgZm9udC13ZWlnaHQ9IjQwMCI+NDAlPC90ZXh0Pgo8bGluZSB4MT0iNTAuMCIgeTE9IjYwLjAiIHgyPSIzMzUuMCIgeTI9IjYwLjAiIHN0cm9rZT0iIzM4MzgzNSIvPgo8dGV4dCB4PSI0Mi4wIiB5PSI2NC4wIiBmb250LXNpemU9IjExIiBmaWxsPSIjOGE4OTdmIiB0ZXh0LWFuY2hvcj0iZW5kIiBmb250LXdlaWdodD0iNDAwIj42MCU8L3RleHQ+CjxwYXRoIGQ9Ik01MS4wLDIxMC4wIHYtMTQxLjkgYTQsNCAwIDAgMSA0LC00IGg3LjggYTQsNCAwIDAgMSA0LDQgdjE0MS45IHoiIGZpbGw9IiNkOTU5MjYiLz4KPHBhdGggZD0iTTY4LjgsMjEwLjAgdi0zOS42IGE0LDQgMCAwIDEgNCwtNCBoNy44IGE0LDQgMCAwIDEgNCw0IHYzOS42IHoiIGZpbGw9IiNkOTU5MjYiLz4KPHBhdGggZD0iTTg2LjYsMjEwLjAgdi0xOS42IGE0LDQgMCAwIDEgNCwtNCBoNy44IGE0LDQgMCAwIDEgNCw0IHYxOS42IHoiIGZpbGw9IiNkOTU5MjYiLz4KPHBhdGggZD0iTTEwNC40LDIxMC4wIHYtMTAuMSBhNCw0IDAgMCAxIDQsLTQgaDcuOCBhNCw0IDAgMCAxIDQsNCB2MTAuMSB6IiBmaWxsPSIjZDk1OTI2Ii8+CjxwYXRoIGQ9Ik0xMjIuMiwyMTAuMCB2LTQuOCBhNCw0IDAgMCAxIDQsLTQgaDcuOCBhNCw0IDAgMCAxIDQsNCB2NC44IHoiIGZpbGw9IiNkOTU5MjYiLz4KPHBhdGggZD0iTTE0MC4xLDIxMC4wIHYtMi44IGEyLjc4OTEzNDQ3MTI0OTY1ODYsMi43ODkxMzQ0NzEyNDk2NTg2IDAgMCAxIDIuNzg5MTM0NDcxMjQ5NjU4NiwtMi43ODkxMzQ0NzEyNDk2NTg2IGgxMC4yIGEyLjc4OTEzNDQ3MTI0OTY1ODYsMi43ODkxMzQ0NzEyNDk2NTg2IDAgMCAxIDIuNzg5MTM0NDcxMjQ5NjU4NiwyLjc4OTEzNDQ3MTI0OTY1ODYgdjIuOCB6IiBmaWxsPSIjZDk1OTI2Ii8+CjxwYXRoIGQ9Ik0xNTcuOSwyMTAuMCB2LTEuNyBhMS43Mzg3MzEyMjIyNzkwODc4LDEuNzM4NzMxMjIyMjc5MDg3OCAwIDAgMSAxLjczODczMTIyMjI3OTA4NzgsLTEuNzM4NzMxMjIyMjc5MDg3OCBoMTIuMyBhMS43Mzg3MzEyMjIyNzkwODc4LDEuNzM4NzMxMjIyMjc5MDg3OCAwIDAgMSAxLjczODczMTIyMjI3OTA4NzgsMS43Mzg3MzEyMjIyNzkwODc4IHYxLjcgeiIgZmlsbD0iI2Q5NTkyNiIvPgo8cGF0aCBkPSJNMTc1LjcsMjEwLjAgdi0xLjEgYTEuMDYzNDg3ODk3ODA0MjI5NSwxLjA2MzQ4Nzg5NzgwNDIyOTUgMCAwIDEgMS4wNjM0ODc4OTc4MDQyMjk1LC0xLjA2MzQ4Nzg5NzgwNDIyOTUgaDEzLjcgYTEuMDYzNDg3ODk3ODA0MjI5NSwxLjA2MzQ4Nzg5NzgwNDIyOTUgMCAwIDEgMS4wNjM0ODc4OTc4MDQyMjk1LDEuMDYzNDg3ODk3ODA0MjI5NSB2MS4xIHoiIGZpbGw9IiNkOTU5MjYiLz4KPHBhdGggZD0iTTE5My41LDIxMC4wIHYtMC44IGEwLjc1LDAuNzUgMCAwIDEgMC43NSwtMC43NSBoMTQuMyBhMC43NSwwLjc1IDAgMCAxIDAuNzUsMC43NSB2MC44IHoiIGZpbGw9IiNkOTU5MjYiLz4KPHBhdGggZD0iTTIxMS4zLDIxMC4wIHYtMC44IGEwLjc1LDAuNzUgMCAwIDEgMC43NSwtMC43NSBoMTQuMyBhMC43NSwwLjc1IDAgMCAxIDAuNzUsMC43NSB2MC44IHoiIGZpbGw9IiNkOTU5MjYiLz4KPHBhdGggZD0iTTIyOS4xLDIxMC4wIHYtMC44IGEwLjc1LDAuNzUgMCAwIDEgMC43NSwtMC43NSBoMTQuMyBhMC43NSwwLjc1IDAgMCAxIDAuNzUsMC43NSB2MC44IHoiIGZpbGw9IiNkOTU5MjYiLz4KPHBhdGggZD0iTTI0Ni45LDIxMC4wIHYtMC44IGEwLjc1LDAuNzUgMCAwIDEgMC43NSwtMC43NSBoMTQuMyBhMC43NSwwLjc1IDAgMCAxIDAuNzUsMC43NSB2MC44IHoiIGZpbGw9IiNkOTU5MjYiLz4KPHBhdGggZD0iTTI2NC44LDIxMC4wIHYtMC44IGEwLjc1LDAuNzUgMCAwIDEgMC43NSwtMC43NSBoMTQuMyBhMC43NSwwLjc1IDAgMCAxIDAuNzUsMC43NSB2MC44IHoiIGZpbGw9IiNkOTU5MjYiLz4KPHBhdGggZD0iTTI4Mi42LDIxMC4wIHYtMC44IGEwLjc1LDAuNzUgMCAwIDEgMC43NSwtMC43NSBoMTQuMyBhMC43NSwwLjc1IDAgMCAxIDAuNzUsMC43NSB2MC44IHoiIGZpbGw9IiNkOTU5MjYiLz4KPHBhdGggZD0iTTMwMC40LDIxMC4wIHYtMC44IGEwLjc1LDAuNzUgMCAwIDEgMC43NSwtMC43NSBoMTQuMyBhMC43NSwwLjc1IDAgMCAxIDAuNzUsMC43NSB2MC44IHoiIGZpbGw9IiNkOTU5MjYiLz4KPHBhdGggZD0iTTMxOC4yLDIxMC4wIHYtMC44IGEwLjc1LDAuNzUgMCAwIDEgMC43NSwtMC43NSBoMTQuMyBhMC43NSwwLjc1IDAgMCAxIDAuNzUsMC43NSB2MC44IHoiIGZpbGw9IiNkOTU5MjYiLz4KPHRleHQgeD0iNzMuOCIgeT0iNzYuMSIgZm9udC1zaXplPSIxMiIgZmlsbD0iI2MzYzJiNyIgdGV4dC1hbmNob3I9InN0YXJ0IiBmb250LXdlaWdodD0iNDAwIj41OCUgb2Ygcm93czwvdGV4dD4KPHRleHQgeD0iMTkyLjUiIHk9IjIzMC4wIiBmb250LXNpemU9IjExIiBmaWxsPSIjOGE4OTdmIiB0ZXh0LWFuY2hvcj0ibWlkZGxlIiBmb250LXdlaWdodD0iNDAwIj5idWNrZXQsIGxvdyB0byBoaWdoIHZhbHVlczwvdGV4dD4KPHRleHQgeD0iMzc1LjAiIHk9IjUyLjAiIGZvbnQtc2l6ZT0iMTMiIGZpbGw9IiNmZmZmZmYiIHRleHQtYW5jaG9yPSJzdGFydCIgZm9udC13ZWlnaHQ9IjYwMCI+UmFua2VkIChlcXVhbCBwb3B1bGF0aW9uKTwvdGV4dD4KPGxpbmUgeDE9IjM3NS4wIiB5MT0iMjEwLjAiIHgyPSI2NjAuMCIgeTI9IjIxMC4wIiBzdHJva2U9IiMzODM4MzUiLz4KPGxpbmUgeDE9IjM3NS4wIiB5MT0iMTYwLjAiIHgyPSI2NjAuMCIgeTI9IjE2MC4wIiBzdHJva2U9IiMzODM4MzUiLz4KPGxpbmUgeDE9IjM3NS4wIiB5MT0iMTEwLjAiIHgyPSI2NjAuMCIgeTI9IjExMC4wIiBzdHJva2U9IiMzODM4MzUiLz4KPGxpbmUgeDE9IjM3NS4wIiB5MT0iNjAuMCIgeDI9IjY2MC4wIiB5Mj0iNjAuMCIgc3Ryb2tlPSIjMzgzODM1Ii8+CjxwYXRoIGQ9Ik0zNzYuMCwyMTAuMCB2LTExLjYgYTQsNCAwIDAgMSA0LC00IGg3LjggYTQsNCAwIDAgMSA0LDQgdjExLjYgeiIgZmlsbD0iIzM5ODdlNSIvPgo8cGF0aCBkPSJNMzkzLjgsMjEwLjAgdi0xMS42IGE0LDQgMCAwIDEgNCwtNCBoNy44IGE0LDQgMCAwIDEgNCw0IHYxMS42IHoiIGZpbGw9IiMzOTg3ZTUiLz4KPHBhdGggZD0iTTQxMS42LDIxMC4wIHYtMTEuNiBhNCw0IDAgMCAxIDQsLTQgaDcuOCBhNCw0IDAgMCAxIDQsNCB2MTEuNiB6IiBmaWxsPSIjMzk4N2U1Ii8+CjxwYXRoIGQ9Ik00MjkuNCwyMTAuMCB2LTExLjYgYTQsNCAwIDAgMSA0LC00IGg3LjggYTQsNCAwIDAgMSA0LDQgdjExLjYgeiIgZmlsbD0iIzM5ODdlNSIvPgo8cGF0aCBkPSJNNDQ3LjIsMjEwLjAgdi0xMS42IGE0LDQgMCAwIDEgNCwtNCBoNy44IGE0LDQgMCAwIDEgNCw0IHYxMS42IHoiIGZpbGw9IiMzOTg3ZTUiLz4KPHBhdGggZD0iTTQ2NS4xLDIxMC4wIHYtMTEuNiBhNCw0IDAgMCAxIDQsLTQgaDcuOCBhNCw0IDAgMCAxIDQsNCB2MTEuNiB6IiBmaWxsPSIjMzk4N2U1Ii8+CjxwYXRoIGQ9Ik00ODIuOSwyMTAuMCB2LTExLjYgYTQsNCAwIDAgMSA0LC00IGg3LjggYTQsNCAwIDAgMSA0LDQgdjExLjYgeiIgZmlsbD0iIzM5ODdlNSIvPgo8cGF0aCBkPSJNNTAwLjcsMjEwLjAgdi0xMS42IGE0LDQgMCAwIDEgNCwtNCBoNy44IGE0LDQgMCAwIDEgNCw0IHYxMS42IHoiIGZpbGw9IiMzOTg3ZTUiLz4KPHBhdGggZD0iTTUxOC41LDIxMC4wIHYtMTEuNiBhNCw0IDAgMCAxIDQsLTQgaDcuOCBhNCw0IDAgMCAxIDQsNCB2MTEuNiB6IiBmaWxsPSIjMzk4N2U1Ii8+CjxwYXRoIGQ9Ik01MzYuMywyMTAuMCB2LTExLjYgYTQsNCAwIDAgMSA0LC00IGg3LjggYTQsNCAwIDAgMSA0LDQgdjExLjYgeiIgZmlsbD0iIzM5ODdlNSIvPgo8cGF0aCBkPSJNNTU0LjEsMjEwLjAgdi0xMS42IGE0LDQgMCAwIDEgNCwtNCBoNy44IGE0LDQgMCAwIDEgNCw0IHYxMS42IHoiIGZpbGw9IiMzOTg3ZTUiLz4KPHBhdGggZD0iTTU3MS45LDIxMC4wIHYtMTEuNiBhNCw0IDAgMCAxIDQsLTQgaDcuOCBhNCw0IDAgMCAxIDQsNCB2MTEuNiB6IiBmaWxsPSIjMzk4N2U1Ii8+CjxwYXRoIGQ9Ik01ODkuOCwyMTAuMCB2LTExLjYgYTQsNCAwIDAgMSA0LC00IGg3LjggYTQsNCAwIDAgMSA0LDQgdjExLjYgeiIgZmlsbD0iIzM5ODdlNSIvPgo8cGF0aCBkPSJNNjA3LjYsMjEwLjAgdi0xMS42IGE0LDQgMCAwIDEgNCwtNCBoNy44IGE0LDQgMCAwIDEgNCw0IHYxMS42IHoiIGZpbGw9IiMzOTg3ZTUiLz4KPHBhdGggZD0iTTYyNS40LDIxMC4wIHYtMTEuNiBhNCw0IDAgMCAxIDQsLTQgaDcuOCBhNCw0IDAgMCAxIDQsNCB2MTEuNiB6IiBmaWxsPSIjMzk4N2U1Ii8+CjxwYXRoIGQ9Ik02NDMuMiwyMTAuMCB2LTExLjYgYTQsNCAwIDAgMSA0LC00IGg3LjggYTQsNCAwIDAgMSA0LDQgdjExLjYgeiIgZmlsbD0iIzM5ODdlNSIvPgo8dGV4dCB4PSIzOTguOCIgeT0iMjA2LjQiIGZvbnQtc2l6ZT0iMTIiIGZpbGw9IiNjM2MyYjciIHRleHQtYW5jaG9yPSJzdGFydCIgZm9udC13ZWlnaHQ9IjQwMCI+PC90ZXh0Pgo8dGV4dCB4PSI1MTcuNSIgeT0iMTg2LjQiIGZvbnQtc2l6ZT0iMTIiIGZpbGw9IiNjM2MyYjciIHRleHQtYW5jaG9yPSJtaWRkbGUiIGZvbnQtd2VpZ2h0PSI0MDAiPjYuMjUlIGVhY2g8L3RleHQ+Cjx0ZXh0IHg9IjUxNy41IiB5PSIyMzAuMCIgZm9udC1zaXplPSIxMSIgZmlsbD0iIzhhODk3ZiIgdGV4dC1hbmNob3I9Im1pZGRsZSIgZm9udC13ZWlnaHQ9IjQwMCI+YnVja2V0LCBsb3cgdG8gaGlnaCB2YWx1ZXM8L3RleHQ+Cjwvc3ZnPgo=" width="720" height="214" class="img_ev3q"></p>
<p>Scaled between its min and max, 58% of rows land in the first bucket and the top half of the range is nearly
empty. The curve can only order rows by their buckets, so for most of the table this column contributes almost
nothing to the order.</p>
<p>Beyond ordering, I've found these generated keys quite useful. Taking the first bit alone approximately halves the
data, two bits quarters and so on. Values that have proximal keys are proximal in the higher-dimensional space,
for the columns that were used to generate the key, allowing for some very rudimentary clustering analysis. In many
cases where I would otherwise be producing a unique single column composed of multiple keys (a proxy key for composite keys),
where one might otherwise use a hash, I've found using these keys to be more space-efficient and meaningful, with the
bonus of providing a natural row order and partitioning axis.</p>
<p>Throughout this article, I'll call the value-space ordering simply "Morton" and "Hilbert", and the rank-space variants
"rank-Morton" and "rank-Hilbert", respectively. In my own use, I've been calling it a 'mash', as a
replacement for a hash - though given the ranks change with each evaluation, it's not appropriate for every situation.
Given its ability to approximate the higher-dimensional distribution - or its manifold -
a viable backronym might be "Manifold-Approximate Sorting Hierarchy". But, let's stick to the rank-* naming here!</p>
<p>This isn't an entirely new idea. Delta Lake's <code>OPTIMIZE ... ZORDER BY</code> passes each column through a function called
<code>range_partition_id</code>, which its source describes as "an approximate rank() function", before interleaving the bits,
and its Liquid Clustering uses the same ranks with a Hilbert curve.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="testing-setup">Testing setup<a href="https://duckstring.com/blog/clustering-for-pipelines#testing-setup" class="hash-link" aria-label="Direct link to Testing setup" title="Direct link to Testing setup" translate="no">​</a></h2>
<p>I generated TPC-DS at scale factor 100 with DuckDB's <code>tpcds</code> extension and took the <code>store_sales</code> fact table: 288
million rows, 14.4 GB of Parquet. I then wrote it in each of these orders, with 122,880-row row groups (DuckDB default):</p>

























<table><thead><tr><th>Order</th><th>Description</th></tr></thead><tbody><tr><td>Random</td><td>sorted by a hash of the primary key</td></tr><tr><td>Sorted</td><td>a plain <code>ORDER BY</code> over the clustering columns</td></tr><tr><td>Morton, Hilbert</td><td>the curve over each column scaled between its min and max</td></tr><tr><td>Rank-Morton, rank-Hilbert</td><td>the curve over each column's quantile rank</td></tr></tbody></table>
<p>I clustered on two sets of columns. The first pairs <code>ss_sold_date_sk</code> with the skewed <code>ss_net_paid</code>. The second is
three surrogate keys (<code>ss_sold_date_sk</code>, <code>ss_item_sk</code> and <code>ss_customer_sk</code>), which are spread fairly evenly. Each layout then ran the same seeded set
of reads: ranges on each column selecting 0.1%, 1% and 10% of rows, boxes over all of the clustering columns, and a
few joins in the style of TPC-DS. This article reports only the 1% results, but you can see the other results in the
<a href="https://github.com/duckstring-dev/duckstring/tree/main/bench/clustering" target="_blank" rel="noopener noreferrer" class="">benchmark directory</a>.
The metric to pay attention to is the 'share of rows read' - the proportion of the
table's rows that could not be skipped. This is then independent of hardware.</p>
<p>Query time is the median of five warm runs on a 10-core laptop with 32 GB of memory, using DuckDB 1.5.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="theory">Theory<a href="https://duckstring.com/blog/clustering-for-pipelines#theory" class="hash-link" aria-label="Direct link to Theory" title="Direct link to Theory" translate="no">​</a></h3>
<p>Measured by rank, every clustered column is spread evenly between the minimum and maximum. Assuming independence,
for <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>k</mi></mrow><annotation encoding="application/x-tex">k</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6944em"></span><span class="mord mathnormal" style="margin-right:0.0315em">k</span></span></span></span> clustered columns, this forms a <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>k</mi></mrow><annotation encoding="application/x-tex">k</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6944em"></span><span class="mord mathnormal" style="margin-right:0.0315em">k</span></span></span></span>-dimensional unit cube, with rows spread evenly through it. Correlation between
columns allows better performance - for the TPC-DS dataset used in these tests, the columns are near independent.</p>
<p>Each of the <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>N</mi></mrow><annotation encoding="application/x-tex">N</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6833em"></span><span class="mord mathnormal" style="margin-right:0.109em">N</span></span></span></span> row groups holds <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mn>1</mn><mi mathvariant="normal">/</mi><mi>N</mi></mrow><annotation encoding="application/x-tex">1/N</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em"></span><span class="mord">1/</span><span class="mord mathnormal" style="margin-right:0.109em">N</span></span></span></span> of the rows, so the box spanned by its min/max values has a volume of at least
<span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mn>1</mn><mi mathvariant="normal">/</mi><mi>N</mi></mrow><annotation encoding="application/x-tex">1/N</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em"></span><span class="mord">1/</span><span class="mord mathnormal" style="margin-right:0.109em">N</span></span></span></span>. A range query selecting a fraction <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>s</mi></mrow><annotation encoding="application/x-tex">s</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.4306em"></span><span class="mord mathnormal">s</span></span></span></span> of one column is a slab of width <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>s</mi></mrow><annotation encoding="application/x-tex">s</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.4306em"></span><span class="mord mathnormal">s</span></span></span></span> through the cube, and a row group has
to be read whenever its box overlaps the slab. For a row group whose box spans a width <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>w</mi></mrow><annotation encoding="application/x-tex">w</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.4306em"></span><span class="mord mathnormal" style="margin-right:0.0269em">w</span></span></span></span> along that column, that
happens with probability <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>s</mi><mo>+</mo><mi>w</mi></mrow><annotation encoding="application/x-tex">s + w</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6667em;vertical-align:-0.0833em"></span><span class="mord mathnormal">s</span><span class="mspace" style="margin-right:0.2222em"></span><span class="mbin">+</span><span class="mspace" style="margin-right:0.2222em"></span></span><span class="base"><span class="strut" style="height:0.4306em"></span><span class="mord mathnormal" style="margin-right:0.0269em">w</span></span></span></span>, so the expected share of rows read is <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>s</mi></mrow><annotation encoding="application/x-tex">s</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.4306em"></span><span class="mord mathnormal">s</span></span></span></span> plus the average width of the row groups
along that column.</p>
<p>If every clustered column matters equally, the quantity to minimise is that average width across all <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>k</mi></mrow><annotation encoding="application/x-tex">k</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6944em"></span><span class="mord mathnormal" style="margin-right:0.0315em">k</span></span></span></span> columns.
For boxes of a fixed volume, it's smallest when every side is equal - a cube with a side of <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msup><mi>N</mi><mrow><mo>−</mo><mn>1</mn><mi mathvariant="normal">/</mi><mi>k</mi></mrow></msup></mrow><annotation encoding="application/x-tex">N^{-1/k}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.888em"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.109em">N</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.888em"><span style="top:-3.063em;margin-right:0.05em"><span class="pstrut" style="height:2.7em"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">−</span><span class="mord mtight">1/</span><span class="mord mathnormal mtight" style="margin-right:0.0315em">k</span></span></span></span></span></span></span></span></span></span></span></span>. So, averaged
across the clustered columns, no layout can read less than:</p>
<span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mi>s</mi><mo>+</mo><msup><mi>N</mi><mrow><mo>−</mo><mn>1</mn><mi mathvariant="normal">/</mi><mi>k</mi></mrow></msup></mrow><annotation encoding="application/x-tex">s + N^{-1/k}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6667em;vertical-align:-0.0833em"></span><span class="mord mathnormal">s</span><span class="mspace" style="margin-right:0.2222em"></span><span class="mbin">+</span><span class="mspace" style="margin-right:0.2222em"></span></span><span class="base"><span class="strut" style="height:0.938em"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.109em">N</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.938em"><span style="top:-3.113em;margin-right:0.05em"><span class="pstrut" style="height:2.7em"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">−</span><span class="mord mtight">1/</span><span class="mord mathnormal mtight" style="margin-right:0.0315em">k</span></span></span></span></span></span></span></span></span></span></span></span></span>
<p>A layout can beat this for one column by favouring it - lexicographic ordering reads close to <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>s</mi></mrow><annotation encoding="application/x-tex">s</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.4306em"></span><span class="mord mathnormal">s</span></span></span></span> for its first
column, but at the expense of the others. For the SF100 tests, with <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>N</mi><mo>=</mo><mn>2,344</mn></mrow><annotation encoding="application/x-tex">N = 2{,}344</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6833em"></span><span class="mord mathnormal" style="margin-right:0.109em">N</span><span class="mspace" style="margin-right:0.2778em"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em"></span></span><span class="base"><span class="strut" style="height:0.8389em;vertical-align:-0.1944em"></span><span class="mord">2</span><span class="mord"><span class="mpunct">,</span></span><span class="mord">344</span></span></span></span> row groups, the bound for a 1%
range is 3.1% with two clustered columns and 8.5% with three.</p>
<p><strong>Lowest possible share of rows read for a 1% range</strong></p>
<p><img decoding="async" loading="lazy" alt="The bound s + N^(-1/k) against the number of row groups for one to four clustered columns, each falling towards the 1% line, more slowly with more columns" src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA3MjAgMzgwIiB3aWR0aD0iNzIwIiBoZWlnaHQ9IjM4MCIgcm9sZT0iaW1nIiBhcmlhLWxhYmVsPSJUaGUgbG93ZXN0IHBvc3NpYmxlIHNoYXJlIG9mIHJvd3MgcmVhZCBmb3IgYSAxJSByYW5nZSwgYWdhaW5zdCB0aGUgbnVtYmVyIG9mIHJvdyBncm91cHMsIGZvciBvbmUgdG8gZm91ciBjbHVzdGVyZWQgY29sdW1ucyIgZm9udC1mYW1pbHk9IkludGVyLCBzeXN0ZW0tdWksIC1hcHBsZS1zeXN0ZW0sICdTZWdvZSBVSScsIHNhbnMtc2VyaWYiPgo8dGl0bGU+VGhlIGxvd2VzdCBwb3NzaWJsZSBzaGFyZSBvZiByb3dzIHJlYWQgZm9yIGEgMSUgcmFuZ2UsIGFnYWluc3QgdGhlIG51bWJlciBvZiByb3cgZ3JvdXBzLCBmb3Igb25lIHRvIGZvdXIgY2x1c3RlcmVkIGNvbHVtbnM8L3RpdGxlPgo8cmVjdCB5PSIwIiB3aWR0aD0iNzIwIiBoZWlnaHQ9IjM4MCIgcng9IjgiIGZpbGw9IiMxYTFhMTkiLz4KPGxpbmUgeDE9IjcwIiB5MT0iMjgyLjEiIHgyPSI1OTAiIHkyPSIyODIuMSIgc3Ryb2tlPSIjMzgzODM1Ii8+Cjx0ZXh0IHg9IjYyLjAiIHk9IjI4Ni4xIiBmb250LXNpemU9IjExIiBmaWxsPSIjOGE4OTdmIiB0ZXh0LWFuY2hvcj0iZW5kIiBmb250LXdlaWdodD0iNDAwIj4xJTwvdGV4dD4KPGxpbmUgeDE9IjcwIiB5MT0iMjQ0LjEiIHgyPSI1OTAiIHkyPSIyNDQuMSIgc3Ryb2tlPSIjMzgzODM1Ii8+Cjx0ZXh0IHg9IjYyLjAiIHk9IjI0OC4xIiBmb250LXNpemU9IjExIiBmaWxsPSIjOGE4OTdmIiB0ZXh0LWFuY2hvcj0iZW5kIiBmb250LXdlaWdodD0iNDAwIj4yJTwvdGV4dD4KPGxpbmUgeDE9IjcwIiB5MT0iMTk0LjAiIHgyPSI1OTAiIHkyPSIxOTQuMCIgc3Ryb2tlPSIjMzgzODM1Ii8+Cjx0ZXh0IHg9IjYyLjAiIHk9IjE5OC4wIiBmb250LXNpemU9IjExIiBmaWxsPSIjOGE4OTdmIiB0ZXh0LWFuY2hvcj0iZW5kIiBmb250LXdlaWdodD0iNDAwIj41JTwvdGV4dD4KPGxpbmUgeDE9IjcwIiB5MT0iMTU2LjAiIHgyPSI1OTAiIHkyPSIxNTYuMCIgc3Ryb2tlPSIjMzgzODM1Ii8+Cjx0ZXh0IHg9IjYyLjAiIHk9IjE2MC4wIiBmb250LXNpemU9IjExIiBmaWxsPSIjOGE4OTdmIiB0ZXh0LWFuY2hvcj0iZW5kIiBmb250LXdlaWdodD0iNDAwIj4xMCU8L3RleHQ+CjxsaW5lIHgxPSI3MCIgeTE9IjExOC4xIiB4Mj0iNTkwIiB5Mj0iMTE4LjEiIHN0cm9rZT0iIzM4MzgzNSIvPgo8dGV4dCB4PSI2Mi4wIiB5PSIxMjIuMSIgZm9udC1zaXplPSIxMSIgZmlsbD0iIzhhODk3ZiIgdGV4dC1hbmNob3I9ImVuZCIgZm9udC13ZWlnaHQ9IjQwMCI+MjAlPC90ZXh0Pgo8bGluZSB4MT0iNzAiIHkxPSI2Ny45IiB4Mj0iNTkwIiB5Mj0iNjcuOSIgc3Ryb2tlPSIjMzgzODM1Ii8+Cjx0ZXh0IHg9IjYyLjAiIHk9IjcxLjkiIGZvbnQtc2l6ZT0iMTEiIGZpbGw9IiM4YTg5N2YiIHRleHQtYW5jaG9yPSJlbmQiIGZvbnQtd2VpZ2h0PSI0MDAiPjUwJTwvdGV4dD4KPGxpbmUgeDE9IjcwIiB5MT0iMzAuMCIgeDI9IjU5MCIgeTI9IjMwLjAiIHN0cm9rZT0iIzM4MzgzNSIvPgo8dGV4dCB4PSI2Mi4wIiB5PSIzNC4wIiBmb250LXNpemU9IjExIiBmaWxsPSIjOGE4OTdmIiB0ZXh0LWFuY2hvcj0iZW5kIiBmb250LXdlaWdodD0iNDAwIj4xMDAlPC90ZXh0Pgo8bGluZSB4MT0iNzAuMCIgeTE9IjMwIiB4Mj0iNzAuMCIgeTI9IjMyMCIgc3Ryb2tlPSIjMzgzODM1Ii8+Cjx0ZXh0IHg9IjcwLjAiIHk9IjMzOC4wIiBmb250LXNpemU9IjExIiBmaWxsPSIjOGE4OTdmIiB0ZXh0LWFuY2hvcj0ibWlkZGxlIiBmb250LXdlaWdodD0iNDAwIj4xMDwvdGV4dD4KPGxpbmUgeDE9IjE3NC4wIiB5MT0iMzAiIHgyPSIxNzQuMCIgeTI9IjMyMCIgc3Ryb2tlPSIjMzgzODM1Ii8+Cjx0ZXh0IHg9IjE3NC4wIiB5PSIzMzguMCIgZm9udC1zaXplPSIxMSIgZmlsbD0iIzhhODk3ZiIgdGV4dC1hbmNob3I9Im1pZGRsZSIgZm9udC13ZWlnaHQ9IjQwMCI+MTAwPC90ZXh0Pgo8bGluZSB4MT0iMjc4LjAiIHkxPSIzMCIgeDI9IjI3OC4wIiB5Mj0iMzIwIiBzdHJva2U9IiMzODM4MzUiLz4KPHRleHQgeD0iMjc4LjAiIHk9IjMzOC4wIiBmb250LXNpemU9IjExIiBmaWxsPSIjOGE4OTdmIiB0ZXh0LWFuY2hvcj0ibWlkZGxlIiBmb250LXdlaWdodD0iNDAwIj4xazwvdGV4dD4KPGxpbmUgeDE9IjM4Mi4wIiB5MT0iMzAiIHgyPSIzODIuMCIgeTI9IjMyMCIgc3Ryb2tlPSIjMzgzODM1Ii8+Cjx0ZXh0IHg9IjM4Mi4wIiB5PSIzMzguMCIgZm9udC1zaXplPSIxMSIgZmlsbD0iIzhhODk3ZiIgdGV4dC1hbmNob3I9Im1pZGRsZSIgZm9udC13ZWlnaHQ9IjQwMCI+MTBrPC90ZXh0Pgo8bGluZSB4MT0iNDg2LjAiIHkxPSIzMCIgeDI9IjQ4Ni4wIiB5Mj0iMzIwIiBzdHJva2U9IiMzODM4MzUiLz4KPHRleHQgeD0iNDg2LjAiIHk9IjMzOC4wIiBmb250LXNpemU9IjExIiBmaWxsPSIjOGE4OTdmIiB0ZXh0LWFuY2hvcj0ibWlkZGxlIiBmb250LXdlaWdodD0iNDAwIj4xMDBrPC90ZXh0Pgo8bGluZSB4MT0iNTkwLjAiIHkxPSIzMCIgeDI9IjU5MC4wIiB5Mj0iMzIwIiBzdHJva2U9IiMzODM4MzUiLz4KPHRleHQgeD0iNTkwLjAiIHk9IjMzOC4wIiBmb250LXNpemU9IjExIiBmaWxsPSIjOGE4OTdmIiB0ZXh0LWFuY2hvcj0ibWlkZGxlIiBmb250LXdlaWdodD0iNDAwIj4xTTwvdGV4dD4KPHRleHQgeD0iMzMwLjAiIHk9IjM1OC4wIiBmb250LXNpemU9IjEyIiBmaWxsPSIjYzNjMmI3IiB0ZXh0LWFuY2hvcj0ibWlkZGxlIiBmb250LXdlaWdodD0iNDAwIj5yb3cgZ3JvdXBzIChOKTwvdGV4dD4KPGxpbmUgeDE9IjIxMi41IiB5MT0iMzAiIHgyPSIyMTIuNSIgeTI9IjMyMCIgc3Ryb2tlPSIjOGE4OTdmIiBzdHJva2Utd2lkdGg9IjEiIHN0cm9rZS1kYXNoYXJyYXk9IjIgMyIvPgo8dGV4dCB4PSIyMTYuNSIgeT0iNDIuMCIgZm9udC1zaXplPSIxMSIgZmlsbD0iIzhhODk3ZiIgdGV4dC1hbmNob3I9InN0YXJ0IiBmb250LXdlaWdodD0iNDAwIj5TRjEwPC90ZXh0Pgo8bGluZSB4MT0iMzE2LjUiIHkxPSIzMCIgeDI9IjMxNi41IiB5Mj0iMzIwIiBzdHJva2U9IiM4YTg5N2YiIHN0cm9rZS13aWR0aD0iMSIgc3Ryb2tlLWRhc2hhcnJheT0iMiAzIi8+Cjx0ZXh0IHg9IjMyMC41IiB5PSI0Mi4wIiBmb250LXNpemU9IjExIiBmaWxsPSIjOGE4OTdmIiB0ZXh0LWFuY2hvcj0ic3RhcnQiIGZvbnQtd2VpZ2h0PSI0MDAiPlNGMTAwPC90ZXh0Pgo8bGluZSB4MT0iNzAiIHkxPSIyODIuMSIgeDI9IjU5MCIgeTI9IjI4Mi4xIiBzdHJva2U9IiNjM2MyYjciIHN0cm9rZS13aWR0aD0iMS41IiBzdHJva2UtZGFzaGFycmF5PSI2IDQiLz4KPHBvbHlsaW5lIHBvaW50cz0iNzAuMCwxNTAuOCA3NC4zLDE1NS42IDc4LjcsMTYwLjMgODMuMCwxNjQuOSA4Ny4zLDE2OS41IDkxLjcsMTc0LjEgOTYuMCwxNzguNiAxMDAuMywxODMuMCAxMDQuNywxODcuNCAxMDkuMCwxOTEuNiAxMTMuMywxOTUuOCAxMTcuNywyMDAuMCAxMjIuMCwyMDQuMCAxMjYuMywyMDcuOSAxMzAuNywyMTEuOCAxMzUuMCwyMTUuNSAxMzkuMywyMTkuMiAxNDMuNywyMjIuNyAxNDguMCwyMjYuMSAxNTIuMywyMjkuNCAxNTYuNywyMzIuNiAxNjEuMCwyMzUuNyAxNjUuMywyMzguNiAxNjkuNywyNDEuNCAxNzQuMCwyNDQuMSAxNzguMywyNDYuNyAxODIuNywyNDkuMSAxODcuMCwyNTEuNCAxOTEuMywyNTMuNiAxOTUuNywyNTUuNyAyMDAuMCwyNTcuNiAyMDQuMywyNTkuNSAyMDguNywyNjEuMiAyMTMuMCwyNjIuOCAyMTcuMywyNjQuMyAyMjEuNywyNjUuNyAyMjYuMCwyNjcuMCAyMzAuMywyNjguMiAyMzQuNywyNjkuNCAyMzkuMCwyNzAuNCAyNDMuMywyNzEuNCAyNDcuNywyNzIuMyAyNTIuMCwyNzMuMSAyNTYuMywyNzMuOSAyNjAuNywyNzQuNiAyNjUuMCwyNzUuMiAyNjkuMywyNzUuOCAyNzMuNywyNzYuMyAyNzguMCwyNzYuOCAyODIuMywyNzcuMyAyODYuNywyNzcuNyAyOTEuMCwyNzguMSAyOTUuMywyNzguNSAyOTkuNywyNzguOCAzMDQuMCwyNzkuMSAzMDguMywyNzkuMyAzMTIuNywyNzkuNiAzMTcuMCwyNzkuOCAzMjEuMywyODAuMCAzMjUuNywyODAuMiAzMzAuMCwyODAuNCAzMzQuMywyODAuNSAzMzguNywyODAuNyAzNDMuMCwyODAuOCAzNDcuMywyODAuOSAzNTEuNywyODEuMCAzNTYuMCwyODEuMSAzNjAuMywyODEuMiAzNjQuNywyODEuMyAzNjkuMCwyODEuMyAzNzMuMywyODEuNCAzNzcuNywyODEuNSAzODIuMCwyODEuNSAzODYuMywyODEuNiAzOTAuNywyODEuNiAzOTUuMCwyODEuNyAzOTkuMywyODEuNyA0MDMuNywyODEuNyA0MDguMCwyODEuOCA0MTIuMywyODEuOCA0MTYuNywyODEuOCA0MjEuMCwyODEuOCA0MjUuMywyODEuOSA0MjkuNywyODEuOSA0MzQuMCwyODEuOSA0MzguMywyODEuOSA0NDIuNywyODEuOSA0NDcuMCwyODEuOSA0NTEuMywyODEuOSA0NTUuNywyODIuMCA0NjAuMCwyODIuMCA0NjQuMywyODIuMCA0NjguNywyODIuMCA0NzMuMCwyODIuMCA0NzcuMywyODIuMCA0ODEuNywyODIuMCA0ODYuMCwyODIuMCA0OTAuMywyODIuMCA0OTQuNywyODIuMCA0OTkuMCwyODIuMCA1MDMuMywyODIuMCA1MDcuNywyODIuMCA1MTIuMCwyODIuMCA1MTYuMywyODIuMCA1MjAuNywyODIuMCA1MjUuMCwyODIuMCA1MjkuMywyODIuMCA1MzMuNywyODIuMCA1MzguMCwyODIuMCA1NDIuMywyODIuMCA1NDYuNywyODIuMCA1NTEuMCwyODIuMCA1NTUuMywyODIuMCA1NTkuNywyODIuMSA1NjQuMCwyODIuMSA1NjguMywyODIuMSA1NzIuNywyODIuMSA1NzcuMCwyODIuMSA1ODEuMywyODIuMSA1ODUuNywyODIuMSA1OTAuMCwyODIuMSIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjOWVjNWY0IiBzdHJva2Utd2lkdGg9IjIiLz4KPHRleHQgeD0iMTE3LjQiIHk9IjE4Ni4wIiBmb250LXNpemU9IjEyIiBmaWxsPSIjOWVjNWY0IiB0ZXh0LWFuY2hvcj0ic3RhcnQiIGZvbnQtd2VpZ2h0PSI0MDAiPjEgY29sdW1uPC90ZXh0Pgo8cG9seWxpbmUgcG9pbnRzPSI3MC4wLDkxLjMgNzQuMyw5My45IDc4LjcsOTYuNCA4My4wLDk4LjkgODcuMywxMDEuNSA5MS43LDEwNC4wIDk2LjAsMTA2LjUgMTAwLjMsMTA5LjAgMTA0LjcsMTExLjUgMTA5LjAsMTE0LjAgMTEzLjMsMTE2LjUgMTE3LjcsMTE5LjAgMTIyLjAsMTIxLjUgMTI2LjMsMTI0LjAgMTMwLjcsMTI2LjUgMTM1LjAsMTI5LjAgMTM5LjMsMTMxLjQgMTQzLjcsMTMzLjkgMTQ4LjAsMTM2LjMgMTUyLjMsMTM4LjggMTU2LjcsMTQxLjIgMTYxLjAsMTQzLjYgMTY1LjMsMTQ2LjAgMTY5LjcsMTQ4LjQgMTc0LjAsMTUwLjggMTc4LjMsMTUzLjIgMTgyLjcsMTU1LjYgMTg3LjAsMTU3LjkgMTkxLjMsMTYwLjMgMTk1LjcsMTYyLjYgMjAwLjAsMTY0LjkgMjA0LjMsMTY3LjIgMjA4LjcsMTY5LjUgMjEzLjAsMTcxLjggMjE3LjMsMTc0LjEgMjIxLjcsMTc2LjMgMjI2LjAsMTc4LjYgMjMwLjMsMTgwLjggMjM0LjcsMTgzLjAgMjM5LjAsMTg1LjIgMjQzLjMsMTg3LjQgMjQ3LjcsMTg5LjUgMjUyLjAsMTkxLjYgMjU2LjMsMTkzLjggMjYwLjcsMTk1LjggMjY1LjAsMTk3LjkgMjY5LjMsMjAwLjAgMjczLjcsMjAyLjAgMjc4LjAsMjA0LjAgMjgyLjMsMjA2LjAgMjg2LjcsMjA3LjkgMjkxLjAsMjA5LjkgMjk1LjMsMjExLjggMjk5LjcsMjEzLjcgMzA0LjAsMjE1LjUgMzA4LjMsMjE3LjQgMzEyLjcsMjE5LjIgMzE3LjAsMjIxLjAgMzIxLjMsMjIyLjcgMzI1LjcsMjI0LjQgMzMwLjAsMjI2LjEgMzM0LjMsMjI3LjggMzM4LjcsMjI5LjQgMzQzLjAsMjMxLjAgMzQ3LjMsMjMyLjYgMzUxLjcsMjM0LjIgMzU2LjAsMjM1LjcgMzYwLjMsMjM3LjIgMzY0LjcsMjM4LjYgMzY5LjAsMjQwLjAgMzczLjMsMjQxLjQgMzc3LjcsMjQyLjggMzgyLjAsMjQ0LjEgMzg2LjMsMjQ1LjQgMzkwLjcsMjQ2LjcgMzk1LjAsMjQ3LjkgMzk5LjMsMjQ5LjEgNDAzLjcsMjUwLjMgNDA4LjAsMjUxLjQgNDEyLjMsMjUyLjUgNDE2LjcsMjUzLjYgNDIxLjAsMjU0LjcgNDI1LjMsMjU1LjcgNDI5LjcsMjU2LjcgNDM0LjAsMjU3LjYgNDM4LjMsMjU4LjYgNDQyLjcsMjU5LjUgNDQ3LjAsMjYwLjMgNDUxLjMsMjYxLjIgNDU1LjcsMjYyLjAgNDYwLjAsMjYyLjggNDY0LjMsMjYzLjYgNDY4LjcsMjY0LjMgNDczLjAsMjY1LjAgNDc3LjMsMjY1LjcgNDgxLjcsMjY2LjQgNDg2LjAsMjY3LjAgNDkwLjMsMjY3LjYgNDk0LjcsMjY4LjIgNDk5LjAsMjY4LjggNTAzLjMsMjY5LjQgNTA3LjcsMjY5LjkgNTEyLjAsMjcwLjQgNTE2LjMsMjcwLjkgNTIwLjcsMjcxLjQgNTI1LjAsMjcxLjggNTI5LjMsMjcyLjMgNTMzLjcsMjcyLjcgNTM4LjAsMjczLjEgNTQyLjMsMjczLjUgNTQ2LjcsMjczLjkgNTUxLjAsMjc0LjIgNTU1LjMsMjc0LjYgNTU5LjcsMjc0LjkgNTY0LjAsMjc1LjIgNTY4LjMsMjc1LjUgNTcyLjcsMjc1LjggNTc3LjAsMjc2LjEgNTgxLjMsMjc2LjMgNTg1LjcsMjc2LjYgNTkwLjAsMjc2LjgiIGZpbGw9Im5vbmUiIHN0cm9rZT0iIzU1OThlNyIgc3Ryb2tlLXdpZHRoPSIyIi8+Cjx0ZXh0IHg9IjQzNy42IiB5PSIyNDkuMSIgZm9udC1zaXplPSIxMiIgZmlsbD0iIzU1OThlNyIgdGV4dC1hbmNob3I9InN0YXJ0IiBmb250LXdlaWdodD0iNDAwIj4yIGNvbHVtbnM8L3RleHQ+Cjxwb2x5bGluZSBwb2ludHM9IjcwLjAsNzAuOCA3NC4zLDcyLjYgNzguNyw3NC4zIDgzLjAsNzYuMCA4Ny4zLDc3LjcgOTEuNyw3OS40IDk2LjAsODEuMSAxMDAuMyw4Mi44IDEwNC43LDg0LjUgMTA5LjAsODYuMiAxMTMuMyw4Ny45IDExNy43LDg5LjYgMTIyLjAsOTEuMyAxMjYuMyw5My4wIDEzMC43LDk0LjcgMTM1LjAsOTYuNCAxMzkuMyw5OC4xIDE0My43LDk5LjggMTQ4LjAsMTAxLjUgMTUyLjMsMTAzLjEgMTU2LjcsMTA0LjggMTYxLjAsMTA2LjUgMTY1LjMsMTA4LjIgMTY5LjcsMTA5LjkgMTc0LjAsMTExLjUgMTc4LjMsMTEzLjIgMTgyLjcsMTE0LjkgMTg3LjAsMTE2LjUgMTkxLjMsMTE4LjIgMTk1LjcsMTE5LjkgMjAwLjAsMTIxLjUgMjA0LjMsMTIzLjIgMjA4LjcsMTI0LjggMjEzLjAsMTI2LjUgMjE3LjMsMTI4LjEgMjIxLjcsMTI5LjggMjI2LjAsMTMxLjQgMjMwLjMsMTMzLjEgMjM0LjcsMTM0LjcgMjM5LjAsMTM2LjMgMjQzLjMsMTM3LjkgMjQ3LjcsMTM5LjYgMjUyLjAsMTQxLjIgMjU2LjMsMTQyLjggMjYwLjcsMTQ0LjQgMjY1LjAsMTQ2LjAgMjY5LjMsMTQ3LjYgMjczLjcsMTQ5LjIgMjc4LjAsMTUwLjggMjgyLjMsMTUyLjQgMjg2LjcsMTU0LjAgMjkxLjAsMTU1LjYgMjk1LjMsMTU3LjEgMjk5LjcsMTU4LjcgMzA0LjAsMTYwLjMgMzA4LjMsMTYxLjggMzEyLjcsMTYzLjQgMzE3LjAsMTY0LjkgMzIxLjMsMTY2LjUgMzI1LjcsMTY4LjAgMzMwLjAsMTY5LjUgMzM0LjMsMTcxLjEgMzM4LjcsMTcyLjYgMzQzLjAsMTc0LjEgMzQ3LjMsMTc1LjYgMzUxLjcsMTc3LjEgMzU2LjAsMTc4LjYgMzYwLjMsMTgwLjEgMzY0LjcsMTgxLjUgMzY5LjAsMTgzLjAgMzczLjMsMTg0LjUgMzc3LjcsMTg1LjkgMzgyLjAsMTg3LjQgMzg2LjMsMTg4LjggMzkwLjcsMTkwLjIgMzk1LjAsMTkxLjYgMzk5LjMsMTkzLjEgNDAzLjcsMTk0LjUgNDA4LjAsMTk1LjggNDEyLjMsMTk3LjIgNDE2LjcsMTk4LjYgNDIxLjAsMjAwLjAgNDI1LjMsMjAxLjMgNDI5LjcsMjAyLjcgNDM0LjAsMjA0LjAgNDM4LjMsMjA1LjMgNDQyLjcsMjA2LjYgNDQ3LjAsMjA3LjkgNDUxLjMsMjA5LjIgNDU1LjcsMjEwLjUgNDYwLjAsMjExLjggNDY0LjMsMjEzLjEgNDY4LjcsMjE0LjMgNDczLjAsMjE1LjUgNDc3LjMsMjE2LjggNDgxLjcsMjE4LjAgNDg2LjAsMjE5LjIgNDkwLjMsMjIwLjQgNDk0LjcsMjIxLjUgNDk5LjAsMjIyLjcgNTAzLjMsMjIzLjkgNTA3LjcsMjI1LjAgNTEyLjAsMjI2LjEgNTE2LjMsMjI3LjIgNTIwLjcsMjI4LjMgNTI1LjAsMjI5LjQgNTI5LjMsMjMwLjUgNTMzLjcsMjMxLjYgNTM4LjAsMjMyLjYgNTQyLjMsMjMzLjcgNTQ2LjcsMjM0LjcgNTUxLjAsMjM1LjcgNTU1LjMsMjM2LjcgNTU5LjcsMjM3LjcgNTY0LjAsMjM4LjYgNTY4LjMsMjM5LjYgNTcyLjcsMjQwLjUgNTc3LjAsMjQxLjQgNTgxLjMsMjQyLjMgNTg1LjcsMjQzLjIgNTkwLjAsMjQ0LjEiIGZpbGw9Im5vbmUiIHN0cm9rZT0iIzJhNzhkNiIgc3Ryb2tlLXdpZHRoPSIyIi8+Cjx0ZXh0IHg9IjU5OC4wIiB5PSIyNDguMSIgZm9udC1zaXplPSIxMiIgZmlsbD0iIzJhNzhkNiIgdGV4dC1hbmNob3I9InN0YXJ0IiBmb250LXdlaWdodD0iNDAwIj4zIGNvbHVtbnM8L3RleHQ+Cjxwb2x5bGluZSBwb2ludHM9IjcwLjAsNjAuNSA3NC4zLDYxLjggNzguNyw2My4xIDgzLjAsNjQuNCA4Ny4zLDY1LjcgOTEuNyw2Ny4wIDk2LjAsNjguMyAxMDAuMyw2OS42IDEwNC43LDcwLjggMTA5LjAsNzIuMSAxMTMuMyw3My40IDExNy43LDc0LjcgMTIyLjAsNzYuMCAxMjYuMyw3Ny4zIDEzMC43LDc4LjUgMTM1LjAsNzkuOCAxMzkuMyw4MS4xIDE0My43LDgyLjQgMTQ4LjAsODMuNyAxNTIuMyw4NC45IDE1Ni43LDg2LjIgMTYxLjAsODcuNSAxNjUuMyw4OC44IDE2OS43LDkwLjAgMTc0LjAsOTEuMyAxNzguMyw5Mi42IDE4Mi43LDkzLjkgMTg3LjAsOTUuMSAxOTEuMyw5Ni40IDE5NS43LDk3LjcgMjAwLjAsOTguOSAyMDQuMywxMDAuMiAyMDguNywxMDEuNSAyMTMuMCwxMDIuNyAyMTcuMywxMDQuMCAyMjEuNywxMDUuMiAyMjYuMCwxMDYuNSAyMzAuMywxMDcuOCAyMzQuNywxMDkuMCAyMzkuMCwxMTAuMyAyNDMuMywxMTEuNSAyNDcuNywxMTIuOCAyNTIuMCwxMTQuMCAyNTYuMywxMTUuMyAyNjAuNywxMTYuNSAyNjUuMCwxMTcuOCAyNjkuMywxMTkuMCAyNzMuNywxMjAuMyAyNzguMCwxMjEuNSAyODIuMywxMjIuOCAyODYuNywxMjQuMCAyOTEuMCwxMjUuMiAyOTUuMywxMjYuNSAyOTkuNywxMjcuNyAzMDQuMCwxMjkuMCAzMDguMywxMzAuMiAzMTIuNywxMzEuNCAzMTcuMCwxMzIuNiAzMjEuMywxMzMuOSAzMjUuNywxMzUuMSAzMzAuMCwxMzYuMyAzMzQuMywxMzcuNSAzMzguNywxMzguOCAzNDMuMCwxNDAuMCAzNDcuMywxNDEuMiAzNTEuNywxNDIuNCAzNTYuMCwxNDMuNiAzNjAuMywxNDQuOCAzNjQuNywxNDYuMCAzNjkuMCwxNDcuMiAzNzMuMywxNDguNCAzNzcuNywxNDkuNiAzODIuMCwxNTAuOCAzODYuMywxNTIuMCAzOTAuNywxNTMuMiAzOTUuMCwxNTQuNCAzOTkuMywxNTUuNiA0MDMuNywxNTYuNyA0MDguMCwxNTcuOSA0MTIuMywxNTkuMSA0MTYuNywxNjAuMyA0MjEuMCwxNjEuNCA0MjUuMywxNjIuNiA0MjkuNywxNjMuOCA0MzQuMCwxNjQuOSA0MzguMywxNjYuMSA0NDIuNywxNjcuMiA0NDcuMCwxNjguNCA0NTEuMywxNjkuNSA0NTUuNywxNzAuNyA0NjAuMCwxNzEuOCA0NjQuMywxNzMuMCA0NjguNywxNzQuMSA0NzMuMCwxNzUuMiA0NzcuMywxNzYuMyA0ODEuNywxNzcuNSA0ODYuMCwxNzguNiA0OTAuMywxNzkuNyA0OTQuNywxODAuOCA0OTkuMCwxODEuOSA1MDMuMywxODMuMCA1MDcuNywxODQuMSA1MTIuMCwxODUuMiA1MTYuMywxODYuMyA1MjAuNywxODcuNCA1MjUuMCwxODguNCA1MjkuMywxODkuNSA1MzMuNywxOTAuNiA1MzguMCwxOTEuNiA1NDIuMywxOTIuNyA1NDYuNywxOTMuOCA1NTEuMCwxOTQuOCA1NTUuMywxOTUuOCA1NTkuNywxOTYuOSA1NjQuMCwxOTcuOSA1NjguMywxOTguOSA1NzIuNywyMDAuMCA1NzcuMCwyMDEuMCA1ODEuMywyMDIuMCA1ODUuNywyMDMuMCA1OTAuMCwyMDQuMCIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjMWM1Y2FiIiBzdHJva2Utd2lkdGg9IjIiLz4KPHRleHQgeD0iNTk4LjAiIHk9IjIwOC4wIiBmb250LXNpemU9IjEyIiBmaWxsPSIjMWM1Y2FiIiB0ZXh0LWFuY2hvcj0ic3RhcnQiIGZvbnQtd2VpZ2h0PSI0MDAiPjQgY29sdW1uczwvdGV4dD4KPC9zdmc+Cg==" width="720" height="380" class="img_ev3q"></p>
<p><em>The bound <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>s</mi><mo>+</mo><msup><mi>N</mi><mrow><mo>−</mo><mn>1</mn><mi mathvariant="normal">/</mi><mi>k</mi></mrow></msup></mrow><annotation encoding="application/x-tex">s + N^{-1/k}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6667em;vertical-align:-0.0833em"></span><span class="mord mathnormal">s</span><span class="mspace" style="margin-right:0.2222em"></span><span class="mbin">+</span><span class="mspace" style="margin-right:0.2222em"></span></span><span class="base"><span class="strut" style="height:0.888em"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.109em">N</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.888em"><span style="top:-3.063em;margin-right:0.05em"><span class="pstrut" style="height:2.7em"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">−</span><span class="mord mtight">1/</span><span class="mord mathnormal mtight" style="margin-right:0.0315em">k</span></span></span></span></span></span></span></span></span></span></span></span> for <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>k</mi></mrow><annotation encoding="application/x-tex">k</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6944em"></span><span class="mord mathnormal" style="margin-right:0.0315em">k</span></span></span></span> clustered columns. The dashed line is <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>s</mi><mo>=</mo><mn>1</mn><mi mathvariant="normal">%</mi></mrow><annotation encoding="application/x-tex">s = 1\%</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.4306em"></span><span class="mord mathnormal">s</span><span class="mspace" style="margin-right:0.2778em"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em"></span></span><span class="base"><span class="strut" style="height:0.8056em;vertical-align:-0.0556em"></span><span class="mord">1%</span></span></span></span>, the matching rows themselves.</em></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="results">Results<a href="https://duckstring.com/blog/clustering-for-pipelines#results" class="hash-link" aria-label="Direct link to Results" title="Direct link to Results" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="ranking-helps-with-skew">Ranking helps with skew<a href="https://duckstring.com/blog/clustering-for-pipelines#ranking-helps-with-skew" class="hash-link" aria-label="Direct link to Ranking helps with skew" title="Direct link to Ranking helps with skew" translate="no">​</a></h3>
<p><strong>Share of rows read, clustered on (<code>ss_sold_date_sk</code>, <code>ss_net_paid</code>)</strong></p>
<p><img decoding="async" loading="lazy" alt="Share of rows read for 1% ranges on ss_net_paid and on the sale date, for six orders" src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA3MjAgMzM0IiB3aWR0aD0iNzIwIiBoZWlnaHQ9IjMzNCIgcm9sZT0iaW1nIiBhcmlhLWxhYmVsPSJTaGFyZSBvZiByb3dzIHJlYWQsIGNsdXN0ZXJlZCBvbiAoZGF0ZSwgbmV0IHBhaWQpIiBmb250LWZhbWlseT0iSW50ZXIsIHN5c3RlbS11aSwgLWFwcGxlLXN5c3RlbSwgJ1NlZ29lIFVJJywgc2Fucy1zZXJpZiI+Cjx0aXRsZT5TaGFyZSBvZiByb3dzIHJlYWQsIGNsdXN0ZXJlZCBvbiAoZGF0ZSwgbmV0IHBhaWQpPC90aXRsZT4KPHJlY3QgeT0iMCIgd2lkdGg9IjcyMCIgaGVpZ2h0PSIzMzQiIHJ4PSI4IiBmaWxsPSIjMWExYTE5Ii8+CjxyZWN0IHg9IjE1MCIgeT0iMTQiIHdpZHRoPSIxMiIgaGVpZ2h0PSIxMiIgcng9IjMiIGZpbGw9IiMzOTg3ZTUiLz4KPHRleHQgeD0iMTY4LjAiIHk9IjI0LjAiIGZvbnQtc2l6ZT0iMTMiIGZpbGw9IiNjM2MyYjciIHRleHQtYW5jaG9yPSJzdGFydCIgZm9udC13ZWlnaHQ9IjQwMCI+MSUgcmFuZ2Ugb24gc3NfbmV0X3BhaWQ8L3RleHQ+CjxyZWN0IHg9IjM0MS42IiB5PSIxNCIgd2lkdGg9IjEyIiBoZWlnaHQ9IjEyIiByeD0iMyIgZmlsbD0iI2Q5NTkyNiIvPgo8dGV4dCB4PSIzNTkuNiIgeT0iMjQuMCIgZm9udC1zaXplPSIxMyIgZmlsbD0iI2MzYzJiNyIgdGV4dC1hbmNob3I9InN0YXJ0IiBmb250LXdlaWdodD0iNDAwIj4xJSByYW5nZSBvbiBzc19zb2xkX2RhdGVfc2s8L3RleHQ+CjxsaW5lIHgxPSIxNTAuMCIgeTE9IjMwIiB4Mj0iMTUwLjAiIHkyPSIyOTAiIHN0cm9rZT0iIzM4MzgzNSIgc3Ryb2tlLXdpZHRoPSIxIi8+Cjx0ZXh0IHg9IjE1MC4wIiB5PSIzMDYuMCIgZm9udC1zaXplPSIxMSIgZmlsbD0iIzhhODk3ZiIgdGV4dC1hbmNob3I9Im1pZGRsZSIgZm9udC13ZWlnaHQ9IjQwMCI+MCU8L3RleHQ+CjxsaW5lIHgxPSIyNzUuMCIgeTE9IjMwIiB4Mj0iMjc1LjAiIHkyPSIyOTAiIHN0cm9rZT0iIzM4MzgzNSIgc3Ryb2tlLXdpZHRoPSIxIi8+Cjx0ZXh0IHg9IjI3NS4wIiB5PSIzMDYuMCIgZm9udC1zaXplPSIxMSIgZmlsbD0iIzhhODk3ZiIgdGV4dC1hbmNob3I9Im1pZGRsZSIgZm9udC13ZWlnaHQ9IjQwMCI+MjUlPC90ZXh0Pgo8bGluZSB4MT0iNDAwLjAiIHkxPSIzMCIgeDI9IjQwMC4wIiB5Mj0iMjkwIiBzdHJva2U9IiMzODM4MzUiIHN0cm9rZS13aWR0aD0iMSIvPgo8dGV4dCB4PSI0MDAuMCIgeT0iMzA2LjAiIGZvbnQtc2l6ZT0iMTEiIGZpbGw9IiM4YTg5N2YiIHRleHQtYW5jaG9yPSJtaWRkbGUiIGZvbnQtd2VpZ2h0PSI0MDAiPjUwJTwvdGV4dD4KPGxpbmUgeDE9IjUyNS4wIiB5MT0iMzAiIHgyPSI1MjUuMCIgeTI9IjI5MCIgc3Ryb2tlPSIjMzgzODM1IiBzdHJva2Utd2lkdGg9IjEiLz4KPHRleHQgeD0iNTI1LjAiIHk9IjMwNi4wIiBmb250LXNpemU9IjExIiBmaWxsPSIjOGE4OTdmIiB0ZXh0LWFuY2hvcj0ibWlkZGxlIiBmb250LXdlaWdodD0iNDAwIj43NSU8L3RleHQ+CjxsaW5lIHgxPSI2NTAuMCIgeTE9IjMwIiB4Mj0iNjUwLjAiIHkyPSIyOTAiIHN0cm9rZT0iIzM4MzgzNSIgc3Ryb2tlLXdpZHRoPSIxIi8+Cjx0ZXh0IHg9IjY1MC4wIiB5PSIzMDYuMCIgZm9udC1zaXplPSIxMSIgZmlsbD0iIzhhODk3ZiIgdGV4dC1hbmNob3I9Im1pZGRsZSIgZm9udC13ZWlnaHQ9IjQwMCI+MTAwJTwvdGV4dD4KPHRleHQgeD0iMTM4LjAiIHk9IjU1LjUiIGZvbnQtc2l6ZT0iMTMiIGZpbGw9IiNjM2MyYjciIHRleHQtYW5jaG9yPSJlbmQiIGZvbnQtd2VpZ2h0PSI0MDAiPlJhbmRvbTwvdGV4dD4KPHBhdGggZD0iTTE1MC4wLDM2LjAgaDQ5Ni4wIGE0LDQgMCAwIDEgNCw0IHY2LjAgYTQsNCAwIDAgMSAtNCw0IGgtNDk2LjAgeiIgZmlsbD0iIzM5ODdlNSIvPgo8dGV4dCB4PSI2NTYuMCIgeT0iNDcuMCIgZm9udC1zaXplPSIxMiIgZmlsbD0iI2MzYzJiNyIgdGV4dC1hbmNob3I9InN0YXJ0IiBmb250LXdlaWdodD0iNDAwIj4xMDAlPC90ZXh0Pgo8cGF0aCBkPSJNMTUwLjAsNTIuMCBoNDk2LjAgYTQsNCAwIDAgMSA0LDQgdjYuMCBhNCw0IDAgMCAxIC00LDQgaC00OTYuMCB6IiBmaWxsPSIjZDk1OTI2Ii8+Cjx0ZXh0IHg9IjY1Ni4wIiB5PSI2My4wIiBmb250LXNpemU9IjEyIiBmaWxsPSIjYzNjMmI3IiB0ZXh0LWFuY2hvcj0ic3RhcnQiIGZvbnQtd2VpZ2h0PSI0MDAiPjEwMCU8L3RleHQ+Cjx0ZXh0IHg9IjEzOC4wIiB5PSI5OS41IiBmb250LXNpemU9IjEzIiBmaWxsPSIjYzNjMmI3IiB0ZXh0LWFuY2hvcj0iZW5kIiBmb250LXdlaWdodD0iNDAwIj5Tb3J0ZWQgKGxleGljb2dyYXBoaWMpPC90ZXh0Pgo8cGF0aCBkPSJNMTUwLjAsODAuMCBoNDA4LjMgYTQsNCAwIDAgMSA0LDQgdjYuMCBhNCw0IDAgMCAxIC00LDQgaC00MDguMyB6IiBmaWxsPSIjMzk4N2U1Ii8+Cjx0ZXh0IHg9IjU2OC4zIiB5PSI5MS4wIiBmb250LXNpemU9IjEyIiBmaWxsPSIjYzNjMmI3IiB0ZXh0LWFuY2hvcj0ic3RhcnQiIGZvbnQtd2VpZ2h0PSI0MDAiPjgyJTwvdGV4dD4KPHBhdGggZD0iTTE1MC4wLDk2LjAgaDIuNiBhMi41NjAwMjY0NTM2MDY2ODgsMi41NjAwMjY0NTM2MDY2ODggMCAwIDEgMi41NjAwMjY0NTM2MDY2ODgsMi41NjAwMjY0NTM2MDY2ODggdjguOSBhMi41NjAwMjY0NTM2MDY2ODgsMi41NjAwMjY0NTM2MDY2ODggMCAwIDEgLTIuNTYwMDI2NDUzNjA2Njg4LDIuNTYwMDI2NDUzNjA2Njg4IGgtMi42IHoiIGZpbGw9IiNkOTU5MjYiLz4KPHRleHQgeD0iMTYxLjEiIHk9IjEwNy4wIiBmb250LXNpemU9IjEyIiBmaWxsPSIjYzNjMmI3IiB0ZXh0LWFuY2hvcj0ic3RhcnQiIGZvbnQtd2VpZ2h0PSI0MDAiPjEuMCU8L3RleHQ+Cjx0ZXh0IHg9IjEzOC4wIiB5PSIxNDMuNSIgZm9udC1zaXplPSIxMyIgZmlsbD0iI2MzYzJiNyIgdGV4dC1hbmNob3I9ImVuZCIgZm9udC13ZWlnaHQ9IjQwMCI+TW9ydG9uPC90ZXh0Pgo8cGF0aCBkPSJNMTUwLjAsMTI0LjAgaDc2LjkgYTQsNCAwIDAgMSA0LDQgdjYuMCBhNCw0IDAgMCAxIC00LDQgaC03Ni45IHoiIGZpbGw9IiMzOTg3ZTUiLz4KPHRleHQgeD0iMjM2LjkiIHk9IjEzNS4wIiBmb250LXNpemU9IjEyIiBmaWxsPSIjYzNjMmI3IiB0ZXh0LWFuY2hvcj0ic3RhcnQiIGZvbnQtd2VpZ2h0PSI0MDAiPjE2JTwvdGV4dD4KPHBhdGggZD0iTTE1MC4wLDE0MC4wIGg4LjggYTQsNCAwIDAgMSA0LDQgdjYuMCBhNCw0IDAgMCAxIC00LDQgaC04LjggeiIgZmlsbD0iI2Q5NTkyNiIvPgo8dGV4dCB4PSIxNjguOCIgeT0iMTUxLjAiIGZvbnQtc2l6ZT0iMTIiIGZpbGw9IiNjM2MyYjciIHRleHQtYW5jaG9yPSJzdGFydCIgZm9udC13ZWlnaHQ9IjQwMCI+Mi42JTwvdGV4dD4KPHRleHQgeD0iMTM4LjAiIHk9IjE4Ny41IiBmb250LXNpemU9IjEzIiBmaWxsPSIjYzNjMmI3IiB0ZXh0LWFuY2hvcj0iZW5kIiBmb250LXdlaWdodD0iNDAwIj5IaWxiZXJ0PC90ZXh0Pgo8cGF0aCBkPSJNMTUwLjAsMTY4LjAgaDU1LjcgYTQsNCAwIDAgMSA0LDQgdjYuMCBhNCw0IDAgMCAxIC00LDQgaC01NS43IHoiIGZpbGw9IiMzOTg3ZTUiLz4KPHRleHQgeD0iMjE1LjciIHk9IjE3OS4wIiBmb250LXNpemU9IjEyIiBmaWxsPSIjYzNjMmI3IiB0ZXh0LWFuY2hvcj0ic3RhcnQiIGZvbnQtd2VpZ2h0PSI0MDAiPjEyJTwvdGV4dD4KPHBhdGggZD0iTTE1MC4wLDE4NC4wIGg3LjcgYTQsNCAwIDAgMSA0LDQgdjYuMCBhNCw0IDAgMCAxIC00LDQgaC03LjcgeiIgZmlsbD0iI2Q5NTkyNiIvPgo8dGV4dCB4PSIxNjcuNyIgeT0iMTk1LjAiIGZvbnQtc2l6ZT0iMTIiIGZpbGw9IiNjM2MyYjciIHRleHQtYW5jaG9yPSJzdGFydCIgZm9udC13ZWlnaHQ9IjQwMCI+Mi4zJTwvdGV4dD4KPHRleHQgeD0iMTM4LjAiIHk9IjIzMS41IiBmb250LXNpemU9IjEzIiBmaWxsPSIjYzNjMmI3IiB0ZXh0LWFuY2hvcj0iZW5kIiBmb250LXdlaWdodD0iNDAwIj5SYW5rLU1vcnRvbjwvdGV4dD4KPHBhdGggZD0iTTE1MC4wLDIxMi4wIGgyMC4zIGE0LDQgMCAwIDEgNCw0IHY2LjAgYTQsNCAwIDAgMSAtNCw0IGgtMjAuMyB6IiBmaWxsPSIjMzk4N2U1Ii8+Cjx0ZXh0IHg9IjE4MC4zIiB5PSIyMjMuMCIgZm9udC1zaXplPSIxMiIgZmlsbD0iI2MzYzJiNyIgdGV4dC1hbmNob3I9InN0YXJ0IiBmb250LXdlaWdodD0iNDAwIj40LjklPC90ZXh0Pgo8cGF0aCBkPSJNMTUwLjAsMjI4LjAgaDE5LjEgYTQsNCAwIDAgMSA0LDQgdjYuMCBhNCw0IDAgMCAxIC00LDQgaC0xOS4xIHoiIGZpbGw9IiNkOTU5MjYiLz4KPHRleHQgeD0iMTc5LjEiIHk9IjIzOS4wIiBmb250LXNpemU9IjEyIiBmaWxsPSIjYzNjMmI3IiB0ZXh0LWFuY2hvcj0ic3RhcnQiIGZvbnQtd2VpZ2h0PSI0MDAiPjQuNiU8L3RleHQ+Cjx0ZXh0IHg9IjEzOC4wIiB5PSIyNzUuNSIgZm9udC1zaXplPSIxMyIgZmlsbD0iI2ZmZmZmZiIgdGV4dC1hbmNob3I9ImVuZCIgZm9udC13ZWlnaHQ9IjYwMCI+UmFuay1IaWxiZXJ0PC90ZXh0Pgo8cGF0aCBkPSJNMTUwLjAsMjU2LjAgaDEzLjcgYTQsNCAwIDAgMSA0LDQgdjYuMCBhNCw0IDAgMCAxIC00LDQgaC0xMy43IHoiIGZpbGw9IiMzOTg3ZTUiLz4KPHRleHQgeD0iMTczLjciIHk9IjI2Ny4wIiBmb250LXNpemU9IjEyIiBmaWxsPSIjYzNjMmI3IiB0ZXh0LWFuY2hvcj0ic3RhcnQiIGZvbnQtd2VpZ2h0PSI0MDAiPjMuNSU8L3RleHQ+CjxwYXRoIGQ9Ik0xNTAuMCwyNzIuMCBoMTYuNCBhNCw0IDAgMCAxIDQsNCB2Ni4wIGE0LDQgMCAwIDEgLTQsNCBoLTE2LjQgeiIgZmlsbD0iI2Q5NTkyNiIvPgo8dGV4dCB4PSIxNzYuNCIgeT0iMjgzLjAiIGZvbnQtc2l6ZT0iMTIiIGZpbGw9IiNjM2MyYjciIHRleHQtYW5jaG9yPSJzdGFydCIgZm9udC13ZWlnaHQ9IjQwMCI+NC4xJTwvdGV4dD4KPC9zdmc+Cg==" width="720" height="334" class="img_ev3q"></p>
<p><em>TPC-DS SF100, 288M rows, 2,344 row groups. Lower is better.</em></p>
<p>In this test, a range query for 1% of <code>ss_sold_date_sk</code> values, and separately a range query for 1% of <code>ss_net_paid</code>,
was executed for clusters based on (<code>ss_sold_date_sk</code>, <code>ss_net_paid</code>).</p>
<p>As expected, the random order does not manage to skip any row group at all, and 100% of the data is read for both the
<code>ss_sold_date_sk</code> and <code>ss_net_paid</code> ranges.</p>
<p>The <code>ss_sold_date_sk</code> column has around 1,800 distinct values across 288M rows, resulting in around one per row group.
That doesn't give much room for <code>ss_net_paid</code> to do much to the order beyond that. Ordered lexicographically, the result
is optimal performance on the <code>ss_sold_date_sk</code> range at 1%, but poor performance on <code>ss_net_paid</code> at 82% of rows read.</p>
<p>The Morton and Hilbert (value-space) orders do a much better job at supporting queries on <code>ss_net_paid</code>, cutting it down
to 12-16% and sacrificing little to the performance against <code>ss_sold_date_sk</code>. This would be much better though, if not
for the significant skew in <code>ss_net_paid</code>. The rank-space methods bring both queries to within the 3-5% range - close
to the theoretical best of 3.1%.</p>
<p>Fewer rows read translates fairly directly into faster reads. With a warm cache, the 1% <code>ss_net_paid</code> range goes
from about 200 ms in random order to 19 ms in rank-Hilbert order:</p>
<p><strong>Query time for a 1% range on <code>ss_net_paid</code></strong></p>
<p><img decoding="async" loading="lazy" alt="Query time for a 1% range on ss_net_paid under six orders, from 199 ms for random down to 19 ms for rank-Hilbert" src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA3MjAgMjIwIiB3aWR0aD0iNzIwIiBoZWlnaHQ9IjIyMCIgcm9sZT0iaW1nIiBhcmlhLWxhYmVsPSJRdWVyeSB0aW1lIGZvciBhIDElIHJhbmdlIG9uIHNzX25ldF9wYWlkIChtcykiIGZvbnQtZmFtaWx5PSJJbnRlciwgc3lzdGVtLXVpLCAtYXBwbGUtc3lzdGVtLCAnU2Vnb2UgVUknLCBzYW5zLXNlcmlmIj4KPHRpdGxlPlF1ZXJ5IHRpbWUgZm9yIGEgMSUgcmFuZ2Ugb24gc3NfbmV0X3BhaWQgKG1zKTwvdGl0bGU+CjxyZWN0IHk9IjAiIHdpZHRoPSI3MjAiIGhlaWdodD0iMjIwIiByeD0iOCIgZmlsbD0iIzFhMWExOSIvPgo8bGluZSB4MT0iMTUwLjAiIHkxPSIxMiIgeDI9IjE1MC4wIiB5Mj0iMTc2IiBzdHJva2U9IiMzODM4MzUiIHN0cm9rZS13aWR0aD0iMSIvPgo8dGV4dCB4PSIxNTAuMCIgeT0iMTkyLjAiIGZvbnQtc2l6ZT0iMTEiIGZpbGw9IiM4YTg5N2YiIHRleHQtYW5jaG9yPSJtaWRkbGUiIGZvbnQtd2VpZ2h0PSI0MDAiPjAgbXM8L3RleHQ+CjxsaW5lIHgxPSIyNzUuMCIgeTE9IjEyIiB4Mj0iMjc1LjAiIHkyPSIxNzYiIHN0cm9rZT0iIzM4MzgzNSIgc3Ryb2tlLXdpZHRoPSIxIi8+Cjx0ZXh0IHg9IjI3NS4wIiB5PSIxOTIuMCIgZm9udC1zaXplPSIxMSIgZmlsbD0iIzhhODk3ZiIgdGV4dC1hbmNob3I9Im1pZGRsZSIgZm9udC13ZWlnaHQ9IjQwMCI+NzAgbXM8L3RleHQ+CjxsaW5lIHgxPSI0MDAuMCIgeTE9IjEyIiB4Mj0iNDAwLjAiIHkyPSIxNzYiIHN0cm9rZT0iIzM4MzgzNSIgc3Ryb2tlLXdpZHRoPSIxIi8+Cjx0ZXh0IHg9IjQwMC4wIiB5PSIxOTIuMCIgZm9udC1zaXplPSIxMSIgZmlsbD0iIzhhODk3ZiIgdGV4dC1hbmNob3I9Im1pZGRsZSIgZm9udC13ZWlnaHQ9IjQwMCI+MTQwIG1zPC90ZXh0Pgo8bGluZSB4MT0iNTI1LjAiIHkxPSIxMiIgeDI9IjUyNS4wIiB5Mj0iMTc2IiBzdHJva2U9IiMzODM4MzUiIHN0cm9rZS13aWR0aD0iMSIvPgo8dGV4dCB4PSI1MjUuMCIgeT0iMTkyLjAiIGZvbnQtc2l6ZT0iMTEiIGZpbGw9IiM4YTg5N2YiIHRleHQtYW5jaG9yPSJtaWRkbGUiIGZvbnQtd2VpZ2h0PSI0MDAiPjIxMCBtczwvdGV4dD4KPGxpbmUgeDE9IjY1MC4wIiB5MT0iMTIiIHgyPSI2NTAuMCIgeTI9IjE3NiIgc3Ryb2tlPSIjMzgzODM1IiBzdHJva2Utd2lkdGg9IjEiLz4KPHRleHQgeD0iNjUwLjAiIHk9IjE5Mi4wIiBmb250LXNpemU9IjExIiBmaWxsPSIjOGE4OTdmIiB0ZXh0LWFuY2hvcj0ibWlkZGxlIiBmb250LXdlaWdodD0iNDAwIj4yODAgbXM8L3RleHQ+Cjx0ZXh0IHg9IjEzOC4wIiB5PSIyOS41IiBmb250LXNpemU9IjEzIiBmaWxsPSIjYzNjMmI3IiB0ZXh0LWFuY2hvcj0iZW5kIiBmb250LXdlaWdodD0iNDAwIj5SYW5kb208L3RleHQ+CjxwYXRoIGQ9Ik0xNTAuMCwxOC4wIGgzNTEuOSBhNCw0IDAgMCAxIDQsNCB2Ni4wIGE0LDQgMCAwIDEgLTQsNCBoLTM1MS45IHoiIGZpbGw9IiM2ZjZlNjYiLz4KPHRleHQgeD0iNTExLjkiIHk9IjI5LjAiIGZvbnQtc2l6ZT0iMTIiIGZpbGw9IiNjM2MyYjciIHRleHQtYW5jaG9yPSJzdGFydCIgZm9udC13ZWlnaHQ9IjQwMCI+MTk5IG1zPC90ZXh0Pgo8dGV4dCB4PSIxMzguMCIgeT0iNTcuNSIgZm9udC1zaXplPSIxMyIgZmlsbD0iI2MzYzJiNyIgdGV4dC1hbmNob3I9ImVuZCIgZm9udC13ZWlnaHQ9IjQwMCI+U29ydGVkIChsZXhpY29ncmFwaGljKTwvdGV4dD4KPHBhdGggZD0iTTE1MC4wLDQ2LjAgaDE5MC41IGE0LDQgMCAwIDEgNCw0IHY2LjAgYTQsNCAwIDAgMSAtNCw0IGgtMTkwLjUgeiIgZmlsbD0iIzZmNmU2NiIvPgo8dGV4dCB4PSIzNTAuNSIgeT0iNTcuMCIgZm9udC1zaXplPSIxMiIgZmlsbD0iI2MzYzJiNyIgdGV4dC1hbmNob3I9InN0YXJ0IiBmb250LXdlaWdodD0iNDAwIj4xMDkgbXM8L3RleHQ+Cjx0ZXh0IHg9IjEzOC4wIiB5PSI4NS41IiBmb250LXNpemU9IjEzIiBmaWxsPSIjYzNjMmI3IiB0ZXh0LWFuY2hvcj0iZW5kIiBmb250LXdlaWdodD0iNDAwIj5Nb3J0b248L3RleHQ+CjxwYXRoIGQ9Ik0xNTAuMCw3NC4wIGg1OC42IGE0LDQgMCAwIDEgNCw0IHY2LjAgYTQsNCAwIDAgMSAtNCw0IGgtNTguNiB6IiBmaWxsPSIjNmY2ZTY2Ii8+Cjx0ZXh0IHg9IjIxOC42IiB5PSI4NS4wIiBmb250LXNpemU9IjEyIiBmaWxsPSIjYzNjMmI3IiB0ZXh0LWFuY2hvcj0ic3RhcnQiIGZvbnQtd2VpZ2h0PSI0MDAiPjM1IG1zPC90ZXh0Pgo8dGV4dCB4PSIxMzguMCIgeT0iMTEzLjUiIGZvbnQtc2l6ZT0iMTMiIGZpbGw9IiNjM2MyYjciIHRleHQtYW5jaG9yPSJlbmQiIGZvbnQtd2VpZ2h0PSI0MDAiPkhpbGJlcnQ8L3RleHQ+CjxwYXRoIGQ9Ik0xNTAuMCwxMDIuMCBoMzkuMCBhNCw0IDAgMCAxIDQsNCB2Ni4wIGE0LDQgMCAwIDEgLTQsNCBoLTM5LjAgeiIgZmlsbD0iIzZmNmU2NiIvPgo8dGV4dCB4PSIxOTkuMCIgeT0iMTEzLjAiIGZvbnQtc2l6ZT0iMTIiIGZpbGw9IiNjM2MyYjciIHRleHQtYW5jaG9yPSJzdGFydCIgZm9udC13ZWlnaHQ9IjQwMCI+MjQgbXM8L3RleHQ+Cjx0ZXh0IHg9IjEzOC4wIiB5PSIxNDEuNSIgZm9udC1zaXplPSIxMyIgZmlsbD0iI2MzYzJiNyIgdGV4dC1hbmNob3I9ImVuZCIgZm9udC13ZWlnaHQ9IjQwMCI+UmFuay1Nb3J0b248L3RleHQ+CjxwYXRoIGQ9Ik0xNTAuMCwxMzAuMCBoMzIuMiBhNCw0IDAgMCAxIDQsNCB2Ni4wIGE0LDQgMCAwIDEgLTQsNCBoLTMyLjIgeiIgZmlsbD0iIzZmNmU2NiIvPgo8dGV4dCB4PSIxOTIuMiIgeT0iMTQxLjAiIGZvbnQtc2l6ZT0iMTIiIGZpbGw9IiNjM2MyYjciIHRleHQtYW5jaG9yPSJzdGFydCIgZm9udC13ZWlnaHQ9IjQwMCI+MjAgbXM8L3RleHQ+Cjx0ZXh0IHg9IjEzOC4wIiB5PSIxNjkuNSIgZm9udC1zaXplPSIxMyIgZmlsbD0iI2ZmZmZmZiIgdGV4dC1hbmNob3I9ImVuZCIgZm9udC13ZWlnaHQ9IjYwMCI+UmFuay1IaWxiZXJ0PC90ZXh0Pgo8cGF0aCBkPSJNMTUwLjAsMTU4LjAgaDI5LjggYTQsNCAwIDAgMSA0LDQgdjYuMCBhNCw0IDAgMCAxIC00LDQgaC0yOS44IHoiIGZpbGw9IiMzOTg3ZTUiLz4KPHRleHQgeD0iMTg5LjgiIHk9IjE2OS4wIiBmb250LXNpemU9IjEyIiBmaWxsPSIjYzNjMmI3IiB0ZXh0LWFuY2hvcj0ic3RhcnQiIGZvbnQtd2VpZ2h0PSI0MDAiPjE5IG1zPC90ZXh0Pgo8L3N2Zz4K" width="720" height="220" class="img_ev3q"></p>
<p><em>TPC-DS SF100, median of 5 warm runs, for a count and a sum over the matching rows.</em></p>
<p>These are warm-cache timings on a local SSD, so they measure the decoding and filtering that DuckDB avoids. When
reading from object storage, where every skipped row group is a request that never has to be made, I'd expect the
gap to be wider.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="hilbert-beats-morton-on-performance">Hilbert beats Morton on performance<a href="https://duckstring.com/blog/clustering-for-pipelines#hilbert-beats-morton-on-performance" class="hash-link" aria-label="Direct link to Hilbert beats Morton on performance" title="Direct link to Hilbert beats Morton on performance" translate="no">​</a></h3>
<p><strong>Share of rows read for a 1% range, clustered on (<code>ss_sold_date_sk</code>, <code>ss_item_sk</code>, <code>ss_customer_sk</code>)</strong></p>
<p><img decoding="async" loading="lazy" alt="Share of rows read for 1% ranges on ss_sold_date_sk, ss_item_sk and ss_customer_sk, for six orders clustered on all three" src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA3MjAgNDMwIiB3aWR0aD0iNzIwIiBoZWlnaHQ9IjQzMCIgcm9sZT0iaW1nIiBhcmlhLWxhYmVsPSJTaGFyZSBvZiByb3dzIHJlYWQsIGNsdXN0ZXJlZCBvbiAoZGF0ZSwgaXRlbSwgY3VzdG9tZXIpIiBmb250LWZhbWlseT0iSW50ZXIsIHN5c3RlbS11aSwgLWFwcGxlLXN5c3RlbSwgJ1NlZ29lIFVJJywgc2Fucy1zZXJpZiI+Cjx0aXRsZT5TaGFyZSBvZiByb3dzIHJlYWQsIGNsdXN0ZXJlZCBvbiAoZGF0ZSwgaXRlbSwgY3VzdG9tZXIpPC90aXRsZT4KPHJlY3QgeT0iMCIgd2lkdGg9IjcyMCIgaGVpZ2h0PSI0MzAiIHJ4PSI4IiBmaWxsPSIjMWExYTE5Ii8+CjxyZWN0IHg9IjE1MCIgeT0iMTQiIHdpZHRoPSIxMiIgaGVpZ2h0PSIxMiIgcng9IjMiIGZpbGw9IiMzOTg3ZTUiLz4KPHRleHQgeD0iMTY4LjAiIHk9IjI0LjAiIGZvbnQtc2l6ZT0iMTMiIGZpbGw9IiNjM2MyYjciIHRleHQtYW5jaG9yPSJzdGFydCIgZm9udC13ZWlnaHQ9IjQwMCI+c3Nfc29sZF9kYXRlX3NrPC90ZXh0Pgo8cmVjdCB4PSIyODQuMCIgeT0iMTQiIHdpZHRoPSIxMiIgaGVpZ2h0PSIxMiIgcng9IjMiIGZpbGw9IiNkOTU5MjYiLz4KPHRleHQgeD0iMzAyLjAiIHk9IjI0LjAiIGZvbnQtc2l6ZT0iMTMiIGZpbGw9IiNjM2MyYjciIHRleHQtYW5jaG9yPSJzdGFydCIgZm9udC13ZWlnaHQ9IjQwMCI+c3NfaXRlbV9zazwvdGV4dD4KPHJlY3QgeD0iMzgyLjAiIHk9IjE0IiB3aWR0aD0iMTIiIGhlaWdodD0iMTIiIHJ4PSIzIiBmaWxsPSIjMTk5ZTcwIi8+Cjx0ZXh0IHg9IjQwMC4wIiB5PSIyNC4wIiBmb250LXNpemU9IjEzIiBmaWxsPSIjYzNjMmI3IiB0ZXh0LWFuY2hvcj0ic3RhcnQiIGZvbnQtd2VpZ2h0PSI0MDAiPnNzX2N1c3RvbWVyX3NrPC90ZXh0Pgo8bGluZSB4MT0iMTUwLjAiIHkxPSIzMCIgeDI9IjE1MC4wIiB5Mj0iMzg2IiBzdHJva2U9IiMzODM4MzUiIHN0cm9rZS13aWR0aD0iMSIvPgo8dGV4dCB4PSIxNTAuMCIgeT0iNDAyLjAiIGZvbnQtc2l6ZT0iMTEiIGZpbGw9IiM4YTg5N2YiIHRleHQtYW5jaG9yPSJtaWRkbGUiIGZvbnQtd2VpZ2h0PSI0MDAiPjAlPC90ZXh0Pgo8bGluZSB4MT0iMjc1LjAiIHkxPSIzMCIgeDI9IjI3NS4wIiB5Mj0iMzg2IiBzdHJva2U9IiMzODM4MzUiIHN0cm9rZS13aWR0aD0iMSIvPgo8dGV4dCB4PSIyNzUuMCIgeT0iNDAyLjAiIGZvbnQtc2l6ZT0iMTEiIGZpbGw9IiM4YTg5N2YiIHRleHQtYW5jaG9yPSJtaWRkbGUiIGZvbnQtd2VpZ2h0PSI0MDAiPjI1JTwvdGV4dD4KPGxpbmUgeDE9IjQwMC4wIiB5MT0iMzAiIHgyPSI0MDAuMCIgeTI9IjM4NiIgc3Ryb2tlPSIjMzgzODM1IiBzdHJva2Utd2lkdGg9IjEiLz4KPHRleHQgeD0iNDAwLjAiIHk9IjQwMi4wIiBmb250LXNpemU9IjExIiBmaWxsPSIjOGE4OTdmIiB0ZXh0LWFuY2hvcj0ibWlkZGxlIiBmb250LXdlaWdodD0iNDAwIj41MCU8L3RleHQ+CjxsaW5lIHgxPSI1MjUuMCIgeTE9IjMwIiB4Mj0iNTI1LjAiIHkyPSIzODYiIHN0cm9rZT0iIzM4MzgzNSIgc3Ryb2tlLXdpZHRoPSIxIi8+Cjx0ZXh0IHg9IjUyNS4wIiB5PSI0MDIuMCIgZm9udC1zaXplPSIxMSIgZmlsbD0iIzhhODk3ZiIgdGV4dC1hbmNob3I9Im1pZGRsZSIgZm9udC13ZWlnaHQ9IjQwMCI+NzUlPC90ZXh0Pgo8bGluZSB4MT0iNjUwLjAiIHkxPSIzMCIgeDI9IjY1MC4wIiB5Mj0iMzg2IiBzdHJva2U9IiMzODM4MzUiIHN0cm9rZS13aWR0aD0iMSIvPgo8dGV4dCB4PSI2NTAuMCIgeT0iNDAyLjAiIGZvbnQtc2l6ZT0iMTEiIGZpbGw9IiM4YTg5N2YiIHRleHQtYW5jaG9yPSJtaWRkbGUiIGZvbnQtd2VpZ2h0PSI0MDAiPjEwMCU8L3RleHQ+Cjx0ZXh0IHg9IjEzOC4wIiB5PSI2My41IiBmb250LXNpemU9IjEzIiBmaWxsPSIjYzNjMmI3IiB0ZXh0LWFuY2hvcj0iZW5kIiBmb250LXdlaWdodD0iNDAwIj5SYW5kb208L3RleHQ+CjxwYXRoIGQ9Ik0xNTAuMCwzNi4wIGg0OTYuMCBhNCw0IDAgMCAxIDQsNCB2Ni4wIGE0LDQgMCAwIDEgLTQsNCBoLTQ5Ni4wIHoiIGZpbGw9IiMzOTg3ZTUiLz4KPHRleHQgeD0iNjU2LjAiIHk9IjQ3LjAiIGZvbnQtc2l6ZT0iMTIiIGZpbGw9IiNjM2MyYjciIHRleHQtYW5jaG9yPSJzdGFydCIgZm9udC13ZWlnaHQ9IjQwMCI+MTAwJTwvdGV4dD4KPHBhdGggZD0iTTE1MC4wLDUyLjAgaDQ5Ni4wIGE0LDQgMCAwIDEgNCw0IHY2LjAgYTQsNCAwIDAgMSAtNCw0IGgtNDk2LjAgeiIgZmlsbD0iI2Q5NTkyNiIvPgo8dGV4dCB4PSI2NTYuMCIgeT0iNjMuMCIgZm9udC1zaXplPSIxMiIgZmlsbD0iI2MzYzJiNyIgdGV4dC1hbmNob3I9InN0YXJ0IiBmb250LXdlaWdodD0iNDAwIj4xMDAlPC90ZXh0Pgo8cGF0aCBkPSJNMTUwLjAsNjguMCBoNDk2LjAgYTQsNCAwIDAgMSA0LDQgdjYuMCBhNCw0IDAgMCAxIC00LDQgaC00OTYuMCB6IiBmaWxsPSIjMTk5ZTcwIi8+Cjx0ZXh0IHg9IjY1Ni4wIiB5PSI3OS4wIiBmb250LXNpemU9IjEyIiBmaWxsPSIjYzNjMmI3IiB0ZXh0LWFuY2hvcj0ic3RhcnQiIGZvbnQtd2VpZ2h0PSI0MDAiPjEwMCU8L3RleHQ+Cjx0ZXh0IHg9IjEzOC4wIiB5PSIxMjMuNSIgZm9udC1zaXplPSIxMyIgZmlsbD0iI2MzYzJiNyIgdGV4dC1hbmNob3I9ImVuZCIgZm9udC13ZWlnaHQ9IjQwMCI+U29ydGVkIChsZXhpY29ncmFwaGljKTwvdGV4dD4KPHBhdGggZD0iTTE1MC4wLDk2LjAgaDIuNiBhMi41NjAwMjY0NTM2MDY2ODgsMi41NjAwMjY0NTM2MDY2ODggMCAwIDEgMi41NjAwMjY0NTM2MDY2ODgsMi41NjAwMjY0NTM2MDY2ODggdjguOSBhMi41NjAwMjY0NTM2MDY2ODgsMi41NjAwMjY0NTM2MDY2ODggMCAwIDEgLTIuNTYwMDI2NDUzNjA2Njg4LDIuNTYwMDI2NDUzNjA2Njg4IGgtMi42IHoiIGZpbGw9IiMzOTg3ZTUiLz4KPHRleHQgeD0iMTYxLjEiIHk9IjEwNy4wIiBmb250LXNpemU9IjEyIiBmaWxsPSIjYzNjMmI3IiB0ZXh0LWFuY2hvcj0ic3RhcnQiIGZvbnQtd2VpZ2h0PSI0MDAiPjEuMCU8L3RleHQ+CjxwYXRoIGQ9Ik0xNTAuMCwxMTIuMCBoMzkyLjAgYTQsNCAwIDAgMSA0LDQgdjYuMCBhNCw0IDAgMCAxIC00LDQgaC0zOTIuMCB6IiBmaWxsPSIjZDk1OTI2Ii8+Cjx0ZXh0IHg9IjU1Mi4wIiB5PSIxMjMuMCIgZm9udC1zaXplPSIxMiIgZmlsbD0iI2MzYzJiNyIgdGV4dC1hbmNob3I9InN0YXJ0IiBmb250LXdlaWdodD0iNDAwIj43OSU8L3RleHQ+CjxwYXRoIGQ9Ik0xNTAuMCwxMjguMCBoNDk2LjAgYTQsNCAwIDAgMSA0LDQgdjYuMCBhNCw0IDAgMCAxIC00LDQgaC00OTYuMCB6IiBmaWxsPSIjMTk5ZTcwIi8+Cjx0ZXh0IHg9IjY1Ni4wIiB5PSIxMzkuMCIgZm9udC1zaXplPSIxMiIgZmlsbD0iI2MzYzJiNyIgdGV4dC1hbmNob3I9InN0YXJ0IiBmb250LXdlaWdodD0iNDAwIj4xMDAlPC90ZXh0Pgo8dGV4dCB4PSIxMzguMCIgeT0iMTgzLjUiIGZvbnQtc2l6ZT0iMTMiIGZpbGw9IiNjM2MyYjciIHRleHQtYW5jaG9yPSJlbmQiIGZvbnQtd2VpZ2h0PSI0MDAiPk1vcnRvbjwvdGV4dD4KPHBhdGggZD0iTTE1MC4wLDE1Ni4wIGgzOC4xIGE0LDQgMCAwIDEgNCw0IHY2LjAgYTQsNCAwIDAgMSAtNCw0IGgtMzguMSB6IiBmaWxsPSIjMzk4N2U1Ii8+Cjx0ZXh0IHg9IjE5OC4xIiB5PSIxNjcuMCIgZm9udC1zaXplPSIxMiIgZmlsbD0iI2MzYzJiNyIgdGV4dC1hbmNob3I9InN0YXJ0IiBmb250LXdlaWdodD0iNDAwIj44LjQlPC90ZXh0Pgo8cGF0aCBkPSJNMTUwLjAsMTcyLjAgaDU0LjIgYTQsNCAwIDAgMSA0LDQgdjYuMCBhNCw0IDAgMCAxIC00LDQgaC01NC4yIHoiIGZpbGw9IiNkOTU5MjYiLz4KPHRleHQgeD0iMjE0LjIiIHk9IjE4My4wIiBmb250LXNpemU9IjEyIiBmaWxsPSIjYzNjMmI3IiB0ZXh0LWFuY2hvcj0ic3RhcnQiIGZvbnQtd2VpZ2h0PSI0MDAiPjEyJTwvdGV4dD4KPHBhdGggZD0iTTE1MC4wLDE4OC4wIGg3OC4zIGE0LDQgMCAwIDEgNCw0IHY2LjAgYTQsNCAwIDAgMSAtNCw0IGgtNzguMyB6IiBmaWxsPSIjMTk5ZTcwIi8+Cjx0ZXh0IHg9IjIzOC4zIiB5PSIxOTkuMCIgZm9udC1zaXplPSIxMiIgZmlsbD0iI2MzYzJiNyIgdGV4dC1hbmNob3I9InN0YXJ0IiBmb250LXdlaWdodD0iNDAwIj4xNiU8L3RleHQ+Cjx0ZXh0IHg9IjEzOC4wIiB5PSIyNDMuNSIgZm9udC1zaXplPSIxMyIgZmlsbD0iI2MzYzJiNyIgdGV4dC1hbmNob3I9ImVuZCIgZm9udC13ZWlnaHQ9IjQwMCI+SGlsYmVydDwvdGV4dD4KPHBhdGggZD0iTTE1MC4wLDIxNi4wIGgzOC44IGE0LDQgMCAwIDEgNCw0IHY2LjAgYTQsNCAwIDAgMSAtNCw0IGgtMzguOCB6IiBmaWxsPSIjMzk4N2U1Ii8+Cjx0ZXh0IHg9IjE5OC44IiB5PSIyMjcuMCIgZm9udC1zaXplPSIxMiIgZmlsbD0iI2MzYzJiNyIgdGV4dC1hbmNob3I9InN0YXJ0IiBmb250LXdlaWdodD0iNDAwIj44LjYlPC90ZXh0Pgo8cGF0aCBkPSJNMTUwLjAsMjMyLjAgaDQyLjcgYTQsNCAwIDAgMSA0LDQgdjYuMCBhNCw0IDAgMCAxIC00LDQgaC00Mi43IHoiIGZpbGw9IiNkOTU5MjYiLz4KPHRleHQgeD0iMjAyLjciIHk9IjI0My4wIiBmb250LXNpemU9IjEyIiBmaWxsPSIjYzNjMmI3IiB0ZXh0LWFuY2hvcj0ic3RhcnQiIGZvbnQtd2VpZ2h0PSI0MDAiPjkuMyU8L3RleHQ+CjxwYXRoIGQ9Ik0xNTAuMCwyNDguMCBoNDcuMiBhNCw0IDAgMCAxIDQsNCB2Ni4wIGE0LDQgMCAwIDEgLTQsNCBoLTQ3LjIgeiIgZmlsbD0iIzE5OWU3MCIvPgo8dGV4dCB4PSIyMDcuMiIgeT0iMjU5LjAiIGZvbnQtc2l6ZT0iMTIiIGZpbGw9IiNjM2MyYjciIHRleHQtYW5jaG9yPSJzdGFydCIgZm9udC13ZWlnaHQ9IjQwMCI+MTAlPC90ZXh0Pgo8dGV4dCB4PSIxMzguMCIgeT0iMzAzLjUiIGZvbnQtc2l6ZT0iMTMiIGZpbGw9IiNjM2MyYjciIHRleHQtYW5jaG9yPSJlbmQiIGZvbnQtd2VpZ2h0PSI0MDAiPlJhbmstTW9ydG9uPC90ZXh0Pgo8cGF0aCBkPSJNMTUwLjAsMjc2LjAgaDQwLjIgYTQsNCAwIDAgMSA0LDQgdjYuMCBhNCw0IDAgMCAxIC00LDQgaC00MC4yIHoiIGZpbGw9IiMzOTg3ZTUiLz4KPHRleHQgeD0iMjAwLjIiIHk9IjI4Ny4wIiBmb250LXNpemU9IjEyIiBmaWxsPSIjYzNjMmI3IiB0ZXh0LWFuY2hvcj0ic3RhcnQiIGZvbnQtd2VpZ2h0PSI0MDAiPjguOCU8L3RleHQ+CjxwYXRoIGQ9Ik0xNTAuMCwyOTIuMCBoNTYuNyBhNCw0IDAgMCAxIDQsNCB2Ni4wIGE0LDQgMCAwIDEgLTQsNCBoLTU2LjcgeiIgZmlsbD0iI2Q5NTkyNiIvPgo8dGV4dCB4PSIyMTYuNyIgeT0iMzAzLjAiIGZvbnQtc2l6ZT0iMTIiIGZpbGw9IiNjM2MyYjciIHRleHQtYW5jaG9yPSJzdGFydCIgZm9udC13ZWlnaHQ9IjQwMCI+MTIlPC90ZXh0Pgo8cGF0aCBkPSJNMTUwLjAsMzA4LjAgaDc5LjYgYTQsNCAwIDAgMSA0LDQgdjYuMCBhNCw0IDAgMCAxIC00LDQgaC03OS42IHoiIGZpbGw9IiMxOTllNzAiLz4KPHRleHQgeD0iMjM5LjYiIHk9IjMxOS4wIiBmb250LXNpemU9IjEyIiBmaWxsPSIjYzNjMmI3IiB0ZXh0LWFuY2hvcj0ic3RhcnQiIGZvbnQtd2VpZ2h0PSI0MDAiPjE3JTwvdGV4dD4KPHRleHQgeD0iMTM4LjAiIHk9IjM2My41IiBmb250LXNpemU9IjEzIiBmaWxsPSIjZmZmZmZmIiB0ZXh0LWFuY2hvcj0iZW5kIiBmb250LXdlaWdodD0iNjAwIj5SYW5rLUhpbGJlcnQ8L3RleHQ+CjxwYXRoIGQ9Ik0xNTAuMCwzMzYuMCBoNDMuMCBhNCw0IDAgMCAxIDQsNCB2Ni4wIGE0LDQgMCAwIDEgLTQsNCBoLTQzLjAgeiIgZmlsbD0iIzM5ODdlNSIvPgo8dGV4dCB4PSIyMDMuMCIgeT0iMzQ3LjAiIGZvbnQtc2l6ZT0iMTIiIGZpbGw9IiNjM2MyYjciIHRleHQtYW5jaG9yPSJzdGFydCIgZm9udC13ZWlnaHQ9IjQwMCI+OS40JTwvdGV4dD4KPHBhdGggZD0iTTE1MC4wLDM1Mi4wIGg0Mi44IGE0LDQgMCAwIDEgNCw0IHY2LjAgYTQsNCAwIDAgMSAtNCw0IGgtNDIuOCB6IiBmaWxsPSIjZDk1OTI2Ii8+Cjx0ZXh0IHg9IjIwMi44IiB5PSIzNjMuMCIgZm9udC1zaXplPSIxMiIgZmlsbD0iI2MzYzJiNyIgdGV4dC1hbmNob3I9InN0YXJ0IiBmb250LXdlaWdodD0iNDAwIj45LjQlPC90ZXh0Pgo8cGF0aCBkPSJNMTUwLjAsMzY4LjAgaDQ4LjAgYTQsNCAwIDAgMSA0LDQgdjYuMCBhNCw0IDAgMCAxIC00LDQgaC00OC4wIHoiIGZpbGw9IiMxOTllNzAiLz4KPHRleHQgeD0iMjA4LjAiIHk9IjM3OS4wIiBmb250LXNpemU9IjEyIiBmaWxsPSIjYzNjMmI3IiB0ZXh0LWFuY2hvcj0ic3RhcnQiIGZvbnQtd2VpZ2h0PSI0MDAiPjEwJTwvdGV4dD4KPC9zdmc+Cg==" width="720" height="430" class="img_ev3q"></p>
<p><em>TPC-DS SF100. Lower is better.</em></p>
<p>Using instead three non-skewed columns, the value-space and rank-space methods are around the same. However,
Hilbert consistently beats Morton for the lowest-importance <code>ss_customer_sk</code> column, giving 9-10% across the board,
against a theoretical best of 8.5%. Hilbert is worthwhile where there's a need for all included columns to be
approximately equally-weighted.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="calculation-and-writing-costs">Calculation and writing costs<a href="https://duckstring.com/blog/clustering-for-pipelines#calculation-and-writing-costs" class="hash-link" aria-label="Direct link to Calculation and writing costs" title="Direct link to Calculation and writing costs" translate="no">​</a></h3>
<p>Rank-Hilbert is the clear winner when it comes to performance, but evaluating the curve is complex.
Its use over Morton is only justified if it's not too expensive to create, though in many cases this operation
can be done rarely to mitigate the cost (e.g. during compaction of a large dataset into cold storage).</p>
<p><strong>Time to write the clustered table</strong></p>
<p><img decoding="async" loading="lazy" alt="Time to write the clustered table for each order" src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA3MjAgMjIwIiB3aWR0aD0iNzIwIiBoZWlnaHQ9IjIyMCIgcm9sZT0iaW1nIiBhcmlhLWxhYmVsPSJUaW1lIHRvIHdyaXRlIHRoZSBjbHVzdGVyZWQgdGFibGUgKHMpIiBmb250LWZhbWlseT0iSW50ZXIsIHN5c3RlbS11aSwgLWFwcGxlLXN5c3RlbSwgJ1NlZ29lIFVJJywgc2Fucy1zZXJpZiI+Cjx0aXRsZT5UaW1lIHRvIHdyaXRlIHRoZSBjbHVzdGVyZWQgdGFibGUgKHMpPC90aXRsZT4KPHJlY3QgeT0iMCIgd2lkdGg9IjcyMCIgaGVpZ2h0PSIyMjAiIHJ4PSI4IiBmaWxsPSIjMWExYTE5Ii8+CjxsaW5lIHgxPSIxOTAuMCIgeTE9IjEyIiB4Mj0iMTkwLjAiIHkyPSIxNzYiIHN0cm9rZT0iIzM4MzgzNSIgc3Ryb2tlLXdpZHRoPSIxIi8+Cjx0ZXh0IHg9IjE5MC4wIiB5PSIxOTIuMCIgZm9udC1zaXplPSIxMSIgZmlsbD0iIzhhODk3ZiIgdGV4dC1hbmNob3I9Im1pZGRsZSIgZm9udC13ZWlnaHQ9IjQwMCI+MCBzPC90ZXh0Pgo8bGluZSB4MT0iMzA1LjAiIHkxPSIxMiIgeDI9IjMwNS4wIiB5Mj0iMTc2IiBzdHJva2U9IiMzODM4MzUiIHN0cm9rZS13aWR0aD0iMSIvPgo8dGV4dCB4PSIzMDUuMCIgeT0iMTkyLjAiIGZvbnQtc2l6ZT0iMTEiIGZpbGw9IiM4YTg5N2YiIHRleHQtYW5jaG9yPSJtaWRkbGUiIGZvbnQtd2VpZ2h0PSI0MDAiPjYwIHM8L3RleHQ+CjxsaW5lIHgxPSI0MjAuMCIgeTE9IjEyIiB4Mj0iNDIwLjAiIHkyPSIxNzYiIHN0cm9rZT0iIzM4MzgzNSIgc3Ryb2tlLXdpZHRoPSIxIi8+Cjx0ZXh0IHg9IjQyMC4wIiB5PSIxOTIuMCIgZm9udC1zaXplPSIxMSIgZmlsbD0iIzhhODk3ZiIgdGV4dC1hbmNob3I9Im1pZGRsZSIgZm9udC13ZWlnaHQ9IjQwMCI+MTIwIHM8L3RleHQ+CjxsaW5lIHgxPSI1MzUuMCIgeTE9IjEyIiB4Mj0iNTM1LjAiIHkyPSIxNzYiIHN0cm9rZT0iIzM4MzgzNSIgc3Ryb2tlLXdpZHRoPSIxIi8+Cjx0ZXh0IHg9IjUzNS4wIiB5PSIxOTIuMCIgZm9udC1zaXplPSIxMSIgZmlsbD0iIzhhODk3ZiIgdGV4dC1hbmNob3I9Im1pZGRsZSIgZm9udC13ZWlnaHQ9IjQwMCI+MTgwIHM8L3RleHQ+CjxsaW5lIHgxPSI2NTAuMCIgeTE9IjEyIiB4Mj0iNjUwLjAiIHkyPSIxNzYiIHN0cm9rZT0iIzM4MzgzNSIgc3Ryb2tlLXdpZHRoPSIxIi8+Cjx0ZXh0IHg9IjY1MC4wIiB5PSIxOTIuMCIgZm9udC1zaXplPSIxMSIgZmlsbD0iIzhhODk3ZiIgdGV4dC1hbmNob3I9Im1pZGRsZSIgZm9udC13ZWlnaHQ9IjQwMCI+MjQwIHM8L3RleHQ+Cjx0ZXh0IHg9IjE3OC4wIiB5PSIyOS41IiBmb250LXNpemU9IjEzIiBmaWxsPSIjYzNjMmI3IiB0ZXh0LWFuY2hvcj0iZW5kIiBmb250LXdlaWdodD0iNDAwIj5ObyBzb3J0PC90ZXh0Pgo8cGF0aCBkPSJNMTkwLjAsMTguMCBoNTYuMSBhNCw0IDAgMCAxIDQsNCB2Ni4wIGE0LDQgMCAwIDEgLTQsNCBoLTU2LjEgeiIgZmlsbD0iIzZmNmU2NiIvPgo8dGV4dCB4PSIyNTYuMSIgeT0iMjkuMCIgZm9udC1zaXplPSIxMiIgZmlsbD0iI2MzYzJiNyIgdGV4dC1hbmNob3I9InN0YXJ0IiBmb250LXdlaWdodD0iNDAwIj4zMSBzPC90ZXh0Pgo8dGV4dCB4PSIxNzguMCIgeT0iNTcuNSIgZm9udC1zaXplPSIxMyIgZmlsbD0iI2MzYzJiNyIgdGV4dC1hbmNob3I9ImVuZCIgZm9udC13ZWlnaHQ9IjQwMCI+U29ydGVkIChsZXhpY29ncmFwaGljKTwvdGV4dD4KPHBhdGggZD0iTTE5MC4wLDQ2LjAgaDMwNi4zIGE0LDQgMCAwIDEgNCw0IHY2LjAgYTQsNCAwIDAgMSAtNCw0IGgtMzA2LjMgeiIgZmlsbD0iIzZmNmU2NiIvPgo8dGV4dCB4PSI1MDYuMyIgeT0iNTcuMCIgZm9udC1zaXplPSIxMiIgZmlsbD0iI2MzYzJiNyIgdGV4dC1hbmNob3I9InN0YXJ0IiBmb250LXdlaWdodD0iNDAwIj4xNjIgczwvdGV4dD4KPHRleHQgeD0iMTc4LjAiIHk9Ijg1LjUiIGZvbnQtc2l6ZT0iMTMiIGZpbGw9IiNjM2MyYjciIHRleHQtYW5jaG9yPSJlbmQiIGZvbnQtd2VpZ2h0PSI0MDAiPk1vcnRvbjwvdGV4dD4KPHBhdGggZD0iTTE5MC4wLDc0LjAgaDM0My4zIGE0LDQgMCAwIDEgNCw0IHY2LjAgYTQsNCAwIDAgMSAtNCw0IGgtMzQzLjMgeiIgZmlsbD0iIzZmNmU2NiIvPgo8dGV4dCB4PSI1NDMuMyIgeT0iODUuMCIgZm9udC1zaXplPSIxMiIgZmlsbD0iI2MzYzJiNyIgdGV4dC1hbmNob3I9InN0YXJ0IiBmb250LXdlaWdodD0iNDAwIj4xODEgczwvdGV4dD4KPHRleHQgeD0iMTc4LjAiIHk9IjExMy41IiBmb250LXNpemU9IjEzIiBmaWxsPSIjYzNjMmI3IiB0ZXh0LWFuY2hvcj0iZW5kIiBmb250LXdlaWdodD0iNDAwIj5IaWxiZXJ0PC90ZXh0Pgo8cGF0aCBkPSJNMTkwLjAsMTAyLjAgaDQwOC40IGE0LDQgMCAwIDEgNCw0IHY2LjAgYTQsNCAwIDAgMSAtNCw0IGgtNDA4LjQgeiIgZmlsbD0iIzZmNmU2NiIvPgo8dGV4dCB4PSI2MDguNCIgeT0iMTEzLjAiIGZvbnQtc2l6ZT0iMTIiIGZpbGw9IiNjM2MyYjciIHRleHQtYW5jaG9yPSJzdGFydCIgZm9udC13ZWlnaHQ9IjQwMCI+MjE1IHM8L3RleHQ+Cjx0ZXh0IHg9IjE3OC4wIiB5PSIxNDEuNSIgZm9udC1zaXplPSIxMyIgZmlsbD0iI2MzYzJiNyIgdGV4dC1hbmNob3I9ImVuZCIgZm9udC13ZWlnaHQ9IjQwMCI+UmFuay1Nb3J0b248L3RleHQ+CjxwYXRoIGQ9Ik0xOTAuMCwxMzAuMCBoMzc3LjQgYTQsNCAwIDAgMSA0LDQgdjYuMCBhNCw0IDAgMCAxIC00LDQgaC0zNzcuNCB6IiBmaWxsPSIjNmY2ZTY2Ii8+Cjx0ZXh0IHg9IjU3Ny40IiB5PSIxNDEuMCIgZm9udC1zaXplPSIxMiIgZmlsbD0iI2MzYzJiNyIgdGV4dC1hbmNob3I9InN0YXJ0IiBmb250LXdlaWdodD0iNDAwIj4xOTkgczwvdGV4dD4KPHRleHQgeD0iMTc4LjAiIHk9IjE2OS41IiBmb250LXNpemU9IjEzIiBmaWxsPSIjZmZmZmZmIiB0ZXh0LWFuY2hvcj0iZW5kIiBmb250LXdlaWdodD0iNjAwIj5SYW5rLUhpbGJlcnQ8L3RleHQ+CjxwYXRoIGQ9Ik0xOTAuMCwxNTguMCBoMzg0LjUgYTQsNCAwIDAgMSA0LDQgdjYuMCBhNCw0IDAgMCAxIC00LDQgaC0zODQuNSB6IiBmaWxsPSIjMzk4N2U1Ii8+Cjx0ZXh0IHg9IjU4NC41IiB5PSIxNjkuMCIgZm9udC1zaXplPSIxMiIgZmlsbD0iI2MzYzJiNyIgdGV4dC1hbmNob3I9InN0YXJ0IiBmb250LXdlaWdodD0iNDAwIj4yMDMgczwvdGV4dD4KPC9zdmc+Cg==" width="720" height="220" class="img_ev3q"></p>
<p><em>TPC-DS SF100, clustered on (<code>ss_sold_date_sk</code>, <code>ss_net_paid</code>), 7 bits per column. One run each.</em></p>
<p>This shows the results of one run - note that between-run variation was around 20%, so much of the performance difference is
hidden behind noise. The ranks need not be exact, so the ranking part of the process used approximate quantiles.</p>
<p>Writing the table without sorting took around 30 seconds. The lexicographic sort approximates the minimum that could be
expected by any sorted write, taking around 160 seconds - anything beyond this is likely the evaluation time.
Morton added around 20s, while rank-Morton was another 20s beyond that. The ranking itself appears to cost
around 20s - though closer to 12s when measured directly below.</p>
<p>Interestingly, rank-Hilbert took around the same time as rank-Morton, indicating that the difference in evaluation
costs is well below the noise.</p>
<p>As each row's key depends only on its own values, DuckDB is able to stream the calculation rather than requiring the
entire table to be held in memory. The memory costs are therefore surprisingly light.
Evaluating the <code>approx_quantile</code> pass and key calculation cost directly, to disentangle from sort costs:</p>






























<table><thead><tr><th>Key</th><th>Time</th><th>Peak memory</th></tr></thead><tbody><tr><td>Morton</td><td>3.6 s</td><td>130 MB</td></tr><tr><td>Hilbert</td><td>7.1 s</td><td>150 MB</td></tr><tr><td>Rank-Morton</td><td>16 s</td><td>220 MB</td></tr><tr><td>Rank-Hilbert</td><td>20 s</td><td>260 MB</td></tr></tbody></table>
<p>From a memory perspective there is minimal difference. Compared to Morton, the Rank-Hilbert method adds no more than 20%
to total write time.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="sorting-within-files">Sorting within files<a href="https://duckstring.com/blog/clustering-for-pipelines#sorting-within-files" class="hash-link" aria-label="Direct link to Sorting within files" title="Direct link to Sorting within files" translate="no">​</a></h3>
<p>The Delta Lake implementation doesn't sort rows within a file by default. It range-partitions rows into files by the curve key, so
each file covers a slice of the curve, and leaves the rows inside each file unordered. That makes sense for Delta,
which records each file's min and max in its transaction log and skips whole files without opening them - on object
storage, that saves a request per file. I tried the same, partitioning into files of 256 MB and 1 GB:</p>
<p><strong>Share of rows read when sorting inside files, or only between them</strong></p>
<p><img decoding="async" loading="lazy" alt="Share of rows read when rows are sorted within files, against files that only cover key ranges, at 256 MB and 1 GB" src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA3MjAgMzEwIiB3aWR0aD0iNzIwIiBoZWlnaHQ9IjMxMCIgcm9sZT0iaW1nIiBhcmlhLWxhYmVsPSJTaGFyZSBvZiByb3dzIHJlYWQ6IHNvcnRpbmcgaW5zaWRlIGZpbGVzLCBvciBvbmx5IGJldHdlZW4gdGhlbSIgZm9udC1mYW1pbHk9IkludGVyLCBzeXN0ZW0tdWksIC1hcHBsZS1zeXN0ZW0sICdTZWdvZSBVSScsIHNhbnMtc2VyaWYiPgo8dGl0bGU+U2hhcmUgb2Ygcm93cyByZWFkOiBzb3J0aW5nIGluc2lkZSBmaWxlcywgb3Igb25seSBiZXR3ZWVuIHRoZW08L3RpdGxlPgo8cmVjdCB5PSIwIiB3aWR0aD0iNzIwIiBoZWlnaHQ9IjMxMCIgcng9IjgiIGZpbGw9IiMxYTFhMTkiLz4KPHJlY3QgeD0iMTgwIiB5PSIxNCIgd2lkdGg9IjEyIiBoZWlnaHQ9IjEyIiByeD0iMyIgZmlsbD0iIzM5ODdlNSIvPgo8dGV4dCB4PSIxOTguMCIgeT0iMjQuMCIgZm9udC1zaXplPSIxMyIgZmlsbD0iI2MzYzJiNyIgdGV4dC1hbmNob3I9InN0YXJ0IiBmb250LXdlaWdodD0iNDAwIj5Tb3J0ZWQgd2l0aGluIGZpbGVzPC90ZXh0Pgo8cmVjdCB4PSIzNDIuOCIgeT0iMTQiIHdpZHRoPSIxMiIgaGVpZ2h0PSIxMiIgcng9IjMiIGZpbGw9IiNkOTU5MjYiLz4KPHRleHQgeD0iMzYwLjgiIHk9IjI0LjAiIGZvbnQtc2l6ZT0iMTMiIGZpbGw9IiNjM2MyYjciIHRleHQtYW5jaG9yPSJzdGFydCIgZm9udC13ZWlnaHQ9IjQwMCI+RmlsZXMgYnkga2V5IHJhbmdlLCAyNTYgTUI8L3RleHQ+CjxyZWN0IHg9IjU1Ni4wIiB5PSIxNCIgd2lkdGg9IjEyIiBoZWlnaHQ9IjEyIiByeD0iMyIgZmlsbD0iIzE5OWU3MCIvPgo8dGV4dCB4PSI1NzQuMCIgeT0iMjQuMCIgZm9udC1zaXplPSIxMyIgZmlsbD0iI2MzYzJiNyIgdGV4dC1hbmNob3I9InN0YXJ0IiBmb250LXdlaWdodD0iNDAwIj5GaWxlcyBieSBrZXkgcmFuZ2UsIDEgR0I8L3RleHQ+CjxsaW5lIHgxPSIxODAuMCIgeTE9IjMwIiB4Mj0iMTgwLjAiIHkyPSIyNjYiIHN0cm9rZT0iIzM4MzgzNSIgc3Ryb2tlLXdpZHRoPSIxIi8+Cjx0ZXh0IHg9IjE4MC4wIiB5PSIyODIuMCIgZm9udC1zaXplPSIxMSIgZmlsbD0iIzhhODk3ZiIgdGV4dC1hbmNob3I9Im1pZGRsZSIgZm9udC13ZWlnaHQ9IjQwMCI+MCU8L3RleHQ+CjxsaW5lIHgxPSIyNzQuMCIgeTE9IjMwIiB4Mj0iMjc0LjAiIHkyPSIyNjYiIHN0cm9rZT0iIzM4MzgzNSIgc3Ryb2tlLXdpZHRoPSIxIi8+Cjx0ZXh0IHg9IjI3NC4wIiB5PSIyODIuMCIgZm9udC1zaXplPSIxMSIgZmlsbD0iIzhhODk3ZiIgdGV4dC1hbmNob3I9Im1pZGRsZSIgZm9udC13ZWlnaHQ9IjQwMCI+MTAlPC90ZXh0Pgo8bGluZSB4MT0iMzY4LjAiIHkxPSIzMCIgeDI9IjM2OC4wIiB5Mj0iMjY2IiBzdHJva2U9IiMzODM4MzUiIHN0cm9rZS13aWR0aD0iMSIvPgo8dGV4dCB4PSIzNjguMCIgeT0iMjgyLjAiIGZvbnQtc2l6ZT0iMTEiIGZpbGw9IiM4YTg5N2YiIHRleHQtYW5jaG9yPSJtaWRkbGUiIGZvbnQtd2VpZ2h0PSI0MDAiPjIwJTwvdGV4dD4KPGxpbmUgeDE9IjQ2Mi4wIiB5MT0iMzAiIHgyPSI0NjIuMCIgeTI9IjI2NiIgc3Ryb2tlPSIjMzgzODM1IiBzdHJva2Utd2lkdGg9IjEiLz4KPHRleHQgeD0iNDYyLjAiIHk9IjI4Mi4wIiBmb250LXNpemU9IjExIiBmaWxsPSIjOGE4OTdmIiB0ZXh0LWFuY2hvcj0ibWlkZGxlIiBmb250LXdlaWdodD0iNDAwIj4zMCU8L3RleHQ+CjxsaW5lIHgxPSI1NTYuMCIgeTE9IjMwIiB4Mj0iNTU2LjAiIHkyPSIyNjYiIHN0cm9rZT0iIzM4MzgzNSIgc3Ryb2tlLXdpZHRoPSIxIi8+Cjx0ZXh0IHg9IjU1Ni4wIiB5PSIyODIuMCIgZm9udC1zaXplPSIxMSIgZmlsbD0iIzhhODk3ZiIgdGV4dC1hbmNob3I9Im1pZGRsZSIgZm9udC13ZWlnaHQ9IjQwMCI+NDAlPC90ZXh0Pgo8bGluZSB4MT0iNjUwLjAiIHkxPSIzMCIgeDI9IjY1MC4wIiB5Mj0iMjY2IiBzdHJva2U9IiMzODM4MzUiIHN0cm9rZS13aWR0aD0iMSIvPgo8dGV4dCB4PSI2NTAuMCIgeT0iMjgyLjAiIGZvbnQtc2l6ZT0iMTEiIGZpbGw9IiM4YTg5N2YiIHRleHQtYW5jaG9yPSJtaWRkbGUiIGZvbnQtd2VpZ2h0PSI0MDAiPjUwJTwvdGV4dD4KPHRleHQgeD0iMTY4LjAiIHk9IjYzLjUiIGZvbnQtc2l6ZT0iMTMiIGZpbGw9IiNjM2MyYjciIHRleHQtYW5jaG9yPSJlbmQiIGZvbnQtd2VpZ2h0PSI0MDAiPjElIHJhbmdlIG9uIHNzX25ldF9wYWlkPC90ZXh0Pgo8cGF0aCBkPSJNMTgwLjAsMzYuMCBoMjkuNCBhNCw0IDAgMCAxIDQsNCB2Ni4wIGE0LDQgMCAwIDEgLTQsNCBoLTI5LjQgeiIgZmlsbD0iIzM5ODdlNSIvPgo8dGV4dCB4PSIyMTkuNCIgeT0iNDcuMCIgZm9udC1zaXplPSIxMiIgZmlsbD0iI2MzYzJiNyIgdGV4dC1hbmNob3I9InN0YXJ0IiBmb250LXdlaWdodD0iNDAwIj4zLjYlPC90ZXh0Pgo8cGF0aCBkPSJNMTgwLjAsNTIuMCBoMTM5LjkgYTQsNCAwIDAgMSA0LDQgdjYuMCBhNCw0IDAgMCAxIC00LDQgaC0xMzkuOSB6IiBmaWxsPSIjZDk1OTI2Ii8+Cjx0ZXh0IHg9IjMyOS45IiB5PSI2My4wIiBmb250LXNpemU9IjEyIiBmaWxsPSIjYzNjMmI3IiB0ZXh0LWFuY2hvcj0ic3RhcnQiIGZvbnQtd2VpZ2h0PSI0MDAiPjE1JTwvdGV4dD4KPHBhdGggZD0iTTE4MC4wLDY4LjAgaDIzOS4yIGE0LDQgMCAwIDEgNCw0IHY2LjAgYTQsNCAwIDAgMSAtNCw0IGgtMjM5LjIgeiIgZmlsbD0iIzE5OWU3MCIvPgo8dGV4dCB4PSI0MjkuMiIgeT0iNzkuMCIgZm9udC1zaXplPSIxMiIgZmlsbD0iI2MzYzJiNyIgdGV4dC1hbmNob3I9InN0YXJ0IiBmb250LXdlaWdodD0iNDAwIj4yNiU8L3RleHQ+Cjx0ZXh0IHg9IjE2OC4wIiB5PSIxMjMuNSIgZm9udC1zaXplPSIxMyIgZmlsbD0iI2MzYzJiNyIgdGV4dC1hbmNob3I9ImVuZCIgZm9udC13ZWlnaHQ9IjQwMCI+MSUgcmFuZ2Ugb24gZGF0ZTwvdGV4dD4KPHBhdGggZD0iTTE4MC4wLDk2LjAgaDMzLjMgYTQsNCAwIDAgMSA0LDQgdjYuMCBhNCw0IDAgMCAxIC00LDQgaC0zMy4zIHoiIGZpbGw9IiMzOTg3ZTUiLz4KPHRleHQgeD0iMjIzLjMiIHk9IjEwNy4wIiBmb250LXNpemU9IjEyIiBmaWxsPSIjYzNjMmI3IiB0ZXh0LWFuY2hvcj0ic3RhcnQiIGZvbnQtd2VpZ2h0PSI0MDAiPjQuMCU8L3RleHQ+CjxwYXRoIGQ9Ik0xODAuMCwxMTIuMCBoMTQ2LjAgYTQsNCAwIDAgMSA0LDQgdjYuMCBhNCw0IDAgMCAxIC00LDQgaC0xNDYuMCB6IiBmaWxsPSIjZDk1OTI2Ii8+Cjx0ZXh0IHg9IjMzNi4wIiB5PSIxMjMuMCIgZm9udC1zaXplPSIxMiIgZmlsbD0iI2MzYzJiNyIgdGV4dC1hbmNob3I9InN0YXJ0IiBmb250LXdlaWdodD0iNDAwIj4xNiU8L3RleHQ+CjxwYXRoIGQ9Ik0xODAuMCwxMjguMCBoMjk0LjYgYTQsNCAwIDAgMSA0LDQgdjYuMCBhNCw0IDAgMCAxIC00LDQgaC0yOTQuNiB6IiBmaWxsPSIjMTk5ZTcwIi8+Cjx0ZXh0IHg9IjQ4NC42IiB5PSIxMzkuMCIgZm9udC1zaXplPSIxMiIgZmlsbD0iI2MzYzJiNyIgdGV4dC1hbmNob3I9InN0YXJ0IiBmb250LXdlaWdodD0iNDAwIj4zMiU8L3RleHQ+Cjx0ZXh0IHg9IjE2OC4wIiB5PSIxODMuNSIgZm9udC1zaXplPSIxMyIgZmlsbD0iI2MzYzJiNyIgdGV4dC1hbmNob3I9ImVuZCIgZm9udC13ZWlnaHQ9IjQwMCI+MC4xJSBib3ggb24gYm90aDwvdGV4dD4KPHBhdGggZD0iTTE4MC4wLDE1Ni4wIGgxLjYgYTEuNjQ0MzkwMzI1MzY2Njk1NCwxLjY0NDM5MDMyNTM2NjY5NTQgMCAwIDEgMS42NDQzOTAzMjUzNjY2OTU0LDEuNjQ0MzkwMzI1MzY2Njk1NCB2MTAuNyBhMS42NDQzOTAzMjUzNjY2OTU0LDEuNjQ0MzkwMzI1MzY2Njk1NCAwIDAgMSAtMS42NDQzOTAzMjUzNjY2OTU0LDEuNjQ0MzkwMzI1MzY2Njk1NCBoLTEuNiB6IiBmaWxsPSIjMzk4N2U1Ii8+Cjx0ZXh0IHg9IjE4OS4zIiB5PSIxNjcuMCIgZm9udC1zaXplPSIxMiIgZmlsbD0iI2MzYzJiNyIgdGV4dC1hbmNob3I9InN0YXJ0IiBmb250LXdlaWdodD0iNDAwIj4wLjMlPC90ZXh0Pgo8cGF0aCBkPSJNMTgwLjAsMTcyLjAgaDE5LjQgYTQsNCAwIDAgMSA0LDQgdjYuMCBhNCw0IDAgMCAxIC00LDQgaC0xOS40IHoiIGZpbGw9IiNkOTU5MjYiLz4KPHRleHQgeD0iMjA5LjQiIHk9IjE4My4wIiBmb250LXNpemU9IjEyIiBmaWxsPSIjYzNjMmI3IiB0ZXh0LWFuY2hvcj0ic3RhcnQiIGZvbnQtd2VpZ2h0PSI0MDAiPjIuNSU8L3RleHQ+CjxwYXRoIGQ9Ik0xODAuMCwxODguMCBoNzMuNSBhNCw0IDAgMCAxIDQsNCB2Ni4wIGE0LDQgMCAwIDEgLTQsNCBoLTczLjUgeiIgZmlsbD0iIzE5OWU3MCIvPgo8dGV4dCB4PSIyNjMuNSIgeT0iMTk5LjAiIGZvbnQtc2l6ZT0iMTIiIGZpbGw9IiNjM2MyYjciIHRleHQtYW5jaG9yPSJzdGFydCIgZm9udC13ZWlnaHQ9IjQwMCI+OC4yJTwvdGV4dD4KPHRleHQgeD0iMTY4LjAiIHk9IjI0My41IiBmb250LXNpemU9IjEzIiBmaWxsPSIjYzNjMmI3IiB0ZXh0LWFuY2hvcj0iZW5kIiBmb250LXdlaWdodD0iNDAwIj5Ob3ZlbWJlciBqb2luPC90ZXh0Pgo8cGF0aCBkPSJNMTgwLjAsMjE2LjAgaDQ4LjUgYTQsNCAwIDAgMSA0LDQgdjYuMCBhNCw0IDAgMCAxIC00LDQgaC00OC41IHoiIGZpbGw9IiMzOTg3ZTUiLz4KPHRleHQgeD0iMjM4LjUiIHk9IjIyNy4wIiBmb250LXNpemU9IjEyIiBmaWxsPSIjYzNjMmI3IiB0ZXh0LWFuY2hvcj0ic3RhcnQiIGZvbnQtd2VpZ2h0PSI0MDAiPjUuNiU8L3RleHQ+CjxwYXRoIGQ9Ik0xODAuMCwyMzIuMCBoMjAwLjAgYTQsNCAwIDAgMSA0LDQgdjYuMCBhNCw0IDAgMCAxIC00LDQgaC0yMDAuMCB6IiBmaWxsPSIjZDk1OTI2Ii8+Cjx0ZXh0IHg9IjM5MC4wIiB5PSIyNDMuMCIgZm9udC1zaXplPSIxMiIgZmlsbD0iI2MzYzJiNyIgdGV4dC1hbmNob3I9InN0YXJ0IiBmb250LXdlaWdodD0iNDAwIj4yMiU8L3RleHQ+CjxwYXRoIGQ9Ik0xODAuMCwyNDguMCBoNDM0LjkgYTQsNCAwIDAgMSA0LDQgdjYuMCBhNCw0IDAgMCAxIC00LDQgaC00MzQuOSB6IiBmaWxsPSIjMTk5ZTcwIi8+Cjx0ZXh0IHg9IjYyNC45IiB5PSIyNTkuMCIgZm9udC1zaXplPSIxMiIgZmlsbD0iI2MzYzJiNyIgdGV4dC1hbmNob3I9InN0YXJ0IiBmb250LXdlaWdodD0iNDAwIj40NyU8L3RleHQ+Cjwvc3ZnPgo=" width="720" height="310" class="img_ev3q"></p>
<p><em>TPC-DS SF100, rank-Hilbert key on (<code>ss_sold_date_sk</code>, <code>ss_net_paid</code>). Lower is better.</em></p>
<p>It saved 25 to 40% of the write time, but read 4 to 8 times more rows with 256 MB files, and more again with 1 GB
files. DuckDB prunes at the row group, inside the file, so for DuckDB most of the benefit comes from the order
within each file. When DuckDB is the reader, it's worth sorting the whole thing.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="trying-it-in-plain-duckdb">Trying it in plain DuckDB<a href="https://duckstring.com/blog/clustering-for-pipelines#trying-it-in-plain-duckdb" class="hash-link" aria-label="Direct link to Trying it in plain DuckDB" title="Direct link to Trying it in plain DuckDB" translate="no">​</a></h2>
<p>None of this needs anything special. Here's the same approach in plain DuckDB SQL, ranking with <code>approx_quantile</code>
and walking the ranks with the <code>lindel</code> community extension's <code>hilbert_encode</code>, which the DuckDB post also uses:</p>
<div class="language-sql codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-sql codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">INSTALL lindel </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">FROM</span><span class="token plain"> community</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">LOAD</span><span class="token plain"> lindel</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">COPY </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">WITH</span><span class="token plain"> bounds </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">AS</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">SELECT</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">            approx_quantile</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">ss_sold_date_sk</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain">i </span><span class="token operator">/</span><span class="token plain"> </span><span class="token number">64</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">FOR</span><span class="token plain"> i </span><span class="token operator">IN</span><span class="token plain"> range</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token number">1</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token number">64</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain">::</span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">FLOAT</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">AS</span><span class="token plain"> d</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">            approx_quantile</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">ss_net_paid</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain">     </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain">i </span><span class="token operator">/</span><span class="token plain"> </span><span class="token number">64</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">FOR</span><span class="token plain"> i </span><span class="token operator">IN</span><span class="token plain"> range</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token number">1</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token number">64</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain">::</span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">FLOAT</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">AS</span><span class="token plain"> p</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">FROM</span><span class="token plain"> store_sales</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">SELECT</span><span class="token plain"> s</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token operator">*</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">FROM</span><span class="token plain"> store_sales s</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> bounds</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">ORDER</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">BY</span><span class="token plain"> hilbert_encode</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token function" style="color:rgb(80, 250, 123)">len</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">list_filter</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">d</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> b </span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token plain"> b </span><span class="token operator">&lt;</span><span class="token plain"> s</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">ss_sold_date_sk</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token function" style="color:rgb(80, 250, 123)">len</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">list_filter</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">p</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> b </span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token plain"> b </span><span class="token operator">&lt;</span><span class="token plain"> s</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">ss_net_paid</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain">::UTINYINT</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token number">2</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">TO</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'store_sales_clustered.parquet'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></div></code></pre></div></div>
<p>The <code>list_filter</code> count is each row's bucket: the number of boundaries its value sits above. 64 buckets per column
makes 4,096 cells on the grid, which suits a table of a few thousand row groups. It's worth aiming for roughly one
cell per row group, since finer cells can't help a min/max check. On TPC-DS at scale factor 10 this clustered 29
million rows in about 6 seconds, and a 1% range on <code>ss_net_paid</code> then read 23 of 235 row groups. NULLs land in
bucket 0 here, which is fine for most tables.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="in-duckstring">In Duckstring<a href="https://duckstring.com/blog/clustering-for-pipelines#in-duckstring" class="hash-link" aria-label="Direct link to In Duckstring" title="Direct link to In Duckstring" translate="no">​</a></h2>
<p>As of <code>v0.6.1</code> of <a href="https://duckstring.com/" target="_blank" rel="noopener noreferrer" class="">Duckstring</a>, <code>cluster_by</code> uses the rank-Hilbert order described here. The columns to cluster on
are declared when a table is written:</p>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">pond</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">merge_table</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"sales"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> rows</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> pk</span><span class="token operator">=</span><span class="token string" style="color:rgb(255, 121, 198)">"ticket_id"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> cluster_by</span><span class="token operator">=</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token string" style="color:rgb(255, 121, 198)">"sold_date"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"net_paid"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><br></div></code></pre></div></div>
<p>For merge tables this applies only during compaction, which occurs any time a table's warm, freshness-tagged data
exceeds the existing cold, compacted data (after reaching a minimum size). That keeps recent data recency-ordered (which tends to be most useful in
incremental pipelines), and older data rank-Hilbert-ordered. For overwritten tables (using <code>pond.write_table()</code>), it's
applied at every write.</p>
<p>Duckstring picks the number of buckets from the table's size, so that each cell is just under one row group. It
keeps a separate bucket for NULLs, uses exact quantiles for tables under ten million rows (so small tables come out
identical every time) and approximate ones above that. The
<a class="" href="https://duckstring.com/guides/append_and_merge#clustering-a-merge-table">guide</a> has additional details.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="summary">Summary<a href="https://duckstring.com/blog/clustering-for-pipelines#summary" class="hash-link" aria-label="Direct link to Summary" title="Direct link to Summary" translate="no">​</a></h2>
<ul>
<li class="">In Parquet, the single row order matters enormously for query performance, especially against multiple columns.</li>
<li class="">The Hilbert curve outperforms Morton Z-order slightly, and lexicographic sort dramatically.</li>
<li class="">Ranking each column before mapping helps address skew and unevenness.</li>
<li class="">Write and evaluation times are mild against the minimum time to order.</li>
<li class="">In DuckDB, sorting within files is generally worthwhile.</li>
</ul>]]></content:encoded>
            <category>duckdb</category>
            <category>parquet</category>
            <category>performance</category>
        </item>
    </channel>
</rss>