Должен ли worker сам определять, обрабатывать ему задачу или нет?
С коллегой возник следующий спор.
Представим ситуацию.
Есть задача - неким способом обработать входной файл. Бизнес логика такая, что файл можно разбить на части и обрабатывать из независимо.
Архитектура следующая.
Обработчик файла делит его на куски и в очередь (в нашем случае в RabbitMQ) шлет сообщения типа "обработай кусочек вот этих данных". Worker'ы забирают задачи из очереди и трудятся. Причем самим Worker'ам нет необходимости знать что-то еще о других частях большого файла. Им достаточно их кусочка.
Обработав все кусочки у нас в результате складывается общий результат обработки исходного файла.
Файлы регулярно приходят от разных пользователей. Все обрабатывается и все довольны.
До этого момента все гладко.
Теперь про сложность.
Бизнес логика такая, что если пользователь отправил на обработку за короткий срок два входных файла, то результат обработки второго по времени файла затирает первый. По сути, первый можно было бы вообще не обрабатывать.
Но по хронологии событий в очереди уже есть задачи на обработку кусков первого файла. И в ту же очередь приходят задачи на обработку второго файла. Получается Worker'ы вхолостую перемолотят первый файл и тут же возьмутся за второй. Но это только часть проблемы.
Сейчас я постараюсь объяснить как можно проще вторую часть проблемы.
Как я выше написал, обработка кусков файла настолько независима, что как только один Worker обработал свою часть файла, то эта часть уже видна другим пользователям.
Упростим ситуацию.
Пусть входной файл можно разбить на две части: А и Б
После обработки бы получились результат А' и Б' который видят другие пользователи.
Представим, что пользователь с небольшим промежутком отправил на обработку два файла их двух частей:
А и Б
В и Г
Очередь будет такой
А Б В Г
Далее хронология такая:
А взял на обработку Worker1
Б взял на обработку Worker2
Worker1 справился и выдал результат А'
Worker2 задумался
В взял на обработку Worker3
Г взял на обработку Worker1
-----
Очередь пуста
-----
Worker3 справился и выдал результат B'
Worker1 справился и выдал результат Г'
---------
Клиенты видят результат В'Г' - все хорошо
Вспоминаем про Worker2 - он задумался и упал и сообщение (Б) обратно ВЕРНУЛОСЬ в очередь
Первый свободный Worker его подхватил и выдал результат Б' который перезатирает Г'
В результате пользователи видят B'Б' (один кусок от старого входного файла).
Надеюсь, что не сильно запутал.
И вот наконец-то мы добрались к сути вопроса! Как такое избежать?
Вариант 1:
Тот, кто кладет в очередь ждет, пока предыдущий файл полностью обработается. Но тогда Worker'ы как бы вхолостую перемолотят старые данные и тут же им пойдут задачи с новыми данными.
Вариант 2:
Каким-то способом изъять из очереди уже ненужные задачи.
Вариант 3:
Worker, получая задачу, как-то умеет определять, нужно ли обрабатывать этот кусок данных или он уже старый и неактуальный и тогда он ничего не делает и берет следующее сообщение из очереди.
Причем в описанном примере определить достаточно просто. В очередь сообщений можно вместе с информацией о куске, который нужно обработать передать ID файла (уникальный). И Worker может дернуть, например, какой-то REST сервис, передав туда ID файла и узнать - были ли более свежие файлы от этого пользователя? И если были, то уже нет смысла обрабатывать этот кусок файла.
Я склоняюсь к варианту 3. Но мой коллега говорит, что Worker должен быть максимально прост и никакой логики определения нужно ли обрабатывать задачу из очереди или нет в нем не должно быть. Т.е. взял задачу из очереди - молоти.
Для правильного вопроса надо знать половину ответа
Очередью должен управлять менеджер очереди, именно он знает от кого поступило новое задание и надо ли удалять необработанные фрагменты предыдущего задания из очереди. Worker просто получает очередное задание и не должен просматривать всю очередь, а без такого просмотра он не в состоянии определить, есть в очереди более свежее задание от того же пользователя или нет. Максимум - Worker может получать команду от менеджера на досрочное прерывание обработки, если обрабатываемая им задача уже не нужна.
Дмитрий Лабутин: Если взять именно RabbitMQ, то у него нет нормального способа отозвать сообщение из очереди, разве что использовать Management Plugin с Rest API. Но и у Workera в RabbitMQ нет способа просмотреть очередь и получить полный список сообщений, так что остаётся обрабатывать всё подряд.
Я это отлично понимаю. Поэтому и переспросил.
Просто без RabbitMQ придется логику очереди самому писать? Решать вопросы с тем, чтобы два Worker'а не схватились за одну задачу. Чтобы при падении Worker'а задачи снова появлялись в очереди. Не получится изобретение велосипеда?
Возможно есть готовое решение решение для организации очереди так, как требуется в моем случае?
Я в тексте вопроса дописал. В моем случае даже нет необходимости Worker'у просматривать всю очередь. Он может по-другому определить необходимо обрабатывать или нет.
В очередь сообщений можно вместе с информацией о куске, который нужно обработать передать ID файла (уникальный). И Worker может дернуть, например, какой-то REST сервис, передав туда ID файла и узнать - были ли более свежие файлы от этого пользователя? И если были, то уже нет смысла обрабатывать этот кусок файла.
Дмитрий Лабутин: Смотреть надо, что будет быстрее - обработать лишний файл или на каждое задание делать дополнительный запрос. Тут всё будет зависеть от количества таких повторных файлов и времени обработки/задержки на запросе.
Обработать лишний файл будет точно дольше, чем дополнительный вопрос. Тут скорее вопрос идеологический - этот лишний запрос как бы ломает изолированность Worker'а. Насколько это плохо? Вот в оценки этого плохо мы с коллегой и не сошлись в едином мнении.
Дмитрий Лабутин: Если один такой файл из тысячи в час, то это ещё вопрос, что лучше - делать тысячу лишних запросов (или десять тысяч, если каждый файл разбивается на десять кусков) или обработать один лишний файл.
Ну а изолированность Worker'а это хорошо, но нет смысла возводить её в абсолют, если решение с запросом экономит время, то его стоит использовать.