Верстакфорум практиков
рекламаiprazon: приватные серверные адреса IPv4 и SOCKS5, безлимитный трафик, бесплатный тест до 2 часов
ФорумСкрипты и автоматизация

Отложенные задачи: как не потерять при перезапуске

batchrunner
batchrunner
Участник
сообщений 470
с окт
8 июня, 11:17первое сообщение

Держу отложенные задачи в памяти процесса: словарь «время запуска, что сделать». Перезапустил службу, и всё пропало, а замечаешь это по отсутствию выгрузки на следующее утро.

Заводить под такое отдельную базу с сервером кажется перебором, задач штук триста в сутки. Кто как хранит отложенное, чтобы оно переживало перезапуск?

dobrynin_a
dobrynin_a
Знаток
сообщений 1690
с апр
15 ноября, 14:34#2

SQLite тут не база в смысле «поднимать сервер», это файл рядом со скриптом. Три сотни задач в сутки он даже не заметит.

CREATE TABLE IF NOT EXISTS zadachi (
  id     TEXT PRIMARY KEY,
  kogda  INTEGER NOT NULL,
  telo   TEXT NOT NULL,
  vzyato INTEGER DEFAULT 0,
  gotovo INTEGER DEFAULT 0
);
CREATE INDEX IF NOT EXISTS i_kogda ON zadachi(kogda, gotovo);

Выборка на каждом тике одна: SELECT * FROM zadachi WHERE gotovo=0 AND kogda<=? ORDER BY kogda LIMIT 50.

Оксана В.
Оксана В.
Участник
сообщений 350
с дек
22 апреля, 17:51#3

Плюс за SQLite. Только сразу выставьте режим журнала, иначе на втором рабочем поймаете «database is locked» и будете думать, что файл сломался:

PRAGMA journal_mode=WAL;
PRAGMA busy_timeout=5000;
Ринат Ахметов
Ринат Ахметов
Участник
сообщений 700
с авг
1 сентября, 08:08#4

Хранилище тут половина дела. Вторая половина в том, что происходит с задачей, которую взяли в работу и не довели до конца.

Смотри, как оно ломается: скрипт выбрал задачу, проставил vzyato=1, пошёл выполнять и словил kill посреди сетевого запроса. Задача навсегда осталась взятой и никем не сделанной, в выборку она больше не попадёт. Поэтому вместо флага пишут срок аренды.

UPDATE zadachi
   SET vzyato = strftime('%s','now') + 300
 WHERE id = ? AND vzyato < strftime('%s','now');

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

Отсюда вытекает главное: любая задача может выполниться дважды. Значит писать её надо безопасной к повтору. У меня выгрузка идёт во временный файл и переезжает на место через rename, повтор просто перезапишет то же самое содержимое.

tmp = put + ".tmp"
with open(tmp, "w") as f:
    f.write(vygruzka)
os.replace(tmp, put)
batchrunner
batchrunner
Участник
сообщений 470
с окт
8 февраля, 11:25#5
Ринат Ахметов: любая задача может выполниться дважды

Вот это меня и пугало, потому и держал всё в памяти. Перепишу выгрузку через временный файл с переездом, там как раз половина задач такого рода.

skript_kid
skript_kid
Новичок
сообщений 54
с сен, второй сезон
15 июля, 14:42#6

а если задача письмо шлёт? повтор же второе письмо пошлёт

dobrynin_a
dobrynin_a
Знаток
сообщений 1690
с апр
22 декабря, 17:59#7

skript_kid, для таких заводят отметку о выполнении с тем же id, что и у задачи, и пишут её в одной транзакции с самим фактом отправки. Второй заход видит отметку и проходит мимо.

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

Кирилл Бакин
Кирилл Бакин
Знаток
сообщений 1560
с апр
1 мая, 08:16#8

Добавлю про сам тик. Опрос файла раз в секунду на трёх сотнях задач это ерунда, а вот когда таких служб на машине десяток, постоянные пробуждения жгут её впустую.

Считаю ближайший срок и сплю ровно до него, будильник переставляю при добавлении задачи ближе текущего:

путь отложенной задачи
путь отложенной задачи
Ринат Ахметов
Ринат Ахметов
Участник
сообщений 700
с авг
8 октября, 11:33#9

И сроки держи в UTC, иначе перевод стрелок разбудит тебя дважды за одну ночь.

batchrunner
batchrunner
Участник
сообщений 470
с окт
15 марта, 14:50#10

Собрал за выходные, отчитываюсь. SQLite с WAL, аренда на 300 секунд с продлением для долгих, отметка о выполнении в одной транзакции с отправкой, сроки в UTC, сон до ближайшего срока. Проверял грубо: убивал процесс посреди прогона три раза подряд, все задачи доехали, дублей ноль.

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