Upserts, updates and deletes

Apache Cassandra · Essentials · 10 min

Cassandra's write semantics differ fundamentally from SQL, and assuming SQL behaviour leads to silent data surprises.

In Cassandra INSERT and UPDATE are both upserts. INSERT on an existing primary key overwrites the given columns; UPDATE ... WHERE key = ... on a row that doesn't exist creates it. Neither reads before writing, which is why writes are so fast, and neither reports whether the row existed (use a lightweight transaction, IF NOT EXISTS or IF EXISTS, when you need that).

Every cell carries a write timestamp (microseconds, set by the coordinator or client). When replicas hold different values for the same cell, the one with the highest timestamp wins: last write wins. Clock skew between clients can therefore make an older write win, so keep clocks synchronized with NTP.

A DELETE doesn't remove data immediately; it writes a tombstone, a marker with a timestamp that shadows older values. Tombstones are purged during compaction after gc_grace_seconds (10 days by default). You can delete a whole partition, a range of rows by clustering column, a single row, or individual columns.

Writing null into a column also creates a tombstone. Drivers that bind null for every unset field of a prepared statement can create millions of tombstones; leave values unset instead.

UPDATE works only on non-key columns; to change a primary key value you delete the old row and insert a new one.

Example

INSERT INTO shop.users (user_id, email) VALUES (5b6962dd-3f90-4c93-8f61-eabfa4a803e2, 'a@x.io');
INSERT INTO shop.users (user_id, email) VALUES (5b6962dd-3f90-4c93-8f61-eabfa4a803e2, 'b@x.io');
-- no error: the second insert overwrote email

UPDATE shop.users SET email = 'c@x.io'
WHERE user_id = 0d1d8cd6-31ab-4a4b-a6d3-6c0e2b1b9a10;   -- row didn't exist: now it does

SELECT email, WRITETIME(email) FROM shop.users
WHERE user_id = 5b6962dd-3f90-4c93-8f61-eabfa4a803e2;
--  email  | writetime(email)
--  b@x.io | 1760691600123456

DELETE prefs FROM shop.users WHERE user_id = 5b6962dd-3f90-4c93-8f61-eabfa4a803e2; -- column
DELETE FROM shop.readings WHERE sensor_id = 's-17' AND day = '2026-10-17'
  AND ts < '2026-10-17 06:00:00+0000';                                           -- row range

Remember

  • INSERT and UPDATE are both upserts and don't read first
  • Conflicts resolve by highest write timestamp (last write wins)
  • Deletes and null writes create tombstones, purged after gc_grace_seconds
  • Leave fields unset rather than binding null

Watch out for

  • Expecting INSERT to fail on a duplicate key like in SQL.

Check yourself

  1. You INSERT a row whose primary key already exists. What happens?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: cassandra.apache.org/doc/latest/cassandra/developing/cql/dml.html