Skip to main content

Transactions

Manage atomic, durable transactions using ekoDB's REST API with support for savepoints and full rollback capabilities.

Isolation is not enforced yet

ekoDB transactions today guarantee atomicity (all-or-nothing, via a per-transaction undo log) and durability (WAL). They do not currently enforce isolation between concurrent transactions. There is no locking, no MVCC, and no conflict detection. The isolation_level parameter is accepted by the API and recorded on the transaction, but it does not change runtime behavior. Every level behaves as ReadUncommitted today, so concurrent transactions can observe each other's uncommitted writes regardless of the level requested. Enforced isolation is on the roadmap. Do not rely on any isolation guarantee yet; serialize correctness-critical concurrent work at the application layer. See the Isolation Levels section for the precise current status.

Architecture & Concepts

For detailed information about transactions architecture, isolation levels, and best practices, see the Transactions Architecture Reference.

REST API Transaction Considerations

When using the REST API directly (without client libraries), be aware of these important limitations:

  1. Manual Transaction ID Management - You must track and include the transaction_id with every operation within a transaction
  2. Connection Stateless - REST is stateless; transaction state is maintained server-side with timeout expiration
  3. No Automatic Rollback - If your client crashes, the transaction will remain pending until timeout (default: 5 minutes)
  4. Network Failures - A failed commit response doesn't guarantee the commit failed; use idempotency keys for critical operations

Recommendation: Use the official client libraries which handle transaction lifecycle automatically.

Isolation Levels

Accepted but NOT enforced

The four levels below are defined in the IsolationLevel enum and can be passed to the API. They are recorded on the transaction and surfaced in status responses, but they do not currently change runtime behavior. ekoDB has no concurrency control (no read/write locks, no MVCC, no snapshot versioning, no conflict detection), so every transaction effectively runs at ReadUncommitted semantics regardless of the level requested. The parameter exists for forward compatibility. Enforced isolation (Serializable is not yet implemented) is roadmap work.

ekoDB defines four isolation-level names. The descriptions below are the intended (not-yet-enforced) semantics:

  • ReadUncommitted - Intended: dirty reads allowed, no read locks. This is also the de-facto behavior of every level today.
  • ReadCommitted (default) - Intended: no dirty reads. Not yet enforced; behaves as ReadUncommitted today.
  • RepeatableRead - Intended: consistent reads within a transaction. Not yet enforced; behaves as ReadUncommitted today.
  • Serializable - Intended: highest consistency via full MVCC / conflict detection. Not yet implemented; behaves as ReadUncommitted today.

Begin Transaction

Start a new transaction with optional isolation level and timeout.

let tx_id = client.begin_transaction(Some(TransactionOptions {
isolation_level: IsolationLevel::Serializable,
timeout_seconds: Some(600),
})).await?;

println!("{}", tx_id); // 'tx-abc-123'
Working Examples

Prefer a client library? See examples in Rust, Python, TypeScript, Go, or Kotlin

Want to use the REST API directly? See examples in JavaScript, Python, Go, or Rust

Parameters:

  • isolation_level (optional) - ReadUncommitted, ReadCommitted, RepeatableRead, Serializable
  • timeout_seconds (optional) - Default: 300 (5 minutes)

Commit Transaction

Commit all operations within the transaction.

client.commit_transaction(&tx_id).await?;

Rollback Transaction

Rollback all operations and abort the transaction.

client.rollback_transaction(&tx_id).await?;

Create Savepoint

Create a savepoint for partial rollback within a transaction.

client.create_savepoint(&tx_id, "checkpoint1").await?;

Rollback to Savepoint

Rollback to a specific savepoint, undoing operations after it.

client.rollback_to_savepoint(&tx_id, "checkpoint1").await?;

Release Savepoint

Remove a savepoint that's no longer needed.

client.release_savepoint(&tx_id, "checkpoint1").await?;

Get Transaction Status

Check the current status of a transaction.

let status = client.get_transaction_status(&tx_id).await?;

println!("{:?}", status);

List Active Transactions

List all currently active transactions.

const transactions = await client.listActiveTransactions();

console.log(transactions);
/*
[
{ transaction_id: 'tx-abc-123', isolation_level: 'Serializable', created_at: 1699814400 },
{ transaction_id: 'tx-def-456', isolation_level: 'ReadCommitted', created_at: 1699814500 }
]
*/

Complete Example

Here's a complete example of a multi-step transaction with savepoints:

# 1. Begin transaction
curl -X POST https://{EKODB_API_URL}/api/transactions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer {YOUR_API_TOKEN}" \
-d '{"isolation_level": "Serializable", "timeout_seconds": 600}'
# Response: {"transaction_id": "tx-001"}

# 2. Create initial savepoint
curl -X POST https://{EKODB_API_URL}/api/transactions/tx-001/savepoints \
-H "Content-Type: application/json" \
-H "Authorization: Bearer {YOUR_API_TOKEN}" \
-d '{"name": "start"}'

