У меня типичная проблема производитель-потребитель:
Несколько приложений-производителей записывают запросы заданий в таблицу заданий в базе данных PostgreSQL.
В запросах заданий есть поле состояния, которое начинается с QUEUED при создании.
Существует несколько пользовательских приложений, которые уведомляются правилом, когда производитель добавляет новую запись:
CREATE OR REPLACE RULE "jobrecord.added" AS
ON INSERT TO jobrecord DO
NOTIFY "jobrecordAdded";
Они попытаются зарезервировать новую запись, установив для нее состояние RESERVED. Конечно, только на потребителя должно получиться. Все остальные потребители не должны иметь возможность зарезервировать ту же запись. Вместо этого они должны зарезервировать другие записи с состоянием = QUEUED.
Пример:
какой-то производитель добавил следующие записи в таблицу jobrecord :
id state owner payload
------------------------
1 QUEUED null <data>
2 QUEUED null <data>
3 QUEUED null <data>
4 QUEUED null <data>
теперь два потребителя A , B хотят обработать их. Они начинают работать одновременно.
Один должен зарезервировать идентификатор 1, другой должен зарезервировать идентификатор 2, затем первый, кто заканчивает, должен зарезервировать идентификатор 3 и т. Д.
В чистом многопоточном мире я бы использовал мьютекс для управления доступом к очереди заданий, но потребители - это разные процессы, которые могут выполняться на разных машинах. Они имеют доступ только к одной и той же базе данных, поэтому вся синхронизация должна осуществляться через базу данных.
Я прочитал много документации о параллельном доступе и блокировке в PostgreSQL, например. http://www.postgresql.org/docs/9.0/interactive/explicit-locking.html
Выбрать разблокированную строку в Postgresql
PostgreSQL и блокировка
Из этих тем я узнал, что следующий SQL-оператор должен делать то, что мне нужно:
UPDATE jobrecord
SET owner= :owner, state = :reserved
WHERE id = (
SELECT id from jobrecord WHERE state = :queued
ORDER BY id LIMIT 1
)
RETURNING id; // will only return an id when they reserved it successfully
К сожалению, когда я запускаю это в нескольких пользовательских процессах, примерно в 50% времени они по-прежнему резервируют одну и ту же запись, обрабатывая и перезаписывая изменения другой.
Что мне не хватает? Как мне написать оператор SQL, чтобы несколько потребителей не зарезервировали одну и ту же запись?