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
postgres: could not serialize access due to concurrent update
Steps
- 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. - 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.
- Make retries safe: ensure the transaction body is idempotent or guarded so a retried partial execution cannot double-apply. Expected: retries never corrupt data.
- Reduce contention: shorten transactions, touch fewer rows, and avoid serializable for workloads that do not need it. Expected: fewer conflicts per second.
- 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