## TL;DR
Under `SERIALIZABLE` isolation, concurrent transactions that would violate serializability are aborted with this error by design: the application must retry. Add retry-with-backoff around the transaction and reduce contention (shorter transactions, less overlap). The workload succeeds once retries absorb the conflicts.

## Error
```text
postgres: could not serialize access due to concurrent update
```

## Steps
1. Confirm the isolation level: check that the failing transactions run at `SERIALIZABLE`. Expected: the error is the documented behavior of that level, not a bug.
2. Add retries: wrap the transaction in application code that catches the serialization failure (SQLSTATE 40001) and retries with exponential backoff. Expected: transient conflicts resolve on retry.
3. Make retries safe: ensure the transaction body is idempotent or guarded so a retried partial execution cannot double-apply. Expected: retries never corrupt data.
4. Reduce contention: shorten transactions, touch fewer rows, and avoid serializable for workloads that do not need it. Expected: fewer conflicts per second.
5. Monitor the retry rate: alert if it climbs, since rising conflicts signal a design problem. Expected: a stable, low retry rate.

## When to use
- Serializable transactions fail with "could not serialize access"
- Designing retry logic for strict isolation
- Tuning contended workloads

## When not to use
- Deadlock errors (different SQLSTATE, different handling)
- Read-committed anomalies (consider whether you need serializable at all)

## Tool compatibility
- Postgres `SERIALIZABLE` isolation, SQLSTATE 40001; application retry patterns

## Variant phrasings
### postgres 40001 retry
Catch SQLSTATE 40001 and retry with backoff; the database is asking the app to try again.

### serializable too many conflicts
Shorten transactions and reduce overlap; the isolation level is working as designed.

## Why it happens
Serializable isolation promises the effect of some serial order; when concurrent transactions make that impossible, Postgres aborts one rather than produce a non-serializable result. The abort is the mechanism, and the application retry is the other half of the contract.

## Provenance

Resolved from the public thread: https://vectle.com/posts/pst_Dd5o7n0f--Tn3oxDbodobA
