← Back to list

Partitioning and Sharding in Distributed Systems

Apart from Replication, the most common way data is distributed across multiple nodes is Partitioning. Normally, partitions are defined in…

Chamuditha Kekulawala · 2026-04-05 16:50 · 57 claps · 7.2 min read
#partitioning #sharding #database-sharding #database-replication #distributed-systems
Open on Medium ↗
Wiki topics: CRY · Crypto & Web3

Partitioning and Sharding in Distributed Systems

Apart from Replication, the most common way data is distributed across multiple nodes is Partitioning. Normally, partitions are defined in such a way that each piece of data (each record, row, or document) belongs to exactly one partition.

In effect, each partition is a small database of its own, although the database may support operations that touch multiple partitions at the same time. The main reason for wanting to partition data is scalability. Different partitions can be placed on different nodes in a shared-nothing cluster. Thus, a large dataset can be distributed across many disks, and the query load can be distributed across many processors.

For queries that operate on a single partition, each node can independently execute the queries for its own partition, so query throughput can be improved by adding more nodes. This also leads to efficient resource utilization. Partitioning also provides enhanced manageability, since smaller partitions are easier to back up, restore, and maintain.

Replication

Partitioning is usually combined with replication, so that copies of each partition are stored on multiple nodes. This means that, even though each record belongs to exactly one partition, it may still be stored on several different nodes for fault tolerance. If a leader–follower replication model is used, the combination of partitioning and replication can look like this:

There are 2 key methods of partitioning:

  1. Vertical Partitioning — splits tables by columns, grouping frequently accessed data together
  2. Horizontal Partitioning (Sharding) — splits tables by rows to distribute data across nodes

Horizontal partitioning, is best for high-volume data enhancing scalability for massive datasets, while Vertical partitioning is optimal for reducing table width to reduce I/O overhead.

Vertical partitioning

Vertical partitioning

Horizontal Partitioning

Partitioning Key-Value Stores

How do you decide which records to store on which nodes? Since our goal is to spread the data and the query load evenly across nodes, we can give every node a fair share. However, if the partitioning is unfair, so that some partitions have more data or queries than others, we call it skewed. The presence of skew makes equal partitioning much less effective.

In an extreme case, all the load could end up on one partition, while the other nodes are idle and your bottleneck is the single busy node. A partition with disproportionately high load is called a hot spot. The simplest approach for avoiding hot spots would be to assign records to nodes randomly. That would distribute the data quite evenly across the nodes.

However, this has a big disadvantage: when you’re trying to read a particular item, you have no way of knowing which node it is on, so you have to query all nodes in parallel. But, we have 2 key alternatives:

Partitioning by Key Range

Here, we assign a continuous range of keys (from some minimum to some maximum) to each partition. If you know the boundaries between the ranges, you can easily determine which partition contains a given key. If you also know which partition is assigned to which node, then you can make your request directly to the appropriate node.

The ranges of keys are not necessarily evenly spaced, because your data may not be evenly distributed. For example, in the following figure, node 1 contains words starting with A and B, but node 12 contains words starting with T, U, V, X, Y, and Z.

Simply having one node per two letters of the alphabet would lead to some nodes being much bigger than others. In order to distribute the data evenly, the partition boundaries need to adapt to the data.

Within each partition, we can keep keys in sorted order. This has the advantage that range scans are easy, and you can treat the key as a concatenated index in order to fetch several related records in one query.

For example, consider an application that stores data from a network of sensors, where the key is the timestamp of the measurement (year-month-day-hour-minute-second). Range scans are very useful in this case, because they let you easily fetch, say, all the readings from a particular month.

However, the downside of key range partitioning is that certain access patterns can lead to hot spots. If the key is a timestamp, then the partitions correspond to ranges of time — e.g., one partition per day. Unfortunately, because we write data from the sensors to the database as the measurements happen, all the writes end up going to the same partition (the one for today), so that partition can be overloaded with writes while others sit idle.

To avoid this problem in the sensor database, you need to use something other than the timestamp as the first element of the key. For example, you could prefix each timestamp with the sensor name so that the partitioning is first by sensor name and then by time. Assuming you have many sensors active at the same time, the write load will end up more evenly spread across the partitions.

