Lesson F02.1Distributed data processing course / Foundations / Partitions

Every Record Belongs to Exactly One Partition

How overlapping partition boundaries turned 21,154 records into 21,207, duplicating 53 events across 11 orders.

18 min read · 6 sections · 4 predictions · Junior
· Verified source figures:Python 3.13 · router 0.1 · captured 2026-10-13
Before you start

Complete F01 or pass its diagnostic assessment before starting this lesson. Basic Python knowledge is sufficient, and no additional software is required.

We’ll continue using the 21,154-record orders dataset introduced in F01. All examples and results can be reproduced in the F02 workbench.

Terms are defined in this margin where they first appear.

01

The Problem: How Do We Distribute Records Across Partitions?

In F01, we learned how distributing work across multiple workers can improve processing speed. Now let’s consider a different problem: How do we decide which records each worker should process? Suppose our nightly validation job processes 21,154 order events using 12 workers. To distribute the workload, we divide the dataset into 12 partitions. Each partition contains a subset of the records and can be processed independently by a worker.

But who decides which record belongs to which partition? That’s the responsibility of a partition function. A partition function examines a record and assigns it to a partition.

Although this sounds straightforward, incorrect partitioning can cause two serious problems:

  1. Missing records: Some records are not assigned to any partition and are never processed.
  2. Duplicate records: Some records are assigned to multiple partitions and get processed more than once.

For example, an incorrect partitioning rule could turn our 21,154 input records into 21,207 partitioned records. The result is that 53 records appear twice, leading to incorrect order counts.

Rules for Correct Partitioning

To prevent these problems, a partitioning strategy must satisfy two properties:
1. Completeness: Every record must be assigned to a valid partition.
2. Disjointness: Every record must belong to only one partition.

Together, these properties ensure that each input record is assigned to exactly one partition.

Why the Partition Function Matters

A well-defined partition function takes one record and returns exactly one valid partition number. This makes completeness and disjointness easier to guarantee. However, if partitions are created using separate filtering conditions, mistakes such as overlapping ranges or gaps between boundaries can lead to duplicate or missing records. We’ll also explore stable partitioning, which ensures that records with the same key are routed consistently across different runs.

02

Common Partitioning Strategies

Now that we understand why partitioning matters, let’s explore how records are assigned to different partitions.
We’ll use the same dataset of 21,154 order events and distribute it across 12 partitions, numbered from 0 to 11.

Consider the following order event:

{
  "event_id": "ev-000001",
  "delivery_id": "dl-27e4102e32",
  "supersedes_event_id": null,
  "order_id": "ord-00001",
  "customer_id": "cust-0134",
  "event_type": "order_placed",
  "amount_cents": 699,
  "event_time_utc": "2026-03-02T00:08:54.332Z",
  "ingest_time_utc": "2026-03-02T00:09:02.929Z"
}

How do we decide which partition should receive this record? There are several partitioning strategies. Let’s understand three common approaches.

1. Round-Robin Partitioning

Round-robin partitioning distributes records across partitions in sequence, without considering their values.
For example, with 12 partitions:

  • Record 0 goes to Partition 0.
  • Record 1 goes to Partition 1.
  • Record 2 goes to Partition 2.
  • ...
  • Record 11 goes to Partition 11.
  • Record 12 goes back to Partition 0.

The assignment follows a simple rule: Partition = Record position % Number of partitions

Here, % is the modulo operator, which returns the remainder after division.
For our first record: 0 % 12 = 0
Therefore, ev-000001 is assigned to Partition 0.

Round-robin is useful when we want to distribute records evenly. However, it doesn’t guarantee that records belonging to the same customer or order will stay together.

2. Hash Partitioning

Hash partitioning uses a record’s key to determine its partition. A key can be a field such as customer_id, order_id, or event_id.

Suppose we want all events belonging to the same customer to be processed together. We can partition records using customer_id.
The process works as follows:

  1. Take the customer’s ID, such as cust-0134.
  2. Pass it through a hash function, which converts the key into a numeric value.
  3. Divide that number by the number of partitions and use the remainder to select a partition.

The formula is: Partition = Hash(key) % Number of partitions

Our reference router uses SHA-1 and converts the first eight bytes of the hash into an integer.

For cust-0134, the resulting value is: 2,381,997,355,534,491,765
Applying the partitioning rule: 2,381,997,355,534,491,765 % 12 = 9

Therefore, this event is assigned to Partition 9.

Why is this useful?
Every event belonging to cust-0134 produces the same hash value. As long as the hashing rule and partition count remain unchanged, all those events go to Partition 9. This is important for operations that need to process related records together, such as calculating customer-level metrics or maintaining customer history. However, hash partitioning doesn’t guarantee that every partition receives the same number of records. We’ll examine why shortly.

3. Range Partitioning

Range partitioning assigns records based on the range in which a key falls. For example, suppose we want to distribute order events using order_id. We can sort the order IDs and divide their values into 12 ordered ranges. Each partition is responsible for a particular range of order IDs. In our reference dataset, the first range ends just before ord-00367. Since ord-00001 falls within that first range, the record is assigned to Partition 0.

Unlike round-robin or hash partitioning, range partitioning keeps nearby key values together. This is useful when processing or retrieving records by ordered ranges, but the range boundaries must be chosen carefully to avoid uneven distribution.

Comparing the Three Strategies

Let’s apply these strategies to all 21,154 records and compare how evenly they distribute the data.

Our round-robin strategy assigns records using: Partition = Record position % 12

Pause and predict · P1Commit before you read on

We have 21,154 records and 12 partitions. How many records will the largest partition contain?

Choosing the Right Partitioning Strategy

Different partitioning strategies serve different purposes:

  • Round-robin distributes records almost evenly but doesn’t keep related records together.
  • Hash partitioning keeps records with the same key together, but partitions may contain different numbers of records.
  • Range partitioning groups records by ordered key ranges, but its effectiveness depends on how those ranges are defined.

We’ll explore uneven partition sizes and their performance impact in F02.2.

Tip

There is no single partitioning strategy that is best for every workload. The right choice depends on whether we prioritise balanced distribution, keeping related records together, or organising records by key ranges.

03

What Makes a Partitioning Strategy Correct

Distributing records across partitions isn’t just about balancing the workload. We must also ensure that every record is assigned correctly and related records are grouped when necessary. As we discussed above, what is more important in terms of partitioning is its completeness, disjointness, and, if needed, keeping related records together.

What Makes a Good Partition Function?

A partition function should take one record as an input and return exactly one valid partition number as its output. For example: partition_id = partition_function(record)

If the function assigns every input record to exactly one valid partition, completeness and disjointness are guaranteed by design.

However, consider a different approach. Instead of using one function, a developer creates 12 independent filters, one for each partition. For example:

  • Partition 0 accepts orders from ord-00001 through ord-00367.
  • Partition 1 accepts orders from ord-00367 through ord-00733.

Notice that ord-00367 appears in both ranges. If both boundaries are inclusive, records belonging to ord-00367 will be assigned to both partitions. Similarly, gaps between ranges could cause some records to be excluded entirely. Unlike a single-valued partition function, independent filters don’t automatically guarantee completeness or disjointness.

Key Ownership: Keeping Related Records Together

Some distributed operations require records with the same key to be processed together. Suppose we’re building a complete order history for each customer. A customer may have placed several orders, and each order may generate multiple events. To process that customer’s history efficiently, we want all events belonging to the customer in the same partition. This property is called key ownership.

Hash partitioning by customer_id provides this guarantee because the same customer ID always produces the same partition number when the routing configuration is unchanged.

Now, let’s see what happens when we use different partitioning strategies. Below, we compare our reference dataset.

Partitioning StrategyCustomers Split Across Partitions
Round-robin797 of 797
Hash by event_id797 of 797
Hash by order_id592 of 797
Hash by customer_id0 of 797

Here’s what the results tell us:

  • Round-robin: Customer events are distributed independently of customer identity. In our dataset, every customer has events across multiple partitions.
  • Hash by event_id: Each event is routed using its event ID, so events from the same customer can land in different partitions.
  • Hash by order_id: All events belonging to the same order stay together, but one customer may have multiple orders assigned to different partitions.
  • Hash by customer_id: All events belonging to the same customer are guaranteed to stay in one partition.

Hash partitioning keeps records with the same partitioning key together. For example, partitioning by order_id keeps all events for an order in the same partition, but events from different orders belonging to the same customer may end up in different partitions.

