<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://eribeiro.github.io/feed.xml" rel="self" type="application/atom+xml" /><link href="https://eribeiro.github.io/" rel="alternate" type="text/html" /><updated>2026-09-07T19:20:07+00:00</updated><id>https://eribeiro.github.io/feed.xml</id><title type="html">Edward Ribeiro</title><subtitle>MSc student at University of Utah</subtitle><author><name>Edward Ribeiro</name><email>edward.ribeiro@gmail.com</email></author><entry><title type="html">Sharding vs. Partitioning: When Definitions Got Sliced and Fractured</title><link href="https://eribeiro.github.io/blog/partitioning-vs-sharding/" rel="alternate" type="text/html" title="Sharding vs. Partitioning: When Definitions Got Sliced and Fractured" /><published>2026-09-06T00:00:00+00:00</published><updated>2026-09-06T00:00:00+00:00</updated><id>https://eribeiro.github.io/blog/partitioning-vs-sharding</id><content type="html" xml:base="https://eribeiro.github.io/blog/partitioning-vs-sharding/"><![CDATA[<center>
<div style="display: flex; justify-content: center; align-items: center; gap: 20px; flex-wrap: wrap;">
<img src="/images/posts/partitioning_vs_sharding/fruit-ninja.gif" alt="fruit-ninja" style="width: 40%;" />
<img src="/images/posts/partitioning_vs_sharding/giphy.gif" alt="giphy" style="width: 40%;" />
</div>
<br />
<br />
</center>

<p>As I slowly, <em>and reluctantly</em>, try to make a comeback to microblogging sites after many years out, I stumbled across the following tweet in my very first days there:</p>

<center>
<img src="/images/posts/partitioning_vs_sharding/twitter-post.png" alt="Twitter post" style="width: 50%;" />
<br />
<br />
</center>

<p>For a long time, I used <em>sharding</em> and <em>partitioning</em> as interchangeable concepts, almost like synonyms, even though <em>in my head</em> sharding has always been an <strong>industry-crafted term</strong> that heavily implied scaled-out multi-node architectures while partitioning was the more general concept above it. But to my surprise, many replies to this particular tweet were saying essentially this:</p>

<blockquote>
  <p><em>“Partitioning splits data within a single server, while sharding distributes data accross multiple servers to scale horizontally.”</em></p>
</blockquote>

<p>The same adage, expressed by different individuals, indicated a more solid, ingrained or established definition that went straight under my radar this whole time! I was mesmerized… Maybe something changed while I was out, hahah? Maybe I didn’t get the memo back then?</p>

<p>In a previous life, this small discordance would immediately engage me on a real-life version of that famous <a href="https://xkcd.com/386/">XKCD cartoon</a>:</p>

<center>
<img src="/images/posts/partitioning_vs_sharding/duty_calls_2x.png" alt="Duty Calls XKCD" style="width: 40%;" />
<br />
<br />
</center>

<p>But jumping headfirst into heated discussions for countless hours in a medium of limited expressiveness feels so dated, tiresome and, well, wasteful… Therefore, I decided to hit the respectable literature and products’ documentations, so that I could challenge my own (mis-)understandings, fill in the gaps, and <em>maybe</em> write an educational material in the process.</p>

<p><em>And for what’s worth, a genuine and relevant doubt unfolded itself into a nice oportunity to review some crucial concepts in databases and distributed systems!</em> 😄</p>

<center>
<img src="/images/posts/partitioning_vs_sharding/duty_calls_3x.jpg" alt="Duty Calls 2 XKCD" style="width: 50%;" />
<br />
<br />
</center>

<div class="custom-box">
<p>To cut to the chase, there's no really right or wrong side. The <i>"partitioning is within one node; sharding is across nodes"</i> definition is a <strong>useful convention</strong> adopted by some database products and communities, <strong>but it is not an universal definition,</strong> and it is definitely <strong>not how the broader distributed (database) systems' communities define partitioning and sharding.</strong> The terminology became <i>muddy</i> because different database traditions evolved their vocabulary independently. Alas, <i>a similar thing</i> happened with <a href="/blog/clustered-index-vs-clustering-index/">clustered vs. clustering indexes</a>! 😝</p>
</div>

<!-- **UPDATE [09-05-2026]**: while wrapping up this post, I stumbled upon yet another post mentioning the aforementioned distinction between partitioning and sharding... Maybe this could be a timely blog post, after all? 😏

<center>
<img src="/images/posts/partitioning_vs_sharding/threads-post.png" alt="Duty Calls 2 XKCD" style="width: 40%;">
<br/>
<br/>
</center> -->

<p>To kickstart the discussion, let’s see how this kind of intermingling between partitioning and sharding definitions can be witnessed <em>nowadays</em> on many real-world systems’ terminologies. In the sample listing of <a href="#table-1">Table 1</a>, <em>“partition”</em> is being used to describe both single-node systems (e.g. PostgreSQL) and multi-node systems (e.g. DynamoDB), with a few other synonym terms (e.g., tablet and region) thrown into the mix! Therefore, <strong>the assertion that partitions are only for single nodes just doesn’t stand up to a closer look.</strong> :smiling_face_with_tear:</p>

<table>
  <thead>
    <tr>
      <th>System</th>
      <th>Name</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><a href="https://www.allthingsdistributed.com/files/amazon-dynamo-sosp2007.pdf">Dynamo</a></td>
      <td>partition</td>
    </tr>
    <tr>
      <td><a href="https://cassandra.apache.org/_/cassandra-basics.html">Cassandra</a></td>
      <td>partition / token range</td>
    </tr>
    <tr>
      <td><a href="https://kafka.apache.org/intro/">Kafka</a></td>
      <td>partition</td>
    </tr>
    <tr>
      <td><a href="https://docs.cloud.google.com/bigtable/docs/overview">Bigtable</a></td>
      <td>tablet</td>
    </tr>
    <tr>
      <td><a href="https://hbase.apache.org/book.html">HBase</a></td>
      <td>region</td>
    </tr>
    <tr>
      <td><a href="https://www.cockroachlabs.com/docs/stable/architecture/distribution-layer">CockroachDB</a></td>
      <td>range</td>
    </tr>
    <tr>
      <td><a href="https://www.mongodb.com/docs/manual/core/sharding-data-partitioning/">MongoDB</a></td>
      <td>shard / chunk</td>
    </tr>
    <tr>
      <td><a href="https://www.elastic.co/docs/deploy-manage/distributed-architecture/clusters-nodes-shards">Elasticsearch</a></td>
      <td>shard</td>
    </tr>
    <tr>
      <td><a href="https://solr.apache.org/guide/solr/latest/deployment-guide/solrcloud-shards-indexing.html">SolrCloud</a></td>
      <td>shard</td>
    </tr>
    <tr>
      <td><a href="https://www.postgresql.org/docs/current/ddl-partitioning.html">PostgreSQL</a></td>
      <td>partition</td>
    </tr>
    <tr>
      <td><a href="https://docs.oracle.com/en/database/oracle/oracle-database/26/vldbg/partition-admin.html">Oracle</a></td>
      <td>partition</td>
    </tr>
    <tr>
      <td><a href="https://vitess.io/docs/25.0/concepts/shard/">Vitess</a></td>
      <td>shard</td>
    </tr>
    <tr>
      <td><a href="https://docs.cloud.google.com/spanner/docs/schema-design">Spanner</a></td>
      <td>split</td>
    </tr>
    <tr>
      <td><a href="https://docs.pingcap.com/tidb/stable/tikv-overview/">TiDB</a></td>
      <td>region</td>
    </tr>
    <tr>
      <td><a href="https://docs.yugabyte.com/stable/architecture/docdb-sharding/">YugaByteDB</a></td>
      <td>tablet</td>
    </tr>
    <tr>
      <td><a href="https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/HowItWorks.Partitions.html">DynamoDB</a></td>
      <td>partition</td>
    </tr>
    <tr>
      <td><a href="https://iceberg.apache.org/docs/latest/partitioning/">Apache Iceberg</a></td>
      <td>partition</td>
    </tr>
    <tr>
      <td><a href="https://docs.voltdb.com/UsingVoltDB/DesignPartition.php">H-Store/VoltDB</a><sup id="fnref:voltdb" role="doc-noteref"><a href="#fn:voltdb" class="footnote" rel="footnote">1</a></sup></td>
      <td>partition</td>
    </tr>
  </tbody>
