How Cassandra Uses Time
Every write Cassandra stores carries a timestamp, and the clock that produced it determines how conflicting writes to the same data resolve. This page covers where that timestamp originates, which side generates it in current drivers, and the limits of the ordering guarantee a coordinator provides.
Where the write timestamp comes from
Section titled “Where the write timestamp comes from”Every mutation Cassandra stores carries a timestamp expressed in microseconds since the Unix epoch. Three sources can set it, in decreasing order of precedence:
| Source | Mechanism | Clock used |
|---|---|---|
| Statement-level | USING TIMESTAMP <microseconds> in the Cassandra Query Language (CQL) statement | Supplied by the application, from any source |
| Client driver | The default timestamp field of the native protocol request | System clock of the client application host |
| Coordinator | Assigned by the node handling the request when no timestamp is supplied | System clock of the coordinator node |
Native protocol v3, introduced in Cassandra 2.1, added the per-request default timestamp field (CASSANDRA-6855) that makes client-side generation possible at the protocol level, and current drivers populate it by default. In deployments using such a driver, the write timestamp originates on the application host, not on any Cassandra node. Synchronizing only the cluster and leaving application hosts unsynchronized therefore leaves the ordering problem unsolved.
USING TIMESTAMP overrides both of the other sources in Cassandra 3.11, 4.x, and 5.0. Applications that set it must derive the value from a clock that is synchronized with the rest of the deployment, or from a source of ordering that is not a wall clock at all.
Which side generates the timestamp in practice
Section titled “Which side generates the timestamp in practice”Whether the timestamp comes from the application host or from the coordinator is a driver decision, and the answer has changed over time and differs between languages. It must be established per driver and per version rather than assumed.
The Java driver is now the Apache Cassandra Java driver, from driver 4.18 onward, having been donated to the Apache Cassandra project under Cassandra Enhancement Proposal 8 (CEP-8). The DataStax-branded driver line that preceded it is legacy and no longer maintained. CEP-8 covered the DataStax-maintained drivers generally, so the Node.js and Python drivers moved to the project by the same route. The Go driver, gocql, was never a DataStax driver; it was donated to the Apache Software Foundation separately, and its first Apache release was 1.7.0 in September 2024 (CASSGO-63).
The switch to client-side generation happened during the legacy DataStax era, which is why deployments predating the donation are already generating timestamps on the application host. Client-side generation, using an atomic monotonic generator, became the default in the DataStax Java driver 3.0 whenever native protocol v3 or later is negotiated. Coordinator assignment was the behaviour before that, so the 3.0 upgrade is when most Java deployments moved to client time without any configuration change.
The table below summarizes the current default for each driver and the setting that controls it.
| Driver | Default | Control |
|---|---|---|
| Apache Cassandra Java driver (4.x) | Client-side | advanced.timestamp-generator in the driver configuration, selecting an atomic, thread-local, or server-side generator |
| Apache Cassandra Node.js driver | Client-side since driver 3.2, using a monotonic generator | Client option for the timestamp generator |
| Apache Cassandra Python driver | Client-side: Session.use_client_timestamp defaults to True, applying with protocol v3 and later. The monotonic generator (MonotonicTimestampGenerator, one per Cluster) was introduced in driver 3.8.0 | Session.use_client_timestamp |
| Apache Cassandra Go driver (gocql) | Client-side: DefaultTimestamp in the cluster configuration defaults to true, effective with protocol v3 and later | Set DefaultTimestamp to false in the cluster configuration |
| C# driver | Client-side since driver 3.3, using AtomicMonotonicTimestampGenerator (DataStax-era release) | Driver timestamp generator option |
| C++ driver | Client-side by default (DataStax-era C++ driver 2.17 documentation) | Driver timestamp generator option |
The C# and C++ driver lines were also donated to the Apache Cassandra project under CEP-8, and the DataStax-branded releases of both are legacy. The version facts cited for them above are from those legacy releases, which remain the documented behaviour for deployments running them.
Three mechanisms coexist, in decreasing order of precedence. USING TIMESTAMP in the CQL statement overrides everything else. A statement-level driver API, where the application sets a timestamp on one statement, overrides the driver's generator for that statement only. The generator configured on the session supplies the value for everything else, and when the driver sends no timestamp, which the Java driver 4.x signals with Long.MIN_VALUE, the coordinator assigns one.
The operational consequence is that a deployment must verify what is actually generating its timestamps rather than reasoning from a general expectation. This matters most in polyglot environments: two services written in different languages, using drivers with different defaults or different major versions, can write the same tables with timestamps drawn from different clocks, and nothing in Cassandra reports the mismatch.
Application time compared with coordinator time
Section titled “Application time compared with coordinator time”Client-side generation has three properties that coordinator generation does not:
- A single client's operations keep the order in which they were issued, even when consecutive requests are handled by different coordinators. Coordinator-to-coordinator skew cannot reorder one client's writes, because those writes never consult a coordinator's clock.
- A monotonic generator guarantees strictly increasing timestamps within a client process even if that host's own clock steps backwards.
- The timestamp is assigned once, when the driver builds the request message, which happens before any retry policy or speculative execution comes into play. Attempts derived from that request therefore carry the timestamp the original attempt carried, so under last-write-wins such an attempt reapplies the same value rather than becoming a newer write that could overwrite an intervening update.
The cost is the size of the correctness domain. With coordinator timestamps, only the cluster nodes need tight synchronization. With client timestamps, every application host that writes joins that domain, and an application fleet is usually larger than the cluster and frequently runs on less controlled infrastructure. This is why the recommended architecture has client application hosts synchronizing to the same internal tier as the Cassandra nodes: under client-side generation they are as much a part of the write path's correctness as the nodes are.
Two failure modes follow from that. Writes to the same cells from different application instances are ordered by those instances' clocks, so skew across the application fleet produces the same silent last-write-wins anomalies that clock skew failure modes describes for cluster nodes. And a single badly skewed application host can shadow data cluster-wide: a timestamp far in the future suppresses every subsequent write to those cells until the wall clock catches up. The Cassandra 5.0 timestamp guardrails are the server-side bound on that case, and they are a bound rather than a fix.
Coordinator generation trades those problems for the two the list above names: one client's consecutive writes through different coordinators can be reordered by inter-coordinator skew, and because the timestamp is assigned by whichever coordinator handles the attempt rather than once at request construction, a reattempt is stamped later than the original, which removes the reapplication property described above. Neither choice removes the requirement for synchronized clocks. Client-side generation moves where the requirement applies and widens it.
Last-write-wins conflict resolution
Section titled “Last-write-wins conflict resolution”Cassandra stores every write as an immutable cell version. Two versions of the same cell are compared during a read, during compaction (the background process that merges stored files and discards superseded data), during read repair (the reconciliation Cassandra performs inline when a read finds replicas disagreeing), and during anti-entropy repair (the nodetool repair operation that reconciles replicas offline of any read). In every case the version with the higher timestamp wins. In Cassandra 3.11, 4.x, and 5.0, ties on timestamp are broken by comparing the cell values bytewise, which makes the outcome deterministic but arbitrary. That tie-break is deliberate rather than incidental: a proposal to change it was closed as Won't Fix (CASSANDRA-14323).
Because the comparison is on timestamps and not on arrival order, a write that reaches a replica second can be discarded in favour of a write that reaches it first. That is the intended behaviour when the timestamps reflect real time. It becomes data corruption when they do not.
Coordinator timestamps are monotonic per node, not across nodes
Section titled “Coordinator timestamps are monotonic per node, not across nodes”In Cassandra 3.11, 4.x, and 5.0, when the coordinator assigns the timestamp it derives the value from its own system clock and enforces monotonicity locally: if the system clock has not advanced since the previous request, the coordinator returns a value one microsecond greater than the one it issued last. This guarantees that two requests handled by the same coordinator receive strictly increasing timestamps. The behaviour is implemented in ClientState.getTimestamp(), and the guarantee has been node-wide, rather than per client connection, since Cassandra 2.1.8 (CASSANDRA-9649, also backported to 2.0.17).
No equivalent guarantee exists across nodes. Two coordinators handling two requests have no shared counter and exchange no timestamp information for this purpose, so the relative order of the timestamps they issue is exactly the relative order of their system clocks.
Undefined Behavior
The relative order of two writes issued through different coordinators, or from different client hosts, is not guaranteed by Cassandra in any version. It is determined entirely by the clocks of the machines that generated the timestamps. An application that requires two writes to be ordered must not assume that issuing them sequentially, or receiving an acknowledgement for the first before issuing the second, produces the expected order in storage.
Related pages
Section titled “Related pages”- Clock skew failure modes - what these mechanisms produce when the clocks disagree
- Consistency - how consistency levels interact with conflict resolution
- Lightweight Transactions - Paxos ballots and LWT semantics