FjellSystem Design Lab
Lesson 5.4• 7 min read

Partition keys, skew and polyglot persistence

Hot partition mitigation, salted hashing, and multi-model polyglot microservice architectures.

What you will learn

  • Split a NoSQL primary key into its partition key and clustering key.
  • Spot hot partitions from time buckets and very large tenants, and spread them by salting the key.
  • Choose a store per kind of data, and keep the stores in sync with change data capture.

Primary key anatomy in distributed NoSQL

In Cassandra (and, with different names, DynamoDB), the primary key has two parts:

Primary key=(Partition key⏟which node(s) store it,Clustering / sort key⏟order within the partition)\text{Primary key} = (\underbrace{\text{Partition key}}_{\text{which node(s) store it}}, \underbrace{\text{Clustering / sort key}}_{\text{order within the partition}})
  1. Partition key: Hashed to pick the nodes that own the partition (Cassandra's default partitioner is Murmur3).
  2. Clustering key: Sets the sort order of rows within that partition, so ranges within one partition are cheap to read.
-- Cassandra CQL Example:
CREATE TABLE sensor_readings (
    device_id uuid,          -- Partition Key (Determines Shard)
    recorded_at timestamp,   -- Clustering Key (Determines On-Disk Sort)
    temperature double,
    humidity double,
    PRIMARY KEY (device_id, recorded_at)
) WITH CLUSTERING ORDER BY (recorded_at DESC);

The Hot Partition & Skew Catastrophe

If partition keys are poorly chosen, all writes or reads will concentrate on a single physical shard, neutralizing horizontal scaling:

1. The time-bucket hot partition

  • Mistake: Using a time bucket, such as the current date, as the partition key (PRIMARY KEY (date)).
  • Consequence: Every write made today has the same partition key, so all current writes land on the nodes that own today's partition while the rest of the cluster idles.
  • Note: A precise timestamp as the partition key would not do this (distinct values hash to different nodes), but it scatters each time range across the whole cluster, so you could not read a range by key.

2. Celebrity / high-volume entity hotspotting

  • Mistake: Using tenant_id or influencer_user_id as the partition key when a few tenants or users are far bigger than the rest.
  • Consequence: One very large tenant concentrates its load on one partition, which can hit that partition's limits while the others idle.

3. Write sharding (salting) the key

Spread one hot key's writes over KK partitions by adding a suffix. DynamoDB's guide gives two variants:

  • Random suffix: e.g. 2026-11-13.1 to 2026-11-13.200, chosen at random per write. Writes spread evenly, but to read a day you must query every suffix and merge the results.
  • Calculated suffix: derive the suffix from something you query by, such as a hash of the order ID:
Partition key=date∥“.”∥(hash(order_id) mod K)\text{Partition key} = \text{date} \parallel \text{“.”} \parallel (\text{hash}(\text{order\_id}) \bmod K)

Writes still spread evenly, and a single item can be read directly because its suffix can be recomputed; reading the whole day still needs all KK queries.


Polyglot persistence

Instead of one database for everything, many systems use different data stores for different kinds of data, choosing each for its access pattern:

Kind of data Example store Why
Money, orders, anything needing transactions Relational (PostgreSQL) Transactions, constraints, complex joins
High-volume time series and event data Wide column (Cassandra) Cheap writes; rows sorted within a partition
Sessions, counters, caches Key-value in memory (Redis) Simple operations on rapidly changing data
Highly connected data (social graphs) Graph database Complex many-to-many relationships

Martin Fowler, who popularized the term, is clear about the costs: more complexity (each store has its own interface to learn and tune), new questions about consistency and availability, and no longer a single transactional source of truth.

Keeping the stores in sync: change data capture (CDC)

Writing to two stores from application code (a "dual write") is fragile: if the process crashes or the network fails between the two writes, the stores silently disagree. Change data capture instead reads the source database's own change log (MySQL's binlog, PostgreSQL's logical replication stream) and publishes each change, for example with Debezium into Kafka, for other stores to apply. Debezium delivers every change at least once: after a crash, consumers may see duplicates, so they must apply changes idempotently.

Partition keys, hot spots and salting

100,000 Write RPS Ingestion Across 4 Distributed Shards:Hot spot (severe skew)
Shard 1HOT
94,000 RPS
94% of total writes
Shard 2
2,000 RPS
2% of total writes
Shard 3
2,000 RPS
2% of total writes
Shard 4
2,000 RPS
2% of total writes
Time-bucket hot partition: Using a time bucket such as the current date as the partition key gives every current write the same key, so they all land on the one node that owns it while the rest of the cluster idles. (A precise timestamp would not cause this: distinct values hash to different nodes, but then you cannot query a time range by key.)
Figure 4: Polyglot architecture combining relational, wide-column, and search engines.
  • IoT Devices & Web Users: 1 × Clients & Users
  • Anycast DNS: 2 × Anycast DNS / Route53
  • Ingest Load Balancers: 4 × Application Load Balancer
  • Kafka Ingestion Pipeline: 4 × Message Queue / Kafka
  • Cassandra Telemetry Store: 8 × Distributed NoSQL Store
  • Postgres Account Billing: 2 × Primary Relational DB
  • IoT Devices & Web Users sends requests to Anycast DNS
  • Anycast DNS sends requests to Ingest Load Balancers
  • Ingest Load Balancers sends requests to Kafka Ingestion Pipeline
  • Kafka Ingestion Pipeline sends requests to Cassandra Telemetry Store
  • Ingest Load Balancers sends requests to Postgres Account Billing

Key takeaways

  • The partition key decides which nodes hold the data; the clustering key orders rows within a partition.
  • A date as the partition key sends every current write to one partition, and a celebrity tenant can do the same.
  • Salting spreads a hot key over KK partitions; a calculated suffix keeps single-item reads direct, but reading the whole key still takes KK queries.
  • Polyglot persistence picks each store for its access pattern but adds complexity; change data capture beats fragile dual writes.

Sources

Adapted from The System Design Primer by Donne Martin, licensed under CC BY 4.0. Changes were made.