</table>

<p><a id="table-1"></a></p>

<p><strong>Table 1 - Real world systems’ terminologies</strong></p>

<h2 id="then-why-do-people-say-partitioning--one-machine">Then why do people say “partitioning = one machine”?</h2>

<p><em>Probably</em> because of <strong>the commercial database vendors’ terminology and the dot-com era’s web companies</strong>. The <em>“partitioning = single node, sharding = multiple nodes”</em> distinction <em>seems</em> to have emerged from these two different, but related, arenas, as we should see shortly.</p>

<h4 id="the-commercial-dbms-prism-lens">The commercial DBMS’ prism lens</h4>

<p>On one side, we had commercial DBMSs popularizing and strongly supporting, to this day, <strong>table partitioning</strong> as a product feature for subdividing one logical table into physical pieces under a single database installation. <a href="https://docs.oracle.com/cd/B13789_01/server.101/b10743/partconc.htm">Oracle 10g</a> describes partitioning as decomposing a large table or index into smaller pieces called partitions. <a href="https://www.postgresql.org/docs/18/ddl-partitioning.html">PostgreSQL</a> uses essentially the same product-level vocabulary: <em>“splitting what is logically one large table into smaller physical pieces.”</em> We also had other instances of this trend in products like <a href="https://learn.microsoft.com/en-us/sql/relational-databases/partitions/partitioned-tables-and-indexes">SQL Server</a>, <a href="https://dev.mysql.com/doc/refman/9.7/en/partitioning.html">MySQL</a>, and <a href="https://www.ibm.com/docs/en/db2/11.5.x?topic=tables-table-partitioning">Db2</a>, for example.</p>

<p>So, when a DBA in the 2000s said that <em>“we partitioned the orders table”</em>, they usually meant a native DBMS feature like this:</p>

<p><a href="/images/posts/partitioning_vs_sharding/table-partitioning.png"><img src="/images/posts/partitioning_vs_sharding/table-partitioning.png" alt="table partitioning" /></a></p>

<p>These features often look like:</p>

<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">CREATE</span> <span class="k">TABLE</span> <span class="n">orders</span> <span class="p">(...)</span>
<span class="k">PARTITION</span> <span class="k">BY</span> <span class="k">RANGE</span> <span class="p">(</span><span class="nb">timestamp</span><span class="p">);</span>
</code></pre></div></div>

<h4 id="the-web-scale-systems-prism-lens">The web-scale systems’ prism lens</h4>

<p>On the other side, also during the 2000s, web companies faced a somewhat different problem due to the exponential growth of the web: a single database server couldn’t support all their users’ data and load, so web companies started using the term <em>sharding</em> specifically for describing the distribution of subsets of the application’s data over multiple database servers. Sharding was the operational term for companies like eBay, Yahoo!, Facebook, among others. An old engineering post by <a href="https://code.flickr.net/2010/02/08/ticket-servers-distributed-unique-primary-keys-on-the-cheap">Flickr</a> summarises how many companies operated back in the day:</p>

<blockquote>
  <p><em>“<strong>Sharding</strong> (aka <strong>data partioning</strong>) is how we scale Flickr’s datastore. Instead of storing all our data on one really big database, we have lots of databases, each with some of the data, and spread the load between them.”</em></p>
</blockquote>

<p>Back in the day, when an eBay/Flickr/Facebook/Google engineer said that <em>“we sharded users”</em>, they usually meant something like:</p>

<p><a href="/images/posts/partitioning_vs_sharding/sharding-architecture.png"><img src="/images/posts/partitioning_vs_sharding/sharding-architecture.png" alt="sharding architecture" /></a></p>

<p>And later, during the 2010s, the “sharding” term solidified through the success of NoSQL systems such as MongoDB and Riak, for example. <a href="https://www.mongodb.com/docs/manual/sharding">MongoDB documentation</a> states that <em>“Sharding is a method for distributing data across multiple machines.”</em></p>

<p>All of this produced an extremely convenient <strong>practitioner</strong> distinction:</p>

<blockquote>
  <p>Partitioning = split a table inside the database.
Sharding = split the database across machines.</p>
</blockquote>

<p>There was a sociotechnical reason for the distinction: those two activities had radically different operational implications and consequences, so having two words was useful. The distinction is pedagogically convenient and historically understandable, but people eventually started treating the convention as if it were the formal definition. That is, the problem arose when the useful practitioner distinction became retroactively interpreted as a definition of partitioning itself, something that many decades of study and research on databases never supported.</p>

<p>Nevertheless, there are examples in the industry that acknowledge the relation between partitioning and sharding. <a href="https://vitess.io/docs/archive/14.0/reference/features/sharding">Vitess documentation</a> is more explicit and intellectually cleaner, as it does not treat partitioning and sharding as mutually exclusive:</p>

<blockquote>
  <p><em>“<strong>Sharding</strong> is a method of <strong>horizontally partitioning</strong> a database to store data across two or more database servers.”</em></p>
</blockquote>

<p>And <a href="https://docs.oracle.com/en/database/oracle/oracle-database/12.2/admin/sharding-physical-organization.html">Oracle’s sharding documentation</a> has a section literally titled <em>“Sharding as Distributed Partitioning”</em>, showing that sharding is based on horizontal partitioning of data across multiple independent physical databases. It further says that table partitions within a shard are the same kind of partitions used in a non-sharded Oracle database.</p>

<p>But things are far from being settled. Google Cloud currently has explanatory material saying that partitioning keeps pieces on the same server whereas sharding places them on different servers. Its own <a href="https://cloud.google.com/discover/what-is-database-sharding">explainer on data sharding</a> draws the line almost as cleanly as the tweet that started all this:</p>

<blockquote>
  <p><em>“<strong>Sharding is a specific type of horizontal partitioning</strong> where the data pieces are distributed across completely different servers… Partitioning involves splitting a large table into smaller, more manageable pieces (like splitting a log table by month) but keeping them on the same server instance.”</em></p>
</blockquote>

<p>But a few paragraphs further down <strong>the very same page</strong>, it describes Spanner — a “distributed SQL database” — as offering <em>“a ‘no-sharding’ experience”</em> that <em>“automatically shards data and balances load across regions,”</em> and credits Bigtable’s tablet splitting as “automatic sharding” too. And the same Google ecosystem also talks about <a href="https://cloud.google.com/spanner/docs/geo-partitioning">geo-partitioning</a>, which <em>“lets you further segment and store rows in your database table across different instance configurations,”</em> and about <a href="https://cloud.google.com/spanner/docs/create-manage-partitions">distributed Spanner instance partitions</a>, which are explicitly regional or multi-region constructs, each with their own compute capacity, spanning multiple servers by design. So even industry terminology isn’t internally uniform — sometimes not even within the same web page!</p>