Partitioning by Hash of Key

Because of this risk of skew and hot spots, many distributed datastores use a hash function to determine the partition for a given key. A good hash function takes skewed data and makes it uniformly distributed. Say you have a 32-bit hash function that takes a string. Whenever you give it a new string, it returns a seemingly random number between 0 and 232 − 1. Even if the input strings are very similar, their hashes are evenly distributed across that range of numbers.

For partitioning purposes, the hash function need not be cryptographically strong. Once you have a suitable hash function for keys, you can assign each partition a range of hashes (rather than a range of keys), and every key whose hash falls within a partition’s range will be stored in that partition

This technique is good at distributing keys fairly among the partitions. The partition boundaries can be evenly spaced, or they can be chosen pseudorandomly (consistent hashing). This is a way of evenly distributing load across an internet-wide system of caches such as a content delivery network (CDN). It uses randomly chosen partition boundaries to avoid the need for central control or distributed consensus.

Consistent Hashing actually doesn’t work very well for databases, so it is rarely used in practice.

Unfortunately, by using the hash of the key for partitioning we lose a nice property of key-range partitioning: the ability to do efficient range queries. Keys that were once adjacent are now scattered across all the partitions, so their sort order is lost. In MongoDB, if you have enabled hash-based sharding mode, any range query has to be sent to all partitions.

Cassandra achieves a compromise between the two partitioning strategies. A table in Cassandra can be declared with a compound primary key consisting of several columns. Only the first part of that key is hashed to determine the partition, but the other columns are used as a concatenated index for sorting the data in Cassandra’s SSTables. A query therefore cannot search for a range of values within the first column of a compound key, but if it specifies a fixed value for the first column, it can perform an efficient range scan over the other columns of the key.

Request Routing

We have now partitioned our dataset across multiple nodes running on multiple machines. But there’s still a problem: when a client wants to make a request, how does it know which node to connect to? As partitions are rebalanced, the assignment of partitions to nodes changes. Somebody needs to stay on top of those changes in order to answer the question: if I want to read or write the key “foo”, which IP address and port number do I need to connect to?

This is an instance of a more general problem called service discovery, which isn’t limited to just databases. Any piece of software that is accessible over a network has this problem, especially if it is aiming for high availability (running in a redundant configuration on multiple machines). Many companies have written their own in-house service discovery tools.

On a high level, there are a few different approaches to this problem:

  1. Allow clients to contact any node (e.g., via a round-robin load balancer). If that node coincidentally owns the partition to which the request applies, it can handle the request directly; otherwise, it forwards the request to the appropriate node, receives the reply, and passes the reply along to the client.
  2. Send all requests from clients to a routing tier first, which determines the node that should handle each request and forwards it accordingly. This routing tier does not itself handle any requests; it only acts as a partition-aware load balancer.
  3. Require that clients be aware of the partitioning and the assignment of partitions to nodes. In this case, a client can connect directly to the appropriate node, without any intermediary.

In all cases, the key problem is: how does the component making the routing decision (which may be one of the nodes, or the routing tier, or the client) learn about changes in the assignment of partitions to nodes? This is a challenging problem, because it is important that all participants erach consensus; otherwise requests would be sent to the wrong nodes and not handled correctly.

Many distributed data systems rely on a separate coordination service such as ZooKeeper to keep track of this cluster metadata:

When using a routing tier or when sending requests to a random node, clients still need to find the IP addresses to connect to. These are not as fast-changing as the assignment of partitions to nodes, so it is often sufficient to use DNS for this purpose.

Thanks for reading 🎉

References: Designing Data-intensive Applications


메타데이터
post_id
72045a6d16d0
slug
partitioning-and-sharding-in-distributed-systems-72045a6d16d0
url
https://medium.com/@ckekula/partitioning-and-sharding-in-distributed-systems-72045a6d16d0
canonical_url
https://medium.com/@ckekula/partitioning-and-sharding-in-distributed-systems-72045a6d16d0
author_url
https://medium.com/@ckekula
status
ok
fetched_at
2026-06-13 07:35:29