Skip to content

AxonOps — AI-Native Control Plane for Open Source Data Platforms

Data Manipulation Language (DML)

Data Manipulation Language (DML) commands retrieve and modify data in Cassandra tables. Unlike traditional SQL databases, Cassandra's DML operations are designed for distributed, eventually consistent storage with specific semantics around timestamps, tombstones, and partition-aware queries.


Cassandra's DML differs fundamentally from SQL databases:

AspectSQL DatabasesCassandra
INSERT semanticsFails if row existsUpsert (insert or update)
UPDATE semanticsFails if row doesn't existUpsert (insert or update)
DELETE behaviorImmediate removalTombstone marker
Query flexibilityAny column in WHEREPartition key required
ConsistencyACID transactionsTunable consistency
Conflict resolutionLocksLast-write-wins (timestamp)

CQL (Cassandra Query Language) was introduced in Cassandra 0.8 (2011) as a SQL-like interface to replace the Thrift API. The language evolved significantly:

VersionCQL VersionKey DML Features
0.8CQL 1.0Basic SELECT, INSERT, UPDATE, DELETE
1.2CQL 3.0Compound primary keys, collections, prepared statements
2.0CQL 3.1Lightweight transactions (IF NOT EXISTS/IF condition)
2.1CQL 3.2User-defined functions, JSON support
2.2CQL 3.3User-defined aggregates, GROUP BY
3.0CQL 3.4Materialized views, SASI indexes
4.0CQL 3.4.5Virtual tables, audit logging
5.0CQL 3.4.7SAI indexes, vector search

Understanding how writes flow through Cassandra is essential for effective DML usage.

ClientCoordinatorReplicaSSTableClientClientCoordinator(Parser)Coordinator(Parser)Replica(Storage)Replica(Storage)SSTableSSTableWrite request1. Parse statement2. Calculate partition token3. Route to replicas4. Write to commit log5. Write to memtable6. Acknowledge(based on consistency level)Async flush(when memtable full)
  1. Client sends request to any node (coordinator)
  2. Coordinator parses the CQL statement
  3. Partition key hashed to determine token and replica nodes
  4. Request forwarded to replica nodes based on replication factor
  5. Each replica writes to commit log (durability) then memtable (speed)
  6. Coordinator waits for acknowledgments based on consistency level
  7. Response returned when sufficient replicas acknowledge

Every write in Cassandra carries a timestamp (microseconds since Unix epoch):

-- Cassandra assigns current time if not specified
INSERT INTO users (id, name) VALUES (1, 'Alice');
-- Explicit timestamp
INSERT INTO users (id, name) VALUES (1, 'Alice')
USING TIMESTAMP 1705315800000000;

Timestamps serve critical functions:

  • Conflict resolution: Higher timestamp wins (last-write-wins)
  • Tombstone expiration: Determines when deleted data can be purged
  • Read repair: Identifies which value is most recent

Clock Synchronization

Cassandra relies on synchronized clocks across nodes. Use NTP to keep clocks synchronized within milliseconds. Clock skew can cause:

  • Unexpected conflict resolution results
  • Data appearing to "come back" after deletion
  • Inconsistent reads

Reads in Cassandra follow a different path optimized for distributed data retrieval.

ClientCoordinatorReplicaClientClientCoordinator(Query Router)Coordinator(Query Router)Replica(Storage Engine)Replica(Storage Engine)SELECT query1. Parse queryextract partition key2. Route to replicas3. Check bloom filter4. Read memtable + SSTablesRow data5. Merge results(resolve by timestamp)Result set
  1. Client sends query to coordinator
  2. Coordinator extracts partition key and calculates token
  3. Request sent to replicas based on consistency level
  4. Each replica checks:
    • Bloom filter (may contain partition?)
    • Partition key cache
    • Memtable (in-memory recent writes)
    • SSTables (on-disk, newest to oldest)
  5. Coordinator merges results, resolving conflicts by timestamp
  6. Optional read repair if inconsistencies detected

Cassandra requires the partition key in most queries because it determines which nodes hold the data:

-- Efficient: partition key specified
SELECT * FROM users WHERE user_id = 123;
-- Inefficient: full cluster scan
SELECT * FROM users WHERE name = 'Alice' ALLOW FILTERING;

ALLOW FILTERING

The ALLOW FILTERING clause forces Cassandra to scan all partitions. This operation:

  • Contacts every node in the cluster
  • Does not scale with cluster size
  • Can cause timeouts on large tables
  • Should be avoided in production application queries (exceptions: small tables, admin/operational queries, or when combined with partition key)

Consistency levels control how many replicas must respond before a query succeeds.

LevelReplicas RequiredUse Case
ANY1 (including hints)Maximum availability, risk of data loss
ONE1Low latency, eventual consistency
TWO2Slightly stronger consistency
THREE3Stronger consistency
QUORUM(RF/2) + 1Balance of consistency and availability
LOCAL_QUORUMQuorum in local DCMulti-DC with local consistency
EACH_QUORUMQuorum in each DCStrong multi-DC consistency
ALLAll replicasMaximum consistency, lowest availability
LevelReplicas ContactedUse Case
ONE1Low latency reads
TWO2Slightly stronger consistency
THREE3Stronger consistency
QUORUM(RF/2) + 1Strong consistency
LOCAL_QUORUMQuorum in local DCMulti-DC with local reads
EACH_QUORUMQuorum in each DCNot typically used for reads
ALLAll replicasMaximum consistency
LOCAL_ONE1 in local DCLow latency local reads
SERIALPaxos quorumFor LWT operations only (not general reads)
LOCAL_SERIALLocal Paxos quorumFor LWT operations only (not general reads)

To achieve strong consistency (read-your-writes) for single-partition operations within the same datacenter:

READ_CL + WRITE_CL > REPLICATION_FACTOR

Note: This formula applies to single-partition operations. Multi-partition queries and cross-datacenter scenarios have additional considerations.

Common patterns:

Write CLRead CLRF=3Guarantee
QUORUMQUORUM2 + 2 > 3
ONEALL1 + 3 > 3
ALLONE3 + 1 > 3
ONEONE1 + 1 ≤ 3

Cassandra does not immediately delete data. Instead, it writes tombstones—markers indicating data should be considered deleted.

DELETE statementTombstone written(timestamp recorded)Tombstone replicatedto all replicasgc_grace_seconds elapsed?(default: 10 days)yesreplica down too longCompactionremoves dataData resurrection!Deleted data reappears

In a distributed system without central coordination:

  1. No global delete propagation: DELETE cannot contact offline nodes
  2. Prevents resurrection: Tombstones override older data when replicas sync
  3. Eventually consistent: All replicas eventually see the deletion
TypeCreated ByScope
Cell tombstoneDELETE column FROM tableSingle column value
Row tombstoneDELETE FROM table WHERE pk = xEntire row
Range tombstoneDELETE FROM table WHERE pk = x AND ck > yRange of clustering keys
Partition tombstoneDELETE FROM table WHERE pk = x (all rows)Entire partition
TTL tombstoneAutomatic when TTL expiresColumn or row

The gc_grace_seconds table property (default: 864000 = 10 days) determines how long tombstones persist before compaction can remove them:

CREATE TABLE events (
...
) WITH gc_grace_seconds = 86400; -- 1 day

Reducing gc_grace_seconds

Before reducing gc_grace_seconds:

  1. Ensure repairs run more frequently than the new value
  2. Verify no nodes stay down longer than the new value
  3. Understand that nodes down longer than gc_grace_seconds may resurrect deleted data

StatementPurposeDocumentation
SELECTRetrieve rows and columnsQuery syntax, filtering, paging
INSERTAdd or replace rowsUpsert semantics, TTL, JSON
UPDATEModify column valuesCollection operations, counters
DELETERemove rows or columnsTombstones, range deletes
BATCHAtomic multi-statement operationsLogged vs unlogged, anti-patterns
Secondary Index QueriesQuery non-primary-key columnsIndex types, performance, anti-patterns
Vector SearchSimilarity search on embeddingsANN queries, similarity functions
Lightweight TransactionsCompare-and-set operationsPaxos, serial consistency

Write Best Practices

  1. Batch by partition: Group writes to the same partition for efficiency
  2. Avoid large batches: Keep batches under 5KB (warn threshold)
  3. Use UNSET for nulls: Avoid creating tombstones with null values
  4. Prepare statements: Reuse prepared statements for repeated queries
  5. Appropriate TTL: Set TTL at write time, not retroactively

Read Best Practices

  1. Include partition key: Every query should specify partition key
  2. Avoid ALLOW FILTERING: Design data model for query patterns
  3. Use token-aware drivers: Route queries directly to replicas
  4. Limit result sets: Use LIMIT and paging for large results
  5. Consider LOCAL_ consistency*: For multi-DC deployments

Delete Best Practices

  1. Minimize deletes: Design data model to avoid frequent deletes
  2. Use TTL instead: Let data expire naturally when possible
  3. Range deletes: More efficient than many row deletes
  4. Monitor tombstones: Use nodetool tablestats to track tombstone counts
  5. Regular repairs: Ensure tombstones propagate before gc_grace expires