<p>Therefore, saying <em>“partitioning means single node”</em> without qualification is historically and technically misleading. A more precise statement is:</p>

<blockquote>
  <p><em>“In some RDBMS discussions, partitioning conventionally means intra-database table partitioning, while sharding means horizontal partitioning across database instances.”</em></p>
</blockquote>

<p>And even though partitioning was never inherently single-node in the literature, Silberschatz, Korth &amp; Sudarshan<a href="#ref-silberschatz2020">[3]</a> acknowledge that partitions may have such a narrower meaning:</p>

<blockquote>
  <p><em>“We also note that several database vendors use the term partitioning to denote the partitioning of tuples of a relation \(r\) into multiple physical relations \(r_1, r_2 ,…, r_n\), where all the physical relations \(r_i\) are stored in a single node. The relation \(r\) is not stored, but treated as a view defined by the query \(r_1 \cup r_2 \cup … \cup r_n\). Such intra-node partitioning of a relation is typically used to ensure that frequently accessed tuples are stored separately from infrequently accessed tuples and is different from horizontal partitioning across nodes.”</em></p>
</blockquote>

<p>Kleppmann and Riccomini<a href="#ref-kleppmann2026">[4]</a>, who devote no less than a whole chapter to sharding, also explain the nonconformity of the nomenclature surrounding sharding and partition:</p>

<blockquote>
  <p><em>“<strong>What we call a shard in this chapter has many names depending on which software you’re using.</strong> It’s called a partition in Kafka, a range in CockroachDB, a region in HBase and TiDB, a vBucket in Couchbase, a vnode in Riak, a token-range in Cassandra, and a tablet in Bigtable, YugaByteDB, and ScyllaDB, to name just a few.</em>
<em>Some databases treat partitions and shards as two distinct concepts. For example, <strong>in PostgreSQL, partitioning is a way of splitting a large table into several files that are stored on the same machine</strong> (which has several advantages, such as making it very fast to delete an entire partition), whereas sharding splits a dataset accross multiple machines[1,2]. <strong>In many other systems, partitioning is just another word for sharding.</strong>“</em></p>
</blockquote>

<p>So far, we have two reputable sources stating that:</p>

<ul>
  <li><em>a)</em> there is no clear separation between the concepts of partitioning and sharding, either in industry or academia, up to the point that the two terms are often used as synonyms (<em>phew!</em> 😅);</li>
  <li><em>b)</em> what is usually known as a partition can also mean something confined to a single node!</li>
</ul>

<h3 id="how-has-academia-treated-these-concepts">How has academia treated these concepts?</h3>

<p>To really understand how academia has dealt with these concepts for decades, we should focus on their vernacular and their assumptions first. It may even come as a surprise to newcomers that what the practitioners usually call <em>“partitioning”</em> is frequently referred to by respectable sources in the literature as <em>“fragmentation”</em>. And the terminology mismatch doesn’t stop there, as we have words like <em>“sites”</em> for denoting nodes or computers, for example.</p>

<p>In addition to that, it was already mentioned that classical and modern database literature doesn’t restrict the applicability of data partitioning to an intra-node setup. Also, data partitioning can be applied to either parallel or distributed database systems (and arrays of disks too!). As explained by Özsu and Valduriez<a href="#ref-ozsu2020">[2]</a>, the main reasons and objectives for fragmentation in distributed versus parallel databases are slightly different. For example, data localization is not that much of a concern in parallel DBMSs since the communication cost among nodes is much less than in geo-distributed DBMSs.</p>

<h2 id="fragmentation-allocation-and-replication">Fragmentation, Allocation and Replication</h2>
<center>
<img src="/images/posts/partitioning_vs_sharding/good-bad-ugly-movie.gif" alt="The Good, The Bad and The Ugly" style="width: 70%;" />
<br />
<br />
</center>

<p>The academic distributed/database literature cleanly separates the concepts of <em><strong>Fragmentation, Allocation, and Replication</strong></em> (or should we say <a href="https://en.wikipedia.org/wiki/The_Good,_the_Bad_and_the_Ugly"><em>“The Good, The Bad and The Ugly”</em></a>?) as three related but orthogonal axes of classification (see <a href="#figure-1">Figure 1</a>), with the following definitions:</p>

<div class="custom-box">
  <p><strong>- Fragmentation/Partitioning</strong>: the techniques for breaking a database <strong>relation (table)</strong> into logical units, called <strong>fragments (partitions)</strong>, and <i>maybe</i> distributing them among <strong>sites (nodes)</strong> or RAID disk arrays, for example;</p>
  <p><strong>- Allocation (Placement):</strong> the process that maps, <i>for each fragment</i>, the sites at which it is stored;</p>
  <p><strong>- Replication:</strong> the techniques that allow the fragments and its copies to be distributed in one or more sites to improve availability and performance;</p>
</div>

<p><a id="figure-1"></a></p>

<p><a href="/images/posts/partitioning_vs_sharding/three-axis.png"><img src="/images/posts/partitioning_vs_sharding/three-axis.png" alt="Three axes of classification" /></a></p>

<p><strong>Figure 1 - Three axes of classification</strong></p>

<h3 id="1-partitioning">1. Partitioning</h3>

<p>Ramakrishnan and Gehrke<a href="#ref-ramakrishnan2003">[1]</a> define fragmentation (i.e., partitioning) as:</p>

<blockquote>
  <p><em>“<strong>Fragmentation</strong> consists of breaking a relation into smaller relations or <strong>fragments</strong> and storing the fragments (instead of the relation itself), possibly at different sites. In <strong>horizontal fragmentation</strong>, each fragment consists of a subset of rows of the original relation. In <strong>vertical fragmentation</strong>, each fragment consists of a subset of columns of the original relation.”</em></p>
</blockquote>

<p>So, this definition can be summarised in a short paragraph (or tweet!) as:</p>

<blockquote>
  <p><em>Partitioning means dividing a dataset into disjoint subsets according to some rule.</em></p>
</blockquote>

<p><em>The partitions (fragments) are defined as the logical unit of division and distribution in a database</em>, so that a relation \(R\) is split into (<em>usually</em>) disjoint \(P_i\) partitions over one or more attributes (called <em>partitioning attributes, partitioning keys or shard keys</em>) of \(R\), such that:</p>

\[R = P_1 \cup P_2 \cup P_3 \cup ... \cup P_n\]

<p>This definition doesn’t conditionate partitioning of data to its  placement, be it local or distributed among nodes. Those partitions \(P_{i}\), for \(i = \{1,.., n\}\) could either reside on a single node,</p>

<p><a href="/images/posts/partitioning_vs_sharding/single-node-partition.png"><img src="/images/posts/partitioning_vs_sharding/single-node-partition.png" alt="single node partition" /></a></p>

<p>or be distributed among various nodes,</p>

<p><a href="/images/posts/partitioning_vs_sharding/multi-node-partition.png"><img src="/images/posts/partitioning_vs_sharding/multi-node-partition.png" alt="multi-node partition" /></a></p>

<p>or even be a combination of single and multi-node, but, in any case, they are still <em>partitions</em>.</p>

<h4 id="11-partitioning-strategies">1.1. Partitioning Strategies</h4>

<p>As seen in Ramakrishnan and Gehrke’s definition, data partitioning is further divided into horizontal and vertical partitioning, and this is also defined by Özsu and Valduriez<a href="#ref-ozsu2020">[2]</a>:</p>

<blockquote>
  <p><em>“Relational tables can be partitioned either horizontally or vertically. The basis of horizontal fragmentation is the select operator where the selection predicates determine the fragmentation, while vertical fragmentation is performed by means of the project operator. The fragmentation may, of course, be nested. If the nestings are of different types, one gets hybrid fragmentation.”</em></p>
