Question presented to candidate: "We moved a critical booking flow to SERIALIZABLE to stop a write-skew bug, and now under load we see lots of 'could not serialize access' errors and slow retries. Explain what write skew is, why SERIALIZABLE throws these errors, and when you would use an explicit lock such as an advisory lock instead."
What a strong answer should cover:
- Write skew: two transactions read the same data, each writes a different row based on what it read, and together they break a rule neither broke alone; REPEATABLE READ (snapshot isolation) allows it
- How PostgreSQL SERIALIZABLE (SSI) catches it: it tracks read/write dependencies with SIRead predicate locks and aborts one transaction with SQLSTATE
40001; it never blocks to do so - The contract SSI imposes: every transaction involved must run at SERIALIZABLE, and the application must retry the whole transaction on
40001 - Pitfalls: false positives from coarse predicate locks (whole relation on a sequential scan, pages on small indexes), retry storms on hot rows, side effects repeated on retry
- Alternatives:
SELECT ... FOR UPDATEon the rows the decision depends on,pg_advisory_xact_lockon a key that names the rule (works even when the rows do not exist yet), or a constraint when the rule can be expressed as one - How to choose: SSI for many varied, rarely conflicting rules; explicit locks for a few known, hot invariants
Clarifying questions expected:
- Which invariant is being protected, and does it depend on rows that might not exist yet (no row to lock)?
- How hot is the contention: many writers on the same key, or occasional overlaps?
- Are there side effects (emails, payments, external calls) inside the transaction that a retry would repeat?
Code / implementation expected: Yes — reproduce write skew under REPEATABLE READ, show SERIALIZABLE rejecting it, and show the same rule enforced with an advisory lock.
Schema used by the examples: