Files
scylladb/docs/kb/lwt-differences.rst
Nadav Har'El f8aaeb5e87 cql: atomic add/subtract operations with LWT
ScyllaDB has special counter columns for which atomic add/subtract
operations like `SET a = a + 1` are allowed. Such operations have not
been allowed on ordinary non-counter columns, as they would not be
properly atomic - the read an the write are separate, and concurrent
operations can have incorrect results.

This patch makes it allowed to use such atomic add/subtract operations
in *LWT* statements. Some examples:

        UPDATE ... SET a = a - 1 IF a > 0

        UPDATE ... SET a = a + 1 IF EXISTS

        UPDATE ... SET a = a + 1 a != NULL

The row updated in the operation, and the updated column (a) should
be initialized before the update - arithmetic operations on missing
column values silently leave the column null (no error is generated).

This add/subtract operations is allowed on any numeric column -
integer or floating point of any size.

The ability of LWT to fetch the old values of a column and use it to
calculate the new value has long been available in our internal CAS
implementation - and has been in use for years in Alternator - but until
this patch it was not exposed in CQL's LWT.

This patch does not add new syntax to CQL - the "SET a = a + b"
and "SET a = a - b" syntax that already existed for counters is now
allowed for non-counters.

This is a new Scylla-only feature that does not exist in Cassandra.

Fixes #10568

Signed-off-by: Nadav Har'El <nyh@scylladb.com>
2026-05-25 10:09:11 +03:00

111 lines
7.2 KiB
ReStructuredText
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
====================================================
How does ScyllaDB LWT Differ from Apache Cassandra ?
====================================================
ScyllaDB is making an effort to be compatible with Cassandra, down to the level of limitations of the implementation.
How is it different?
* ScyllaDB most commonly uses fewer rounds than Cassandra to complete a lightweight transaction. While Cassandra issues a separate read query to fetch the old record, scylla piggybacks the read result on the response to the prepare round.
* ScyllaDB will automatically use synchronous commit log write mode for all lightweight transaction writes. Before a lightweight transaction completes, scylla will ensure that the data in it has hit the device. This is done in all commitlog_sync modes.
* Conditional statements return a result set, and unlike Cassandra, ScyllaDB result set metadata doesnt change from execution to execution: ScyllaDB always returns the old version of the row, regardless of whether the condition is true or not. This ensures conditional statements work well with prepared statements.
* For batch statement, the returned result set contains an old row for every conditional statement in the batch, in statement order. Cassandra returns results in clustering key order.
* For batch statement, ScyllaDB allows mixing `IF EXISTS`, `IF NOT EXISTS`, and other conditions for the same row.
* Unlike Cassandra, ScyllaDB uses per-core data partitioning, so the RPC that is done to perform a transaction talks directly to the right core on a peer replica, avoiding the concurrency overhead. This is, of course, true, if ScyllaDBs own shard-aware driver is used - otherwise we add an extra hop to the right core at the coordinator node.
* ScyllaDB does not store hints for lightweight transaction writes, since this is redundant as all such writes are already present in system.paxos table.
* ScyllaDB supports arithmetic ``SET`` operations (``col = col + value`` and ``col = col - value``) on non-counter numeric columns inside LWT conditional updates. Cassandra does not support this syntax.
More on :doc:`Lightweight Transactions (LWT) </features/lwt>`
Additional Notes
================
Arithmetic SET operations in LWT updates
----------------------------------------
ScyllaDB allows ``SET col = col + value`` and ``SET col = col - value`` on non-counter numeric
columns inside a conditional ``UPDATE``. The Paxos read-modify-write cycle guarantees atomicity:
the old value is read in the same transaction round, the arithmetic is applied, and the result
is written — all without a separate read or a race condition.
If the column is null, the arithmetic result is also null (standard SQL null-propagation
semantics), so the column remains unset. To catch an uninitialized column, use an ``IF``
condition that checks the column value (e.g. ``IF score != null``) rather than ``IF EXISTS``.
This syntax is a ScyllaDB extension; Cassandra rejects it with an ``InvalidRequest`` error.
An ``IF`` condition (``IF EXISTS`` or a column predicate) is required; omitting the ``IF``
clause is rejected even in ScyllaDB, because without LWT atomicity the read-modify-write
would not be safe.
Example — atomic counter on a regular column:
.. code-block:: cql
-- Initialize a row, with score=0
INSERT INTO mytable (pk, ck, score) VALUES (1, 1, 0) IF NOT EXISTS;
-- Increment atomically:
UPDATE mytable SET score = score + 1 WHERE pk = 1 AND ck = 1 IF EXISTS;
-- Increment only when score is already set (fails if score is null):
UPDATE mytable SET score = score + 1 WHERE pk = 1 AND ck = 1 IF score != null;
-- Decrement only when the value is large enough:
UPDATE mytable SET score = score - 5 WHERE pk = 1 AND ck = 1 IF score >= 5;
Mixing LWT IF clauses in BATCH statements
-----------------------------------------
`Conditional batches` are `BATCH` statements that contain one or more conditional statements. A batch is executed only if all conditions in all statements are true, and all conditions are evaluated against the initial database state. ScyllaDB allows using different conditions such as `IF EXISTS`, `IF NOT EXISTS`, and other `IF` expressions within the same batch, even if the statements affect the same row.
For example, the following batch statement is valid in ScyllaDB and will be applied successfully:
.. code-block:: cql
BEGIN BATCH
UPDATE movies.nowshowing SET main_actor = NULL WHERE movie = 'Invisible Man' IF director = 'Leigh Whannell'
UPDATE movies.nowshowing SET released = NULL WHERE movie = 'Invisible Man' IF EXISTS
APPLY BATCH;
[applied] | movie | location | run_day | run_time | director | main_actor | released | theater
-----------+---------------+----------+---------+----------+----------------+----------------+------------+---------
True | Invisible Man | null | null | null | Leigh Whannell | Elisabeth Moss | 2022-04-06 | null
True | Invisible Man | null | null | null | Leigh Whannell | Elisabeth Moss | 2022-04-06 | null
By contrast, Cassandra does not allow mixing `IF EXISTS`, `IF NOT EXISTS`, and other conditions for the same row:
.. code-block:: cql
BEGIN BATCH
UPDATE movies.nowshowing SET main_actor = NULL WHERE movie = 'Invisible Man' IF director = 'Leigh Whannell'
UPDATE movies.nowshowing SET released = NULL WHERE movie = 'Invisible Man' IF EXISTS
APPLY BATCH;
InvalidRequest: Error from server: code=2200 [Invalid query] message="Cannot mix IF conditions and IF EXISTS for the same row"
Moreover, ScyllaDB does not return an error even if the conditions are impossible to satisfly, i.e. a batch contains `IF EXISTS` and `IF NOT EXISTS` clauses for the same row. The following query will be executed, though not applied, as the conditions are impossible to satisfy:
.. code-block:: cql
BEGIN BATCH
INSERT INTO movies.nowshowing (movie, location, theater, run_day, run_time) VALUES ('Invisible Man', 'Times Square', 'AMC Empire 25', 'Saturday', '23:00:00') IF NOT EXISTS
UPDATE movies.nowshowing SET theater = NULL WHERE movie = 'Invisible Man' AND location = 'Times Square' AND run_day = 'Saturday' AND run_time = '23:00:00' IF EXISTS
APPLY BATCH;
[applied] | movie | location | run_day | run_time | director | main_actor | released | theater
-----------+-------+----------+---------+----------+----------+------------+----------+---------
False | null | null | null | null | null | null | null | null
False | null | null | null | null | null | null | null | null
In comparison, Cassandra returns an error in this case:
.. code-block:: cql
BEGIN BATCH
INSERT INTO movies.nowshowing (movie, location, theater, run_day, run_time) VALUES ('Invisible Man', 'Times Square', 'AMC Empire 25', 'Saturday', '23:00:00') IF NOT EXISTS
UPDATE movies.nowshowing SET theater = NULL WHERE movie = 'Invisible Man' AND location = 'Times Square' AND run_day = 'Saturday' AND run_time = '23:00:00' IF EXISTS
APPLY BATCH;
InvalidRequest: Error from server: code=2200 [Invalid query] message="Cannot mix IF EXISTS and IF NOT EXISTS conditions for the same row"