</blockquote>

<p>Regardless of the partitioning strategy applied, the fragmentation should guarantee that the database doesn’t undergo semantic changes during the process and ensure the reconstructibility property, that is, still be able to recover the original relation \(R\) from its fragments. Although not always present, it is usually desirable for the fragmentation technique to have a set of properties during decomposition of the tuples among partitions, as listed below:</p>

<ul>
  <li>Completeness: for any tuple \(t\) of a relation \(R\), \(t\) will be in at least one partition, that is, no information is lost. This is based on the selection and projection predicates chosen;</li>
  <li>Reconstructibility: it should be possible to reconstruct the original relation \(R\) by using the UNION operator (for horizontal fragmentation) or the OUTER UNION operator (for vertical fragmentation);</li>
  <li>Disjointness: there’s no intersection between two partitions under horizontal partitioning, and vertical partitions only share the unique key attribute (so it’s possible to reconstruct the original relation);</li>
</ul>

<h5 id="horizontal-partitioning">Horizontal Partitioning</h5>

<p>Horizontal Partitioning splits the relation \(R\) into (usually) <em>disjoint</em> subsets of the tuples of the original relation, specified by a condition \(C\). The horizontal partitioning can be expressed as the relational algebra’s SELECT (\(\sigma\))<sup id="fnref:select-sql" role="doc-noteref"><a href="#fn:select-sql" class="footnote" rel="footnote">2</a></sup> operation:</p>

\[\sigma_{C}(R)\]

<p>Condition \(C\) is a predicate often composed of a single attribute – with the form \(R.\text{attr} \mathrel{\mathbf{op}} \text{value}\), where \(R.attr\) is an attribute of relation \(R\), <strong>op</strong> is a conditional operator (e.g., =, \(&lt;, \ge\)), and <em>value</em> is a literal.</p>

<p>Suppose we have the following <em>PROJ</em> table and apply conditions \(C_{1} = BUDGET &lt; 250000\) and \(C_{2} = BUDGET \ge 250000\), this will produce two horizontal partitionings as below:</p>

<p><a href="/images/posts/partitioning_vs_sharding/horizontal-partitioning.png"><img src="/images/posts/partitioning_vs_sharding/horizontal-partitioning.png" alt="horizontal partitioning" /></a></p>

<p>Elmasri and Navathe define an interesting property of horizontal partitioning:</p>

<blockquote>
  <p><em>“A set of horizontal fragments whose conditions \(C_1, C_2, … , C_n\) include all the tuples in R—that is, every tuple in R satisfies (\(C_1\) OR \(C_2\) OR … OR \(C_n\))—is called a <strong>complete horizontal fragmentation</strong> of R. In many cases a complete horizontal fragmentation is also <strong>disjoint</strong>; that is, no tuple in \(R\) satisfies (\(C_i\) AND \(C_j\)) for any \(i \neq j\).”</em></p>
</blockquote>

<p>And to reconstruct the relation R from a <strong>complete horizontal fragmentation</strong>, it is necessary to apply the UNION operation to the partitions.</p>

<p>It’s also important to observe that depending on the \(C_i\) conditions of the horizontal partitioning, this may result in an uneven data distribution among the partitions (<strong>data skew</strong>), as illustrated by the image below where we split the horizontal fragments by the Dno column. This can potentially create <strong>hot spots</strong> and performance bottlenecks, with some partitions overloaded while others are mostly idle.</p>

<p><a href="/images/posts/partitioning_vs_sharding/employee-horizontal-partitioning.png"><img src="/images/posts/partitioning_vs_sharding/employee-horizontal-partitioning.png" alt="employee horizontal partitioning" /></a></p>

<p>Both Silberschatz et al and Özsu and Valduriez mention that horizontal partitioning is widely used for parallel and distributed database systems because of its inherent opportunities for both interquery and intraquery parallelism<sup id="fnref:intra-inter-query" role="doc-noteref"><a href="#fn:intra-inter-query" class="footnote" rel="footnote">3</a></sup>.</p>

<h5 id="horizontal-partitioning-strategies">Horizontal Partitioning Strategies</h5>

<p>Horizontal partitioning can be further divided into different schemes to split the rows into disjoint subgroups:</p>

<p><strong>1. Round-Robin Partitioning</strong></p>

<p>For n processors, the ith tuple is assigned to the processor \(i \bmod n\). This technique is ideally suited for applications that read the entire relation sequentially for each query. As far as the literature goes, it is especially used in RAID disk arrays.</p>

<p><a href="/images/posts/partitioning_vs_sharding/round-robin-partitioning.png"><img src="/images/posts/partitioning_vs_sharding/round-robin-partitioning.png" alt="round-robin partitioning" /></a></p>

<p><strong>2. Hash Partitioning</strong></p>

<p>There’s a hash function that maps a key value to a partition number. This keeps data evenly distributed even if data grows and shrinks over time. It is best suited for point queries based on the partitioning attribute, but it’s also useful for sequential scans over the entire relation or range queries on the partitioning attribute. If the hash function is good, the number of tuples in each node is roughly the same, i.e. \(\frac{1}{N}\), for \(N\) nodes. Nevertheless, it is not well suited for point queries on nonpartitioning attributes.</p>

<p><a href="/images/posts/partitioning_vs_sharding/hash-partitioning.png"><img src="/images/posts/partitioning_vs_sharding/hash-partitioning.png" alt="hash partitioning" /></a></p>

<p><strong>3. Range Partitioning</strong></p>

<p>This strategy partitions tuples by the range of their key values. Tuples are sorted (conceptually), and n ranges are chosen for the sort key values so that each range contains roughly the same number of tuples. We assign contiguous attribute value ranges to each node. Given a set of nodes \(N_1, N_2, ..., N_n\), we choose a partitioning attribute \(A\) and a <strong>partitioning vector</strong> \([v_1, v_2, ..., v_{n-1}]\) such that, if \(i &lt; j\), then \(v_i &lt; v_j\). The relation \(R\) is partitioned as follows for a tuple \(t\), where \(t[A] = x\):</p>

\[\begin{cases}
  \text{if } x &lt; v_1 \implies \text{t goes to } N_1 \\
  \text{if } x \geq v_n \implies \text{t goes to } N_n \\
  \text{if } v_i \leq x &lt; v_{i+1} \implies \text{t goes to } N_{i+1}
\end{cases}\]

<p><a href="/images/posts/partitioning_vs_sharding/range-partitioning.png"><img src="/images/posts/partitioning_vs_sharding/range-partitioning.png" alt="range partitioning" /></a></p>

<!-- ```
Partitioning strategy

   ├── Range
   ├── Hash
   ├── List
   ├── Consistent hash
   ├── Directory
   └── Composite
``` -->

<h5 id="primary-and-secondary-horizontal-partitioning">Primary and Secondary Horizontal Partitioning</h5>

<p>A <strong>primary horizontal partitioning</strong> is a partition defined by predicates on root tables (source tables): tables that exist as independent entities, without any Foreign Key (FK) pointing to it, like, for example, the CUSTOMER table in the example below. This table can be partitioned by its range of IDs, for example.</p>

<p>On the other hand, <strong>derived horizontal partitioning</strong> is the partitioning of a relation that results from predicates being defined by another relation (that is, the table depends on one or more other tables to exist and have uniqueness). Tables like ORDERS and INVENTORY are examples of such tables, so that we can partition them according to how their referenced parent table (e.g., CUSTOMER) was already partitioned, keeping related rows co-located on the same fragment for faster joins.</p>

