@nakem

Как реализовать алгоритм экспайринга элементов в базе данных?

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

Мое решение.
Я создаю мапу ключ-значение. Ключ - время, когда таска заэкспайрится, Значение - айди таски. Далее каждый раз, когда человек берет таск из бд, я в прикладе добавляю значения в мапу. Далее я запускаю джобу каждую секунду, которая чекает мапу на наличие ключей со значением настоящего времени time.Now(). Если такие имеются, то экспайрим таску в базе, добавляем такую же и тд. Если человек вовремя закинул результат таски, то просто удаляем значение из мапы.
Есть подводный камень, который я смог заметить "на берегу". Это если джоба не запустилась из-за чего-то. Чтоб не возникало проблем, мы просто сохраняем последний запуск джобы, если он раньше, чем секунду назад, то запускаемся еще и каждую секунду, в которые не запустились.
Я надеюсь, что нормально объяснил. Хотел бы как-нибудь улучшить алгоритм. Нагрузка будет.

UPD: Забыл сказать, что сейчас мое решение такое, что я просто проверяю при загрузке результата заэкспайрилась ли таска или нет. Но проблема в том, что результат может и не закинется человеком вовсе или он будет это долго делать. А мне надо, чтобы таска быстро в пул попала и ее мог взять другой человек, если она доступна.
  • Вопрос задан
  • 227 просмотров
Пригласить эксперта
Ответы на вопрос 4
@Akina
Сетевой и системный админ, SQL-программист.
По-моему, ты накрутил сверх меры. Всё решается куда проще.

Структура таблицы, максимально упрощённая:
CREATE TABLE tasks (
    id PRIMARY KEY,
    definition,
    performer_id REFERENCES performer (id)
    expired_at DATETIME
);

Взятие (параметры - id обработчика и id задачи):
UPDATE tasks
SET performer_id = @performer_idб
    expired_at  = NOW() + INTERVAL 'performing time'
WHERE ( expired_at IS NULL or expired_at < NOW() )
  AND ( id = @task_id )

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

performing time может либо поставляться снаружи как параметр, либо быть свойством задачи (с соотв. полем в структуре таблицы).
Ответ написан
@Akela_wolf
Extreme Programmer
Зачем так сложно?

В БД делаем поля
status - статус таска (NEW, COMPLETED)
assignee - исполнитель
scheduled - дата до которой исполнитель должен предоставить результат.

Когда выбираем список задач для взятия - используем условие
(status = NEW) AND (assignee IS NULL /*не назначена*/ OR scheduled < NOW() /*истекла*/)

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

И никаких демонов, тем более каждую секунду.
Ответ написан
wataru
@wataru Куратор тега Алгоритмы
Разработчик на С++, экс-олимпиадник.
То, что вы делаете, называется очередью событий.

По нормальному, это должен быть один поток с приоритетной очередью, который работает постоянно.

Он должен спать, но при получении сообщения о новой таске просыпаться, добавлять время в которое истекает следющая таска в приоритетную очередь и потом засыпать на время до минимального в приоритетной очереди.

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

Обычно такое реализуется через какой-то примитив событий: не знаю, что там есть в go. Должна быть функция ожидания события с таймаутом. Вот там надо ждать события, которое выстреливает при добавлении новой таски пользователю, а таймаут должен браться из приоритетной очереди.

При падении этого потока его можно перезапустить. При старте он должен получить из базы данных пока не истекшие таски и сложить их все в очередь и сразу удалить все с истекшем сроком.
Ответ написан
Комментировать
mayton2019
@mayton2019
Bigdata Engineer
Топик тегирован "Базами Данных". Какими - чорт его знает.

Поэтому есть следующие коробочные решения. Cassandra, Redis, Amazon DynamoDb. Все они поддерживают дополнительное поле TTL и удаляют записи автоматически без участия разработчика.

По поводу подводных камней о которых пишет автор. Это всё очень плохо и почти не работает в боевых условиях. Пока бэкап данных делается в обычном плановом режиме - никто не знает о существовании всяких там левых файлов на сервере приложений. Грубо говоря все думают что состояние системы (state) лежит в базе и только в базе. Поэтому попытка размазать состояние системы по нескольким нодам вычислительной сети приводит к сложным и трудноуловимым последствиям.
Ответ написан
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы