DB Notes — A Database Field Guide

Scaling

Partitioning

Splitting a large dataset across many nodes so both its size and its query load can scale beyond a single machine.

Partitioning (also called sharding) splits a large dataset into smaller pieces spread across many nodes, so both the data volume and the query load can scale horizontally. The core design question is: given a key, which partition does it live on? Get this wrong and some partitions become overloaded “hot” partitions while others sit nearly idle.

Fig. 4 — Key range vs. hash partitioning
2024-06-012024-06-022024-06-032024-06-04Partition 04 keys — hotPartition 10 keysPartition 20 keysPartition 30 keys

Sequential, timestamp-prefixed keys always fall into whichever range is 'newest' — every write lands on the same partition, creating a hotspot no matter how many partitions exist.

Key range partitioning

Each partition owns a contiguous range of keys — like the volumes of a paper encyclopedia. This is great for range queries (WHERE created_at BETWEEN ... touches one or two partitions), but it’s exactly what creates the hotspot in the diagram above: if keys are assigned in some naturally increasing order (timestamps, auto-incrementing IDs), every new write goes to whichever partition currently owns the “newest” range.

Hash partitioning

Run the key through a hash function first, then assign by hash value. This scatters keys evenly and eliminates hotspots from sequential keys — the tradeoff is that range queries no longer map to a contiguous set of partitions, since consecutive keys are now scattered essentially at random.

def partition_for_key(key: str, partition_count: int) -> int:
    return hash(key) % partition_count

Request routing

Once data is scattered across nodes, a request for a given key has to reach the right one. Common approaches: a routing tier that tracks partition ownership, gossip protocols where any node can forward a request to the right owner, or a smart client that caches the partition map itself.