In Cassandra, each mutation has a microsecond-precision timestamp, which is used to order operations relative to each other.
There are various ways to assign it:
USING TIMESTAMP
You can explicitly provide the timestamp in your CQL query:
session.execute("INSERT INTO my_table(c1, c2) values (1, 1) " + "USING TIMESTAMP 1432815430948040");
The driver has a timestamp generator that gets invoked for every outgoing request; it either assigns a client-side timestamp to the request, or indicates that the server should assign it.
The timestamp generator is defined in the configuration.
datastax-java-driver.advanced.timestamp-generator { class = AtomicTimestampGenerator }
This is the default implementation. It always generates a client timestamp, and guarantees monotonicity (i.e. ever-increasing timestamps) across all application threads.
Note that, in order to achieve monotonicity, the generator might return timestamps that drift out in the future. This happens if timestamps are generated at a rate of more than one per microsecond, or more likely in the event of a system clock skew. When this happens, the generator logs a warning message in the category com.datastax.oss.driver.internal.core.time.MonotonicTimestampGenerator
:
Clock skew detected: current tick (...) was ... microseconds behind the last generated timestamp (...), returned timestamps will be artificially incremented to guarantee monotonicity.
You can control that message with these options:
datastax-java-driver.advanced.timestamp-generator { drift-warning { # How far in the future timestamps are allowed to drift before the warning is logged. # If it is undefined or set to 0, warnings are disabled. threshold = 1 second # How often the warning will be logged if timestamps keep drifting above the threshold. interval = 10 seconds } }
This generator strives to achieve microsecond resolution on a best-effort basis. But in practice, the real accuracy of generated timestamps is largely dependent on the granularity of the operating system's clock. For most systems, this minimum granularity is millisecond, and the sub-millisecond part is simply a counter that gets incremented until the next clock tick, as provided by System.currentTimeMillis()
.
On some systems, however, it is possible to have a better granularity by using a JNR call to gettimeofday. This native call will be used when available, unless use of the Java clock is forced with this configuration option:
datastax-java-driver.advanced.timestamp-generator { force-java-clock = true }
To check what the driver is currently using, turn on INFO
logs for the category com.datastax.oss.driver.internal.core.time
, and look for one of the following messages at initialization:
Using Java system clock because this was explicitly required in the configuration
Could not access native clock (see debug logs for details), falling back to Java system clock
Using native clock for microsecond precision
datastax-java-driver.advanced.timestamp-generator { class = ThreadLocalTimestampGenerator }
This is similar to the atomic generator, except that it only guarantees monotonicity within each thread. In other words, if a given application thread invokes session.execute()
multiple times, the timestamps will be strictly increasing; but across two or more application threads, there might be duplicates.
This is a bit more efficient, but should only be used when threads are not in direct competition for timestamp ties (i.e., they are executing independent statements).
It uses the same configuration options drift-warning
andforce-java-clock
; see the previous section for details.
datastax-java-driver.advanced.timestamp-generator { class = ServerSideTimestampGenerator }
This implementation always lets the server assign a timestamp.
You can create your own generator by implementing TimestampGenerator, and referencing your implementation class from the configuration.
The timestamp generator can be overridden in execution profiles:
datastax-java-driver { advanced.timestamp-generator.class = AtomicTimestampGenerator profiles { profile1 { advanced.timestamp-generator.class = ServerSideTimestampGenerator } profile2 {} } }
The profile1
profile uses its own generator. The profile2
profile inherits the default profile's. Note that this goes beyond configuration inheritance: the driver only creates a single AtomicTimestampGenerator
instance and reuses it (this also occurs if two sibling profiles have the same configuration).
Each request uses its declared profile‘s generator. If it doesn’t declare any profile, or if the profile doesn‘t have a dedicated policy, then the default profile’s generator is used.
Finally, you can assign a timestamp to a statement directly from application code:
Statement statement = SimpleStatement.builder("UPDATE users SET email = 'x@y.com' where id = 1") .setQueryTimestamp(1432815430948040L) .build(); session.execute(statement);
Client-side timestamps are prohibited for lightweight transactions (used for conditional updates such as INSERT... IF NOT EXISTS
, UPDATE... IF...
, etc.).
If you add a USING TIMESTAMP
clause to such a query, the server will return an error:
cqlsh> UPDATE foo USING TIMESTAMP 1234 SET v=1 WHERE k=0 IF v=2; InvalidRequest: Error from server: code=2200 [Invalid query] message="Cannot provide custom timestamp for conditional updates"
If you execute a conditional update through the driver with a client-side timestamp generator, the client-side timestamp will be silently ignored and the server will provide its own.
Here is the order of precedence of all the methods described so far:
USING TIMESTAMP
clause in the CQL string, use that over anything else;