# 3. Debit source account
curl -X PUT https://{EKODB_API_URL}/api/update/accounts/ACC001 \
-H "Content-Type: application/json" \
-H "Authorization: Bearer {YOUR_API_TOKEN}" \
-d '{"balance": 900}'

# 4. Create savepoint after debit
curl -X POST https://{EKODB_API_URL}/api/transactions/tx-001/savepoints \
-H "Content-Type: application/json" \
-H "Authorization: Bearer {YOUR_API_TOKEN}" \
-d '{"name": "after_debit"}'

# 5. Credit destination account
curl -X PUT https://{EKODB_API_URL}/api/update/accounts/ACC002 \
-H "Content-Type: application/json" \
-H "Authorization: Bearer {YOUR_API_TOKEN}" \
-d '{"balance": 1100}'

# 6. If credit fails, rollback to after_debit
# curl -X POST https://{EKODB_API_URL}/api/transactions/tx-001/savepoints/after_debit/rollback \
# -H "Authorization: Bearer {YOUR_API_TOKEN}"
# Then retry credit operation...

# 7. Commit transaction
curl -X POST https://{EKODB_API_URL}/api/transactions/tx-001/commit \
-H "Authorization: Bearer {YOUR_API_TOKEN}"

Best Practices

Declare Intended Isolation Level

The table below describes which level to declare so intent is captured today and enforced behavior applies automatically once isolation enforcement ships. Until then, no level provides isolation (see Isolation Levels), so a correctness-critical concurrent workload must not depend on the database for isolation yet. Serialize such work at the application layer or run it single-writer.

Use CaseDeclareWhy (intent — see note above)
Analytics/ReportingReadUncommittedPerformance over consistency
General CRUDReadCommittedIntended default
Financial CalculationsRepeatableReadConsistent reads
Banking/CriticalSerializableMaximum safety (when enforced)

Use Savepoints for Multi-Stage Operations

Savepoints allow partial rollback without aborting the entire transaction:

# Create savepoint before risky operation
POST /api/transactions/{id}/savepoints
{"name": "before_risky_op"}

# Perform risky operation
PUT /api/update/...

# If operation fails, rollback to savepoint
POST /api/transactions/{id}/savepoints/before_risky_op/rollback

# Retry or continue with alternative approach

Set Appropriate Timeouts

// Short transaction (1 minute)
{"timeout_seconds": 60}

// Long-running workflow (30 minutes)
{"timeout_seconds": 1800}

// Critical operation (5 minutes, default)
{"timeout_seconds": 300}

Handle Errors Gracefully

Always implement proper error handling with rollback:

# Begin transaction
curl -X POST .../api/transactions ...
# -> {"transaction_id": "tx-001"}

# Try operations
if operation_fails; then
# Rollback on error
curl -X POST .../api/transactions/tx-001/rollback ...
else
# Commit on success
curl -X POST .../api/transactions/tx-001/commit ...
fi

Monitor Active Transactions

Track and debug active transactions:

# List all active transactions
GET /api/transactions

# Check specific transaction status
GET /api/transactions/{id}

# Useful for:
# - Debugging stuck transactions
# - Monitoring long-running operations
# - Identifying transaction bottlenecks

Use Cases

Financial Transfers

Multi-account updates with atomicity guarantees:

# Begin transaction
POST /api/transactions
{"isolation_level": "Serializable"}

# Debit source
PUT /api/update/accounts/{from_id}
{"$decrement": {"balance": 100}}

# Credit destination
PUT /api/update/accounts/{to_id}
{"$increment": {"balance": 100}}

# Commit both or rollback both
POST /api/transactions/{id}/commit

E-Commerce Orders

Order creation with inventory reservation:

# Begin transaction
POST /api/transactions
{"isolation_level": "RepeatableRead"}

# Create savepoint before inventory check
POST /api/transactions/{id}/savepoints
{"name": "before_inventory"}

# Reserve inventory
PUT /api/update/inventory/{product_id}
{"$decrement": {"stock": 1}}

# If insufficient stock, rollback to savepoint
# Otherwise create order
POST /api/insert/orders
{...}

# Commit transaction
POST /api/transactions/{id}/commit

Batch Processing

Process multiple records with error recovery:

# Begin transaction
POST /api/transactions

# Process records in batches
for batch in batches:
# Create savepoint before batch
POST /api/transactions/{id}/savepoints
{"name": "batch_{i}"}

# Process batch
POST /api/batch/insert/...

# If batch fails, rollback and continue
if error:
POST /api/transactions/{id}/savepoints/batch_{i}/rollback
continue

# Commit all successful batches
POST /api/transactions/{id}/commit

Example Code

Direct HTTP/REST API Examples

Raw HTTP examples demonstrating the REST API directly:

Client Library Examples

Production-ready examples using official client libraries: