Cassandra TTL
TTL stands for Time To Live. It is a setting that tells Cassandra to automatically delete a row or a column after a specified number of seconds. TTL removes the need to manually write DELETE statements for temporary data, keeps storage lean, and generates fewer random tombstones than explicit deletes.
The Parking Ticket Analogy
When you pay for parking, the attendant stamps your ticket with a two-hour limit. You do not need to tell anyone when to tow your car — the limit is built into the ticket. When the time expires, the spot is freed automatically. Cassandra TTL works the same way: you stamp the data with an expiration time, and Cassandra handles the cleanup without any further instruction from your application.
Setting TTL on INSERT
Add USING TTL seconds at the end of an INSERT statement to give the row a time limit.
-- Session token that expires in 30 minutes (1800 seconds): INSERT INTO session_tokens (token, user_id, created_at) VALUES (uuid(), [user-uuid], toTimestamp(now())) USING TTL 1800;
Timeline: 10:00 AM INSERT with TTL 1800 10:30 AM TTL expires → Cassandra marks the row deleted Next compaction → tombstone removed from disk
Setting TTL on UPDATE
TTL applies to individual column values when set in an UPDATE. The TTL in an UPDATE overwrites any existing TTL on the specified columns.
UPDATE user_sessions USING TTL 3600 SET last_seen = toTimestamp(now()) WHERE session_id = [uuid];
Only the last_seen column expires after 3600 seconds. Other columns in the same row keep their existing TTL or live indefinitely.
Table-Level Default TTL
Set a default TTL for every row inserted into a table using the default_time_to_live table property. Any INSERT that does not specify its own USING TTL clause uses this default.
CREATE TABLE one_time_codes ( code TEXT PRIMARY KEY, user_id UUID, created_at TIMESTAMP ) WITH default_time_to_live = 900; -- 15 minutes
A per-row USING TTL value overrides the table default for that specific row.
-- Uses the table default of 900 seconds:
INSERT INTO one_time_codes (code, user_id) VALUES ('ABC123', [uuid]);
-- Overrides the default to 60 seconds:
INSERT INTO one_time_codes (code, user_id) VALUES ('XYZ789', [uuid])
USING TTL 60;
Checking Remaining TTL
Use the TTL(column_name) function in a SELECT to see how many seconds remain before a column expires.
SELECT token, TTL(user_id) AS seconds_left FROM session_tokens WHERE token = [uuid]; token | seconds_left ───────────────────────── [uuid] | 1423
When TTL returns null for a column, the column has no expiration set (it lives forever).
Removing TTL from a Row
Set TTL to zero to remove an expiration from a row or column, making it permanent again.
UPDATE session_tokens USING TTL 0 SET user_id = [uuid] WHERE token = [session-uuid];
How Cassandra Expires Rows Internally
When a TTL expires, Cassandra writes a tombstone for the affected cell or row. That tombstone goes through the normal gc_grace_seconds lifecycle before compaction removes it. The data does not disappear from disk the instant the TTL runs out — it becomes invisible to reads immediately but occupies disk space until the next compaction.
INSERT with TTL 3600 at 09:00
│
09:00 data visible
│
10:00 TTL expires → tombstone generated
│
10:00+ reads return null (data invisible)
│
10+ days (after gc_grace_seconds)
│
Next compaction → tombstone + old data removed from disk
TTL and Clustering Columns
TTL applies at the column level in Cassandra, not the row level conceptually. When you insert a row with USING TTL, every non-primary-key column in that row gets the same TTL. Primary key columns never expire — they represent the row's identity. A row becomes fully deleted only when all its non-primary-key columns have expired.
INSERT INTO events (user_id, event_time, event_name, detail) VALUES ([uuid], '2024-06-01', 'login', 'desktop') USING TTL 604800; -- 7 days Expiry behavior: user_id (partition key) → never expires event_time (cluster col) → never expires event_name → expires in 7 days detail → expires in 7 days
TTL Best Practices
Pattern Recommended TTL Approach ────────────────────────────────────────────────────────────────── Session / auth tokens Per-row TTL via USING TTL One-time codes (OTP, magic link) Table-level default_time_to_live IoT sensor cache (last reading) Per-row TTL refreshed on each write Log retention (30-day logs) Table-level TTL; use TWCS compaction Cache warming tables Table-level TTL matching cache policy Audit trails (keep forever) No TTL; use separate cold storage
TTL with TimeWindowCompactionStrategy
For time-series tables that use TTL, pair the table with TimeWindowCompactionStrategy. TWCS groups SSTables by their write time window. When a window's TTL expires, the entire SSTable is eligible for deletion in a single operation rather than cell-by-cell tombstone processing. This drastically reduces compaction overhead on large time-series tables.
CREATE TABLE sensor_readings (
device_id UUID,
read_time TIMESTAMP,
value DOUBLE,
PRIMARY KEY (device_id, read_time)
) WITH default_time_to_live = 2592000 -- 30 days
AND compaction = {
'class': 'TimeWindowCompactionStrategy',
'compaction_window_unit': 'DAYS',
'compaction_window_size': 1
};
Summary
TTL automates data expiration in Cassandra without manual DELETE statements. Set TTL per row with USING TTL on INSERT or UPDATE, or set a table-wide default with the default_time_to_live property. Use the TTL() function in SELECT to check remaining lifetime. Combine TTL with TimeWindowCompactionStrategy for efficient expiration of time-series data. TTL does not erase data instantly — it writes a tombstone and relies on compaction for final removal.