<p><a href="/images/posts/partitioning_vs_sharding/primary-secondary-horizontal-partitioning.png"><img src="/images/posts/partitioning_vs_sharding/primary-secondary-horizontal-partitioning.png" alt="primary and derived horizontal partitioning" /></a></p>

<h5 id="vertical-partitioning">Vertical Partitioning</h5>

<p>Vertical partitioning splits a relation \(R\) into partitions each containing a subset of \(R\)’s attributes (columns). In other words, it divides a relation “vertically” by columns. This particular kind of data partitioning was successfully implemented by column-store DBMSs like MonetDB/X100, Vertica, Snowflake and DuckDB, for example, as well as columnar open data formats and OLAP query engines.</p>

<p>Vertical Partitioning can be expressed as the relational algebra’s PROJECT (\(\pi\)) operation:</p>

\[\pi_{L_i}(R)\]

<p>where \({L_i}\) is the list of attributes (columns) of \(R\), for \(i = 1,..., N\), where \(N\) is the number of attributes of relation \(R\).</p>

<p>Suppose that our PROJ table is split by two lists of attributes, \(L_{1} = \{PNO, BUDGET\}\) and \(L_{2} = \{PNO, PNAME, LOC\}\)</p>

<p><a href="/images/posts/partitioning_vs_sharding/vertical-partitioning.png"><img src="/images/posts/partitioning_vs_sharding/vertical-partitioning.png" alt="horizontal and vertical partitioning" /></a></p>

<p>As we can see in the example above, PNO, the Primary Key (PK) of the PROJ table is duplicated on the two fragments – \(PROJ_{1}\) and \(PROJ_{2}\). This is not a coincidence, but a conscious distributed database design decision so that we are able to reconstruct the original relation \(R\) from its vertical fragments by using an OUTER UNION operation. In other words, our database design has a <em>lossless-join property</em>.</p>

<p>A vertical partitioning is said to be a <em><strong>lossless-join decomposition</strong></em> if it’s always possible to reconstruct the original relation \(R\) from its vertical fragments \(L_1, L_2, ... L_n\). This is more succinctly expressed in relational algebra as:</p>

\[R = \pi_{L_{1}}(R) \bowtie \pi_{L_{2}}(R)\]

<p>That is, the natural join of the projection results <em>exactly</em> in the original relation \(R\). Conversely, if the same natural join computation results in a <strong>superset</strong> of the original relation then the decomposition is said to be lossy. This can be expressed succinctly in the relational algebra as:</p>

\[R \subset \pi_{L_{1}}(R) \bowtie \pi_{L_{2}}(R)\]

<p>Figure 2.2 in Georgiev’s thesis<a href="#ref-georgiev2008">[6]</a> shows examples of a lossy (a) and a lossless (b) decomposition.</p>

<p><a href="/images/posts/partitioning_vs_sharding/lossless-lossy-decomposition.png"><img src="/images/posts/partitioning_vs_sharding/lossless-lossy-decomposition.png" alt="lossless and lossy decompositions" /></a></p>

<p>Finally, Ramakrishnan and Gehrke state the following about  vertical partitioning:</p>

<blockquote>
  <p><em>“To ensure that a vertical fragmentation is lossless-join, systems often assign a <strong>unique tuple id</strong> to each tuple in the original relation, as shown in Figure 22.4, and attach this id to the projection of the tuple in each fragment. If we think of the original relation as containing an additional tuple-id field that is a key, this field is added to each vertical fragment. Such a decomposition is guaranteed to be lossless-join.”</em></p>
</blockquote>

<p>As described by Elmasri and Navathe, a set of vertical partitions whose projection lists \(L_1, L_2, ... , L_n\) include all the attributes in \(R\) but share only the primary key attribute of R is called a <strong>complete vertical fragmentation</strong> of \(R\). Therefore, in this case, the projection lists satisfy the following conditions:</p>
<ol>
  <li>\(L_1 \cup L_2 \cup ... \cup L_n = ATTRS(R)\);</li>
  <li>\(L_i \cap L_j = PK(R)\), for any \(i \neq j\),</li>
</ol>

<p>where ATTRS(R) is the set of all attributes of R and PK(R) is the primary key of R;</p>

<p>And to reconstruct the relation R from a complete vertical fragmentation, we apply the OUTER UNION operation to the vertical fragments. Furthermore, for the \(R_1, R_2, ... R_n\) vertical fragments of \(R\), we have that \(R_1 \cap R_2 \cap ... \cap R_n = A\), where \(A\) is a unique key attribute of \(R\).</p>

<h5 id="hybrid-partitioning">Hybrid Partitioning</h5>

<p>A <strong>hybrid partitioning</strong> is the combination of horizontal partitioning and vertical partitioning, also called <strong>mixed partitioning</strong>. In this kind of partitioning, a fragment of a relation R can be specified by a SELECT-PROJECT combination of operations \(\pi_L(\sigma_C(R))\), and the original relation \(R\) can be reconstructed by applying UNION <strong>and</strong> OUTER UNION (or OUTER JOIN) operations in the appropriate order.</p>

<p>In the Employee table example, if we partition by Department Number (Dno) and Social Security Number (SSN), there are four resulting partitions:</p>

<p><a href="/images/posts/partitioning_vs_sharding/employee-hybrid-partitioning.png"><img src="/images/posts/partitioning_vs_sharding/employee-hybrid-partitioning.png" alt="hybrid partition" /></a></p>

<h3 id="2-allocation">2. Allocation</h3>

<p>Allocation assigns a fragment to a particular node. The choice of site and degree of replication will be driven by performance and availability requirements. Some partitions may be replicated across all nodes of the system, while others remain local to a particular site, for example.</p>

<h3 id="3-replication">3. Replication</h3>

<p>Replication allows the storage of several copies of data to achieve data locality (i.e., keep the data closer to where it is most needed), availability (the probability that a system is continuously available during a time interval), reliability (the probability that a system is running at a certain point in time) and faster query execution by using local copies.</p>

<p>The logical units of replication will vary depending on the requirements of the application, performance requirements, patterns of access, types and frequencies of transactions, and availability requirements. Basically, there are two extremes of replication: no replication at all – that is, each relation or fragment is stored in a single node – on one side and whole database replication on all nodes on the other side (called a <em>fully replicated database</em>). In between, we have a spectrum of partial replications of tables and table partitions. For example, H-Store/VoltDB supports both partitioned and replicated tables (that is, the whole table is replicated among all the nodes). The latter is used for small tables that are read-only or not frequently updated like a COUNTRY table, for example, while other tables are horizontally partitioned among the nodes.</p>

<p>A thorough treatment of replication is beyond the scope of this blog post, but it’s strongly recommended that interested readers check Özsu and Valduriez’s<a href="#ref-ozsu2020">[2]</a>, Kleppmann and Riccomini’s<a href="#ref-kleppmann2026">[4]</a>, or Petrov’s<a href="#ref-petrov2019">[5]</a> chapters devoted to this subject.</p>

<h2 id="so-what-is-sharding-in-the-academic-perspective-after-all">So what is sharding (<em>in the academic perspective</em>), after all?</h2>

<p>All the authors cited so far address <em>sharding</em> in their works, but with slightly different nuances among them. <strong>For Özsu and Valduriez, Elmasri and Navathe, Ramakrishnan and Gehrke, and Silberschatz et al, <em>sharding is a synonym for horizontal partitioning</em></strong>, and they acknowledge that the term is especially widespread in the context of NoSQL databases, Big Data, and Cloud Computing systems.</p>

