Skip to content
Navigation

Type at least two characters. Search covers page titles, headings, tags and database names.

↑ ↓ to navigateEnter to openEsc to close0 pages
Cassandraadvanced

Cassandra Tombstones

Why deletes create markers rather than removing data, how tombstones break reads, and the modelling that avoids them.

2 min readAdvancedUpdated Edit this page

SSTables are immutable, so a delete cannot remove data. It writes a tombstone: a marker saying this cell, row or range is deleted as of a timestamp.

Tombstones are necessary. Without them, a delete could not propagate to a replica that was down, and the old value would return when that replica came back.

They must be read before they can be skipped

To answer a query, a replica merges everything relevant from every SSTable — including tombstones — and only then discards the deleted rows. A partition with a hundred thousand tombstones reads a hundred thousand markers to return possibly zero rows.

# cassandra.yaml
tombstone_warn_threshold: 1000
tombstone_failure_threshold: 100000

Crossing the failure threshold aborts the query. That is a protection: the alternative is a coordinator running out of memory.

nodetool tablestats shop.events | grep -i tombstone
nodetool tablehistograms shop events    # tombstones per read percentiles

Sources of tombstones

Some are less obvious than an explicit DELETE:

  • Range deletesDELETE FROM t WHERE partition = ? AND clustering < ? creates one range tombstone, which is efficient.
  • TTL expiry — every expired cell becomes a tombstone when it expires.
  • Writing NULL — inserting a null value writes a tombstone for that cell. This surprises teams using an ORM that sends every column on every write.
  • Collection updates — overwriting a collection writes a tombstone covering the old contents before inserting the new ones.

gc_grace_seconds

Tombstones are retained for gc_grace_seconds (ten days by default) so they can reach every replica, and are only removed by compaction after that.

Designing to avoid them

Use TTL with TWCS rather than deleting. With TimeWindowCompactionStrategy, an entire expired window is dropped as whole SSTables — no tombstone scanning on the read path.

Partition by time so deletion becomes dropping a partition. Reading a current bucket never touches the tombstones in an old one.

Do not use Cassandra as a queue. Queue semantics mean deleting each item after processing, and reads then scan the tombstones of everything already handled. This is the canonical Cassandra anti-pattern.

Model to overwrite rather than delete. An upsert of new state creates no tombstone; a delete followed by an insert creates one.

Diagnosing

grep -i "tombstone" /var/log/cassandra/system.log | tail -20

Look for "Read N live rows and M tombstone cells". When M dwarfs N, that query is reading deletion markers, and no amount of hardware will fix it — the data model has to change. See the tombstones playbook.