SapixDBSapixDB/Docs
Home
Enterprise · Distributed

Saga Transactions

Write to multiple agents atomically. SapixDB executes each step in sequence and automatically compensates all applied steps if anything fails — no two-phase commit coordinator, no distributed locks.

Automatic compensation
If any step fails, SapixDB writes a TOMBSTONE record to every previously-applied step in reverse order — no manual rollback code needed.
Synchronous execution
POST /v1/saga blocks until all steps commit or all applied steps are compensated. One HTTP call, one definitive outcome.
Cross-instance writes
Each step targets any SapixDB HTTP endpoint — agents on the same instance, different instances, or different cloud regions.
Durable state
Saga state is persisted to RocksDB graph-meta before execution begins. Crashes do not silently lose saga records.
Enterprise featureSaga Transactions require the saga feature flag. This is available on the Enterprise plan. Community builds return 404 on saga routes.

State machine

A saga moves through these states exactly once:

saga state transitions
Pending → Executing → Committed            (all steps applied)
                    → Compensating → Compensated   (all applied steps rolled back)
                                  → Failed          (compensation itself failed)
StateHTTP statusMeaning
pendingCreated, execution not yet started
executingSteps are being applied one by one
committed201 CreatedAll steps applied — the happy path
compensatingA step failed; rolling back applied steps
compensated207 Multi-StatusAll applied steps successfully reversed
failed207 Multi-StatusCompensation itself failed for at least one step

API

Create and execute a saga

A single POST creates and immediately executes the saga. The request blocks until all steps commit or all applied steps are compensated.

POST /v1/saga
POST /v1/saga
Authorization: Bearer <key>
Content-Type: application/json

{
  "steps": [
    {
      "target_agent_url": "http://analytics:7475",
      "payload_b64": "<base64-msgpack>",
      "flags": 0
    },
    {
      "target_agent_url": "http://billing:7475",
      "payload_b64": "<base64-msgpack>",
      "flags": 0
    }
  ]
}
FieldTypeDescription
target_agent_urlstringHTTP base URL of the target SapixDB instance
payload_b64stringBase64-encoded MessagePack record payload
flagsu8BlockFlags bitmask — 0 = plain data, 2 = TOMBSTONE

Response201 Created on full commit, 207 Multi-Status on compensation or failure:

response
{
  "id": "a1b2c3d4e5f6a7b8",
  "coordinator_agent_id": "orders",
  "state": "committed",
  "created_at_ms": 1754478000000,
  "completed_at_ms": 1754478000042,
  "steps": [
    {
      "step_id": "step_1",
      "target_agent_url": "http://analytics:7475",
      "payload_b64": "...",
      "flags": 0,
      "status": "applied",
      "record_hash": "a3f9c2e1...",
      "compensation_hash": null,
      "error": null
    }
  ]
}

Step status values

StatusMeaning
pendingNot yet attempted
appliedRecord written to target agent
compensatedTOMBSTONE written to target agent (step rolled back)
failedApply or compensation failed — check the error field

List all sagas

GET /v1/saga
GET /v1/saga
Authorization: Bearer <key>

// Response
{
  "sagas": [ ... ],
  "total": 5
}

Returns newest-first.

Get one saga

GET /v1/saga/:id
GET /v1/saga/a1b2c3d4e5f6a7b8
Authorization: Bearer <key>

Returns a single SagaTransaction object. Returns 404 if not found.

How compensation works

When a step fails, SapixDB iterates all applied steps in reverse order and writes a TOMBSTONE record (flags: 2) to each one. The compensation payload identifies the original record being reversed:

compensation payload
{
  "_saga_compensation": true,
  "saga_id": "a1b2c3d4e5f6a7b8",
  "compensating_for": "<original_record_hash>"
}

Steps that were never applied (pending) are skipped — only applied steps get compensated. The compensation_hash on each step is set to the content hash of the TOMBSTONE record written for it.

TOMBSTONE semanticsA TOMBSTONE is a SapixDB record with flags: 2. It does not delete the original record — it appends a logical deletion marker to the strand. Readers that filter out TOEMBSTONEs will no longer see the compensated data; the underlying record remains in the immutable chain for audit purposes.

Quick start

This example writes an order event to two agents atomically. If the billing write fails, the analytics write is automatically compensated.

TypeScript
import { encode } from "@msgpack/msgpack";

async function writeSaga(analyticsUrl: string, billingUrl: string) {
  const analyticsPayload = Buffer.from(
    encode({ event: "order_placed", order_id: "ord_123", amount: 4999 })
  ).toString("base64");

  const billingPayload = Buffer.from(
    encode({ charge: 4999, order_id: "ord_123", currency: "usd" })
  ).toString("base64");

  const res = await fetch(`${SAPIX_URL}/v1/saga`, {
    method: "POST",
    headers: {
      Authorization: `Bearer ${API_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      steps: [
        { target_agent_url: analyticsUrl, payload_b64: analyticsPayload, flags: 0 },
        { target_agent_url: billingUrl,   payload_b64: billingPayload,   flags: 0 },
      ],
    }),
  });

  const saga = await res.json();

  if (saga.state !== "committed") {
    throw new Error(`Saga ${saga.id} ended in state: ${saga.state}`);
  }

  return saga.id;
}

Limitations

Synchronous and blocking
The HTTP request blocks until all steps settle. Set your client timeout based on the number of steps × expected network latency. Each step has a 10-second internal timeout.
At-most-once compensation
If a compensation POST itself times out, that step is left as failed and the saga transitions to the failed state. There is no automatic retry for compensations.
No idempotency key
Submitting the same saga twice creates two separate transactions. Guard against duplicates at the caller (e.g. check for an existing committed saga with the same business key before submitting).
Coordinator single point of recovery
If the coordinator instance restarts mid-execution, the in-flight saga is not automatically resumed. The saga record is durable; recovery tooling can read it, but execution must be re-triggered manually.
Primary agent writes only
Each step writes to POST {url}/v1/records on the target instance's primary agent. Writes to a named secondary agent within a target instance are not supported per step.
← Distributed ModeMulti-Region Replication →