Cassandra Primary Keys

The primary key is the most important part of a Cassandra table definition. It controls where data lives in the cluster, how efficiently Cassandra retrieves it, and which queries your table can answer. Getting the primary key right is the foundation of good Cassandra data modeling.

The Postal Address Analogy

Think of the primary key as a postal address for a row of data. The country determines which region receives the mail (this is the partition key). The street and house number pinpoint the exact location within that region (these are clustering columns). Without the full address, the postal system cannot deliver the letter. Without the primary key, Cassandra cannot locate the row.

Primary Key = Partition Key + (optional) Clustering Columns

Full Address:
  Country  ──▶  which data center / ring partition
  City     ──▶  which sub-section of the partition
  Street   ──▶  exact row position within the partition

Simple Primary Key

A simple primary key contains only one column. That column serves as both the partition key and the row identifier.

CREATE TABLE users (
  user_id   UUID,
  name      TEXT,
  email     TEXT,
  PRIMARY KEY (user_id)
);

Every row has a unique user_id. Cassandra hashes this value and places the row on the appropriate node. You can fetch any row instantly by its user_id.

user_id (PK) | name    | email
─────────────────────────────────────────
uuid-1       | Alice   | alice@mail.com
uuid-2       | Bob     | bob@mail.com
uuid-3       | Carol   | carol@mail.com

Composite Primary Key

A composite primary key includes one or more partition key columns plus one or more clustering columns. This structure allows Cassandra to store multiple related rows together in the same partition, which makes range queries and sorted reads very fast.

CREATE TABLE orders_by_customer (
  customer_id UUID,
  order_date  TIMESTAMP,
  order_id    UUID,
  total       DECIMAL,
  PRIMARY KEY (customer_id, order_date, order_id)
);

Here customer_id is the partition key. order_date and order_id are clustering columns. All orders for a single customer live together in one partition, sorted by date.

Partition: customer_id = uuid-A
──────────────────────────────────────────────────────────
order_date          | order_id | total
────────────────────────────────────────────────────────
2024-01-05 10:00:00 | ord-001  | 120.00
2024-01-12 14:30:00 | ord-002  |  45.00
2024-02-01 09:15:00 | ord-003  | 230.00

Partition: customer_id = uuid-B
──────────────────────────────────────────────────────────
order_date          | order_id | total
────────────────────────────────────────────────────────
2024-01-20 11:00:00 | ord-004  |  80.00

Compound Partition Key

When the partition key itself contains multiple columns, you wrap those columns in an extra set of parentheses inside the PRIMARY KEY definition.

CREATE TABLE sensor_readings (
  device_id   TEXT,
  region      TEXT,
  read_time   TIMESTAMP,
  temperature DOUBLE,
  PRIMARY KEY ((device_id, region), read_time)
);

The partition key is the combination of device_id and region. Only when both values are known can Cassandra locate the partition. read_time is the clustering column that sorts rows within each partition.

Partition: device_id='D1' + region='EU'
   read_time            | temperature
   ─────────────────────────────────
   2024-03-01 08:00:00  | 22.5
   2024-03-01 09:00:00  | 23.1
   2024-03-01 10:00:00  | 22.8

Partition: device_id='D1' + region='US'
   read_time            | temperature
   ─────────────────────────────────
   2024-03-01 08:00:00  | 18.3
   2024-03-01 09:00:00  | 19.0

Rules for Primary Keys

Rule                                   Explanation
──────────────────────────────────────────────────────────────────────
Must be unique per partition           Duplicate rows overwrite
                                       each other silently
Cannot be NULL                         NULL values are not allowed
                                       in primary key columns
Cannot be changed after creation       You cannot rename or retype
                                       PK columns
Queries must use the partition key     You cannot filter by a
                                       non-key column without
                                       ALLOW FILTERING
Clustering columns allow range queries You can use >, <, >= on
                                       clustering columns

How the Primary Key Determines Data Placement

Cassandra hashes the partition key to produce a token — a number on the ring. The node responsible for that token range stores the row. All rows with the same partition key land on the same node (and its replicas). This is why the choice of partition key has a direct impact on data distribution across the cluster.

Write: customer_id = 'uuid-A', order_date = '2024-01-05'

Step 1: Hash partition key 'uuid-A'
        ──▶ Token = -4567812345678

Step 2: Find node that owns token -4567812345678
        ──▶ Node B

Step 3: Write row to Node B (and its replicas)

Impact of a Bad Partition Key Choice

Choosing a partition key with too few distinct values creates a hotspot. If you use country as a partition key and 80% of your users are from the same country, 80% of your writes go to the same node. That node becomes overloaded while others sit idle.

Bad partition key: country

Partition 'US': 80% of traffic ──▶ Node A [OVERLOADED]
Partition 'UK':  8% of traffic ──▶ Node B [underused]
Partition 'DE':  4% of traffic ──▶ Node C [underused]
Partition 'FR':  4% of traffic ──▶ Node D [underused]

Better partition key: user_id (high cardinality, even distribution)

Summary

The primary key defines how Cassandra stores, distributes, and retrieves data. A simple primary key is one column. A composite primary key adds clustering columns for sorting within a partition. A compound partition key uses multiple columns to determine the partition. Always choose a partition key with high cardinality to distribute data evenly across the cluster, and design the clustering columns to match the sort order your queries need.

Leave a Comment

Your email address will not be published. Required fields are marked *