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.

Leave a Comment

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