Docs / Concepts
Transactions
Every transaction is strictly serializable, including across shards. Clients must retry conflicts and look up unknown outcomes.
- 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.
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:
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 … COMMITaborts the block. Later statements return25P02until you sendROLLBACK. - No savepoints.
ON CONFLICT ABORT,FAILandROLLBACKdiscard the whole transaction, not one statement. CREATE TABLEis 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
40001and40003. - Idempotency keys recorded in the same commit, for exactly-once writes inside a published window.