Backend · PostgreSQL, Node.js

Four races, four different locks

The problem

I used to think concurrency bugs had one answer: use a transaction.

Then I worked on a system where a calling team pulls contacts from a shared pool, managers reassign work in the middle of a call, background jobs sweep stale work back, and an AI pipeline spends real money on each item. That is four different races, and each one wanted a different tool.

Race 1: two allocators grabbing the same contact

One SQL statement does the claim:

UPDATE contacts SET assigned_to = $1
WHERE id IN (
  SELECT id FROM contacts
  WHERE assigned_to IS NULL
  ORDER BY priority, created_at
  LIMIT $2
  FOR UPDATE SKIP LOCKED
)
RETURNING id;

Two claimers running at the same moment can never take the same row. Each one skips the rows the other has locked. It is raw SQL because the ORM has no row-locking primitive. Sometimes the database solved your problem decades ago.

Race 2: two people creating a profile for the same company

An advisory lock keyed on the company's id, taken inside the transaction. It is cheap, scoped to one company, and releases itself on commit.

Race 3: check, then insert

"Is there already an open transfer for this contact? No? Create one." Two requests both read "no", and both insert.

A partial unique index settles it. The database enforces "at most one open transfer per contact" as a fact of storage:

CREATE UNIQUE INDEX one_open_transfer_per_contact
  ON transfers (contact_id)
  WHERE status = 'open';

The code catches the unique violation and turns it back into a friendly error. The check in code is only a fast path. The index is what actually holds the line.

Race 4: notification emails from an hourly job

A write-ahead ledger. The job claims the notification row first, under a unique key, and only then sends. A crash in the middle of sending, or a retried run, finds the claim already taken and does nothing, so a retry does not resend to a manager's inbox.

The lesson

Thread safety is not something you sprinkle on at the end. Each mechanism exists because of the shape of its race: who writes, how often, and what losing the race should cost. Naming the race before choosing the lock is most of the work.

All case studies