Cassandra rewards you for designing tables around queries — and punishes you for treating it like a relational database. The partition key is the unit of distribution AND the unit of locality; everything else flows from that one decision.

Advertisement

Query first, not entity first

In Postgres you start with entities (User, Order) and add indexes for queries. In Cassandra you start with the queries: 'show me last 30 days of orders for a user' becomes a table; 'show me all orders in a region' becomes a different table. Denormalization is mandatory, not a smell.

Partition key = locality

All rows with the same partition key live on the same node (and its replicas). Reads against a single partition are fast (one node hop). Reads spanning partitions become coordinator-mediated scatter-gathers — slow and fragile. Aim to answer 95% of queries with single-partition reads.

Advertisement

Clustering columns = order

Within a partition, clustering columns define on-disk order. Choose them to match how you'll scan: time DESC for timeline reads, status ASC for state machines. The clustering key also enforces uniqueness within the partition.

Anti-patterns

Wide partitions (>100MB or >100K rows): repair lag, JVM pressure, slow streaming. Tombstone storms (deleting many rows in a partition you keep reading): scan times blow up. Secondary indexes on high-cardinality columns: don't — duplicate the table instead.

Practical sizing rule

Target partition size: 10-50MB, <100K rows. If you can't, time-bucket the partition key (user_id, year_month) and query multiple buckets in parallel from the app. Most production pain comes from skipping this exercise.

Pick the partition key to make your hottest read a single-node operation. Everything else is downstream.