This is an automated email from the ASF dual-hosted git repository.

dcapwell pushed a commit to branch trunk
in repository https://gitbox.apache.org/repos/asf/cassandra.git


The following commit(s) were added to refs/heads/trunk by this push:
     new 998f42984f Add user docs for Accord focused on how to use, what CQL is 
and isnt valid, and use cases
998f42984f is described below

commit 998f42984fb2cbf5c4bd142fb530ec7ed128559b
Author: David Capwell <[email protected]>
AuthorDate: Tue Jan 20 15:24:33 2026 -0800

    Add user docs for Accord focused on how to use, what CQL is and isnt valid, 
and use cases
    
    patch by David Capwell; reviewed by Caleb Rackliffe for CASSANDRA-21127
---
 doc/modules/cassandra/nav.adoc                     |   3 +
 .../cassandra/pages/developing/cql/dml.adoc        |  94 ++++
 .../cassandra/pages/developing/cql/index.adoc      |   3 +
 .../developing/cql/transactions-examples.adoc      | 496 ++++++++++++++++++++
 .../developing/cql/transactions-migration.adoc     | 289 ++++++++++++
 .../pages/developing/cql/transactions.adoc         | 500 +++++++++++++++++++++
 6 files changed, 1385 insertions(+)

diff --git a/doc/modules/cassandra/nav.adoc b/doc/modules/cassandra/nav.adoc
index 52f8677c97..061b9ecb4b 100644
--- a/doc/modules/cassandra/nav.adoc
+++ b/doc/modules/cassandra/nav.adoc
@@ -43,6 +43,9 @@
 *** xref:cassandra:developing/cql/types.adoc[Data types]
 *** xref:cassandra:developing/cql/ddl.adoc[Data definition (DDL)]
 *** xref:cassandra:developing/cql/dml.adoc[Data manipulation (DML)]
+*** xref:cassandra:developing/cql/transactions.adoc[Transactions]
+*** xref:cassandra:developing/cql/transactions-examples.adoc[Transaction 
patterns]
+*** xref:cassandra:developing/cql/transactions-migration.adoc[Transaction 
migration]
 *** xref:cassandra:developing/cql/dynamic-data-masking.adoc[]
 *** xref:cassandra:developing/cql/operators.adoc[Operators]
 *** xref:cassandra:developing/cql/indexing/indexing-concepts.adoc[]
diff --git a/doc/modules/cassandra/pages/developing/cql/dml.adoc 
b/doc/modules/cassandra/pages/developing/cql/dml.adoc
index 4f13ba2b0c..e1b751d708 100644
--- a/doc/modules/cassandra/pages/developing/cql/dml.adoc
+++ b/doc/modules/cassandra/pages/developing/cql/dml.adoc
@@ -474,3 +474,97 @@ partly applied.
 
 Use the `COUNTER` option for batched counter updates. Unlike other
 updates in Cassandra, counter updates are not idempotent.
