Search code examples
databasepostgresqlconcurrencyrace-condition

Resolve write after read race condition


Here is a race condition scenario that I need help with:

We are trying to create Item A and item B in the DB and we want to persist that item B is a "childOf" item A. Let's assume the request to create item B along with the "childOf" relationship data arrives before request to create item A.

here are the steps assuming a single job process these requests one at a time

  • Request to create item B arrives first with the "childOf" relationship data that says item B is a child of item A
  • First, we check if item A exists
  • If item A exists item B is created and a "childOf" relationship record is created between item B and item A
  • If item A doesn't exist, the process creates a pending record in the db saying item A is not yet created
  • When a request to create item A arrives, item A is created, the db is checked for any "pending" records associated with item A as parent, if there is a pending record, a "childOf" relationship is added between item B and item A now that item A is created in the db.

If there are multiple jobs running concurrently, here is a race condition that can occur:

Process A Process B
req to create item B
check db for item A and item A is not found
req to create item A arrives and item A is created
since item A doesn't exist when it was checked last time, create a pending record in db with the relationship details
the pending record stays in the DB forever since item A is already created by process A while the pending record hasn't been inserted in the DB yet

How do I resolve this race condition? Sorry if this is too abstract. let me know if I should elaborate further.


Solution

  • I would create a “pending” object not as a separate, different object, but in the very place where a “real” object would be, just with a flag to indicate its state.

    CREATE TABLE object (
       name varchar(100)
          CONSTRAINT object_pkey PRIMATY KEY,
       pending boolean DEFAULT FALSE NOT NULL
    );
    

    Now I would add a regular object like this:

    INSERT INTO object (name)
    VALUES ('A')
    ON CONFLICT ON CONSTRAINT object_pkey
    DO UPDATE SET pending = FALSE;
    

    A pending object is inserted like this:

    INSERT INTO onject (name, pending)
    VALUES ('A', TRUE)
    ON CONFLICT ON CONSTRAINT object_pkey
    DO NOTHING;
    

    Then you don't need to check for existence before you add an object, and the race condition is gone. INSERT ... ON CONFLICT is designed to be atomic and free from race conditions.