Skip to main content

Transactions

Overview

Kahuna offers distributed transactions to enable safe, consistent, and atomic access to keys across the cluster. Transactions ensure that multiple reads and writes either all succeed together or none take effect—making them essential for maintaining data correctness in concurrent and distributed environments.

Kahuna supports snapshot isolation and serializable consistency through MVCC (Multi-Version Concurrency Control) and optimistic/pessimistic locking.

Why Transactions Matter?

In a distributed system, multiple clients might access and modify overlapping sets of keys. Without transactions, you risk:

  • Lost updates (e.g., one client overwriting another)
  • Read skew (inconsistent reads during writes)
  • Partial updates (only some keys being modified)

Kahuna’s transactional engine addresses these issues by:

  • Isolating reads and writes from each other using MVCC versions
  • Detecting write conflicts during commit
  • Optionally acquiring locks to serialize conflicting transactions

Core Concepts

ConceptDescription
Snapshot IsolationReaders see a consistent snapshot of the data as of the transaction start. Writers commit only if no conflicting writes occurred.
Serializable TransactionsPessimistic locks can be used to enforce total ordering of transactions.
MVCCEach key maintains multiple versions. Reads select the correct version based on transaction timestamp.
Transaction TimestampA Hybrid Logical Clock (HLC) timestamp assigned at transaction start, used for snapshot reads and version tracking.
Write SetThe keys a transaction intends to modify.
Read SetThe keys a transaction read; used for conflict detection.
LocksOptional. Acquired for pessimistic or serialized transactions. Locks have expiration to prevent being held forever.

Transaction API

All operations in a Kahuna Script are implicitly part of a transaction:

set "services/auth" "localhost:8081"
set "services/matchmaking" "localhost:8082"
set "services/inventory" "localhost:8083"

Basic Usage

begin 
let current_alice = get "balance:alice"
let current_bob = get "balance:alice"
if current_alice >= 50 then
set "balance:alice" current - 50
set "balance:bob" current + 50
end
commit
end

Example: Conditional Write Based on a Snapshot

begin
let config = get "settings/feature-x"
if config == "enabled" then
set "logs/feature-x" "used"
end
commit
end

Even if feature-x is disabled mid-transaction by another client, the snapshot ensures this transaction still sees the older version and behaves consistently.

Using get by prefix inside a Transaction

All keys in a given bucket prefix (e.g., services/) are guaranteed to be on the same partition. This enables prefix scans to be consistent and transactional:

begin
let services = get by prefix "services"
if not contains(services, "services/users") then
set "services/users" "localhost:8084"
end
commit
end

Learn more about buckets and keys distribution in the previous section

Transaction Lifecycle

  1. Begin: Kahuna assigns an HLC timestamp and starts tracking reads/writes. Locks are acquired in advance in pessimistic locking.
  2. Read/Write: All operations are buffered in-memory.
  3. Validation:
    • If optimistic: Check for conflicts with newer committed versions.
    • If pessimistic: Lock keys ahead of time to avoid conflicts.
  4. Commit:
    • If successful, MVCC versions are written and optionally replicated (persistent mode).
    • If failed, changes are discarded and client is notified.

Learn more about transaction lifecycle in the architecture section.

Durability Modes in Transactions

ModeBehavior
PersistentCommits are replicated and flushed to disk using Raft. Strong durability guarantees.
EphemeralFor lightweight, non-persistent use cases (e.g., caching, temporary locks). Faster but not durable.

Example:

begin 
eset "session:abc" "active" # session is stored in ephemeral durability (in-memory)
set "session:xyz" "active" # session is stored in persistent durability (disk)
commit
end

Learn more about durabilities in the dedicated section

Best Practices

  • Group keys by prefix when designing schemas to maximize transactional locality.
  • Use ephemeral keys for high-speed, non-critical paths.
  • Consider pessimistic locking for highly contended keys to avoid retries.
  • Monitor retries to detect hotspots in your workload.

Transaction Options

You can specify transaction options to fine-tune how the transaction is executed:

Timeout

Specifies the maximum duration (in milliseconds) that the transaction is allowed to run. If the transaction does not complete within this time, it will be automatically rolled back.

  • Kahuna Scripts are designed for short executions, so increasing this value significantly is not recommended.
  • Default value: 5000ms
begin (timeout=3000)
set `config1` 'some value 1'
set `config2` 'some value 2'
set `config3` 'some value 3'
commit
end

Locking

Defines the locking strategy used by the transaction.

  • pessimistic: Locks keys upfront to ensure full consistency.

  • optimistic: Locks only on write, with version validation during commit.

  • Default value: pessimistic

begin (locking="optimistic")
set `config1` 'some value 1'
set `config2` 'some value 2'
set `config3` 'some value 3'
commit
end

AsyncRelease

Indicates whether acquired locks should be released asynchronously (in the background) or synchronously (blocking the client until fully released).

  • true: Faster response to the client, locks released in background.

  • false: Locks must be released before returning to the client.

  • Default value: false

begin (asyncRelease="true")
set `config1` 'some value 1'
set `config2` 'some value 2'
set `config3` 'some value 3'
commit
end

AutoCommit

Specifies whether an implicit commit should be executed automatically if all operations in the transaction succeed, or if an explicit commit is required to finalize the transaction.

  • true: The transaction will automatically commit if no errors occur.

  • false: A manual commit() is required to indicate when the transaction should be finalized.

  • Default behavior:

    • false when using a begin block
    • true when no begin block is used

Example:

begin (autoCommit=false)
...
commit
end

These options provide greater flexibility and control over performance, consistency, and responsiveness in your Kahuna transactions.