But does Hash Partitioning Guarantee Balanced Work?
Not necessarily. Hashing can distribute different keys across partitions, but it doesn’t account for how many records belong to each key. Some customers generate far more events than others. If several high-volume customers happen to map to the same partition, that partition can become much larger than the rest.

We have:

  • 21,154 order events
  • 797 customers
  • 12 partitions
  • The largest customer has 1,261 events, representing about 6% of all records.
Pause and predict · P2Commit before you read on

We partition the records using a hash of customer_id. Approximately, how many records will the largest partition contain? Take a guess:

Why Does This Matter?

Imagine that each partition is processed by a separate worker. A worker assigned 3,789 records may take significantly longer than one assigned only 926 records, assuming similar processing cost per record. Even if most workers finish quickly, the job may have to wait for the worker processing the largest partition. This problem is known as data skew, which we’ll explore in detail in F02.2.

04

More Partitioning Related Issues

Even when every record is assigned to exactly one partition, routing problems can still occur. Let’s examine two common mistakes involving hash partitioning.

1: Using an Unstable Hash Function

Suppose we’re building customer histories. All events belonging to the same customer must be assigned to the same partition so they can be processed alongside that customer’s existing state.
Let’s consider the partitioning strategy: partition = hash(customer_id) % 16

This looks reasonable. The hash function converts the customer ID into a number, and the modulo operation selects one of 16 partitions. But there’s a problem. Python’s built-in hash() uses randomised hashing for strings by default. Its result can change between independent Python processes.

A customer with ID cust-0001 is processed on two different days by separate Python processes. Both processes use: hash("cust-0001") % 16

Pause and predict · P3Commit before you read on

Will the customer always be assigned to the same partition?

How Can We Fix It?

Use a deterministic hash function, such as the SHA-1-based function in our reference router. A deterministic hash produces the same result for the same input, provided the hashing algorithm, key representation, and configuration remain unchanged. This ensures consistent routing across machines and program executions.

2: Changing the Partition Count

Pause and predict · P4Commit before you read on

After increasing the partition count from 16 to 17, approximately what percentage of customers will move to a different partition?

What About Doubling the Partitions?

When the partition count doubles from 16 to 32, approximately half the customers move.
For a given hash value, the new remainder is either the previous partition number or that number plus 16. This explains why doubling the partition count moves roughly 50% of the keys, which we verified by running it in our experiment.

Why Does This Matter?

Changing the partition count can affect systems that maintain data or processing state by partition. If a customer’s state is stored in its previous partition, moving new events to another partition without migrating the state may cause incorrect results. Systems that support repartitioning or rescaling need a strategy for redistributing data and state safely.

05

Evaluating and Choosing a Partitioning Strategy

How can we tell when records are missing, duplicated, or assigned inconsistently?
We can run the checks:

  1. Missing records: Some input records are not assigned to any partition.
  2. Duplicate assignments: The same input record appears in multiple partition outputs.
  3. Unexpected output counts: The sum of partition counts differs from the number of input records.
  4. Inconsistent routing: The same key is assigned to different partitions across runs, even though the routing configuration was supposed to remain unchanged.

To identify the cause of these problems, examine how records are distributed across partitions and use automated checks to detect missing, duplicated, or incorrectly assigned records.

Choosing a Partitioning Strategy

Before choosing a partitioning strategy, consider these:

  1. How will each record be assigned? Define a clear function that maps every input record to exactly one valid partition.
  2. Which key should determine the partition? Choose a key that keeps together the records required by the computation.
  3. Are the assignments complete and disjoint? Verify that no input record is missing or assigned more than once.
  4. Is the routing deterministic? Ensure the same key maps to the same partition across processes and runs when the configuration is unchanged.
  5. What happens when the partition count changes? Understand how many keys may move and whether existing data or state needs redistribution.

If the implementation uses independent filters instead of a single routing function, we should pay particular attention to overlapping boundaries and gaps.

Tip

A good partitioning strategy must satisfy the computation’s correctness requirements, preserve necessary key ownership, and behave predictably as the system changes.

Check Your Understanding: Does Partitioning by Order Keep Customers Together?

Suppose we partition the orders dataset into 12 partitions using a hash of order_id. A colleague argues:
“Every order belongs to a customer, so partitioning by order ID should also keep all of a customer’s events together.”
Is this correct?

Think about which key the hash function actually uses before revealing the answer.

Reveal

