Skip to main content
Swytch Documentation
Toggle Dark/Light/Auto mode Toggle Dark/Light/Auto mode Toggle Dark/Light/Auto mode Back to homepage

Handle Transaction Conflicts

Swytch normally coordinates transactions across the nodes subscribed to their keys. When separately accepted transaction histories meet after a network partition, a node may discover conflicting transactions during reconstruction.

Swytch records the affected transactions as ordinary Redis keys. Your application can inspect the original inputs, outputs, and effects, then decide whether any additional work is needed. Reports do not undo transactions or reverse external side effects.

Find conflict reports

Use your existing Redis connection. Swytch writes transaction evidence as JSON documents and notifications as lists. JSON commands are built in; no separate Redis module is required.

KeyTypeContents
__swytch::conflicts::<txid>JSON documentEvidence for one transaction
__swytch::conflict::<key>ListReported transaction IDs associated with an application key
__swytch::conflict::__swytchListApplication keys associated with reports

The document prefix is conflicts (plural); the list prefix is conflict (singular). For a per-key list, append the entire application key, including any colons. These names are reserved for Swytch reports.

Inspect the directory, then the transaction IDs for a key:

LRANGE __swytch::conflict::__swytch 0 99
LRANGE __swytch::conflict::orders:42 0 99
JSON.GET __swytch::conflicts::<txid> .

Replace <txid> with an ID returned by the list. JSON.GET ... . returns the document as an object; JSON.GET ... $ returns a one-element JSONPath match array. A missing document returns null.

The per-key lists include all keys in a reported transaction’s bind, including keys it only read. A transaction does not have to write every key appearing in the directory.

Receive notifications

Use BLPOP to wait for an affected application key, or to consume a transaction ID from a specific key’s list:

BLPOP __swytch::conflict::__swytch 0
BLPOP __swytch::conflict::orders:42 0

These are alternative ways to consume notifications. Use a dedicated connection for blocking commands. BLPOP removes an entry; LRANGE inspects the list without removing anything. The lists use Swytch’s normal replication and subscription behavior.

For example, a Python worker can consume directory entries and read the corresponding reports:

import base64
from collections import Counter
import json
import redis

client = redis.Redis(host="127.0.0.1", port=6379)

while True:
    _, key = client.blpop(b"__swytch::conflict::__swytch", timeout=0)
    ids = Counter(client.lrange(b"__swytch::conflict::" + key, 0, -1))

    for txid, count in ids.items():
        raw = client.execute_command("JSON.GET", b"__swytch::conflicts::" + txid, ".")
        if raw is None:
            continue

        report = json.loads(raw)
        print("Transaction:", txid, "notifications:", count)
        for item in report["keys"]:
            print(base64.b64decode(item["key"]), item["before"], item["after"])

Install the client with python -m pip install redis. The example preserves binary keys. For large notification lists, read the per-key lists in bounded pages rather than using LRANGE 0 -1.

Read the evidence

Each document belongs to one transaction. It records what the transaction consumed and the state produced by its effects. It does not contain a list of competing transactions or a flag identifying the current winner.

FieldDescription
versionSchema version, currently 1
txidOpaque transaction ID
bindTransaction envelope reference (node and offset as decimal strings)
state_map_key_encodinghex for map keys in reduced states
keysPer-key transaction evidence
keys[].keyBase64-encoded application key
keys[].consumed_tipsHistory references consumed for that key
keys[].beforeState reconstructed from the consumed history, or null
keys[].afterState after the transaction’s effects
keys[].effectsOrdered engine effects, including data, read markers, and metadata changes

The after field describes that transaction’s result, even when the current reconstructed value comes from a different history. Treat the document as historical evidence, not a live read.

States and effects use protobuf JSON field names. Byte values such as scalar.raw are base64-encoded, and reduced-state map keys (net_adds, net_removes, partitions) use hex. Integer references are strings to preserve precision. Default protobuf fields may be omitted. Expiry values are recorded as part of the historical state, not recalculated when you read the report. Effects are engine operations, not the original Redis command text.

Handle the result

Read the reports, inspect the current affected keys, and apply your application’s reconciliation rules. If a correction is needed, write it through an ordinary transaction using the relevant keys. Handle an unsuccessful EXEC using the application’s existing retry policy.

A later transaction adds history; it does not erase either original transaction from the DAG. If an application already performed an external action, such as charging a customer or sending a message, changing a value in Swytch does not reverse that action. Handle compensation in the application.

Reporting and retention

Reports appear when a node reconstructs histories and finds competing transactions that were previously marked WON and remain unresolved tips. A reconnection alone does not immediately generate reports on every node; the histories must be discovered and inspected. Ordinary non-transactional merges do not generate these reports.

A document is specific to its transaction and is not extended with later discoveries. When the engine finds a new reportable transaction, it writes any missing documents and appends notifications. Reports already present are reused. Concurrent detectors may append duplicate notifications, so consumers should handle repeated entries safely. A notification count is not a count of affected nodes.

Reports remain after the conflicting histories have been reconciled. An existing document is evidence that a conflict was reported, not proof that it is still unresolved. Once another effect depends on a transaction, it no longer meets the unresolved-tip condition, including when that dependency comes through an associated key.

These reserved keys follow normal access-driven subscriptions. An empty list on one node does not establish that no other node has seen a conflict. They also follow normal tiering and local durability behavior, including eviction and recovery limits. Persist evidence elsewhere if your application needs it to survive the loss of every holder. BLPOP is not a durable acknowledgement mechanism.

Deleting a report document may allow an unresolved transaction to be reported again. Deleting it does not make an already-resolved tip reportable.