Лучший способ использовать таблицу БД в качестве очереди заданий (a.k.a пакетная очередь или очередь сообщений) - PullRequest
15 голосов
/ 18 ноября 2008

У меня есть таблица базы данных с ~ 50K строк в ней, каждая строка представляет работу, которая должна быть выполнена. У меня есть программа, которая извлекает работу из БД, выполняет работу и помещает результат обратно в БД. (эта система работает прямо сейчас)

Теперь я хочу разрешить нескольким задачам обработки выполнять задания, но быть уверенным, что ни одна задача не выполняется дважды (из-за проблем с производительностью, которые не вызовут другие проблемы). Поскольку доступ осуществляется посредством sproce, мой текущий способ заключается в том, чтобы заменить упомянутую sproce чем-то, что выглядит примерно так

update tbl set owner=connection_id() where avalable and owner is null limit 1;
select stuff from tbl where owner = connection_id();

КСТАТИ; Задачи работника могут обрывать связь между поиском работы и отправкой результатов. Кроме того, я не ожидаю, что БД приблизится к тому, чтобы стать узким местом, если я не испорчу эту часть (~ 5 заданий в минуту)

Есть ли проблемы с этим? Есть ли лучший способ сделать это?

Примечание: «База данных как шаблон IPC» здесь только немного уместна, потому что 1) Я не делаю IPC (нет процесса, генерирующего строки, все они уже существуют прямо сейчас ) и 2) основной недостаток, описанный для этого антишаблона, заключается в том, что он приводит к ненужной загрузке БД, поскольку процессы ожидают сообщений (в моем случае, если сообщений нет, все может завершиться, как все сделано)

Ответы [ 5 ]

13 голосов
/ 18 ноября 2008

Вот что я успешно использовал в прошлом:

Схема таблицы 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-к-n могут быть вставлены, а процессы 0-к-n могут считывать и обрабатывать сообщения. Каждый процесс чтения обычно обрабатывает один тип сообщения. Для балансировки нагрузки может быть запущено несколько экземпляров типа процесса.

Считыватель извлекает одно сообщение и изменяет состояние на «А», пока работает над ним. Когда это сделано, он меняет состояние на «C». Он может удалить сообщение или нет в зависимости от того, хотите ли вы сохранить контрольный журнал. Сообщения о состоянии = 'N' извлекаются в порядке MsgType / Timestamp, поэтому есть индекс для MsgType + State + CreateTime.

Варианты:
Состояние для "E" rror.
Столбец для кода процесса Reader.
Отметки времени для переходов между состояниями.

Это обеспечило хороший, масштабируемый, видимый, простой механизм для выполнения ряда вещей, которые вы описываете. Если у вас есть базовые знания о базах данных, они довольно надежны и расширяемы.


Код из комментариев:

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
1 голос
/ 16 апреля 2019

Лучший способ реализовать очередь заданий в системе реляционной базы данных - использовать 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_,

    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 запрос на получение блокировки Боба будет блокироваться, пока Алиса не снимет блокировку с первых 2 записей.

Подробнее о SKIP LOCKED читайте в этой статье .

1 голос
/ 18 ноября 2008

Как возможное изменение технологии, вы можете рассмотреть возможность использования MSMQ или чего-то подобного.

Каждое из ваших заданий / потоков может запрашивать очередь сообщений, чтобы узнать, доступно ли новое задание. Поскольку при чтении сообщения оно удаляется из стека, вам гарантировано, что только одно задание / поток получит сообщение.

Конечно, это предполагает, что вы работаете с платформой Microsoft.

0 голосов
/ 18 ноября 2008

Вместо того чтобы иметь owner = null, если он не принадлежит, вместо этого вы должны установить для него фиктивную запись nobody. Поиск нулевого значения не ограничивает индекс, возможно, вам придется сканировать таблицу. (это для оракула, SQL-сервер может отличаться)

0 голосов
/ 18 ноября 2008

Вы пытаетесь внедрить антипаттерн "База данных как IPC". Посмотрите, чтобы понять, почему вы должны подумать о редизайне своего программного обеспечения.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...