<p>But Silberschatz et al also argument that in sharding architectures each node can have a traditional centralized, <em>maybe independent</em>, database instance (like standalone full MySQL DB instances, for example) and the queries would be routed by a middleware. In this architectural model, the sharding query requests can be executed by the database, or a middleware, or even application code. Each shard is an independent and standalone database that replicates all the database schema, but has only \(\frac{1}{N}\) of the data, in a cluster of size N.</p>

<h2 id="key-takeaways">Key Takeaways</h2>

<p>Based on what we have seen so far, a more concise definition of <em>sharding</em> could be:</p>

<blockquote>
  <p><strong>Sharding is horizontal partitioning of data distributed across different nodes.</strong></p>
</blockquote>

<p>And we may establish the following relation:</p>

<blockquote>
  <p><strong>Every sharding is a partitioning, but not every partitioning is a sharding.</strong></p>
</blockquote>

<p>This derives directly from the fact that there are partitioning on a single node, for example, while sharding usually involves horizontal partitioning + allocation among various nodes. Therefore, a key takeaway relationship among all of the main concepts exposed so far could be:</p>

\[Sharding \subseteq \text{Horizontal Partitioning} \subseteq Partitioning\]

<p>Or as a cool cheat sheet:</p>

<p><a href="/images/posts/partitioning_vs_sharding/key-takeways.png"><img src="/images/posts/partitioning_vs_sharding/key-takeways.png" alt="key takeways" /></a></p>

<h2 id="references">References</h2>

<ol>
  <li><span id="ref-ramakrishnan2003"></span>Ramakrishnan, R., &amp; Gehrke, J. (2003). <em><a href="https://pages.cs.wisc.edu/~dbbook/openAccess/thirdEdition/supporting_material.htm">Database Management Systems</a></em> (3rd ed.). McGraw-Hill.</li>
  <li><span id="ref-ozsu2020"></span>Özsu, M. T., &amp; Valduriez, P. (2020). <em><a href="https://cs.uwaterloo.ca/~ddbook/">Principles of Distributed Database Systems</a></em> (4th ed.). Springer.</li>
  <li><span id="ref-silberschatz2020"></span>Silberschatz, A., Korth, H. F., &amp; Sudarshan, S. (2020). <em><a href="https://www.db-book.com/">Database System Concepts</a></em> (7th ed.). McGraw-Hill.</li>
  <li><span id="ref-kleppmann2026"></span>Kleppmann, M., &amp; Riccomini, C. (2026). <em><a href="https://www.oreilly.com/library/view/designing-data-intensive-applications/9781098119058/">Designing Data-Intensive Applications</a></em> (2nd ed.). O’Reilly.</li>
  <li><span id="ref-petrov2019"></span>Petrov, A. (2019). <em><a href="https://www.databass.dev/">Database Internals</a></em> (1st ed.). O’Reilly.</li>
  <li><span id="ref-georgiev2008"></span>Georgiev, N. (2008). <em>A Web-Based Environment For Learning Normalization of Relational Database Schemata</em> (Master’s thesis).</li>
</ol>
<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:voltdb" role="doc-endnote">
      <p>The commercial database is currently known as Volt Active Data. <a href="#fnref:voltdb" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:select-sql" role="doc-endnote">
      <p>The SELECT operation should not be confused with the SQL’s SELECT clause; <a href="#fnref:select-sql" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:intra-inter-query" role="doc-endnote">
      <p>While interquery allows the parallel execution of a number of queries on various nodes, intraquery allows the parallel execution of a single query by breaking it up into subqueries and executing on various partitions in parallel. <a href="#fnref:intra-inter-query" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name>Edward Ribeiro</name><email>edward.ribeiro@gmail.com</email></author><category term="Blog" /><category term="partitioning" /><category term="horizontal-partitioning" /><category term="vertical-partitioning" /><category term="sharding" /><category term="database-internals" /><category term="fragmentation" /><summary type="html"><![CDATA[Social media replies insisting that partitioning stays on one server while sharding spans many nodes sent me on quest through vendor docs, and decades of database textbooks to check whether that clean split actually holds up.]]></summary></entry><entry><title type="html">“Clustered Index” Doesn’t Mean What You Think It Means (And That’s Alright!)</title><link href="https://eribeiro.github.io/blog/clustered-index-vs-clustering-index/" rel="alternate" type="text/html" title="“Clustered Index” Doesn’t Mean What You Think It Means (And That’s Alright!)" /><published>2026-08-28T00:00:00+00:00</published><updated>2026-08-28T00:00:00+00:00</updated><id>https://eribeiro.github.io/blog/clustered-index-vs-clustering-index</id><content type="html" xml:base="https://eribeiro.github.io/blog/clustered-index-vs-clustering-index/"><![CDATA[<p><img src="/images/posts/i-dont-think-meme.gif" alt="I don't think meme" /></p>

<p>Early this week, during a book club session, I found myself stumbling through the nuances of <em><strong>clustered indexes, index-organized tables and covering indexes</strong></em>, only to be corrected by fellow readers that were going strictly by Kleppmann and Riccomini’s <a href="https://www.oreilly.com/library/view/designing-data-intensive-applications/9781098119058/"><em>Designing Data-Intensive Applications</em></a> (2nd Edition, 2026). Driven by my  confusion, I hit the books and the web to untangle it all.</p>
<div class="custom-box">
To cut to the chase, what actually happened is that two database traditions picked  similar words (<i>"clustering index"</i> and <i>"clustered index"</i>) for two genuinely different physical structures!
</div>
<p>While I’d learned it first from Silberschatz, Korth &amp; Sudarshan’s <a href="https://www.db-book.com/"><em>Database System Concepts</em></a> (currently on 7th Edition, 2021), and a couple of lesser-known books,  Kleppmann and Riccomini’s definition matches something else entirely. And it turns out <strong><em>their usage is the one that matches the industry’s actual database engine implementations!</em></strong> Therefore, neither of us were way off during the book discussion or inventing anything, for that matter; we were just each drinking from different wells. 🥲</p>

<p><em>This post is the writeup I wish I’d had going into that conversation.</em> ;-)</p>

<h2 id="the-claim-that-started-it">The claim that started it</h2>

<p>The definition I knew, in Silberschatz, Korth, and Sudarshan’s own words:</p>

<blockquote>
  <p>“… a <strong>clustering index</strong> is an index whose search key also defines the sequential order of the file. Clustering indices are also called <strong>primary indices</strong>; the term <em>primary index</em> may appear to denote an index on a primary key, but such indices can in fact be built on any search key. The search key of a clustering index is often the primary key, although that is not  necessarily so. Indices whose search key specifies an order different from the sequential order of the file are called non clustering indices, or secondary indices. The terms “clustered” and “nonclustered” are often used in place of “clustering” and “non clustering.”</p>
</blockquote>

<p>In other words, they describe a clustering index as one where the heap file itself is ordered according to the index search key.</p>

<p><img src="/images/posts/clustering_index_vs_primary_key_index.svg" alt="silberschatz clustering index concepts" /></p>

<p>But if we check the literature a bit further, Elmasri and Navathe’s <a href="https://www.amazon.com.br/Fundamentals-Database-Systems-Paperback-Elmasri/dp/8131792471">Fundamentals of Database Systems</a> (7th ed, 2016) has an even more specific definition of <strong>Clustering Indexes</strong>:</p>

