Лучший способ использовать таблицу БД в качестве очереди заданий (так называемая пакетная очередь или очередь сообщений)
У меня есть таблица базы данных с ~ 50K строк в ней, каждая строка представляет работу, которая должна быть выполнена. У меня есть программа, которая извлекает работу из БД, выполняет работу и помещает результат обратно в БД. (эта система работает прямо сейчас)
Теперь я хочу разрешить нескольким задачам обработки выполнять задания, но быть уверенным, что ни одна задача не будет выполнена дважды (из-за проблем с производительностью это не вызовет других проблем). Поскольку доступ осуществляется с помощью хранимой процедуры, мой текущий способ - заменить упомянутую хранимую процедуру чем-то вроде этого
update tbl
set owner = connection_id()
where available and owner is null limit 1;
select stuff
from tbl
where owner = connection_id();
КСТАТИ; рабочие задачи могут разорвать связь между получением работы и отправкой результатов. Кроме того, я не ожидаю, что БД приблизится к тому, чтобы стать узким местом, если я не испорчу эту часть (~ 5 заданий в минуту)
Есть ли проблемы с этим? Есть ли лучший способ сделать это?
Примечание. "База данных как антишаблон IPC" здесь только слегка уместна, потому что
- Я не делаю IPC (нет процесса генерации строк, все они уже существуют) и
- основной недостаток, описанный для этого анти-паттерна, заключается в том, что он приводит к ненужной загрузке БД, поскольку процессы ожидают сообщений (в моем случае, если сообщений нет, все может завершиться, поскольку все сделано)
Ответы
Ответ 1
Вот то, что я успешно использовал в прошлом:
Схема таблицы MsgQueue
MsgId identity -- NOT NULL
MsgTypeCode varchar(20) -- NOT NULL
SourceCode varchar(20) -- process inserting the message -- NULLable
State char(1) -- 'N'ew if queued, 'A'(ctive) if processing, 'C'ompleted, default 'N' -- NOT NULL
CreateTime datetime -- default GETDATE() -- NOT NULL
Msg varchar(255) -- NULLable
Ваши типы сообщений - это то, что вы ожидаете, - сообщения, которые соответствуют контракту между вложением процесса (процессами) и процессом (процессами) чтения, структурированными с помощью XML или другим выбором представления (JSON будет удобен в некоторых случаи, например).
Затем могут быть вставлены процессы 0-to-n, и процессы 0-to-n могут считывать и обрабатывать сообщения. Каждый процесс чтения обычно обрабатывает один тип сообщения. Для балансировки нагрузки может выполняться несколько экземпляров типа процесса.
Читатель извлекает одно сообщение и изменяет состояние на "A" ctive, пока он работает на нем. Когда это будет сделано, он изменит состояние на "C". Он может удалить сообщение или нет в зависимости от того, хотите ли вы сохранить контрольный журнал. Сообщения состояния = 'N' вытягиваются в порядке MsgType/Timestamp, поэтому есть индекс в MsgType + State + CreateTime.
Варианты:
Состояние для "E" rror.
Колонка для кода процесса чтения.
Временные метки для переходов состояний.
Это обеспечило приятный, масштабируемый, видимый и простой механизм для выполнения ряда вещей, подобных описанию. Если у вас есть базовое понимание баз данных, оно довольно надежное и расширяемое.
Код из комментариев:
CREATE PROCEDURE GetMessage @MsgType VARCHAR(8) )
AS
DECLARE @MsgId INT
BEGIN TRAN
SELECT TOP 1 @MsgId = MsgId
FROM MsgQueue
WHERE MessageType = @pMessageType AND State = 'N'
ORDER BY CreateTime
IF @MsgId IS NOT NULL
BEGIN
UPDATE MsgQueue
SET State = 'A'
WHERE MsgId = @MsgId
SELECT MsgId, Msg
FROM MsgQueue
WHERE MsgId = @MsgId
END
ELSE
BEGIN
SELECT MsgId = NULL, Msg = NULL
END
COMMIT TRAN
Ответ 2
Лучший способ реализовать очередь заданий в системе реляционных баз данных - это использовать SKIP LOCKED
.
SKIP LOCKED
- это опция получения блокировки, которая применяется как к блокировкам чтения/обмена (FOR SHARE
), так и блокировки записи/эксклюзивности (FOR UPDATE
) и в настоящее время широко поддерживается:
- Oracle 10g и более поздние версии
- PostgreSQL 9.5 и более поздние версии
- SQL Server 2005 и более поздние версии
- MySQL 8.0 и более поздние версии
Теперь предположим, что у нас есть следующая таблица post
, которая используется в качестве очереди заданий:
CREATE TABLE post (
id int8 NOT NULL,
body varchar(255),
status int4,
title varchar(255),
PRIMARY KEY (id)
)
Столбец status
используется как Enum со значениями PENDING (0), APPROVED (1) или SPAM (2).
Если у нас есть несколько одновременно работающих пользователей, пытающихся модерировать записи post
, нам нужен способ скоординировать их усилия, чтобы два модератора не просматривали одну и ту же строку post
.
Итак, SKIP LOCKED
это именно то, что нам нужно. Если два одновременно работающих пользователя, Алиса и Боб, выполняют следующие запросы SELECT, которые блокируют только записи записей, одновременно добавляя параметр SKIP LOCKED
:
[Alice]:
SELECT
p.id AS id1_0_,1
p.body AS body2_0_,
p.status AS status3_0_,
p.title AS title4_0_
FROM
post p
WHERE
p.status = 0
ORDER BY
p.id
LIMIT 2
FOR UPDATE OF p SKIP LOCKED
[Bob]:
SELECT
p.id AS id1_0_,
p.body AS body2_0_,
p.status AS status3_0_,
p.title AS title4_0_
FROM
post p
WHERE
p.status = 0
ORDER BY
p.id
LIMIT 2
FOR UPDATE OF p SKIP LOCKED
Мы видим, что Алиса может выбирать первые две записи, а Боб выбирает следующие две записи. Без SKIP LOCKED
запрос на блокировку Боба будет блокироваться, пока Алиса не снимет блокировку с первых двух записей.
Подробнее о SKIP LOCKED
читайте в этой статье.
Ответ 3
Как возможное изменение технологии, вы можете использовать MSMQ или что-то подобное.
Каждый из ваших заданий/потоков может запрашивать очередь сообщений, чтобы узнать, доступно ли новое задание. Поскольку действие чтения сообщения удаляет его из стека, вы гарантируете, что только одно задание/поток получит сообщение.
Конечно, предполагается, что вы работаете с платформой Microsoft.
Ответ 4
Вместо того, чтобы иметь владельца = null, когда он не принадлежит, вы должны установить его вместо поддельной записи. Поиск нулевого значения не ограничивает индекс, вы можете завершить сканирование таблицы. (это для оракула, SQL-сервер может быть другим)
Ответ 5
Вы пытаетесь реализовать антипаттерн "База данных как IPC". Посмотрите, чтобы понять, почему вы должны правильно пересмотреть свое программное обеспечение.