No. Partitioning by order_id keeps events belonging to the same order together, but it does not guarantee that all events belonging to the same customer stay together.

A customer may have multiple orders, each with a different order_id. Since these order IDs can produce different hash values, the customer’s events may be distributed across multiple partitions.

Try It Yourself

When the F02 workbench is available, run:

Run it
python -m router counts --strategy hash --key customer_id --partitions 12

The reference result shows Partition 7 containing 3,789 records.

Now change the partitioning key to event_id:

Run it
python -m router counts --strategy hash --key event_id --partitions 12

The largest partition now contains 1,867 records.

Compare the results and consider why changing the key affects both partition sizes and which records stay together.

Key takeaway

Partitioning by a particular key guarantees ownership of that key, not every related entity. Choose the key based on which records your computation needs together.

06

Summary

Partitioning allows distributed systems to divide data into smaller pieces that can be processed independently.

In this lesson, we explored three important properties of partitioning:

  1. Completeness: Every input record must be assigned to a partition.
  2. Disjointness: No input record should be assigned to multiple partitions.
  3. Key ownership: Records sharing a required key should be assigned to the same partition when the computation needs them together.

We also learned that:

  • Incorrect range boundaries can duplicate records. Our example produced 21,207 assignments from 21,154 input records because 53 records appeared in two partitions.
  • Hash partitioning doesn’t guarantee equal workloads. Hashing by customer_id produced a partition containing 3,789 records, more than twice the average.
  • Routing must be deterministic when consistency matters. Python’s built-in string hash() may produce different results across processes.
  • Changing partition counts can move many keys. Increasing from 16 to 17 partitions reassigned approximately 94% of customers in our reference dataset.
Where this matters

Partitioning is fundamental to distributed systems such as Apache Spark, Kafka, and Flink. Choosing the wrong partitioning strategy can lead to missing records, duplicate assignments, uneven workloads, and inconsistent state.

In Kafka, for example, partition selection also affects which records share a partition-level ordering guarantee. We’ll explore that behaviour in K01.

Sources. Capture: reference router 0.1, Python 3.13, 2026-10-13; fixture orders_s0_small.jsonl sha256 5ac4be13…, 21,154 records, 797 customers. The stable hash is SHA-1 of the UTF-8 key, first eight bytes as an integer; Python’s per-process string hashing is documented under PYTHONHASHSEED in the Python reference. The “every record exactly once” framing follows the partition-function definition used by MapReduce-style systems (one key, one reducer); the page’s figures are the module’s own.

Ask Colearn about this lesson

3 of 3 free questions left

Ask why independent filters can duplicate or lose records, why a customer can land in a different partition on the next run, or whether partitioning by order keeps a customer’s events together.

Instant answers, grounded in the same verified material the diagnostic grades against.

Why can independent filters duplicate or lose records?

A partition function assigns each input record to exactly one partition.

Independent filters make separate decisions about which records belong to each partition.

If the filters overlap, a record may appear in multiple partitions. If gaps exist between their conditions, some records may not be assigned anywhere.

In our example, inclusive boundaries caused 53 event records to appear in two partitions.

Using half-open ranges or a single-valued partition function prevents these assignment errors when implemented correctly.

Why did the same customer move to another partition on the next run?

The router used Python's built-in hash() function, which can produce different results for strings across independent processes because of hash randomisation.

As a result, the same customer ID may map to a different partition even when the partition count hasn't changed.

Using a deterministic hash function fixes inconsistent routing for future runs. If existing customer state was stored using the earlier assignments, that state may also need to be migrated.

Does partitioning by order_id keep all customer events together?

No.

Hashing by order_id ensures that events sharing the same order ID go to the same partition.

However, a customer can have multiple orders with different order IDs, which may map to different partitions.

In our dataset, 592 of 797 customers have events spread across multiple partitions when hashing by order_id.

To keep complete customer histories together, partition by customer_id.

Don’t paste credentials, personal data, or confidential production records.
Related

Workbench: L-F02 Partition Router · Coming soon

Diagnostic: Test your understanding of partition completeness, disjointness, stable routing, and key ownership.

Up next · Lesson F02.2Why One Partition Can Slow Down the Entire Job

In F02.2 · Balance, Skew & the Straggler, we’ll explore why some partitions contain much more work than others, how uneven distribution affects job performance, and what engineers can do when they need both key ownership and balanced workloads.