Documentation menu

Docs / Concepts

Transactions

Every transaction is strictly serializable, including across shards. Clients must retry conflicts and look up unknown outcomes.

Available View Markdown
Works
Strict serializability as the default and only isolation level. Atomic, isolated commits across shards inside one database. Interactive transactions and atomic HTTP batches.
Differs
Locking is no-wait, so a conflicting transaction fails at once with 40001 and the client retries.
Missing
Savepoints. Transactions that span separate databases. Server-side automatic retry.
Operational impact
A commit waits for conditional writes to the bucket.
Evidence
Fault-injection tests, deterministic simulation and bounded TLA+ models. Not an independent audit.

#The guarantee

Transactions are strictly serializable: the result is the same as running them one at a time, in an order consistent with real time. If transaction A is acknowledged before transaction B starts, B sees A.

This holds for every transaction. There is no weaker default to opt out of, and a stale read is never given silently.

It also holds across shards. A transaction that touches several shards of one database commits atomically and in isolation: prepare on each shard, one write-once decision object, then resolve. Index entries are written in the same attempt as the row, so a unique index is never briefly wrong.

The guarantee is per database. There is no transaction across two separate databases.

#What you must handle

Two error codes are part of normal operation. No stock PostgreSQL driver handles either for you.

#40001: conflict, retry the whole transaction

Locking is no-wait. A transaction that meets a conflicting lock is not queued; it fails with 40001. An ownership change while a transaction is open gives the same code.

Roll back, then run the whole transaction again. Do not retry only the failed statement.

Python
import psycopg

def run(conn, work, attempts=5):
    for attempt in range(attempts):
        try:
            with conn.transaction():
                return work(conn)
        except psycopg.errors.SerializationFailure:
            continue  # 40001: retry the whole transaction
    raise RuntimeError("still conflicting after retries")

#40003: the commit outcome is unknown

40003 means the commit may or may not have happened, for example because a bucket write timed out or a node stopped mid-commit. Do not retry yet. The error carries the transaction ID. Ask any node:

Shell
curl --cacert ca/ca.crt -H "Authorization: Bearer $TOKEN" \
  https://n1.example.net:7101/db/app/txn/<txid>
status Meaning
committed Final. Do not run it again
aborted It did not happen. You may run the operation again, as a new transaction
unknown, quarantined, or HTTP 503 lookup_failed Not resolved. Repeat the lookup. Do not replay the operation

Only aborted permits a new attempt. An unknown outcome is not a confirmed rollback.

#Read-only transactions

BEGIN READ ONLY gives a consistent read-only image of the whole database while writers continue. A write inside it returns 25006.

#Things that differ from PostgreSQL

  • Any error inside BEGIN … COMMIT aborts the block. Later statements return 25P02 until you send ROLLBACK.
  • No savepoints. ON CONFLICT ABORT, FAIL and ROLLBACK discard the whole transaction, not one statement.
  • CREATE TABLE is not transactional. It is one standalone statement and commits at once.
  • A multi-statement simple query is one implicit transaction.

#Coming

  • Server-side retry of standalone statements and SDK helpers for 40001 and 40003.
  • Idempotency keys recorded in the same commit, for exactly-once writes inside a published window.

Search runs in your browser over an index of these pages. No query leaves this site.