Review observed PostgreSQL schedules and transaction reasoningSUPPORTING MATERIAL
REFERENCE SHELF
Your guided curriculum
SUPPORTING MATERIALMock interview

Review observed PostgreSQL schedules and transaction reasoning

Use this key after the two-session PostgreSQL exercise. Compare the actual reservation, on-call and retry outcomes with the table below. Ask the learner to explain the statement order that produced each result before naming an isolation level.

The candidate should operate two independent sessions and narrate observed state, not merely recite isolation names. The runner (download file, source below) automates the same schedules; its result is reference evidence, not the candidate's attempt.

Read the supplied code · run_schedules.py
runner · run_schedules.py
"""Execute real two-session PostgreSQL schedules. Requires psycopg and LAB_PG_DSN."""
import os
from concurrent.futures import ThreadPoolExecutor
from pathlib import Path
from retry import transaction_retry


def main():
    import psycopg
    dsn = os.environ["LAB_PG_DSN"]
    def connect():
        connection = psycopg.connect(dsn, autocommit=True)
        connection.execute("SET search_path=interview_lab")
        connection.execute("SET statement_timeout='8s'")
        connection.execute("SET lock_timeout='6s'")
        return connection

    with connect() as admin:
        admin.execute(Path(__file__).with_name("schema.sql").read_text())
        with connect() as a, connect() as b:
            # Lost update: both application reads see 1, then write the literal 0.
            a.execute("BEGIN")
            b.execute("BEGIN")
            assert a.execute("SELECT available FROM stock WHERE id=1").fetchone()[0] == 1
            assert b.execute("SELECT available FROM stock WHERE id=1").fetchone()[0] == 1
            for conn, buyer in ((a, "a"), (b, "b")):
                conn.execute("UPDATE stock SET available=0 WHERE id=1")
                conn.execute("INSERT INTO reservations VALUES (%s,1)", (buyer,))
                conn.execute("COMMIT")
            assert admin.execute("SELECT count(*) FROM reservations").fetchone()[0] == 2
            print("FAILURE reproduced: stock=0, reservations=2 for initial stock=1")
            admin.execute("TRUNCATE reservations")
            admin.execute("UPDATE stock SET available=1")
            a.execute("BEGIN")
            b.execute("BEGIN")
            assert a.execute("UPDATE stock SET available=available-1 WHERE id=1 AND available>0 RETURNING id").fetchone() == (1,)
            a.execute("INSERT INTO reservations VALUES ('a',1)")
            with ThreadPoolExecutor(max_workers=1) as pool:
                waiting = pool.submit(b.execute, "UPDATE stock SET available=available-1 WHERE id=1 AND available>0 RETURNING id")
                a.execute("COMMIT")
                assert waiting.result().fetchone() is None
            b.execute("COMMIT")
            assert admin.execute("SELECT count(*) FROM reservations").fetchone()[0] == 1
            assert admin.execute("SELECT available FROM stock WHERE id=1").fetchone()[0] == 0
            print("FIX passed: conditional decrement admits one reservation")

            # Write skew under snapshot isolation: disjoint writes evade row conflict.
            for conn in (a, b):
                conn.execute("BEGIN ISOLATION LEVEL REPEATABLE READ")
                assert conn.execute("SELECT count(*) FROM doctors WHERE on_call").fetchone()[0] == 2
            a.execute("UPDATE doctors SET on_call=false WHERE id=1")
            b.execute("UPDATE doctors SET on_call=false WHERE id=2")
            a.execute("COMMIT")
            b.execute("COMMIT")
            assert admin.execute("SELECT count(*) FROM doctors WHERE on_call").fetchone()[0] == 0
            print("FAILURE reproduced: repeatable-read write skew leaves zero doctors")
            admin.execute("UPDATE doctors SET on_call=true")
            for conn in (a, b):
                conn.execute("BEGIN ISOLATION LEVEL SERIALIZABLE")
                assert conn.execute("SELECT count(*) FROM doctors WHERE on_call").fetchone()[0] == 2
            a.execute("UPDATE doctors SET on_call=false WHERE id=1")
            b.execute("UPDATE doctors SET on_call=false WHERE id=2")
            failures = []
            for conn, doctor in ((a, 1), (b, 2)):
                try:
                    conn.execute("COMMIT")
                except psycopg.Error as error:
                    conn.execute("ROLLBACK")
                    assert error.sqlstate == "40001", error
                    failures.append(doctor)
            assert len(failures) == 1
            def retry_off_call():
                with connect() as conn:
                    try:
                        conn.execute("BEGIN ISOLATION LEVEL SERIALIZABLE")
                        count = conn.execute("SELECT count(*) FROM doctors WHERE on_call").fetchone()[0]
                        if count > 1:
                            conn.execute("UPDATE doctors SET on_call=false WHERE id=%s", (failures[0],))
                        conn.execute("COMMIT")
                        return count
                    except Exception:
                        conn.execute("ROLLBACK")
                        raise
            assert transaction_retry(retry_off_call) == 1
            assert admin.execute("SELECT count(*) FROM doctors WHERE on_call").fetchone()[0] == 1
            print("FIX passed: serialization abort plus complete transaction retry preserves one doctor")

            # Deadlock: opposite lock order. Victim rolls back before retry.
            a.execute("BEGIN")
            b.execute("BEGIN")
            a.execute("UPDATE counters SET hits=hits+1 WHERE id=1")
            b.execute("UPDATE counters SET hits=hits+1 WHERE id=2")
            def finish(conn, second_id):
                try:
                    conn.execute("UPDATE counters SET hits=hits+1 WHERE id=%s", (second_id,))
                    conn.execute("COMMIT")
                    return "ok"
                except psycopg.Error as error:
                    conn.execute("ROLLBACK")
                    return error.sqlstate
            with ThreadPoolExecutor(max_workers=2) as pool:
                first = pool.submit(finish, a, 2)
                second = pool.submit(finish, b, 1)
                outcomes = sorted((first.result(), second.result()))
            assert outcomes == ["40P01", "ok"], outcomes
            assert admin.execute("SELECT hits FROM counters ORDER BY id").fetchall() == [(1,), (1,)]
            print("FAILURE reproduced: deadlock 40P01; victim transaction rolled back")

            def ordered_transaction():
                with connect() as conn:
                    try:
                        conn.execute("BEGIN")
                        conn.execute("SELECT id FROM counters ORDER BY id FOR UPDATE").fetchall()
                        conn.execute("UPDATE counters SET hits=hits+1")
                        conn.execute("COMMIT")
                    except Exception:
                        conn.execute("ROLLBACK")
                        raise
            transaction_retry(ordered_transaction)
            assert admin.execute("SELECT hits FROM counters ORDER BY id").fetchall() == [(2,), (2,)]
            admin.execute("UPDATE counters SET hits=0")
            with ThreadPoolExecutor(max_workers=2) as pool:
                one = pool.submit(transaction_retry, ordered_transaction)
                two = pool.submit(transaction_retry, ordered_transaction)
                one.result()
                two.result()
            assert admin.execute("SELECT hits FROM counters ORDER BY id").fetchall() == [(2,), (2,)]
            print("FIX passed: ordered locks plus bounded whole-transaction retry")


if __name__ == "__main__":
    main()
Boundary Expected evidence Reasoning that is insufficient
Lost update Two reservations in baseline; one after conditional decrement “Stock is nonnegative, therefore no oversell”
Write skew Repeatable-read count 0; serializable 40001; whole retry count 1 “BEGIN” or a row lock on only the doctor's own row
Deadlock 40P01 victim rollback; survivor count 1; full retry counts 2 Retrying only the blocked statement in an aborted transaction
Retry budget Three total attempts; 40001/40P01 only; no external effect in callback Retrying every integrity error or claiming every timeout is safe to repeat
Plans Actual/estimated rows, buffers, ordering and limit explained Index must always win; elapsed time alone proves heap layout
Physical model Separate heap and B-tree; conditional HOT eligibility Ordered primary key automatically clusters heap

Score each 0 absent, 1 explained, 2 independently reproduced and defended. Senior practice should show all boundaries without the answer file. For an unseen follow-up, add a third doctor and a concurrent shift transfer, or make the owner predicate broad but keep LIMIT 20; ask the candidate to adjust the invariant or predicted plan before running it. Lead scope adds lock/retry budgets across teams and a compatible old-writer rollout. Do not convert scores into a pass probability. No PostgreSQL execution in the author's environment is claimed.