+
+=== BATCH vs Transactions
+
+BATCH statements have limitations compared to Accord transactions. 
Understanding the differences helps you choose the right approach for your use 
case.
+
+==== BATCH Statement Capabilities
+
+BATCH statements offer:
+
+* **Single-partition atomicity**: All operations within a single partition 
succeed or fail together atomically
+* **Multi-partition batching**: LOGGED batches can span multiple partitions, 
but are not atomic. The batch log ensures all operations eventually complete, 
but there can be a window where only some operations are visible.
+* **Single partition isolation**: Operations on the same partition are isolated
+* **Network efficiency**: Multiple operations in one round-trip
+* **Automatic timestamps**: All operations use the same timestamp by default
+* **Conditional updates**: Batches support `IF` clauses for lightweight 
transactions (LWT/CAS) on a single partition. If any condition fails, the 
entire batch is rejected.
+* **Basic business rule enforcement**: `IF` clauses can validate conditions 
before applying changes, though limited to what CAS syntax supports
+
+==== BATCH Statement Limitations
+
+However, BATCH statements cannot provide:
+
+* **Cross-partition atomicity**: Multi-partition LOGGED batches are not truly 
atomic (see above)
+* **Read-before-write patterns**: Cannot read data within the batch to make 
decisions based on current values
+* **Cross-partition conditional updates**: Conditional batches (using `IF` 
clauses) must operate on a single partition and table
+* **Complex conditional logic**: `IF` clauses are limited to simple 
equality/inequality checks on existing column values
+
+==== When to Use Accord Transactions Instead
+
+Consider using xref:developing/cql/transactions.adoc[Accord transactions] when 
you need:
+
+**Read-Modify-Write Patterns:**
+[source,cql]
+----
+-- BATCH cannot check balance before transfer
+BEGIN BATCH
+  UPDATE accounts SET balance = balance - 100 WHERE user_id = 1;
+  UPDATE accounts SET balance = balance + 100 WHERE user_id = 2;
+APPLY BATCH;
+
+-- Transaction can validate before transfer
+BEGIN TRANSACTION
+  LET sender = (SELECT balance FROM accounts WHERE user_id = 1);
+
+  IF sender.balance >= 100 THEN
+    UPDATE accounts SET balance = balance - 100 WHERE user_id = 1;
+    UPDATE accounts SET balance = balance + 100 WHERE user_id = 2;
+  END IF
+COMMIT TRANSACTION
+----
+
+**Complex Business Logic:**
+
+NOTE: Row reference arithmetic in SET clauses and comparing two row references 
are not currently supported. Pass values as parameters instead.
+
+[source,cql]
+----
+-- Application code:
+-- double productPrice = 50.00;  // Retrieved from products table
+
+-- Transaction can enforce multi-step business rules
+BEGIN TRANSACTION
+  LET user_account = (SELECT balance, status FROM accounts WHERE user_id = ?);
+  LET product_info = (SELECT quantity FROM products WHERE id = ?);
+
+  IF user_account.status = 'active'
+     AND user_account.balance >= ?  -- Pass product_price as parameter
+     AND product_info.quantity > 0 THEN
+
+    UPDATE accounts SET balance = balance - ? WHERE user_id = ?;  -- Pass 
product_price
+    UPDATE products SET quantity = quantity - 1 WHERE id = ?;
+    INSERT INTO orders (id, user_id, product_id, amount) VALUES (?, ?, ?, ?);  
-- Pass product_price
+  END IF
+COMMIT TRANSACTION
+----
+
+**Error Prevention:**
+Transactions prevent invalid operations like:
+
+* Overdrawing accounts
+* Selling out-of-stock items
+* Creating duplicate records
+* Violating business constraints
+
+==== When to Continue Using BATCH
+
+BATCH statements remain appropriate for:
+
+* **Simple multi-table updates** without conditional logic
+* **Same-partition operations** where isolation is sufficient
+* **High-throughput scenarios** where transaction overhead isn't justified
+* **Counter updates** (transactions don't support counters)
+* **Backward compatibility** with existing applications
+
+For detailed transaction syntax and examples, see the 
xref:developing/cql/transactions.adoc[Transactions] and 
xref:developing/cql/transactions-examples.adoc[Transaction Examples] 
documentation.
diff --git a/doc/modules/cassandra/pages/developing/cql/index.adoc 
b/doc/modules/cassandra/pages/developing/cql/index.adoc
index 97204ce568..5af9f814c7 100644
--- a/doc/modules/cassandra/pages/developing/cql/index.adoc
+++ b/doc/modules/cassandra/pages/developing/cql/index.adoc
@@ -13,6 +13,9 @@ For that reason, when used in this document, these terms 
(tables, rows and colum
 * xref:developing/cql/types.adoc[Data types]
 * xref:developing/cql/ddl.adoc[Data definition language]
 * xref:developing/cql/dml.adoc[Data manipulation language]
+* xref:developing/cql/transactions.adoc[Transactions]
+* xref:developing/cql/transactions-examples.adoc[Transaction patterns]
+* xref:developing/cql/transactions-migration.adoc[Transaction migration]
 * xref:developing/cql/dynamic-data-masking.adoc[Dynamic data masking]
 * xref:developing/cql/operators.adoc[Operators]
 * xref:developing/cql/indexing/indexing-concepts.adoc[Indexing]
diff --git 
a/doc/modules/cassandra/pages/developing/cql/transactions-examples.adoc 
b/doc/modules/cassandra/pages/developing/cql/transactions-examples.adoc
new file mode 100644
index 0000000000..be8cab501f
--- /dev/null
+++ b/doc/modules/cassandra/pages/developing/cql/transactions-examples.adoc
@@ -0,0 +1,496 @@
+= Accord Transaction Design Patterns
+:page-nav-title: Transaction Patterns
+
+This page provides common patterns and advanced design patterns for Accord 
transactions. These patterns solve common distributed system challenges that 
were difficult or impossible to address with eventual consistency.
+
+For basic syntax and getting started, see 
xref:developing/cql/transactions.adoc[Accord Transactions]. For migration from 
LWT/BATCH, see xref:developing/cql/transactions-migration.adoc[Transaction 
Migration].
+
+== Common Patterns
+
+=== Read-Modify-Write
+
+The fundamental transaction pattern: read current state, check a condition, 
then write. This covers conditional updates, conditional inserts, and any 
operation that must validate preconditions.
+
+**Conditional update (e.g., sufficient balance):**
+
+[source,cql]
+----
+BEGIN TRANSACTION
+  LET sender_account = (SELECT balance FROM accounts WHERE id = ?);
+
+  IF sender_account.balance >= ? THEN
+    UPDATE accounts SET balance = balance - ? WHERE id = ?;
+    UPDATE accounts SET balance = balance + ? WHERE id = ?;
+
+    INSERT INTO transactions (id, from_account, to_account, amount, timestamp)
+    VALUES (?, ?, ?, ?, toTimestamp(now()));
+  END IF
+COMMIT TRANSACTION
+----
+
+**Conditional insert (e.g., prevent duplicates):**
+
+[source,cql]
+----
+BEGIN TRANSACTION
+  LET existing_user = (SELECT user_id FROM email_index WHERE email = ? LIMIT 
1);
+
+  IF existing_user IS NULL THEN
+    INSERT INTO users (id, email, created_at, status)
+    VALUES (?, ?, toTimestamp(now()), 'active');
+
+    INSERT INTO email_index (email, user_id) VALUES (?, ?);
+
+    INSERT INTO user_profiles (user_id, display_name)
+    VALUES (?, ?);
+  END IF
+COMMIT TRANSACTION
+----
+
+=== Multi-Table Updates
+
+Maintain referential integrity across tables:
+
+[source,cql]
+----
+BEGIN TRANSACTION
+  UPDATE users SET status = 'suspended', suspended_at = toTimestamp(now())
+  WHERE id = ?;
+
+  UPDATE orders SET status = 'cancelled'
+  WHERE order_id = ?;
+
+  INSERT INTO audit_log (id, user_id, action, timestamp)
+  VALUES (?, ?, 'user_suspended', toTimestamp(now()));
+COMMIT TRANSACTION
+----
+
+=== Cross-Partition Transactions
+
+Coordinate updates across different partitions (see 
xref:developing/cql/transactions.adoc#row-reference-limitations[row reference 
limitations] for parameter passing):
+
+[source,cql]
+----
+BEGIN TRANSACTION
+  LET user_data = (SELECT balance, status FROM users WHERE id = ?);
+
+  IF user_data.status = 'active' AND user_data.balance >= ? THEN
+    UPDATE users SET balance = balance - ? WHERE id = ?;
+
+    INSERT INTO orders (id, user_id, total, status, created_at)
+    VALUES (?, ?, ?, 'confirmed', toTimestamp(now()));
+
+    UPDATE inventory SET quantity = quantity - ? WHERE product_id = ?;
+  END IF
+COMMIT TRANSACTION
+----
+
+== Pattern: Synchronous Unique Constraints
+
+Cassandra's primary key enforces uniqueness, but what if you need uniqueness 
on a non-primary-key column like `email` or `username`? This pattern uses 
user-maintained lookup tables to enforce multiple unique constraints atomically.
+
+=== The Challenge
+
+You have a `users` table keyed by `user_id`, but you also need:
+
+* Unique `email` addresses
+* Unique `username` values
+* The ability to **change** email or username while maintaining uniqueness
+
+=== Schema
+
+[source,cql]
+----
+CREATE TABLE users (
+  user_id uuid PRIMARY KEY,
+  username text,
+  email text,
+  display_name text,
+  created_at timestamp
+) WITH transactional_mode = 'full';
+
+-- Lookup tables for uniqueness enforcement
+CREATE TABLE username_index (
+  username text PRIMARY KEY,
+  user_id uuid
+) WITH transactional_mode = 'full';
+
+CREATE TABLE email_index (
+  email text PRIMARY KEY,
+  user_id uuid
+) WITH transactional_mode = 'full';
+----
+
+=== Creating a User with Unique Constraints
+
+[source,cql]
+----
+BEGIN TRANSACTION
+  LET existing_username = (SELECT user_id FROM username_index WHERE username = 
? LIMIT 1);
+  LET existing_email = (SELECT user_id FROM email_index WHERE email = ? LIMIT 
1);
+
+  IF existing_username IS NULL AND existing_email IS NULL THEN
+    INSERT INTO users (user_id, username, email, display_name, created_at)
+    VALUES (?, ?, ?, ?, toTimestamp(now()));
+
+    INSERT INTO username_index (username, user_id) VALUES (?, ?);
+    INSERT INTO email_index (email, user_id) VALUES (?, ?);
+  END IF
+COMMIT TRANSACTION
+----
+
+=== Changing a Username
+
+Renaming requires atomically: (1) verifying the new name is available, (2) 
deleting the old index entry, and (3) inserting the new one. Without 
transactions, a crash between steps could leave orphaned index entries or allow 
duplicates.
+
+[source,cql]
+----
+-- Application provides: user_id, old_username, new_username
+BEGIN TRANSACTION
+  LET current_user = (SELECT username FROM users WHERE user_id = ?);
+  LET new_name_owner = (SELECT user_id FROM username_index WHERE username = ? 
LIMIT 1);
+
+  -- Verify: user exists, old username matches, new username is available
+  IF current_user IS NOT NULL
+     AND current_user.username = ?  -- old_username parameter
+     AND new_name_owner IS NULL THEN
+
+    -- Update the user record
+    UPDATE users SET username = ? WHERE user_id = ?;  -- new_username
+
+    -- Atomically swap index entries
+    DELETE FROM username_index WHERE username = ?;  -- old_username
+    INSERT INTO username_index (username, user_id) VALUES (?, ?);  -- 
new_username, user_id
+  END IF
+COMMIT TRANSACTION
+----
+
+This pattern ensures the index tables are always consistent with the `users` 
table, even under concurrent modifications or partial failures.
+
+== Pattern: Distributed State Machine
+
+Many business objects follow a lifecycle with strict state transitions. An 
order might be `PENDING` -> `PAID` -> `SHIPPED` -> `DELIVERED`. Without 
transactions, concurrent operations (e.g., "cancel" and "ship") could both 
succeed, leaving the system in an invalid state.
+
+=== The Challenge
+
+* Ensure state transitions follow valid paths
+* Prevent race conditions between competing operations
+* Maintain audit trail of transitions
+
+NOTE: The `IN` and `OR` operators are not currently supported in transaction 
`IF` conditions. To check multiple valid states, use **numeric status codes** 
with range comparisons (e.g., `status_code < 30` to mean "any state before 
SHIPPED").
+
+=== Schema
+
+[source,cql]
+----
+CREATE TABLE orders (
+  order_id uuid PRIMARY KEY,
+  customer_id uuid,
+  status_code int,  -- 10=PENDING, 20=PAID, 30=SHIPPED, 40=DELIVERED, 
99=CANCELLED
+  status_name text,
+  total_amount decimal,
+  updated_at timestamp
+) WITH transactional_mode = 'full';
+
+CREATE TABLE order_status_history (
+  order_id uuid,
+  transition_time timestamp,
+  from_status int,
+  to_status int,
+  actor_id uuid,
+  PRIMARY KEY (order_id, transition_time)
+) WITH transactional_mode = 'full';
+----
+
+=== Valid Transition: PAID -> SHIPPED
+
+[source,cql]
+----
+-- Application provides: order_id, actor_id
+BEGIN TRANSACTION
+  LET current_order = (SELECT status_code FROM orders WHERE order_id = ?);
+
+  -- Only allow transition from PAID (20) state
+  IF current_order IS NOT NULL AND current_order.status_code = 20 THEN
+    UPDATE orders
+    SET status_code = 30, status_name = 'SHIPPED', updated_at = 
toTimestamp(now())
+    WHERE order_id = ?;
+
+    INSERT INTO order_status_history (order_id, transition_time, from_status, 
to_status, actor_id)
+    VALUES (?, toTimestamp(now()), 20, 30, ?);
+  END IF
+COMMIT TRANSACTION
+----
+
+=== Handling Cancellation (Competing Transition)
+
+Cancellation is only valid from certain states. If a "ship" and "cancel" 
operation race, exactly one will succeed.
+
+[source,cql]
+----
+-- Application provides: order_id, actor_id
+BEGIN TRANSACTION
+  LET current_order = (SELECT status_code, customer_id, total_amount FROM 
orders WHERE order_id = ?);
+
+  -- Cancellation allowed only from PENDING (10) or PAID (20) states.
+  -- Use range comparison (status_code <= 20) since IN/OR are not supported.
+  IF current_order IS NOT NULL
+     AND current_order.status_code <= 20 THEN
+
+    UPDATE orders
+    SET status_code = 99, status_name = 'CANCELLED', updated_at = 
toTimestamp(now())
+    WHERE order_id = ?;
+
+    INSERT INTO order_status_history (order_id, transition_time, from_status, 
to_status, actor_id)
+    VALUES (?, toTimestamp(now()), current_order.status_code, 99, ?);
+  END IF
+COMMIT TRANSACTION
+----
+
+If a concurrent "ship" operation already moved the order to `SHIPPED` (30), 
the `status_code <= 20` condition fails and the cancellation is rejected. The 
application can check the result and inform the user.
+
+== Pattern: Synchronous Denormalization
+
+Cassandra best practices often involve denormalizing data for read 
performance. Keeping summary tables in sync with detail tables has 
traditionally been eventually consistent. Accord enables **synchronous 
denormalization** where aggregates are always accurate.
+
+=== The Challenge
+
+You have a `posts` table and want to maintain accurate counts per author 
without using counters (which aren't supported in transactions) and without 
eventual consistency lag.
+
+=== Schema
+
+[source,cql]
+----
+CREATE TABLE posts (
+  post_id uuid PRIMARY KEY,
+  author_id uuid,
+  title text,
+  content text,
+  status text,  -- draft, published, archived
+  created_at timestamp
+) WITH transactional_mode = 'full';
+
+CREATE TABLE author_stats (
+  author_id uuid PRIMARY KEY,
+  draft_count bigint,
+  published_count bigint,
+  archived_count bigint,
+  last_post_at timestamp
+) WITH transactional_mode = 'full';
+----
+
+=== Publishing a New Post (Increment Count)
+
+This example demonstrates **idempotent creation**: if the application retries 
a failed publish request, the post won't be duplicated and the counter won't be 
incremented twice.
+
+[source,cql]
+----
+-- Application provides: post_id, author_id, title, content
+BEGIN TRANSACTION
+  -- Check if this post already exists (idempotency guard)
+  LET existing_post = (SELECT status FROM posts WHERE post_id = ?);
+
+  IF existing_post IS NULL THEN
+    -- Create the post
+    INSERT INTO posts (post_id, author_id, title, content, status, created_at)
+    VALUES (?, ?, ?, ?, 'published', toTimestamp(now()));
+
+    -- Synchronously update the count exactly once
+    UPDATE author_stats
+    SET published_count = published_count + 1,
+        last_post_at = toTimestamp(now())
+    WHERE author_id = ?;
+  END IF
+COMMIT TRANSACTION
+----
+
+=== Changing Post Status (Transfer Between Counts)
+
+When a post moves from `published` to `archived`, both counts must update 
atomically.
+
+[source,cql]
+----
+-- Application provides: post_id, author_id
+BEGIN TRANSACTION
+  LET current_post = (SELECT status, author_id FROM posts WHERE post_id = ?);
+
+  IF current_post IS NOT NULL AND current_post.status = 'published' THEN
+    -- Update post status
+    UPDATE posts SET status = 'archived' WHERE post_id = ?;
+
+    -- Atomically transfer between counts
+    UPDATE author_stats
+    SET published_count = published_count - 1,
+        archived_count = archived_count + 1
+    WHERE author_id = ?;  -- author_id passed as parameter
+  END IF
+COMMIT TRANSACTION
+----
+
+=== Deleting a Post (Decrement Count)
+
+[source,cql]
+----
+-- Application provides: post_id, author_id, current_status
+-- Application must query the post first to get status and author_id
+BEGIN TRANSACTION
+  LET current_post = (SELECT status FROM posts WHERE post_id = ?);
+
+  IF current_post IS NOT NULL AND current_post.status = ? THEN  -- 
current_status parameter
+    -- Delete the post
+    DELETE FROM posts WHERE post_id = ?;
+
+    -- Decrement the appropriate counter based on status
+    -- Application passes which counter to decrement based on current_status
+    UPDATE author_stats
+    SET published_count = published_count - ?  -- pass 1 if published, 0 
otherwise
+    WHERE author_id = ?;
+  END IF
+COMMIT TRANSACTION
+----
+
+== Pattern: Multi-Entity Referential Integrity
+
+Relational databases use foreign keys to prevent orphaned records. In 
Cassandra, you can achieve similar guarantees with transactions.
+
+=== The Challenge
+
+* A `Task` must belong to an existing `Project`
+* When a `Project` is deleted, handle its `Tasks` appropriately
+* Prevent creating tasks for non-existent projects
+
+=== Schema
+
+[source,cql]
+----
+CREATE TABLE projects (
+  project_id uuid PRIMARY KEY,
+  name text,
+  owner_id uuid,
+  status text,  -- active, completed, deleted
+  task_count bigint,
+  created_at timestamp
+) WITH transactional_mode = 'full';
+
+CREATE TABLE tasks (
+  task_id uuid PRIMARY KEY,
+  project_id uuid,
+  title text,
+  status text,  -- open, in_progress, done
+  assignee_id uuid,
+  created_at timestamp
+) WITH transactional_mode = 'full';
+
+-- Index for finding tasks by project (for cleanup operations)
+CREATE TABLE tasks_by_project (
+  project_id uuid,
+  task_id uuid,
+  title text,
+  status text,
+  PRIMARY KEY (project_id, task_id)
+) WITH transactional_mode = 'full';
+----
+
+=== Creating a Task (Enforce Parent Exists)
+
+[source,cql]
+----
+-- Application provides: task_id, project_id, title, assignee_id
+BEGIN TRANSACTION
+  LET project = (SELECT status FROM projects WHERE project_id = ?);
+
+  -- Only create task if project exists and is active
+  IF project IS NOT NULL AND project.status = 'active' THEN
+    INSERT INTO tasks (task_id, project_id, title, status, assignee_id, 
created_at)
+    VALUES (?, ?, ?, 'open', ?, toTimestamp(now()));
+
+    INSERT INTO tasks_by_project (project_id, task_id, title, status)
+    VALUES (?, ?, ?, 'open');
+
+    UPDATE projects SET task_count = task_count + 1 WHERE project_id = ?;
+  END IF
+COMMIT TRANSACTION
+----
+
+=== Soft-Deleting a Project
+
+Rather than cascading deletes (which would require iterating over all tasks), 
mark the project as deleted. Tasks can be cleaned up asynchronously or remain 
for audit purposes.
+
+[source,cql]
+----
+-- Application provides: project_id
+BEGIN TRANSACTION
+  LET project = (SELECT status FROM projects WHERE project_id = ?);
+
+  IF project IS NOT NULL AND project.status = 'active' THEN
+    UPDATE projects
+    SET status = 'deleted'
+    WHERE project_id = ?;
+  END IF
+COMMIT TRANSACTION
+----
+
+Future task operations will fail the `project.status = 'active'` check, 
preventing modifications to a deleted project's tasks.
+
+=== Moving a Task Between Projects
+
+This pattern ensures both projects exist and are active, and maintains 
accurate task counts.
+
+[source,cql]
+----
+-- Application provides: task_id, old_project_id, new_project_id, task_title, 
task_status
+BEGIN TRANSACTION
+  LET task = (SELECT project_id, title, status FROM tasks WHERE task_id = ?);
+  LET old_project = (SELECT status FROM projects WHERE project_id = ?);  -- 
old_project_id
+  LET new_project = (SELECT status FROM projects WHERE project_id = ?);  -- 
new_project_id
+
+  IF task IS NOT NULL
+     AND task.project_id = ?  -- verify task belongs to old_project_id
+     AND old_project.status = 'active'
+     AND new_project.status = 'active' THEN
+
+    -- Update task's project reference
+    UPDATE tasks SET project_id = ? WHERE task_id = ?;  -- new_project_id
+
+    -- Update denormalized index: remove from old, add to new
+    DELETE FROM tasks_by_project WHERE project_id = ? AND task_id = ?;  -- 
old_project_id
+    INSERT INTO tasks_by_project (project_id, task_id, title, status)
+    VALUES (?, ?, ?, ?);  -- new_project_id, task_id, task_title, task_status
+
+    -- Update counts on both projects
+    UPDATE projects SET task_count = task_count - 1 WHERE project_id = ?;  -- 
old_project_id
+    UPDATE projects SET task_count = task_count + 1 WHERE project_id = ?;  -- 
new_project_id
+  END IF
+COMMIT TRANSACTION
+----
+
+== Summary
+
+These patterns demonstrate how Accord transactions solve problems that were 
previously difficult in Cassandra:
+
+|===
+| Pattern | Problem Solved | Key Technique
+
+| Synchronous Unique Constraints
+| Non-primary-key uniqueness
+| Lookup index tables with atomic swap
+
+| Distributed State Machine
+| Race conditions in status changes
+| IF condition guards valid transitions
+
+| Synchronous Denormalization
+| Stale aggregate counts, duplicate increments on retry
+| Idempotent creation with atomic detail + summary updates
+
+| Multi-Entity Referential Integrity
+| Orphaned child records
+| Parent existence check before child operations
+|===
+
+All patterns share common principles:
+
+* **Read what you need**: Use LET to capture current state
+* **Guard with IF**: Validate preconditions before modifications
+* **Atomic updates**: All changes succeed or fail together
+* **Pass computed values as parameters**: Row-reference arithmetic in 
SET/VALUES is not supported
diff --git 
a/doc/modules/cassandra/pages/developing/cql/transactions-migration.adoc 
b/doc/modules/cassandra/pages/developing/cql/transactions-migration.adoc
new file mode 100644
index 0000000000..6b237a4971
--- /dev/null
+++ b/doc/modules/cassandra/pages/developing/cql/transactions-migration.adoc
@@ -0,0 +1,289 @@
+= Migrating to Accord Transactions
+:page-nav-title: Transaction Migration
+
+This page covers migrating existing CQL patterns to Accord transactions, 
including LWT (Paxos), BATCH statements, and multi-statement workflows.
+
+For transaction syntax and usage, see 
xref:developing/cql/transactions.adoc[Accord Transactions]. For design 
patterns, see xref:developing/cql/transactions-examples.adoc[Transaction 
Patterns].
+
+== From Light Weight Transactions (LWT)
+
+=== IF EXISTS
+
+**Before (LWT):**
+[source,cql]
+----
+UPDATE accounts SET balance = balance - 100
+WHERE user_id = 12345
+IF EXISTS;
+----
+
+**After (Accord):**
+[source,cql]
+----
+BEGIN TRANSACTION
+  LET account_data = (SELECT balance FROM accounts WHERE user_id = 12345);
+
+  IF account_data IS NOT NULL THEN
+    UPDATE accounts SET balance = balance - 100 WHERE user_id = 12345;
+  END IF
+COMMIT TRANSACTION
+----
+
+=== IF NOT EXISTS
+
+**Before (LWT):**
+[source,cql]
+----
+INSERT INTO users (id, email, status)
+VALUES (?, ?, 'active')
+IF NOT EXISTS;
+----
+
+**After (Accord):**
+[source,cql]
+----
+BEGIN TRANSACTION
+  LET existing_user = (SELECT id FROM users WHERE id = ? LIMIT 1);
+
+  IF existing_user IS NULL THEN
+    INSERT INTO users (id, email, status) VALUES (?, ?, 'active');
+  END IF
+COMMIT TRANSACTION
+----
+
+=== IF column = null (NULL Comparison)
+
+[IMPORTANT]
+====
+LWT and Accord handle `IF column = null` differently. See <<Null Handling>> 
for complete details.
+====
+
+LWT treats `column = null` as "column is null," but Accord follows SQL 
semantics where `column = null` is always FALSE.
+
+**Before (LWT):**
+[source,cql]
+----
+-- LWT: Matches when email is null/missing
+UPDATE users SET email = '[email protected]'
+WHERE id = ?
+IF email = null;
+----
+
+**After (Accord):**
+[source,cql]
+----
+BEGIN TRANSACTION
+  LET user_data = (SELECT email FROM users WHERE id = ?);
+
+  -- Must use IS NULL, not = null
+  IF user_data IS NOT NULL AND user_data.email IS NULL THEN
+    UPDATE users SET email = '[email protected]' WHERE id = ?;
+  END IF
+COMMIT TRANSACTION
+----
+
+=== Complex CAS with Multiple Operations
+
+**Before (Multiple LWT operations with race condition risk):**
+[source,cql]
+----
+UPDATE accounts SET balance = balance - 50
+WHERE user_id = 12345 AND balance >= 50
+IF balance >= 50;
+
+-- Separate operation (not atomic with above)
+INSERT INTO transaction_log (id, user_id, amount, timestamp)
+VALUES (?, 12345, -50, toTimestamp(now()));
+----
+
+**After (Single atomic transaction):**
+[source,cql]
+----
+BEGIN TRANSACTION
+  LET account = (SELECT balance FROM accounts WHERE user_id = 12345);
+
+  IF account.balance >= 50 THEN
+    UPDATE accounts SET balance = balance - 50 WHERE user_id = 12345;
+
+    INSERT INTO transaction_log (id, user_id, amount, timestamp)
+    VALUES (?, 12345, -50, toTimestamp(now()));
+  END IF
+COMMIT TRANSACTION
+----
+
+== From BATCH Statements
+
+Single-partition BATCH provides atomicity (all mutations apply or none do). 
Multi-partition (LOGGED) BATCH does not guarantee atomicity — it uses a 
batchlog for best-effort delivery. CAS can be used with BATCH for conditional 
checks, but only on a single partition, and if the condition fails the entire 
batch is rejected.
+
+Accord transactions provide atomicity across multiple partitions with richer 
conditional logic:
+
+**Before (Multi-partition BATCH - no atomicity guarantee, no condition 
checking):**
+[source,cql]
+----
+BEGIN BATCH
+  UPDATE users SET balance = 400 WHERE id = ?;  -- Application pre-computes 
new balance
+  INSERT INTO orders (id, user_id, amount) VALUES (?, ?, 100);
+APPLY BATCH;
+-- Problem: No atomicity across partitions, can't check if balance is 
sufficient!
+-- Application had to read balance separately, race condition possible.
+----
+
+**After (Transaction with condition and cross-partition atomicity):**
+[source,cql]
+----
+BEGIN TRANSACTION
+  LET user_data = (SELECT balance FROM users WHERE id = ?);
+
+  IF user_data.balance >= 100 THEN
+    UPDATE users SET balance = balance - 100 WHERE id = ?;
+    INSERT INTO orders (id, user_id, amount) VALUES (?, ?, 100);
+  END IF
+COMMIT TRANSACTION
+----
+
+For single-partition operations that already use CAS BATCH, transactions 
provide equivalent guarantees with more flexible conditions:
+
+**Before (CAS BATCH - single partition, single table only; condition fails = 
nothing applies):**
+[source,cql]
+----
+BEGIN BATCH
+  UPDATE users SET balance = balance - 100 WHERE id = ?
+  IF balance >= 100;
+  UPDATE users SET last_action = 'debit' WHERE id = ?;
+APPLY BATCH;
+----
+
+**After (Transaction - same guarantees, works across partitions and tables):**
+[source,cql]
+----
+BEGIN TRANSACTION
+  LET user_data = (SELECT balance FROM users WHERE id = ?);
+
+  IF user_data.balance >= 100 THEN
+    UPDATE users SET balance = balance - 100 WHERE id = ?;
+    INSERT INTO user_history (user_id, action) VALUES (?, 'debit');
+  END IF
+COMMIT TRANSACTION
+----
+
+== Null Handling
+
+[IMPORTANT]
+====
+Accord transactions follow SQL standard null semantics, which differ from LWT 
(Paxos) behavior. This is a critical difference when migrating from LWT to 
Accord.
+====
+
+=== LWT vs Accord: NULL Comparison Behavior
+
+|===
+| Condition | Column Value | LWT (Paxos) Result | Accord Result
+
+| `IF column = null`
+| Column is NULL/missing
+| **TRUE** (matches)
+| **FALSE** (never matches)
+
+| `IF column = null`
+| Column has a value
+| FALSE
+| FALSE
+
+| `IF column IS NULL`
+| Column is NULL/missing
+| TRUE
+| TRUE
+
+| `IF column != null`
+| Column is NULL/missing
+| FALSE
+| **FALSE** (null comparisons always false)
+
+| `IF column != null`
+| Column has a value
+| TRUE
+| TRUE
+|===
+
+=== Why the Difference?
+
+**LWT (Paxos) behavior:** LWT treats `column = null` as a special case meaning 
"column is null or missing." This is a Cassandra-specific convenience that 
deviates from SQL standards.
+
+**Accord behavior:** Accord follows SQL standard semantics where `NULL` 
represents an unknown value. Comparing anything to `NULL` using `=`, `!=`, `<`, 
`>`, etc. always returns `FALSE` (or more precisely, `UNKNOWN`, which is 
treated as `FALSE` in conditionals). This is because you cannot know if an 
unknown value equals another value.
+
+=== Migration from LWT
+
+If your LWT code uses `IF column = null`, you must update it when migrating to 
Accord transaction syntax.
+
+NOTE: This does not affect LWT statements executed on Accord-enabled tables. 
Those statements retain their original LWT null semantics.
+
+**Before (LWT):**
+[source,cql]
+----
+-- LWT: This returns TRUE when balance is null/missing
+UPDATE users SET balance = 100 WHERE id = ? IF balance = null;
+----
+
+**After (Accord):**
+[source,cql]
+----
+BEGIN TRANSACTION
+  LET user_data = (SELECT balance FROM users WHERE id = ?);
+
+  -- Use IS NULL for null checks
+  IF user_data IS NOT NULL AND user_data.balance IS NULL THEN
+    UPDATE users SET balance = 100 WHERE id = ?;
+  END IF
+COMMIT TRANSACTION
+----
+
+TIP: Null checks are recursive. If `user_data` is null (row doesn't exist), 
then `user_data.balance` is also null. So `IF user_data.balance IS NULL` will 
be true both when the row doesn't exist AND when the row exists but the column 
is null. Use `user_data IS NOT NULL` first only if you need to distinguish 
between these cases.
+
+=== Common Null Patterns
+
+**Check if a column is null (row missing OR column unset):**
+[source,cql]
+----
+IF user_data.balance IS NULL THEN
+  -- Either row doesn't exist, OR row exists but balance is null
+END IF
+----
+
+**Check if a row exists (regardless of column value):**
+[source,cql]
+----
+IF user_data IS NOT NULL THEN
+  -- Row exists (balance could be null or have a value)
+END IF
+----
+
+**Check if a column is null but row exists:**
+[source,cql]
+----
+IF user_data IS NOT NULL AND user_data.balance IS NULL THEN
+  -- Row exists AND balance is null (distinguishes from missing row)
+END IF
+----
+
+**Check if a column has a specific value:**
+[source,cql]
+----
+IF user_data IS NOT NULL AND user_data.balance = 100 THEN
+  -- Row exists AND balance equals 100
+END IF
+----
+
+**Check if a row does not exist:**
+[source,cql]
+----
+IF user_data IS NULL THEN
+  -- Row does not exist
+END IF
+----
+
+=== Null Propagation Rules
+
+* Any comparison with null using `=`, `!=`, `<`, `>`, `<=`, `>=` returns 
**FALSE**
+* Only `IS NULL` and `IS NOT NULL` can meaningfully test for null
+* Arithmetic operations with null return null
+* Null columns in row references return null (not errors)
+* Accessing a field on a null row reference (e.g., `missing_row.column`) 
returns null
diff --git a/doc/modules/cassandra/pages/developing/cql/transactions.adoc 
b/doc/modules/cassandra/pages/developing/cql/transactions.adoc
new file mode 100644
index 0000000000..8e30fbdd68
--- /dev/null
+++ b/doc/modules/cassandra/pages/developing/cql/transactions.adoc
@@ -0,0 +1,500 @@
+= Accord Transactions
+:page-nav-title: Transactions
+
+Accord provides strong consistency and ACID guarantees for Cassandra 
operations.
+When enabled on a table, **all CQL operations automatically execute through 
Accord** - no code changes required.
+For complex multi-step operations, explicit transaction syntax (`BEGIN 
TRANSACTION ... COMMIT TRANSACTION`) allows you to read, apply conditions, and 
write atomically across multiple partitions and tables.
+
+== Overview
+
+=== Key Benefits
+
+* **Automatic Strong Consistency**: Normal CQL reads and writes become 
linearizable when `transactional_mode='full'`
+* **ACID Guarantees**: Atomicity, Consistency, Isolation, and Durability 
across multiple operations
+* **Multi-Partition Consistency**: Coordinate updates across different 
partition keys
+* **Multi-Table Support**: Update multiple tables atomically within a single 
transaction
+* **Complex Business Logic**: Support for conditional operations with multiple 
steps
+
+=== When to Use Explicit Transactions
+
+While normal CQL operations are automatically transactional with 
`transactional_mode='full'`, use explicit `BEGIN TRANSACTION ... COMMIT 
TRANSACTION` syntax when you need:
+
+* **Read-Modify-Write Patterns**: Check a condition before making changes
+* **Complex Business Logic**: Multi-step operations that must be atomic
+* **Cross-Partition Operations**: Updates that span multiple partition keys
+* **Multi-Table Atomicity**: Ensure related changes across tables succeed or 
fail together
+
+=== Safety & Consistency
+
+Accord ensures data integrity through:
+
+* **Strict Serializability**: Transactions execute as if in a single, total 
order that respects real-time ordering
+* **Conflict Detection**: Automatic handling of concurrent access to the same 
data
+* **Atomic Commitment**: All changes commit together or none at all
+* **Durable Writes**: Committed transactions survive node failures
+
+== Getting Started
+
+=== Prerequisites
+
+Before using transactions:
+
+. **Enable Accord globally** in `cassandra.yaml`:
++
+[source,yaml]
+----
+accord:
+  enabled: true
+----
+
+. **Enable transactional mode on tables**:
++
+[source,cql]
+----
+CREATE TABLE users (
+  id UUID PRIMARY KEY,
+  email text,
+  balance decimal
+) WITH transactional_mode = 'full';
+----
+
+See <<transactional-modes>> for detailed mode explanations.
+
+=== Normal CQL Operations Are Transactional
+
+When a table has `transactional_mode='full'`, your existing CQL statements are 
automatically executed through Accord. **You do not need to rewrite your 
application code.**
+
+[source,cql]
+----
+-- These normal CQL operations are automatically transactional:
+
+-- Reads are executed through Accord
+SELECT id, email, balance FROM users WHERE id = 
123e4567-e89b-12d3-a456-426614174000;
+
+-- Writes are executed through Accord
+INSERT INTO users (id, email, balance) VALUES 
(123e4567-e89b-12d3-a456-426614174000, '[email protected]', 100.00);
+
+UPDATE users SET balance = 50.00 WHERE id = 
123e4567-e89b-12d3-a456-426614174000;
+
+DELETE FROM users WHERE id = 123e4567-e89b-12d3-a456-426614174000;
+----
+
+Each statement executes as an individual Accord transaction, providing 
linearizability, consistency, and durability. Migrating to Accord can be as 
simple as enabling `transactional_mode='full'` on your tables. See 
<<transactional-modes>> for all available modes.
+
+[[transactional-modes]]
+== Transactional Modes
+
+Tables must be configured with one of these transactional modes:
+
+=== transactional_mode='off' (Default)
+
+* No Accord transaction support
+* Uses traditional Cassandra behavior
+* Lightweight transactions use Paxos protocol
+* Cannot participate in Accord transactions
+
+**When to Use:**
+
+* Tables not ready for transaction migration
+* High-throughput tables where transaction overhead isn't justified
+* Tables with existing Paxos-based logic that works well
+
+[source,cql]
+----
+CREATE TABLE tbl (...)
+WITH transactional_mode = 'off';
+----
+
+[[transactional_mode_mixed_reads]]
+=== transactional_mode='mixed_reads'
+
+NOTE: Most users should migrate directly from `off` to `full`. This mode is 
only needed for specific scenarios where you must mix transactional and 
non-transactional access on the same table during a transition period.
+
+* Non-SERIAL writes are routed through Accord but committed at the supplied 
consistency level
+* Allows non-SERIAL reads to see transactionally written data
+* Blocking read repair is routed through Accord to avoid exposing uncommitted 
data
+
+**When to Use:**
+
+* Applications that **require** mixed transactional and non-transactional 
access on the same table simultaneously
+
+**Trade-offs:**
+
+* **Slower transactions**: Accord cannot perform single-replica read 
optimization because it must ensure data is readable at the non-SERIAL 
consistency level
+* **Read repair overhead**: Accord must repair stale replicas during reads
+* **Not recommended for most users**: Direct migration from `off` to `full` is 
simpler and provides better performance
+
+[source,cql]
+----
+ALTER TABLE tbl WITH transactional_mode = 'mixed_reads';
+----
+
+=== transactional_mode='full' (Recommended)
+
+* All reads and writes must occur through Accord transactions
+* Enables single-replica reads since non-transactional readers don't exist
+* Best transaction performance
+
+**When to Use:**
+
+* New tables that will use transactions
+* Tables migrating from `off` mode (recommended for most users)
+* Maximum transaction performance required
+
+**Why this mode is faster:**
+
+* **Single-replica reads**: Accord can read from a single replica and still 
provide correct results
+* **No read repair overhead**: Accord doesn't need to repair data for 
non-transactional readers
+
+[source,cql]
+----
+CREATE TABLE tbl (...)
+WITH transactional_mode = 'full';
+----
+
+== Transaction Syntax
+
+=== Basic Structure
+
+All transactions follow this pattern:
+
+[source,cql]
+----
+BEGIN TRANSACTION
+  [LET assignments]
+  [SELECT statements]
+  [IF conditions THEN]
+    [modification statements]
+  [END IF]
+COMMIT TRANSACTION
+----
+
+=== LET Assignments
+
+LET statements read data and bind it to variables for use later in the 
transaction:
+
+[source,cql]
+----
+BEGIN TRANSACTION
+  LET user_data = (SELECT id, balance FROM users WHERE id = ?);
+  LET account_data = (SELECT account_type FROM accounts WHERE user_id = ?);
+
+  IF user_data.balance > 100 AND account_data.account_type = 'premium' THEN
+    UPDATE users SET balance = balance - 50 WHERE id = ?;
+  END IF
+COMMIT TRANSACTION
+----
+
+**LET Requirements:**
+
+* Each LET must specify a unique variable name
+* SELECT must return exactly one row (use `LIMIT 1` if needed)
+* All partition key columns must be specified with equality operators
+* Cannot use `ORDER BY`, `GROUP BY`, or aggregation functions
+* Cannot use range queries or multi-partition operations
+
+**Valid LET Examples:**
+[source,cql]
+----
+LET user_data = (SELECT balance, status FROM users WHERE id = ?);
+LET order_info = (SELECT total, shipping_fee FROM orders WHERE id = ? LIMIT 1);
+LET static_config = (SELECT max_attempts FROM config WHERE setting_type = 
'retry');
+----
+
+**Invalid LET Examples:**
+[source,cql]
+----
+-- Missing LIMIT 1 with potential multiple results
+LET users = (SELECT * FROM users WHERE status = 'active');
+
+-- Range query not allowed
+LET recent = (SELECT * FROM events WHERE id > ? AND id < ?);
+
+-- Aggregation not supported
+LET total = (SELECT COUNT(*) FROM orders WHERE user_id = ?);
+----
+
+=== Row References
+
+Access fields from LET variables using dot notation:
+
+[source,cql]
+----
+BEGIN TRANSACTION
+  LET current_user = (SELECT balance, status FROM users WHERE id = ?);
+
+  -- Access fields with dot notation
+  SELECT current_user.balance, current_user.status;
+COMMIT TRANSACTION
+----
+
+[[row-reference-limitations]]
+**Row Reference Limitations:**
+
+Row reference arithmetic in SET clauses and row references in VALUES are not 
currently supported. Pass values as parameters instead.
+
+[source,cql]
+----
+-- Application code computes values, passes as parameters
+BEGIN TRANSACTION
+  LET user_data = (SELECT balance FROM users WHERE id = ?);
+
+  IF user_data.balance >= ? THEN  -- Pass order_total as parameter
+    UPDATE users SET balance = balance - ? WHERE id = ?;  -- Pass order_total
+  END IF
+COMMIT TRANSACTION
+----
+
+=== Returning Results
+
+Return data from transactions using SELECT statements that appear before any 
modifications. You can use either row reference values or a normal 
single-partition SELECT.
+
+NOTE: If no SELECT statement is present, the transaction returns an empty 
result set regardless of whether the IF condition was satisfied. Use a SELECT 
before modifications to observe transaction state.
+
+==== Using Row References
+
+[source,cql]
+----
+BEGIN TRANSACTION
+  LET user_data = (SELECT balance, status FROM users WHERE id = ?);
+
+  -- Return row reference values (must come before UPDATE)
+  SELECT user_data.balance, user_data.status;
+
+  UPDATE users SET balance = balance - 50 WHERE id = ?;
+COMMIT TRANSACTION
+----
+
+==== Using a Normal SELECT
+
+[source,cql]
+----
+BEGIN TRANSACTION
+  LET user_data = (SELECT balance FROM users WHERE id = ?);
+
+  -- Return data directly from table (must come before UPDATE)
+  SELECT balance, status, email FROM users WHERE id = ?;
+
+  IF user_data.balance >= 50 THEN
+    UPDATE users SET balance = balance - 50 WHERE id = ?;
+  END IF
+COMMIT TRANSACTION
+----
+
+**Important:** SELECT statements must appear before any UPDATE, INSERT, or 
DELETE statements. To retrieve updated values, query outside the transaction 
after it commits.
+
+=== Conditional Logic
+
+Add conditional logic to transactions with IF blocks:
+
+[source,cql]
+----
+BEGIN TRANSACTION
+  LET sender = (SELECT balance FROM accounts WHERE user_id = ?);
+
+  IF sender.balance >= 100 THEN
+    UPDATE accounts SET balance = balance - 100 WHERE user_id = ?;
+    UPDATE accounts SET balance = balance + 100 WHERE user_id = ?;
+  END IF
+COMMIT TRANSACTION
+----
+
+**Supported Operators:**
+
+* Comparison: `=`, `<`, `<=`, `>`, `>=`, `!=`
+* Null checks: `IS NULL`, `IS NOT NULL`
+* Logical: `AND` only
+
+**Complex Condition Examples:**
+[source,cql]
+----
+-- Multiple conditions with AND
+IF user_data.balance >= 100 AND user_data.status = 'active'
+   AND user_data.credit_limit > 150 THEN
+  -- statements
+END IF
+
+-- Null checking
+IF account_info IS NOT NULL AND account_info.balance > 0 THEN
+  -- statements
+END IF
+----
+
+**Important Notes:**
+
+* Null handling is strict (any null comparison returns false)
+* All modification statements must be inside the IF block when using conditions
+
+== Restrictions
+
+The following features cannot be used within `BEGIN TRANSACTION ... COMMIT 
TRANSACTION` blocks:
+
+* **Counter tables** and counter operations
+* **Aggregation functions** (COUNT, SUM, AVG, etc.)
+* **ORDER BY** and **GROUP BY** clauses
+* **Custom TTL** and **timestamp** specifications (transaction manages 
timestamps)
+* **Range DELETE** operations (must specify complete primary key)
+* **Non-equality partition key** restrictions in LET/SELECT
+* **Individual statement conditions** (use transaction-level IF blocks instead)
+
+=== Schema Requirements
+
+* Accord must be enabled in `cassandra.yaml`
+* Tables must have a `transactional_mode` other than `'off'`
+* Tables being dropped cannot participate in transactions
+
+== Syntax Reference
+
+=== Complete Grammar
+
+[source,cql]
+----
+BEGIN TRANSACTION
+  [LET letStatements]
+  [SELECT selectStatement | SELECT rowDataReferences]
+  [IF conditionalExpression THEN]
+    [modificationStatements]
+  [END IF]
+COMMIT TRANSACTION
+----
+
+=== Modification Statements
+
+**INSERT:**
+[source,cql]
+----
+INSERT INTO table (columns) VALUES (values);
+----
+
+**UPDATE:**
+[source,cql]
+----
+UPDATE table SET assignments WHERE conditions;
+----
+
+**DELETE:**
+[source,cql]
+----
+DELETE [columns] FROM table WHERE conditions;
+----
+
+== Multi-Partition Coordination
+
+Cross-partition transactions add coordination overhead:
+
+[source,cql]
+----
+BEGIN TRANSACTION
+  LET user1 = (SELECT balance FROM accounts WHERE user_id = ?);
+  LET user2 = (SELECT balance FROM accounts WHERE user_id = ?);
+
+  IF user1.balance >= 100 THEN
+    UPDATE accounts SET balance = balance - 100 WHERE user_id = ?;
+    UPDATE accounts SET balance = balance + 100 WHERE user_id = ?;
+  END IF
+COMMIT TRANSACTION
+----
+
+**Performance Impact:**
+
+* Same-partition transactions: Low overhead
+* Cross-partition transactions: Moderate overhead
+* More partitions = higher coordination cost
+
+**Optimization:** Minimize cross-partition operations; consider data model 
changes to reduce cross-partition transactions.
+
+== Automatic Read Generation
+
+When UPDATE statements reference current column values, Accord automatically 
reads the current state:
+
+[source,cql]
+----
+BEGIN TRANSACTION
+  -- This UPDATE automatically reads current balance
+  UPDATE users SET balance = balance + ? WHERE id = ?;
+COMMIT TRANSACTION
+----
+
+**Triggers:**
+
+* SET clauses that reference current column values
+
+== Troubleshooting
+
+=== Configuration Errors
+
+**Accord transactions are disabled**
+
+Enable in cassandra.yaml:
+[source,yaml]
+----
+accord:
+  enabled: true
+----
+
+**Accord transactions are disabled on table**
+
+Enable on table:
+[source,cql]
+----
+ALTER TABLE tbl WITH transactional_mode = 'full';
+----
+
+=== Syntax Errors
+
+**Duplicate variable names**
+
+Use unique variable names:
+[source,cql]
+----
+-- Bad
+LET user_data = (SELECT balance FROM accounts WHERE user_id = 1);
+LET user_data = (SELECT balance FROM accounts WHERE user_id = 2);
+
+-- Good
+LET sender_data = (SELECT balance FROM accounts WHERE user_id = 1);
+LET receiver_data = (SELECT balance FROM accounts WHERE user_id = 2);
+----
+
+=== Performance Issues
+
+**High latency investigation:**
+
+. Check transaction complexity (LET count, partition count)footnote:[Keep LET 
statements under 5 and partitions under 3 as a starting point. These are 
suggested guidelines, not empirically derived limits.]
+. Monitor cross-datacenter operations
+. Check for high contention on specific keys
+
+**Large result sets:**
+
+Select only needed columns:
+[source,cql]
+----
+-- Bad: Reading entire row
+LET user_history = (SELECT * FROM large_history_table WHERE user_id = ? LIMIT 
1);
+
+-- Good: Only read necessary data
+LET user_status = (SELECT status, last_login FROM users WHERE id = ?);
+----
+
+=== Counter Table Alternative
+
+Counters cannot be used in transactions. Use regular columns:
+
+[source,cql]
+----
+-- Bad: Counter in transaction
+CREATE TABLE page_views (
+  page_id uuid PRIMARY KEY,
+  views counter
+) WITH transactional_mode = 'full';
+
+-- Good: Regular column
+CREATE TABLE page_views (
+  page_id uuid PRIMARY KEY,
+  views bigint
+) WITH transactional_mode = 'full';
+
+BEGIN TRANSACTION
+  UPDATE page_views SET views = views + 1 WHERE page_id = ?;
+COMMIT TRANSACTION
+----


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to