1. Context & Architecture Reality
Project Identity, Version & License Baseline
| Project Component | Canonical Repository | Pinned Release | Governance / License | Edition Boundary |
|---|---|---|---|---|
| Apache Cassandra | apache/cassandra | v5.0 (LTS) | Apache Software Foundation (Apache 2.0) | Open-source core |
| ScyllaDB | scylladb/scylladb | v5.4 | ScyllaDB Inc (AGPL v3.0) | Open-source core vs Enterprise |
1. Write Path (Append Only) & CommitLog (Disk)
Establishes the ingress and structural boundary across Write Path (Append Only) & CommitLog (Disk): Sequential append; crash recovery; ConcurrentSkipListMap in memory; Immutable Sorted String Tables Zero in-place updates! Blazing fast.
2. Size-Tiered Compaction (STCS) & Leveled Compaction (LCS)
Coordinates core data-plane execution and state transitions through Size-Tiered Compaction (STCS) & Leveled Compaction (LCS): Groups SSTables of similar size. Great for write-heavy append workloads; Exponential size tiers L0, L1, L2... Guarantees 90% reads hit <= 1 SSTable; Deletes write a Tombstone marker. Reading across 100,000 tombstones triggers TombstoneOverwhelmingException and crashes JVM with severe Out-Of-Memory.
3. Node Storage Engine: Write and Read Paths & Tunable Consistency and Quorum Mathematics
Enforces durability, state convergence, and output verification across Node Storage Engine: Write and Read Paths & Tunable Consistency and Quorum Mathematics: Guarantees read and write quorums overlap on at least 1 current replica node. Write ONE / Read ALL: W=1, R=N (R+W > N) → Blazing writes, slow/brittle reads Write QUORUM / Read QUORUM: W=⌊N/2⌋+1, R=⌊N/2⌋+1 → Balanced strong consistency LOCAL_QUORUM: Quorum strictly inside coordinator's datacenter (Zero cross-DC WAN wait!); Hinted Handoff: Temporarily buffers writes for dead node for up to 3 hours.
Curriculum Bridge (INF-I04-001 CORE-M32-001): Whereas Step 47 (INF-I04-001) established the general Dynamo-style key-value model (tunable quorums , sloppy quorums, and Merkle tree anti-entropy), this open-source architecture study inspects Apache Cassandra v5.0 and ScyllaDB v5.4 codebase internals: Murmur3Partitioner vnode token allocation, CQL wide-row clustering keys, the -Accrual failure detector (FailureDetector.java), StorageProxy mutation stages, CommitLog / MemTable / SSTableReader read-path promotion, tombstone gc_grace_seconds zombie resurrection hazards, and Lightweight Transactions (LWT) / Accord consensus.
In the mid-2000s, traditional relational databases dominated software architectures. When web applications expanded from thousands to millions of concurrent users, database scalability hit a rigid barrier. Relational engines rely on centralized ACID locks, B-Tree in-place page overwrites, and single-primary replication. Scaling writes required buying larger multi-socket SMP servers, creating enormous cost and operational vulnerability.
In 2007, Amazon published the seminal Dynamo paper, outlining a masterless, highly available distributed key-value store. Shortly thereafter, Avinash Lakshman (co-author of Dynamo) and Prashant Malik created Apache Cassandra at Facebook to power inbox search.
Cassandra married Dynamo’s completely decentralized peer-to-peer ring topology with Google Bigtable’s LSM-Tree columnar data model. In Cassandra:
- There is no primary or secondary node. Every node is architecturally identical.
- Any node can accept a read or write request for any data item, acting as the Coordinator.
- Partitions scale horizontally across a token consistent hashing ring.
- Writes append sequentially to an immutable commit log and in-memory skiplist, enabling sustained write throughput of hundreds of thousands of operations per second per node.
+--------------------------------------------------------------------------------------------------+
| THE MASTERLESS TOKEN RING TOPOLOGY |
| |
| Token Space: -2^63 to +2^63-1 |
| |
| [Node 1: Token -4.6e18] |
| / \ |
| / \ |
| / \ |
| [Node 4: Token 4.6e18] [Node 2: Token -1.5e18] |
| \ / |
| \ / |
| \ / |
| [Node 3: Token 1.5e18] |
| |
| Client Query: SELECT * FROM users WHERE user_id = 'usr_84920' |
| 1. Murmur3Partitioner('usr_84920') -> Token: -2,410,582,910,481 |
| 2. Natural Endpoints: Routed to clockwise owners: Node 2, Node 3, Node 4 |
| 3. Coordinator (Node 1) queries replicas using requested Consistency Level (QUORUM) |
+--------------------------------------------------------------------------------------------------+
2. Core Problem & Failure Modes
Designing and operating a masterless, peer-to-peer database introduces fundamental engineering challenges:
-
The Consistency-Availability Dilemma (CAP Theorem): Because Cassandra does not use consensus algorithms (like Paxos or Raft) for normal writes, writes proceed without cluster-wide synchronization. Replicas accept writes with timestamps. If network partitions occur, replicas on different sides of the network accept conflicting writes. Cassandra relies on Last-Write-Wins (LWW) based on client-provided microsecond wall-clock timestamps, making it vulnerable to system clock drift.
-
The Tombstone Catastrophe: Because SSTables are immutable on disk, a
DELETEoperation cannot delete data in place. Instead, it writes an entry called a Tombstone containing a deletion timestamp. If an application performs millions of deletions (e.g., using Cassandra as a message queue), read queries scanning across partitions must read tens of thousands of tombstones into memory to determine which rows are alive, triggering JVM garbage collection pauses and catastrophicTombstoneOverwhelmingExceptioncrashes. -
Zombie Record Resurrection: To prevent tombstones from consuming disk space forever, Cassandra purges them after
gc_grace_seconds(default: 10 days). If a node goes offline for 12 days and then rejoins the cluster after tombstones have been compacted away on all healthy nodes, the resurrected node still holds its old pre-deletion data. During subsequent read repairs, the resurrected node shares its obsolete records, resurrecting deleted data sitewide as “zombies.” -
Hinted Handoff Throttling & Thunder: When a replica is unreachable, the Coordinator writes a “Hint” to local disk to replay once the dead node recovers. If 5 nodes in a 50-node cluster go down for 2 hours, the remaining 45 nodes accumulate gigabytes of hints. When the 5 nodes reboot, all 45 nodes simultaneously replay their hints, overwhelming the recovering nodes with a massive thunderous write storm that instantly crashes them again.
3. Technical Deep Dive
Consistent Hashing & Murmur3 Partitioner
Cassandra maps data partitions to physical nodes using consistent hashing over the signed 64-bit integer token domain (reserving , Long.MIN_VALUE, as an internal minimum sentinel):
Every row has a Partition Key. The default partitioner (org.apache.cassandra.dht.Murmur3Partitioner) computes the 128-bit MurmurHash3 (Murmur3_x64_128) of the serialized partition key bytes and extracts the first signed 64-bit integer word (normalizing to ):
Nodes are assigned positions on the ring. To ensure uniform data distribution and parallel streaming during bootstrap, Cassandra uses Virtual Nodes (vnodes): instead of assigning one monolithic range per server, each physical node owns virtual tokens across the ring. While Cassandra 3.x defaulted to num_tokens: 256, Cassandra 4.0 and 5.0 reduced the default to num_tokens: 16 paired with deterministic replication-aware allocation (allocate_tokens_for_local_replication_factor: 3). Lowering from to slashes anti-entropy repair sub-range fan-out and reduces the probability that two simultaneous node failures share overlapping token ranges, while still keeping per-node storage variance under .
When a partition key with token arrives, Cassandra locates the first node whose token position is . That node, along with the next physically distinct nodes clockwise on the ring (respecting rack and datacenter failure boundaries via NetworkTopologyStrategy), form the Natural Endpoints for that row.
Tunable Quorum Intersection Mathematics
Let be the Replication Factor. A client specifies a Write Consistency Level () and a Read Consistency Level ().
The Quorum Overlap Theorem
Under the pigeonhole principle, if the sum of nodes in the read quorum and the write quorum exceeds the total replication factor :
Then the set of nodes written to and the set of nodes read from must overlap on at least one common node:
Because Cassandra tags every write with a microsecond-precision timestamp, the overlapping node returns the newest timestamp, providing strong read-your-writes overlap without distributed locks.
Quorum Overlap vs. True Linearizability (Paxos v2 & Accord):
As formally demonstrated in CORE-M20-001, satisfying guarantees that a completed write will be observed by a subsequent read, but it does not guarantee strict Linearizability during concurrent in-flight writes. If a write is partially replicated, two concurrent readers may contact different overlapping subsets and observe a newer value followed by an older value. For single-partition compare-and-set linearizability (IF NOT EXISTS), Cassandra provides Lightweight Transactions (LWT) (SERIAL consistency): classic Cassandra 2.0–4.0 required 4 Paxos round-trips, Cassandra 4.1 introduced Paxos v2 (CEP-14) cutting uncontended LWT commits to 2 round-trips, and the Accord protocol (CEP-15) extends Cassandra 5.x toward single-round-trip, leaderless, strictly serializable multi-partition transactions.
Total Replication Factor N = 3
Write Quorum (W = 2): [Node 1] [Node 2]
Read Quorum (R = 2): [Node 2] [Node 3]
^
|
Overlapping Node holding latest timestamp!
Cassandra 5.0 Storage Engine Advances: Trie Memtables, UCS & SAI
In Cassandra 5.0, three major storage engine enhancements modernize the classic Bigtable-inspired LSM pipeline:
- Trie-Indexed Memtables & BTI SSTables (CEP-19 / CEP-25): Byte-ordered trie structures reduce JVM heap object overhead and accelerate point/range partition lookups compared to legacy
ConcurrentSkipListMapMemTables. - Storage-Attached Indexing (
SAI, CEP-7): Replaces brittle legacy secondary indexes (2i/SASI) with column indexes attached directly to the SSTable flush and compaction lifecycle—using balanced kd-trees (BKDtrees) for numeric/range predicates and byte-ordered tries for exact/string predicates without separate index compaction storms. - Unified Compaction Strategy (
UCS, CEP-26): Generalizes Size-Tiered (STCS), Leveled (LCS), and Time-Window (TWCS) compaction under tunable sharded density levels, allowing operators to transition between write-optimized and read-optimized fan-out online.
The (Phi) Accrual Failure Detector
Rather than using a crude binary heartbeat timeout (which misinterprets transient network spikes or GC pauses as node crashes), Cassandra uses the Accrual Failure Detector (Hayashibara et al., 2004).
Nodes exchange state every via Gossip. In Hayashibara’s general formulation, given elapsed silence since the last received heartbeat and historical inter-arrival distribution , the suspicion level is defined on a base-10 logarithmic scale:
While Hayashibara’s original paper modeled inter-arrival times with a Gaussian integral, Apache Cassandra’s actual ArrivalWindow implementation (CASSANDRA-2597, org.apache.cassandra.gms.ArrivalWindow) models heartbeat arrivals using an exponential distribution with sample mean over the last intervals (), avoiding floating-point tail underflow under bursty JVM pauses:
- Numerical Verification: With Cassandra’s gossip cadence () and default
phi_convict_threshold = 8(corresponding to tail probability under the exponential arrival model), a silent peer is convicted at:
If cross-DC congestion stretches the observed mean from to , automatically expands to without operator tuning.
4. System Design Patterns & Trade-offs
+----------------------------------------------------------------------------------------------------+
| TRADEOFF MATRIX: CASSANDRA COMPACTION STRATEGIES |
+--------------------------+------------------------------------+------------------------------------+
| Dimension | Size-Tiered Compaction (STCS) | Leveled Compaction (LCS) |
+--------------------------+------------------------------------+------------------------------------+
| Primary Workload | High write throughput (appends) | Read-heavy, frequent updates/deletes|
| Disk Space Overhead | High (Requires up to 50% free disk)| Low (Requires only ~10% free disk) |
| Read Performance | Poor: Must check many SSTables | Excellent: 90% reads hit 1 SSTable |
| Write Amplification | Moderate | High (Rewrites data into levels) |
+--------------------------+------------------------------------+------------------------------------+
| Dimension | LOCAL_QUORUM | QUORUM (Global) |
+--------------------------+------------------------------------+------------------------------------+
| WAN Latency Penalty | Zero (Acknowledged within local DC)| Adds 50-100ms cross-country RTT |
| Multi-Region Disaster Tol| Survives entire remote DC loss | Blocks if remote DC is partitioned |
+--------------------------+------------------------------------+------------------------------------+
5. Practical Implementation
Below is a complete, production-grade TypeScript simulation demonstrating Cassandra’s core storage and coordinator mechanics:
- MemTable skiplist with on-disk SSTable representation.
- Murmur3 consistent hashing ring routing.
- Quorum write coordinator with timestamp conflict resolution.
- Tombstone deletion and reconciliation.
import { createHash } from 'crypto';
export interface RowData {
partitionKey: string;
data: Record<string, string | number>;
timestampMicros: number;
isTombstone: boolean;
}
/**
* In-Memory MemTable representation
*/
export class MemTable {
private rows: Map<string, RowData> = new Map();
public put(row: RowData): void {
const existing = this.rows.get(row.partitionKey);
if (!existing || row.timestampMicros >= existing.timestampMicros) {
this.rows.set(row.partitionKey, row);
}
}
public get(partitionKey: string): RowData | null {
return this.rows.get(partitionKey) || null;
}
public flush(): SSTable {
const sortedEntries = Array.from(this.rows.values()).sort((a, b) =>
a.partitionKey.localeCompare(b.partitionKey)
);
this.rows.clear();
return new SSTable(sortedEntries);
}
}
/**
* Immutable On-Disk Sorted String Table (SSTable)
*/
export class SSTable {
private bloomFilter: Set<string> = new Set();
constructor(private readonly entries: RowData[]) {
// Populate simple Bloom Filter simulation
for (const entry of entries) {
this.bloomFilter.add(entry.partitionKey);
}
}
public mayContain(partitionKey: string): boolean {
return this.bloomFilter.has(partitionKey);
}
public search(partitionKey: string): RowData | null {
if (!this.mayContain(partitionKey)) return null;
// Binary search across sorted immutable array
let low = 0;
let high = this.entries.length - 1;
while (low <= high) {
const mid = Math.floor((low + high) / 2);
const cmp = this.entries[mid].partitionKey.localeCompare(partitionKey);
if (cmp === 0) return this.entries[mid];
if (cmp < 0) low = mid + 1;
else high = mid - 1;
}
return null;
}
}
/**
* Cassandra Physical Node Replica
*/
export class CassandraNode {
public memTable: MemTable = new MemTable();
public ssTables: SSTable[] = [];
constructor(public readonly nodeId: string, public readonly token: bigint) {}
public write(row: RowData): void {
this.memTable.put(row);
}
public read(partitionKey: string): RowData | null {
// 1. Check MemTable
const memResult = this.memTable.get(partitionKey);
let bestResult: RowData | null = memResult;
// 2. Search SSTables newest to oldest
for (let i = this.ssTables.length - 1; i >= 0; i--) {
const sstResult = this.ssTables[i].search(partitionKey);
if (sstResult) {
if (!bestResult || sstResult.timestampMicros > bestResult.timestampMicros) {
bestResult = sstResult;
}
}
}
return bestResult;
}
}
/**
* Coordinator Node and Ring Router
*/
export class CassandraCoordinator {
private nodes: CassandraNode[] = [];
constructor(nodes: CassandraNode[]) {
this.nodes = [...nodes].sort((a, b) => (a.token < b.token ? -1 : 1));
}
// Simulates 64-bit token hashing across the ring (production Cassandra uses Murmur3_x64_128)
public hashToToken(key: string): bigint {
const hash = createHash('md5').update(key).digest('hex').substring(0, 16);
return BigInt('0x' + hash);
}
public getNaturalEndpoints(key: string, replicationFactor: number): CassandraNode[] {
const token = this.hashToToken(key);
let startIdx = this.nodes.findIndex((n) => n.token >= token);
if (startIdx === -1) startIdx = 0;
const endpoints: CassandraNode[] = [];
for (let i = 0; i < replicationFactor; i++) {
endpoints.push(this.nodes[(startIdx + i) % this.nodes.length]);
}
return endpoints;
}
/**
* Executes Quorum Write
*/
public writeQuorum(
partitionKey: string,
data: Record<string, string | number>,
isTombstone: boolean = false,
timestampMicros: number = Date.now() * 1000
): boolean {
const replicas = this.getNaturalEndpoints(partitionKey, 3);
const quorumCount = Math.floor(replicas.length / 2) + 1; // 2 out of 3
const row: RowData = {
partitionKey,
data,
timestampMicros,
isTombstone
};
let acks = 0;
for (const replica of replicas) {
replica.write(row);
acks++;
}
return acks >= quorumCount;
}
/**
* Executes Quorum Read with Last-Write-Wins and Tombstone resolution
*/
public readQuorum(partitionKey: string): Record<string, string | number> | null {
const replicas = this.getNaturalEndpoints(partitionKey, 3);
const quorumCount = Math.floor(replicas.length / 2) + 1;
const responses: RowData[] = [];
for (const replica of replicas) {
const res = replica.read(partitionKey);
if (res) responses.push(res);
}
if (responses.length < quorumCount) {
return null;
}
// Resolve Last-Write-Wins
responses.sort((a, b) => b.timestampMicros - a.timestampMicros);
const newest = responses[0];
// If newest record is a Tombstone, the row is deleted!
if (!newest || newest.isTombstone) {
return null;
}
return newest.data;
}
}
function runDemo(): void {
console.log('=== Cassandra Peer-to-Peer Token Ring & Quorum Simulation ===');
const nodes = [
new CassandraNode('node-1', 0x0000000000000000n),
new CassandraNode('node-2', 0x4000000000000000n),
new CassandraNode('node-3', 0x8000000000000000n),
new CassandraNode('node-4', 0xc000000000000000n),
];
const coordinator = new CassandraCoordinator(nodes);
const key = 'usr_84920';
const endpoints = coordinator.getNaturalEndpoints(key, 3);
console.log(`Natural endpoints (RF=3) for '${key}': [${endpoints.map((n) => n.nodeId).join(', ')}]`);
const committed = coordinator.writeQuorum(key, { user_id: key, status: 'active' }, false, 1714580000000);
console.log(`Quorum write (W=2) committed: ${committed}`);
// Flush MemTables to immutable SSTables on replicas
for (const replica of endpoints) {
replica.ssTables.push(replica.memTable.flush());
}
console.log('Flushed replica MemTables to immutable SSTables with Bloom filters');
const activeRow = coordinator.readQuorum(key);
console.log('Quorum read (R=2) from SSTables: ' + JSON.stringify(activeRow));
// Issue Tombstone delete with strictly newer microsecond timestamp
coordinator.writeQuorum(key, {}, true, 1714580005000);
const afterDelete = coordinator.readQuorum(key);
console.log('Quorum read after Tombstone deletion: ' + JSON.stringify(afterDelete));
}
runDemo();
Simulation Execution Output & State Transitions
=== Cassandra Peer-to-Peer Token Ring & Quorum Simulation ===
Natural endpoints (RF=3) for 'usr_84920': [node-1, node-2, node-3]
Quorum write (W=2) committed: true
Flushed replica MemTables to immutable SSTables with Bloom filters
Quorum read (R=2) from SSTables: {"user_id":"usr_84920","status":"active"}
Quorum read after Tombstone deletion: null
6. Failure Scenarios & Edge Cases
1. Clock Skew and Last-Write-Wins Silent Overwrites
Cassandra resolves conflicting updates using client-provided timestamps. If Server A’s clock is 200 ms fast due to NTP drift, every write issued through Server A receives a future timestamp. Subsequent writes issued through Server B (with an accurate clock) will be silently ignored because their timestamps appear older than Server A’s write!
- Mitigation: Enforce microsecond clock synchronization via PTP (Precision Time Protocol) or AWS Time Sync. Never generate timestamps in application code; use Cassandra’s server-side coordinator timestamps.
2. High Tombstone Scans Crashing JVM via Out-Of-Memory
A queue pattern is implemented where worker threads write items and delete them upon completion. Over a week, a single partition accumulates 300,000 tombstones. When a worker attempts SELECT * FROM queue WHERE partition_id = 1 LIMIT 1;, Cassandra must read all 300,000 tombstones into JVM off-heap/heap buffers to confirm whether an active record exists.
- Mitigation: Never use Cassandra as a queue. Configure
tombstone_failure_threshold(default: 100,000) to fail fast, and tunegc_grace_secondsto 0 or use TWCS (Time Window Compaction Strategy) for time-series data.
7. Production Operations & Observability
Essential Cassandra Operator Metrics
+----------------------------------------------------------------------------------------------------+
| KEY PRODUCTION CASSANDRA METRICS |
+--------------------------+-----------------------+-------------------------------------------------+
| Metric Name | Source Component | Critical Threshold |
+--------------------------+-----------------------+-------------------------------------------------+
| DroppedMessages: WRITE | Stage Manager / Net | > 0 / min indicates disk saturation / overload |
+--------------------------+-----------------------+-------------------------------------------------+
| PendingTasks: Compaction | CompactionManager | > 50 indicates compaction backlog & read lag |
+--------------------------+-----------------------+-------------------------------------------------+
| StorageProxy: ReadRepair | Coordinator Proxy | Spike indicates replica desynchronization |
+--------------------------+-----------------------+-------------------------------------------------+
| ClientRequest: p99 Lat | Driver Transport | > 50ms indicates JVM GC pause or disk fsync wait|
+--------------------------+-----------------------+-------------------------------------------------+
Disk Space Rules for Compaction
During Size-Tiered Compaction, Cassandra merges four 50 GB SSTables into one 200 GB SSTable. During the merge, both the old and new files reside on disk simultaneously. Cassandra nodes running STCS must never exceed 50% disk utilization, or compaction will fail with disk-full panics!
8. Security & Compliance
- Internode Encryption: Replicas stream raw unencrypted partition data during repair and gossip. Configure
server_encryption_optionswith mutual TLS (mTLS) to encrypt all internode traffic on port 7001. - Client-to-Node Encryption: Enforce TLS 1.3 on native transport port 9042 with client certificate validation.
9. Real-World Case Studies
Netflix Global Cassandra Fleet
Netflix operates tens of thousands of Cassandra nodes storing user viewing history, ratings, and device metadata.
- Multi-Region Active-Active: Netflix deploys Cassandra across 3 AWS regions (
us-east-1,us-west-2,eu-west-1). All application writes useLOCAL_QUORUM. - Async Cross-DC Replication: Cassandra’s background storage proxy streams mutations asynchronously across regions over AWS Direct Connect links, keeping cross-region latency off the customer streaming critical path.
10. Technology Comparison Matrix
+----------------------------------------------------------------------------------------------------+
| DISTRIBUTED NOSQL AND STORAGE ENGINE COMPARISON |
+----------------------+--------------------+--------------------+-----------------------------------+
| System | Topology | Consensus / Coor. | Storage Engine |
+----------------------+--------------------+--------------------+-----------------------------------+
| Apache Cassandra | Masterless Ring | Tunable Quorum | LSM-Tree (CommitLog + SSTables) |
+----------------------+--------------------+--------------------+-----------------------------------+
| ScyllaDB | Masterless Ring | Tunable Quorum | C++ Seastar thread-per-core LSM |
+----------------------+--------------------+--------------------+-----------------------------------+
| Amazon DynamoDB | Managed Multi-AZ | Multi-Paxos | Internal SSD engine (RocksDB-like)|
+----------------------+--------------------+--------------------+-----------------------------------+
| Apache HBase | Master / RegionSrv | ZooKeeper + HDFS | LSM-Tree on distributed HDFS WAL |
+----------------------+--------------------+--------------------+-----------------------------------+
| CockroachDB | Multi-Raft Ranges | Raft Consensus | Pebble (Go LSM-Tree) |
+----------------------+--------------------+--------------------+-----------------------------------+
11. Design Challenge & Exercise
Scenario: High-Volume Sensor Ingestion Pipeline
You are designing the telemetry database for an IoT fleet of 5,000,000 smart meters, each emitting a metric event every 10 seconds (500,000 writes/second total).
Requirements:
- Zero data loss during write ingestion ().
- Queries request the last 24 hours of data for a specific meter (
meter_id). - Historical data older than 90 days must be permanently deleted.
Your Task:
- Design the CQL table schema (Partition Key, Clustering Key, Compaction Strategy).
- Explain why Time Window Compaction Strategy (TWCS) is superior to Size-Tiered Compaction for this workload.
- Calculate the storage footprint and disk space headroom needed for 90 days of retention.
View Reference Architecture & Worked Solutions (CORE-M32-001)
1. CQL Table Schema Design (Partition Key, Clustering Key & Compaction Strategy)
- Partition Sizing Check: Each smart meter emits . At , a single meter generates or over . Bucketing the partition key by
(meter_id, day_bucket)(or(meter_id, month_bucket)) bounds partition size strictly below Cassandra’s safety ceiling while ensuring that a 24-hour range query reads at most partitions in reverse chronological order:
CREATE KEYSPACE iot_telemetry WITH replication = {
'class': 'NetworkTopologyStrategy',
'us_east_1': 3
};
CREATE TABLE iot_telemetry.meter_readings (
meter_id text,
day_bucket date, -- e.g., '2026-11-09' bounds partition to 8,640 rows (~864 KB)
event_ts timestamp,
kilowatt_hrs double,
voltage float,
status_flags smallint,
PRIMARY KEY ((meter_id, day_bucket), event_ts)
) WITH CLUSTERING ORDER BY (event_ts DESC)
AND default_time_to_live = 7776000 -- 90 days in seconds (90 * 86,400)
AND gc_grace_seconds = 86400 -- 1 day (safe for immutable TTL-only time-series)
AND compaction = {
'class': 'TimeWindowCompactionStrategy',
'compaction_window_unit': 'DAYS',
'compaction_window_size': 1
}
AND compression = {
'class': 'LZ4Compressor',
'chunk_length_in_kb': 64
};
2. Why Time Window Compaction Strategy (TWCS) Outperforms Size-Tiered Compaction (STCS)
- STCS Failure Mode on TTL Workloads: Size-Tiered Compaction merges SSTables of similar byte size regardless of the timestamp of the rows inside them. Over 90 days, older SSTables merge into massive multi-hundred-gigabyte files mixing Day 1 data with Day 60 data. When Day 1 rows pass their 90-day TTL, Cassandra cannot drop the SSTable because it still contains live Day 60 rows; moreover, STCS requires free disk headroom to stage merges of its largest SSTables.
- TWCS Advantage:
TimeWindowCompactionStrategygroups flushed MemTables into discrete 1-day time buckets (compaction_window_unit = 'DAYS', size = 1). SSTables within the current 24-hour window are compacted via STCS, and once the day ends, that day’s window is sealed into a single immutable SSTable and never compacted again.- Single-SSTable 24-Hour Reads: A query for the last 24 hours of
meter_idchecks the active MemTable and at most daily TWCS SSTables (via min/max timestamp metadata), avoiding multi-file read amplification. - Zero-I/O Expiration: Once all rows in a sealed 1-day SSTable exceed
default_time_to_live + gc_grace_seconds(), Cassandra drops the entire SSTable file from the filesystem via a singleunlink()syscall—eliminating tombstone compaction scans and rewrite amplification completely.
- Single-SSTable 24-Hour Reads: A query for the last 24 hours of
3. 90-Day Storage Footprint & Disk Headroom Calculation
- Step 1 — Total Row Volume Over 90 Days:
- Step 2 — Raw vs. Compressed Unique Data Size:
- Assuming an average serialized cell/row footprint (including CQL clustering prefix and timestamps) of raw ( cluster-wide ingest):
- With
LZ4Compressor( chunks) achieving a conservative compression ratio on sorted numeric/timestamp columns ( on disk):
- Step 3 — Replication Factor ( for
LOCAL_QUORUM) & Headroom:- Across replicas (plus of
gc_grace_secondsretention, i.e., ): - Unlike STCS (which requires free space), TWCS only compacts SSTables inside the current 1-day window ( cluster-wide). Allocating a operational headroom margin (keeping disk utilization to accommodate CommitLog segments, active-day compactions, and OS page cache):
- Across replicas (plus of
12. Summary & Architectural Decision Guide
When to Choose Apache Cassandra vs. Alternatives
- Choose Apache Cassandra (or ScyllaDB) when your workload demands high-throughput, multi-datacenter active-active writes (
LOCAL_QUORUM), predictable partition-key access patterns, and graceful degradation during regional network partitions without leader election stalls. - Avoid Cassandra for ad-hoc multi-table
JOINqueries, queue/delete-churn workloads (tombstone accumulation), or strict multi-row ACID financial ledgers unless leveraging Cassandra 5.x Accord (CEP-15) or a dedicated Distributed SQL engine (CORE-M33-001).
Production Checklist
- Token Allocation: Use Cassandra 4.x/5.0 defaults (
num_tokens: 16withallocate_tokens_for_local_replication_factor: 3) rather than legacy 256-vnode configurations to keep repair sub-ranges small and reduce correlated replica failure risk. - Consistency & Repair: Pair
LOCAL_QUORUMreads and writes () with scheduled incremental/sub-range anti-entropy repairs (cassandra-reaper) running well insidegc_grace_seconds(e.g., every 7 days for a 10-day grace window). - Compaction & Indexing: Match compaction to workload physics—
TWCSorUCSfor immutable time-series with TTLs,LCSorUCSfor read-heavy updates—and prefer Storage-Attached Indexes (SAI) in Cassandra 5.0 over legacy secondary indexes.
13. Authoritative References & Version Context
- Version Baseline: Verified against Apache Cassandra v4.1 / v5.0 (LTS) (Apache License 2.0) and ScyllaDB Open Source v5.4+ (AGPL v3.0). Key Cassandra 5.0 specifications include CEP-7 (Storage-Attached Indexing), CEP-14 (Paxos v2, introduced in 4.1), CEP-19/CEP-25 (Trie Memtables and BTI SSTables), and CEP-26 (Unified Compaction Strategy), with CEP-15 (Accord general transactions) targeting the 5.x line.
- Lakshman, A., & Malik, P. (2010). Cassandra — A Decentralized Structured Storage System. ACM SIGOPS Operating Systems Review, 44(2), 35–40.
- DeCandia, G., Hastorun, D., Jampani, M., Kakulapati, G., Lakshman, A., Pilchin, A., Sivasubramanian, S., Vosshall, P., & Vogels, W. (2007). Dynamo: Amazon’s Highly Available Key-Value Store. Proceedings of the 21st ACM Symposium on Operating Systems Principles (SOSP ‘07).
- Hayashibara, N., Défago, X., Yared, R., & Katayama, T. (2004). The Accrual Failure Detector. Proceedings of the 23rd IEEE International Symposium on Reliable Distributed Systems (SRDS ‘04).
- Apache Software Foundation. Apache Cassandra 5.0 Official Architecture & Storage Engine Documentation.
- Elliott Smith, B., et al. CEP-15: General Purpose Transactions (Accord Whitepaper & Specification).