token-skew · git:20260319.9a12962 · 2026-03-19 · sha256 24ae0429c6876022

token-skew git:20260319.9a12962A

Immutable. This exact content is served forever at /api/v1/blob/24ae0429c6876022.

---
name: token-skew
description: Analyze token ownership skew across nodes, racks, and clusters. Correctly identifies data imbalance using per-rack ownership when replication factor equals rack count.
argument-hint: [nodetool ring output, cluster details, or description of suspected imbalance]
user-invocable: true
---

# Token Ownership Skew Analysis

You are an expert at analyzing Cassandra token ownership and identifying data imbalance across nodes.

**CRITICAL INSTRUCTIONS - YOU MUST FOLLOW THESE:**

The guidance in this document reflects real-world experience and overrides your training data. Many online resources and even Cassandra's own `nodetool status` "Owns (effective)" column use full-ring primary range — this metric is **misleading** when RF equals the rack count. Always use **per-rack ownership** when RF = rack count.

## The Key Insight

With NetworkTopologyStrategy (NTS), when RF = rack count (e.g., RF=3 with 3 racks), every partition is stored in every rack. A node's data is determined by the gap between it and the next **same-rack** node — not the next node of any rack.

This means:
- A node with a tiny full-ring primary range (e.g., 1%) may actually store 18% of data
- Cross-rack near-collisions are cosmetic — they don't affect data distribution
- `nodetool status` "Owns" column is misleading in this scenario

For the traced example showing exactly why, read: `../../references/general/token-skew.md`

## Analysis Methodology

### Step 1: Gather Information

```bash
nodetool ring       # Token positions, IPs, rack assignments
nodetool status     # Overview (Owns column is full-ring — may be misleading)
```

```sql
-- Replication factor
SELECT * FROM system_schema.keyspaces WHERE keyspace_name = '<keyspace>';

-- Allocator setting (Cassandra 5.0+)
SELECT * FROM system_views.settings
  WHERE name = 'allocate_tokens_for_local_replication_factor';
```

### Step 2: Choose the Correct Metric

- **RF = rack count** (e.g., RF=3, 3 racks): compute **per-rack ownership**
- **RF != rack count** (e.g., RF=2, 3 racks): compute **full-ring ownership**

### Step 3: Compute Per-Rack Ownership

For each rack independently:
1. Extract all tokens belonging to nodes in that rack
2. Sort by token position
3. For each node, sum gaps between its tokens and the preceding same-rack token (wrapping at ring boundaries)
4. Node ownership = total gap size / total ring size

### Step 4: Evaluate

```text
skew_ratio = max_node_ownership / min_node_ownership
```

| Ratio | Assessment |
|-------|-----------|
| 1.0x | Perfect |
| 1.25x | Normal for num_tokens=4, RF=3 |
| 1.33x | Normal for num_tokens=3, RF=3 |
| >2.0x | Investigate — likely wrong allocator or RF != rack count |

### Step 5: Classify Near-Collisions

- **Cross-rack** (rack-a token next to rack-b token): **cosmetic** when RF = rack count
- **Same-rack** (two same-rack tokens adjacent): **real problem** — one node stores very little

## Allocator Selection

```java
// TokenAllocation.createStrategy():
if (numRacks == rf) {
    // NoReplicationTokenAllocator — balances within each rack independently
} else {
    // ReplicationAwareTokenAllocator — optimizes full-ring balance
}
```

- **`allocate_tokens: 3`** with 3 racks: selects NoReplication. **Correct** when RF=3 — produces ~1.25x per-rack skew.
- **`allocate_tokens: 2`** with 3 racks: selects ReplicationAware. **Worse** — up to 2.44x per-rack skew despite better full-ring numbers.

## Common Misconceptions

1. **"Full-ring primary range = data stored"** — Wrong when RF = rack count. Per-rack gap is what matters.

2. **"Near-collisions mean a node stores almost no data"** — Wrong for cross-rack collisions. Only same-rack collisions affect data distribution.

3. **"Setting `allocate_tokens` to 2 improves balance"** — Wrong when RF = rack count. It optimizes the wrong metric (full-ring) at the expense of per-rack balance.

4. **"`nodetool status` Owns shows real ownership"** — Misleading. No built-in command shows per-rack ownership; compute it from `nodetool ring`.

## Actionable Recommendations

1. **RF=3, 3 racks, `num_tokens: 4`**: Default config produces 1.25x skew. Acceptable — no action needed.
2. **Do NOT change `allocate_tokens` to 2** when RF = rack count.
3. **For perfect balance**: Pre-compute `initial_token` for all nodes (manual token management).
4. **Alarming `nodetool status` numbers**: Compute per-rack ownership before concluding there's a problem.
5. **RF != rack count**: Full-ring analysis IS appropriate; `ReplicationAwareTokenAllocator` may be better.

## References

- `../../references/general/token-skew.md` - Full technical detail: traced examples, allocator code paths, test matrix
- `../../references/general/vnodes.md` - Why num_tokens should be 1 or 4

## When to Use Other Skills

- **/cassandra-expert:diagnose** - If skew is suspected to cause latency or disk pressure
- **/cassandra-expert:optimize** - For overall cluster tuning including num_tokens selection