Serializable Transaction Retry

Serializable isolation rejects unsafe histories. The application must safely repeat the complete decision rather than retrying one stale statement.

Database · TypeScript

Serializable Transaction Retry

A serialization abort invalidates the whole decision, not only the statement that noticed it.

async function serializable<T>(run: (db: Client) => Promise<T>) {
  for (let attempt = 1; attempt <= 4; attempt++) {
    const db = await pool.connect()
    try {
      await db.query('BEGIN ISOLATION LEVEL SERIALIZABLE')
      const result = await run(db) // repeat the entire decision
      await db.query('COMMIT')
      return result
    } catch (error) {
      await db.query('ROLLBACK').catch(() => {})
      if (sqlState(error) !== '40001' || attempt === 4) throw error
      await sleep(Math.random() * 20 * attempt)
    } finally { db.release() }
  }
  throw new Error('unreachable')
}

Invariant: Every attempt recomputes the business decision from a fresh serializable snapshot and stops inside a bounded retry budget.

Use when: A PostgreSQL transaction protects a cross-row invariant and may be aborted as non-serializable.

Why this boundary matters

Retrying only the failed statement preserves decisions made from an invalid snapshot. The complete read-decide-write unit must execute again.

Failure policy

BoundaryAction
SQLSTATE 40001Roll back and retry the complete transaction with jitter
Deadlock 40P01Retry only when the operation is safe and budget remains
Unique violationReturn a domain conflict unless explicitly classified otherwise
Commit outcome ambiguousReconcile by stable command identity
Deadline exhaustedFail without another attempt

Trade-offs

Serializable isolation protects multi-row and predicate invariants but can abort transactions under contention. Retries add latency and load, so repeated conflicts should trigger redesign rather than larger limits.

Decision rule: Use it when invalid cross-row state costs more than bounded retries, and keep external side effects outside the retrying transaction.

Further reference

Browse all engineering snippets · Read the architecture guide

>