I have an application that can support a certain number of concurrent actions. This is represented by a table of "slots" in postgres. When nodes come online, they insert a number of rows into the table, one per slot. As jobs claim the slots, they update a row in the table claiming one of the slots and release it again as they finish.
The slots table looks like this:
CREATE TABLE slots (
id INT8 PRIMARY KEY DEFAULT nextval('slots_seq'),
node_name TEXT NOT NULL,
job_name TEXT
);
At any time it has some semi-fixed number of rows each of which may or may not have a job_name filled in.
When a new job wants to start up, it runs these queries to get the name of the node it should run on:
BEGIN;
LOCK TABLE slots IN ACCESS EXCLUSIVE MODE;
SELECT id, node_name
FROM slots
WHERE job_name IS NULL
LIMIT 1
FOR UPDATE;
(the node_name and id are read out of the cursor)
UPDATE slots
SET job_name = %(job_name)s
WHERE id = %(slot_id)s;
COMMIT;
This is often able to claim rows without losing any updates but with higher levels of concurrency, only a few rows will be claimed while many SELECT ... FOR UPDATE and UPDATE queries have been executed. The net result is that we end up with far more jobs running than there are slots for them.
Am I making a locking error? Is there a better way to go about this? Something that doesn't use table locks?
Transaction level SERIALIZABLE does not cut it, only a handful of rows are ever filled.
I'm using postgresql version 8.4.