<blockquote>
  <p><em>“If file records are physically ordered on a nonkey field — which <strong>does not</strong> have a distinct value for each record — that field is called the <strong>clustering field</strong> and the data file is called a <strong>clustered file</strong>. We can create a different type of index, called a <strong>clustering index</strong>, to speed up retrieval of all the records that have the same value for the clustering field. This differs from a primary index, which requires that the ordering field of the data file have a <strong>distinct value</strong> for each record. A <strong>clustering index</strong> is also an ordered file with two fields; the first field is of the same type as the clustering field of the data file, and the second field is a disk block pointer. There is one entry in the clustering index for each distinct value of the clustering field, and it contains the value and a pointer to the first block in the data file that has a record with that value for its clustering field.</em></p>

  <p><em>(…)</em></p>

  <p><em>A clustering index is another example of a nondense (E.N., sparse) index because it has an entry for every distinct value of the indexing field, which is a nonkey by definition and hence has duplicate values rather than a unique value for every record in the file.”</em></p>
</blockquote>

<p>There’s a subtle distinction worth flagging here. In Elmasri and Navathe terminology, <em>if the physical ordering field is the primary key, they normally call the associated index a primary index, not a clustering index.</em></p>

<p><img src="/images/posts/primary_index_vs_clustering_index.svg" alt="navathe clustering index concept" /></p>

<p>Independently, Oracle has used the term <a href="https://docs.oracle.com/en/database/oracle/oracle-database/26/cncpt/indexes-and-index-organized-tables.html"><strong>Index-Organized Table (IOT)</strong></a> since at least Oracle8, though the underlying idea predates the name. In Oracle’s own docs, <em>an IOT is a table with no separate heap file, that is, the data lives directly inside a B-tree, keyed by (typically) the primary key.</em></p>

<p>DDIA collapses both of these into a single definition:</p>

<blockquote>
  <p>“If the actual data (row, document, vertex) is stored directly within the index structure, it is called a <strong>clustered index</strong>. For example, in MySQL’s InnoDB storage engine, the primary key of a table is always a clustered index, and in SQL Server, you can specify one clustered index per table [43].”<sup id="fnref:innodb-primary-key" role="doc-noteref"><a href="#fn:innodb-primary-key" class="footnote" rel="footnote">1</a></sup></p>
</blockquote>

<center>
<img src="/images/posts/peter-parker-meme.jpg" alt="alt text" style="width: 30%;" />
<br />
<br />
</center>

<p><strong>But that’s not an error on their part!</strong> It’s the terminology <a href="https://dev.mysql.com/doc/refman/9.7/en/innodb-index-types.html">MySQL’s InnoDB</a>, <a href="https://learn.microsoft.com/en-us/sql/relational-databases/indexes/clustered-and-nonclustered-indexes-described">SQL Server</a> and <a href="https://docs.pingcap.com/tidb/stable/clustered-indexes/">TiDB</a>, among others, actually use in their own documentation. The “clustered index” in their world <em>is</em> what Oracle calls an index-organized table. Same structure, different products/companies, different words.</p>

<p>Markus Winand’s <a href="https://sql-performance-explained.com">SQL Performance Explained</a> (2nd edition, 2025) reconciles IOT and clustered index definitions, as quoted below:</p>

<blockquote>
  <p><em>“Some databases can indeed use an index as primary table store. The Oracle database calls this concept <strong>index-organized tables (IOT)</strong>, other databases use the term <strong>clustered index</strong>. In this section, both terms are used to either put the emphasis on the table or the index characteristics as needed. An index-organized table is thus a B-tree index without a heap table. This results in two benefits: (1) it saves the space for the heap structure; (2) every access on a clustered index is automatically an index-only scan. Both benefits sound promising but are hardly achievable in practice.”</em></p>
</blockquote>

<h2 id="three-structures-one-collision">Three structures, one collision</h2>

<p>Before getting to the confusing part, here’s the structure everyone agrees on and the baseline the other two get compared against.</p>

<h3 id="1-the-baseline-heap-file--ordinary-secondary-index">1. The baseline: heap file + ordinary secondary index</h3>

<p>Rows sit in an unordered heap and a B+-Tree index points into it.</p>

<p><a href="/images/posts/01-heap-plus-secondary-index.svg"><img src="/images/posts/01-heap-plus-secondary-index.svg" alt="Heap file with secondary index" /></a></p>

<h3 id="2-clustering-index-the-academic-vernacular-silberschatzsnavathes">2. “Clustering index”: the academic vernacular (Silberschatz’s/Navathe’s)</h3>

<p><em>The heap file is still there, but it’s been physically sorted to match the index key.</em> Because order matches, the index doesn’t even need an entry for every row, one entry per disk block is enough (i.e., it can be a sparse index).</p>

<p><a href="/images/posts/02-clustering-index-silberschatz.svg"><img src="/images/posts/02-clustering-index-silberschatz.svg" alt="Clustering index over a sorted heap" /></a></p>

<p>The important detail: this is a <em>maintained property</em>, not a structural guarantee. New inserts can land out of order until the next reorganize.  <code class="language-plaintext highlighter-rouge">OPTIMIZE TABLE</code> in <a href="https://dev.mysql.com/doc/refman/8.0/en/optimize-table.html">MySQL’s MyISAM</a> engine, or <code class="language-plaintext highlighter-rouge">CLUSTER</code> in <a href="https://www.postgresql.org/docs/current/sql-cluster.html">PostgreSQL</a> are both real-world instances of this exact concept.</p>

<p><strong>Db2</strong> goes a step further than MySQL and PostgreSQL: it names this concept directly. Its own <a href="https://www.ibm.com/docs/en/db2/11.5.x?topic=indexes-clustered-non-clustered">documentation</a> describes a clustering index as one that determines how rows are physically ordered in a table space; a genuinely separate object from the table, exactly like Silberschatz’s model. <a href="https://www.ibm.com/docs/en/db2-for-zos/12.0.0?topic=gia-clustering-indexes">Db2</a> even tracks a cluster ratio to measure how well-sorted the heap currently is, and recommends a <code class="language-plaintext highlighter-rouge">REORG</code> when it drifts.</p>

<p>One caveat: Db2’s clustering index is still a dense (B-tree) index, with one entry per row. Silberschatz’s definition allows the index itself to go sparse once ordering is guaranteed, but Db2 doesn’t take that shortcut. So it’s a real match on the heap-file question, just not on the sparse-index detail.</p>

<h3 id="3-clustered-index--index-organized-table-the-engineering-vernacular-innodb-sql-server-oracle-iot-tidb">3. “Clustered index” / index-organized table: the engineering vernacular (InnoDB, SQL Server, Oracle IOT, TiDB)</h3>

<p><em>No heap file exists.</em> The B-tree’s leaf nodes directly hold the row data.</p>

<p><a href="/images/posts/03-clustered-index-iot.svg"><img src="/images/posts/03-clustered-index-iot.svg" alt="Clustered index with no separate heap" /></a></p>

<p>Here the sort order isn’t something you maintain. It’s structurally impossible to violate, because there’s nothing to fall out of sync. The row <em>is</em> the leaf.</p>

<p>One more consequence follows directly from structure 3:</p>

<h3 id="4-the-two-hop-cost-of-secondary-indexes-on-a-clustered-index">4. The two-hop cost of secondary indexes on a clustered index</h3>

<p>Once there’s no heap to point into, a secondary index (say, on <code class="language-plaintext highlighter-rouge">email</code>) can’t store a physical rowid. It stores the clustering key instead, then does a second lookup into the clustered B-tree index to fetch the row.</p>

<p><a href="/images/posts/04-secondary-index-over-clustered-index.svg"><img src="/images/posts/04-secondary-index-over-clustered-index.svg" alt="Secondary index pointing into a clustered index" /></a></p>

<p><strong><em>This two-hop pattern is exactly what Kleppmann and Riccomini are describing when they discuss the cost of secondary indexes on clustered-index tables in DDIA book!</em></strong></p>

<p>By the way, Winand lays out exactly why this hurts once you add a second index on top of a clustered one. As the rows inside an index-organized table can move at any time to preserve B-tree order, a secondary index can’t store a physical pointer (<em>rowid</em>) to them, so it has to store the clustering key (often the primary key) instead and use that to look the row up.</p>

<p>In practice, this means every lookup through a secondary index costs two searches instead of one: an INDEX RANGE SCAN<sup id="fnref:index-range-scan" role="doc-noteref"><a href="#fn:index-range-scan" class="footnote" rel="footnote">2</a></sup> on the secondary index, followed by an INDEX UNIQUE SCAN<sup id="fnref:index-unique-scan" role="doc-noteref"><a href="#fn:index-unique-scan" class="footnote" rel="footnote">3</a></sup> into the clustered index for each match. <em>By the way, I am borrowing Oracle’s EXPLAIN PLAN vocabulary here purely as an illustration of the two-hop pattern; InnoDB and SQL Server surface the same cost under their own plan-operation names.</em> As Winand puts it, <em>“accessing an index-organized table via a secondary index is very inefficient.”</em></p>

<p>The fix is the same one used for heap tables: an index-only scan, though here it’s better described as a “secondary-index-only scan”; and the payoff is even bigger, since it eliminates an entire INDEX UNIQUE SCAN per row rather than a single table access.</p>

<h2 id="why-the-definitions-collided">Why the definitions collided</h2>

<ul>
  <li><strong>Oracle</strong> calls this structure an <em>index-organized table</em> and reserves <em>cluster</em> for something unrelated (i.e., multiple tables sharing storage blocks by a cluster key);</li>
  <li><strong>MySQL/InnoDB and Microsoft SQL Server</strong> call the same structure a <em>clustered index</em>;</li>
  <li><strong>Classical database texts</strong> like Silberschatz et al’s and Navathe et al’s are describing a <em>different</em> axis of classification: does index order match file order? Not whether the heap exists at all…</li>
  <li><strong>IBM Db2</strong> is the outlier that actually lines up with the classical texts: its clustering index is a genuinely separate object from the table, and Db2 tracks how well the two stay in sync, that is, the same axis Silberschatz is describing, not the IOT axis Oracle/MySQL/SQL Server are on.</li>
</ul>

<div class="custom-box">
<strong>Heads up:</strong> Silberschatz's <i>"primary index"</i> (roughly a clustering index) and Elmasri/Navathe's <i>"primary index"</i> (ordered-by-unique-key, contrasted against their <i>"clustering index"</i>) are two more terms colliding under one name! Track which book you're in.
</div>

<p>Two structures, several vocabularies, one underlying set of concepts, with Db2 being the rare case where a vendor’s terminology and the classical textbook terminology actually agree. But none of the sources is “wrong” in isolation; the confusion only shows up when you read across them, or cross-references them, which is exactly what happens in a book club drawing on multiple references.</p>

<h2 id="bonus-round-covering-index-no-controversy-here">Bonus round: covering index (no controversy here!)</h2>

<p>Not every indexing term is contested. While looking into this, I also revisited <strong>covering index</strong>, as defined in Markus Winand’s <a href="https://sql-performance-explained.com"><em>SQL Performance Explained</em></a> (2025). Unlike the clustered/clustering mess, this one is consistent everywhere: SQL Server docs, PostgreSQL literature, Couchbase docs, and Winand’s book all agree:</p>

<blockquote>
  <p>A <strong>covering index</strong> contains every column a query needs —<code class="language-plaintext highlighter-rouge">SELECT</code>-list columns as well as<code class="language-plaintext highlighter-rouge">WHERE</code>and<code class="language-plaintext highlighter-rouge">JOIN</code> columns —, so the database engine never has to visit the underlying table at all.</p>
</blockquote>

<p><a href="/images/posts/05-covering-index.svg"><img src="/images/posts/05-covering-index.svg" alt="Covering index compared to a non-covering index" /></a></p>

<p>Or specifically quoting Winand’s book:</p>

<blockquote>
  <p><em>“If an index prevents a table access it is also called a <strong>covering index</strong>.The term is misleading, however, because it sounds like an index property. The phrase index-only scan correctly suggests that it is an execution plan operation.”</em></p>
</blockquote>

<p>The nice part: this concept is orthogonal to the clustering debate above. It applies whether you’re skipping a hop to a heap file (Silberschatz et al’s clustering index) or skipping a hop into a clustered B-tree (DDIA’s clustered index).</p>

<p>By the way, a <a href="https://www.yugabyte.com/blog/how-a-distributed-sql-database-boosts-secondary-index-queries-with-index-only-scan/">YugaByte’s blog post</a> goes further by citing other covering indexes’ diverse names:</p>

<blockquote>
  <p><em>“The solution is simple and has many names: “covering index”, “include index”, “projection index”, “fat index” and even “Tapio index” from the name of the author of “Interscience Relational Database Index Design and the Optimizers” (Tapio Lahdenmäki) who explained this in detail.”</em></p>
</blockquote>

<p>But, all in all, they’re all aliases/nicknames for the same mechanism, not competing definitions. <em>Phew!</em> 😅</p>

<h2 id="a-key-takeaway">A key takeaway</h2>

<p>If you’re reading multiple database books then assume “clustered/clustering index” means different things depending on which book is in your hand, until proven otherwise. When in doubt, ask yourself: <em>is there a separate heap file here, or not?</em> That question cuts through the vocabulary every time.</p>

<p><img src="/images/posts/heap_file_decision_takeaway.svg" alt="key-takeway" /></p>

<p><em>Cheers!<br />
Edward</em></p>

<hr />

<p><em>August, 28th, 2026</em></p>
<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:innodb-primary-key" role="doc-endnote">
      <p>More precisely, if a table has no primary key, InnoDB clusters on the first unique NOT NULL index instead, or a hidden internal row ID as a last resort, as discussed <a href="https://blog.jcole.us/2013/05/02/how-does-innodb-behave-without-a-primary-key/">here</a> and <a href="https://dev.mysql.com/doc/refman/5.7/en/innodb-index-types.html">here</a>. <a href="#fnref:innodb-primary-key" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:index-range-scan" role="doc-endnote">
      <p><strong>Index Range Scan</strong> is a database operation where the system traverses an index tree to find a starting point and then walks through connected leaf nodes to read a targeted range of matching entries. Source: <a href="https://use-the-index-luke.com/sql/explain-plan/oracle/operations">Use The Index, Luke — Execution Plan Operations</a>. <a href="#fnref:index-range-scan" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:index-unique-scan" role="doc-endnote">
      <p><strong>INDEX UNIQUE SCAN</strong> is a database operation that performs a B-tree traversal to find a single, specific row using a unique index. Source: <a href="https://use-the-index-luke.com/sql/explain-plan/oracle/operations">Use The Index, Luke — Execution Plan Operations</a>. <a href="#fnref:index-unique-scan" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name>Edward Ribeiro</name><email>edward.ribeiro@gmail.com</email></author><category term="Blog" /><category term="clustered-index" /><category term="clustering-index" /><category term="index-organized-table" /><category term="covering-index" /><category term="database-internals" /><summary type="html"><![CDATA[A book club argument over Kleppmann and Riccomini's DDIA sent me chasing down why "clustering index" and "clustered index" mean two different physical structures, depending on which database book (or vendor's docs) you're reading.]]></summary></entry></feed>