Choose a partitioning
Records are spread across N partitions, shards or consumers, say order events across the partitions of a topic. Where each record goes decides what can run in parallel, what stays in order, and which partition gets overloaded. Which strategy and key do you route by, and how many partitions?
You will end with a partitioning strategy and key, a partition count, and policies for skew, order, failed records and unroutable records.
Decide
Does anything downstream need related records together?
Per-customer state, per-order ordering, or a join on the same key.
Why
If nothing needs related records together, spreading them evenly is the only goal, and round-robin does it perfectly with no hot partitions.
Do you need range scans or sorted access on the key?
'All orders between two dates' or 'customers A to F' needs ranges; lookups by exact ID do not.
Why
Hashing spreads keys evenly but destroys their order. Range partitioning keeps order, so scans touch few partitions, but new keys that increase over time (timestamps, sequence IDs) all land in the last partition.
Where you can land
Round-robin or random
Use it when nothing downstream needs related records together.
Perfectly even load. What it costs: no per-key order and no per-key state.
Hash partitioning
Use it when related records must be together, and access is by exact key.
Before you build it
If membership will change, use a stable hash
hash(key) % N moves almost every key when N changes, which empties caches and shuffles state. For many nodes (hundreds or more): consistent hashing with virtual nodes. For fewer: rendezvous hashing, which is simpler and spreads better. For storage: many logical shards mapped onto fewer machines, so machines can change without re-hashing data.
Range partitioning
Use it when related records must be together and you scan ranges or need sorted access.
Before you build it
Spread sequential writes
If keys increase over time, every new write hits the last range. Use a composite key (for example, a bucket number before the timestamp) to spread them.
Whichever you land on, settle these
Choose the key from the real distribution
The key is the entity downstream groups by. Check its number of distinct values and its actual frequency histogram, not the theoretical one.
Deal with skew
If a few keys carry much of the traffic (one seller at 35% of orders): isolate the hot keys, or split each hot key across M partitions by adding a random suffix and combine the partial results in a second step. That works only for aggregations whose order does not matter (sum, count, min, max), and it breaks per-key order for those keys. Often the real fix is a better key.
Test whether per-key order really matters
Full-state updates with a version number need no order: the highest version wins. Increments, appends and insert–delete pairs do. If you send changes (deltas): either keep strict order (one writer per key, an idempotent producer, one consumer thread per partition, no dead-letter queue) or switch to full-state updates. Switching is usually cheaper.
Decide what happens to a record that cannot be processed
Classify the failure first. Transient (a timeout, a 503): retry with backoff, never dead-letter. Bad data: send to the dead-letter queue at once. A bug in your code: pause and alert. The downstream is down: stop consuming. Then set the dead-letter rate above which you stop instead of draining: a dead-letter queue with no limit moves your whole stream into it during an outage.
Decide where unroutable records go
Define the default destination for a record that matches no route, now rather than later.
Choose N for the ceiling, not today
At least peak throughput ÷ one consumer's throughput, times 2–3 for headroom, and at least the most consumers you will ever want: N is hard to raise later. Prefer a number with many divisors (12, 24, 60, 120) so it splits evenly across consumer counts, and over-partition slightly. 2,000 orders a second at 400 per consumer gives 5, so 10 to 15 with headroom; wanting up to 24 consumers makes it 24.