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.
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.
| Key | Type | Contents |
|---|---|---|
__swytch::conflicts::<txid> | JSON document | Evidence for one transaction |
__swytch::conflict::<key> | List | Reported transaction IDs associated with an application key |
__swytch::conflict::__swytch | List | Application 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.
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.
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.
| Field | Description |
|---|---|
version | Schema version, currently 1 |
txid | Opaque transaction ID |
bind | Transaction envelope reference (node and offset as decimal strings) |
state_map_key_encoding | hex for map keys in reduced states |
keys | Per-key transaction evidence |
keys[].key | Base64-encoded application key |
keys[].consumed_tips | History references consumed for that key |
keys[].before | State reconstructed from the consumed history, or null |
keys[].after | State after the transaction’s effects |
keys[].effects | Ordered 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.
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.
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.