- Apache Cassandra is an open source (Apache License 2.0), distributed, partitioned wide-column NoSQL database with eventually consistent semantics and per-request tunable consistency.
- Data modelling is query-driven: each table serves a known query, there are no joins or foreign keys, and data is duplicated (denormalised) across tables on purpose.
- For telemetry, put the series and a time bucket in the partition key, for example ((site, point_id, day)), and the timestamp as a clustering column, so partitions stay bounded.
- The documentation gives a general guideline of below 100,000 values and 100 MB per partition; TTL expires readings, and the 5.0 documentation recommends both TWCS and UCS for time-series data, so evaluate both on staging.
- Cassandra does not do cross-partition aggregation well, has no multi-partition transactions, and is a poor fit for CMMS records or ad hoc analytics; pair it with an analytics engine.
What Apache Cassandra is, and who this is for
Apache Cassandra describes itself as an open source, distributed NoSQL database that implements a partitioned wide-column storage model with eventually consistent semantics. Its design goals include full multi-primary replication, global availability at low latency, scaling out on commodity hardware and partitioned, key-oriented queries. Applications use CQL, the Cassandra Query Language, which looks like SQL but deliberately leaves out joins, cross-partition transactions and foreign keys.
This guide is for architects and developers who must store a large, continuous stream of readings (one-second meter data, equipment vibration, fleet or estate-wide sensor data) with no single point of failure, often across sites or regions. It assumes you know relational databases. For a direct comparison with a document database, see Cassandra vs MongoDB; for time-series storage in general, see the time-series database overview. On 27 October 2026 the latest GA release line was Apache Cassandra 5.0 (5.0.9, released 7 August 2026), with 4.1 and 4.0 still maintained.
How Cassandra distributes and replicates readings
- Partitions. Every table has a partition key, the first part of the primary key. Cassandra hashes it to a token and places the partition on the nodes that own that token range. A partition is never split across nodes, and the documentation says that all performant queries supply the partition key.
- Clustering columns. The remaining primary key columns sort rows inside a partition. For telemetry the timestamp is the clustering column, so a time range within one partition is a sequential read.
- Replication. Each keyspace has a replication strategy and factor; with a replication factor of 3, every partition is written to three distinct nodes. The documentation says all production deployments should use
NetworkTopologyStrategy, which sets a factor per data centre and spreads replicas across racks. - Tunable consistency. Each read and write chooses a consistency level such as
ONE,LOCAL_QUORUMorALL. The documentation says it is common to pick read and write levels whose replica sets overlap, so that acknowledged writes are visible to later reads, and that in a multi-data-centre clusterLOCAL_QUORUMgives a weaker guarantee: reads see the latest write from within the same data centre. Which levels and replication factor you use is a trade-off between consistency, latency and availability for your workload; the replication factor of 3 in the example below is illustrative. - Write path and storage. Writes are buffered in memtables and flushed to immutable SSTables on disk; compaction later merges SSTables and removes deleted or expired data.
The role split for an IoT pipeline is the same as elsewhere: MQTT brokers collect from devices and gateways, Kafka buffers and fans out the stream, a consumer service writes readings to Cassandra, and an analytics engine (Spark, a warehouse or a lakehouse) answers questions that span many partitions. In this split Cassandra is the replicated store for the raw stream and for per-series reads (its availability depends on the replication factor and consistency level you choose, covered below), not the analytics layer.
Data modelling by query
The Cassandra documentation puts it plainly: data modelling is query-driven. Instead of normalising entities and joining at query time, you list the queries the application will run and design one table per query, duplicating data where needed. Joins are not available, so a "join" is either a second, denormalised table written at the same time or (rarely) a join in application code. Sort order is a design decision too: rows come back only in the clustering order defined in CREATE TABLE.
For a building portfolio, the access patterns might be:
- Q1: readings for one point (for example zone CO2 sensor L3-Z04) between two times, newest first, for a trend chart.
- Q2: the latest value of every point in a site, for a live dashboard.
- Q3: hourly averages per point over the past year, for energy and comfort reports.
Q1 and Q2 map to tables below. Q3 is an aggregation across many partitions, which Cassandra is not designed for: compute hourly rollups in a stream processor or a scheduled Spark job and write them to their own table keyed the same way, or answer it in the analytics engine. The CQL reference for 5.0 says that "the GROUP BY option only accepts primary key columns in defined order as arguments", so do not plan on it for hourly bucketing across many partitions; check the CQL reference for your version before relying on any time-range grouping.
Time bucketing in the partition key
A partition per sensor, PRIMARY KEY (point_id, ts), looks natural but grows without limit: every reading the sensor ever sends lands in one partition on the same replicas. The documentation's answer is bucketing: add a column to the partition key that splits each series into moderate-size pieces. For telemetry the bucket is a time period (day, week or month) derived from the timestamp, so the partition key becomes (site, point_id, day). Including site keeps tenants or estates apart and lets you drop or migrate them separately.
Choose the bucket from the reading rate and the documented guideline, which the data modelling introduction gives as "a general guideline", noting that requirements may vary by application: keep partitions below roughly 100,000 values and 100 MB (the hard limit is 2 billion cells per partition, but the documentation warns of performance issues before that). The day buckets, 396-day TTL and 14-day compaction window in the examples below are illustrative assumptions for a one-minute BMS point; work out your own from the reading interval, the number of non-key columns, retention and the date ranges your queries span. The number of values per row is the number of non-key columns, so a table with value and quality stores two values per reading. The trade-off is reads: a query spanning several buckets has to read one partition per bucket, so the application (or driver) computes the bucket list and runs the queries in parallel.
CREATE KEYSPACE telemetry
WITH replication = {'class': 'NetworkTopologyStrategy', 'dc1': 3};
CREATE TABLE telemetry.readings_by_point_day (
site text,
point_id text, -- e.g. 'L3-Z04.CO2', 'AHU-01.SAT', 'MTR-MAIN.KWH'
day date, -- bucket derived from ts by the writer
ts timestamp,
value double,
quality tinyint,
PRIMARY KEY ((site, point_id, day), ts)
) WITH CLUSTERING ORDER BY (ts DESC)
AND default_time_to_live = 34214400 -- 396 days
AND compaction = {'class': 'TimeWindowCompactionStrategy',
'compaction_window_unit': 'DAYS',
'compaction_window_size': 14};-- Q1: one bucket per query; the application issues one query per day in the range
SELECT ts, value, quality
FROM telemetry.readings_by_point_day
WHERE site = 'HQ' AND point_id = 'L3-Z04.CO2' AND day = '2026-10-27'
AND ts >= '2026-10-27 08:00:00+0000' AND ts < '2026-10-27 12:00:00+0000';
-- Q2: one row per point, overwritten on every reading (INSERT is an upsert in CQL)
CREATE TABLE telemetry.latest_by_site (
site text,
point_id text,
ts timestamp,
value double,
PRIMARY KEY ((site), point_id)
);
INSERT INTO telemetry.latest_by_site (site, point_id, ts, value)
VALUES ('HQ', 'L3-Z04.CO2', '2026-10-27 08:15:00+0000', 742);
SELECT point_id, ts, value FROM telemetry.latest_by_site WHERE site = 'HQ';Working out buckets and partition sizes (plain PostgreSQL)
Two pieces of arithmetic come up in every design review: which buckets a time range touches, and how many rows each partition will hold. Both are plain SQL, shown here on PostgreSQL 18.6 so they can be checked in any SQL client; they are planning aids, not Cassandra code. A query from 26 October 22:00 to 28 October 06:00 UTC needs three day partitions. The second query is illustrative arithmetic for one sensor in the table above, which stores two values per row (value and quality): a one-second reading fills 86,400 rows and 172,800 values a day, above the 100,000-value guideline, so one-second data would need a shorter bucket such as an hour; a 10-second reading in a 30-day bucket is also above it. The guideline is approximate, and on-disk size (the documentation's second measure) depends on the column types, so check both for your own table.
SET TimeZone = 'UTC';
-- day buckets a query for 26 Oct 22:00 to 28 Oct 06:00 UTC must read, one partition per (sensor, day)
SELECT 'zone-co2-l3-01' AS sensor_id, to_char(d, 'YYYY-MM-DD') AS day_bucket
FROM generate_series(date_trunc('day', timestamptz '2026-10-26 22:00+00'),
date_trunc('day', timestamptz '2026-10-28 06:00+00'),
interval '1 day') AS d;
-- illustrative: rows and values per partition for one sensor with two non-key columns (value, quality)
SELECT interval_s, bucket, rows_per_partition,
rows_per_partition * 2 AS values_per_partition,
CASE WHEN rows_per_partition * 2 < 100000 THEN 'yes' ELSE 'no' END AS below_100k_values
FROM (VALUES (60, 'day', 86400 / 60), (10, 'day', 86400 / 10), (1, 'day', 86400 / 1),
(1, 'hour', 3600 / 1), (60, 'month (30 days)', 30 * 86400 / 60),
(10, 'month (30 days)', 30 * 86400 / 10)) AS v(interval_s, bucket, rows_per_partition)
ORDER BY bucket, interval_s DESC; sensor_id | day_bucket
----------------+------------
zone-co2-l3-01 | 2026-10-26
zone-co2-l3-01 | 2026-10-27
zone-co2-l3-01 | 2026-10-28
interval_s | bucket | rows_per_partition | values_per_partition | below_100k_values
------------+-----------------+--------------------+----------------------+-------------------
60 | day | 1440 | 2880 | yes
10 | day | 8640 | 17280 | yes
1 | day | 86400 | 172800 | no
1 | hour | 3600 | 7200 | yes
60 | month (30 days) | 43200 | 86400 | yes
10 | month (30 days) | 259200 | 518400 | noTTL, tombstones and compaction for time series
TTL. A TTL in seconds can be set per write (USING TTL) or as a table's default_time_to_live; expired values are removed automatically. Internally an expired value becomes a tombstone, a deletion marker that is purged by compaction after the grace period. Explicit deletes also create tombstones, which is why telemetry tables should expire data by TTL rather than by large DELETE jobs.
What the 5.0 documentation says about TWCS and UCS. The 5.0 compaction pages recommend both strategies for time series, in these words (quoted as published on 27 October 2026):
- TWCS page: "The TimeWindowCompactionStrategy (TWCS) is the recommended compaction strategy for time-series and expiring Time-To-Live (TTL) workloads." The same page opens with: "The Unified Compaction Strategy (UCS) is the recommended compaction strategy for most workloads starting with" Cassandra 5.0, followed by "If you are creating new tables, use this strategy."
- UCS page: "The UnifiedCompactionStrategy (UCS) is recommended for most workloads, whether read-heavy, write-heavy, mixed read-write, or time-series." It also says that with suitable scaling parameters "UCS can accomplish the same compaction as TWCS".
- Compaction overview: "UCS is a good choice for most workloads and is recommended for new workloads." and "Time Window Compaction Strategy is designed for TTL'ed, mostly immutable time-series data." It also states that "STCS is the default compaction strategy" if you set nothing.
We make no recommendation of our own between the two: evaluate both with your own table, TTL and write pattern on a staging cluster before choosing. How TWCS works, per its documentation: SSTables are grouped by time window, each finished window is compacted into a single SSTable, and an SSTable whose data has all expired can be dropped whole, so disk space "can be reclaimed much more reliably than when using STCS or LCS". The documentation suggests choosing a window that produces about 20 to 30 windows over the retention period; as illustrative arithmetic, a 90-day TTL gives 30 windows of 3 days, and the illustrative 396-day TTL above gives about 28 windows of 14 days. TWCS depends on data arriving roughly in time order: the documentation says to avoid writes that set the timestamp with USING TIMESTAMP, and backfilling months of old readings mixes old and new data in the same SSTables.
Sizing, security and operations
- Capacity. Estimate partition size from the documentation's formula (key and clustering column sizes, values per row, plus about 8 bytes of metadata per cell), multiply by the number of partitions and by the replication factor. As an illustrative assumption, a 20,000-point estate at one reading a minute writes 28.8 million rows a day (20,000 x 1,440) before replication; with a replication factor of 3 every row is stored three times. Replace these figures with your own point count, intervals and replication settings.
- Hot partitions. Bucketing bounds size, but a single very busy series still lands on one replica set. Our design suggestion, not taken from the 5.0 documentation we checked: for extremely high-rate sources, consider adding a small shard number to the partition key and reading all shards, and evaluate it on staging first.
- Avoid full scans. Queries without the partition key need
ALLOW FILTERING, which the documentation describes as a full scan with unpredictable performance. Storage-attached indexes (SAI) help some secondary lookups but do not turn Cassandra into an analytics engine. - Security. Authentication, authorisation and TLS for client-to-node and node-to-node traffic are disabled by default; the documentation warns that an out-of-the-box installation presents a large attack surface. Enable them, restrict JMX, and use roles with least privilege for writers and readers.
- Backups and repair. Cassandra offers snapshots and incremental backups; run regular repairs so replicas converge, which TWCS also depends on. Plan node replacement and upgrades as routine work, because a cluster is several machines to look after.
- Cost. Running Cassandra yourself costs nodes, storage, replication across sites and operations staff. Managed Cassandra-compatible cloud services exist, but their pricing is not covered here.
- Licence. The LICENSE.txt in the apache/cassandra repository is the "Apache License Version 2.0, January 2004", whose section 2 grants "a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license" subject to the licence terms. This is a factual description, not legal advice: read the licence, and the terms of any managed service, and take advice for your situation.
When not to use Cassandra
- Analytics across many series. Portfolio-wide averages, percentiles or ad hoc questions scan many partitions; use an analytics engine such as ClickHouse or a warehouse, fed from the same Kafka stream.
- CMMS and CAFM records. Work orders, assets and maintenance histories need joins, constraints and multi-row transactions; Cassandra offers only single-partition lightweight transactions and no referential integrity. Keep them in PostgreSQL or similar.
- Unknown or changing query patterns. Every new query may need a new table and a backfill. Our editorial view: if analysts will explore the data freely, a SQL time-series database is likely to be easier.
- Modest volume on one site. Our editorial view: if a single PostgreSQL or TimescaleDB server holds the data comfortably, a multi-node Cassandra cluster adds operational work for little benefit; see How to Store IoT Sensor Data.
- Strong consistency on every read. Cassandra is eventually consistent by design; quorum reads narrow the gap but cost latency and availability.
If you already run MongoDB, its native time-series collections may be simpler; see MongoDB Time-Series Collections for Sensor Data.
Frequently asked questions
Is Cassandra good for time-series data?
Our editorial view: for high write volumes with known, per-series queries it can be a good fit, using partitions keyed by series and time bucket, TTL for retention and a compaction strategy chosen after evaluating TWCS and UCS. Aggregations across many series need cross-partition reads, which Cassandra does not do well, so they belong in an analytics engine.
What is time bucketing in Cassandra?
Adding a time period such as the day or month to the partition key, for example ((site, point_id, day), ts), so each sensor's data is split into bounded partitions instead of one partition that grows forever.
How big should a Cassandra partition be?
The data modelling guide suggests keeping partitions below about 100,000 values and 100 MB on disk as a general guideline. The hard limit is 2 billion cells, but performance degrades long before that.
Should I use TWCS or UCS for telemetry tables?
The Cassandra 5.0 documentation recommends both. The TWCS page calls TWCS "the recommended compaction strategy for time-series and expiring Time-To-Live (TTL) workloads", while the UCS page says UCS "is recommended for most workloads, whether read-heavy, write-heavy, mixed read-write, or time-series", and the overview says UCS "is recommended for new workloads". We do not pick one for you: evaluate both on your own data in staging. Size-tiered compaction (STCS) is the default if you set nothing.
Is the Cassandra database free?
Apache Cassandra is distributed under the Apache License 2.0, whose copyright grant is described in the licence as "no-charge, royalty-free" subject to its terms. Running it still costs servers, storage and operations, or a managed service fee if you choose one. This is not legal advice: read the licence and take advice for your situation.
Sources
- Apache Cassandra 5.0 documentation: Architecture overview
- Apache Cassandra 5.0 documentation: Dynamo
- Apache Cassandra 5.0 documentation: Guarantees
- Apache Cassandra 5.0 documentation: Data modeling introduction
- Apache Cassandra 5.0 documentation: RDBMS design
- Apache Cassandra 5.0 documentation: Evaluating and refining data models
- Apache Cassandra 5.0 documentation: CQL data definition
- Apache Cassandra 5.0 documentation: CQL data manipulation
- Apache Cassandra 5.0 documentation: Compaction overview
- Apache Cassandra 5.0 documentation: Time Window Compaction Strategy
- Apache Cassandra 5.0 documentation: Unified Compaction Strategy
- Apache Cassandra 5.0 documentation: Security
- Apache Cassandra downloads
- apache/cassandra repository: LICENSE.txt
Checked 27 October 2026.
How we research these articles: our editorial method. Research-based from official documentation; SQL examples are run on a local database where the page says so. CodeWithSQL earns nothing from the vendors mentioned.