(Умный) Планировщик заданий

zerkms

TDD infected
Команда форума
(Умный) Планировщик заданий

Здравствуйте, господа.

В замешательстве, как обозвать тему релевантнее %) Кто поправит - исправим на более корректную.

Заканчивая предисловия - начнём с конкретного сферического примера:

Есть некий сервис (интранет), представляющий собой наколенную нано-CRM: задачи, распределение, выполнение, проверка, радость.
Для сервиса нужно реализовать систему уведомлений о всяческих хитрых событий, как-то:

- программер не прочитал ни разу распределённый на него тикет - ежедневно ему + ежедневно боссу;
- программер долго выполняет тикет - раз в неделю;
итд итп

Уходя от этого сферического примера: в интернете мне попадался ряд сервисов (из последних навскидку - livemocha.com), которые, после некоторого периода неактивности начинают напоминать о себе, расспрашивая почему ты перестал заходить и какие же они клёвые (теперь стали).

Вопрос о реализации этой вещи. Навскидку в голову приходят 2 варианта. Начну с менее перспективного, на мой взгляд:

1.
Заводим таблицу, в неё пишем события (факты) уведомлений.
Пример workflow: по шедулеру дёргается приложение, которое находит пользователя, который был неактивен более месяца. Далее проверяем таблицу уведомлений, смотрим когда было отослано уведомление по событию "давно не появлялись онлайн", и если оно было давнее, чем месяц назад (предполагаем, что рассылать подобные вещи будем раз в месяц) - тогда шлём напоминалку + добавляем запись о событии в таблицу.

2.
(Вариант второй, более интересный, на мой взгляд)
Заводим аналогичную таблицу, НО, принципиальная разница в том, что события в неё пишем упреждающие.
Т.е. пользователь авторизовался - пишем в таблицу событие "мы о тебе соскучились" в момент времени NOW() + 1 MONTH, все старые уведомления этого типа удаляем (или делаем REPLACE, не важно - конкретная реализация неинтересна)
Затем шедулер проходится по этой таблице и выполняет те задания, которые уже пора выполнить. Выполненные задания удаляет или помечает флажком.


Мысли? Предложения?
 

baev

‹°°¬•
Команда форума
Честно говоря, не вижу здесь «теории» — речь идёт о вариантах практической реализации.
 

zerkms

TDD infected
Команда форума
Ок, уехали в общую тему.

Вопрос: как такие вещи правильно реализуются?

PS: очень странное замечание о теории, имхо, в любом случае любой вопрос по программированию подразумевает какую-то реализацию. Иначе ему место не в теории, а в оффтопе.
 

Adelf

Administrator
Команда форума
Во втором варианте... когда ты захочешь ввести эту фишку, то все пользователи, о которых мы уже сейчас должны будем соскучиться(если не зайдут они на сайт после введения фичи).. мы о них никогда и не соскучимся :)
Очень часто в таких "более интересных" вариантах попадаются логические бомбочки :), иногда ооочень неявные(не в пример той, вышеописанной).
 

zerkms

TDD infected
Команда форума
Adelf
С одной стороны:
в момент введения этого подхода (№2) можно разово прогнать задачу планирования для всех.
С другой стороны:
Вариант 1 действительно будет более гибкий в том плане, что:
- если мы изменим политику (время, способ, ...) выполнения события - тогда нам не нужно будет дожидаться следующего запуска;
- после перехода объекта в другое состояние (исполнитель закрыл таск -> таск ушёл на начальника для утрвеждение) нам не придётся подчищать запланированные, но не выполненные задания.

И всё таки - неужели всё так примитивно, и нет чего-то более красивого? :)
 

флоппик

promotor fidei
Команда форума
Партнер клуба
Правильный - первый вариант, т.к. в каждый конкретный момент времени (в нашем случае, в момент запуска шедулера) процесс имеет достаточно данных для принятия однозначного решения о поведении, и количество необходимых действий конечно, а главное - однозначно, имеем одну точку входа, имеем одну точку выхода, т.е. в случае ошибки ты точно знаешь где искать.
Второй вариант предполагает возможные потери информации о состоянии(два месяца назад не отработал процесс создающий будущее событие - узнаем только сейчас) и размазывание ответственности, что противоречит принципу разумного проектирования: одна сущность - один объект, в результате имеем огромную кучу проблем с отладкой, расширением, и естественным процессом разработки.

Ах, да. Это всего лишь мое мнение, конечно.
 

zerkms

TDD infected
Команда форума
флоппик
я таки надеялся на третий вариант, который "ваще-лучше-всех" :) первый - слишком "в лоб", не наш метод :)
 

флоппик

promotor fidei
Команда форума
Партнер клуба
А, хотел рассказать примерчик из практики. Была такая же почти мининаколенная нано ЦРМ, и была она event-based, и нужно нам было как раз таки создать эти самые уведомления. Тогда мы создали новый тип событий в диспетчере, обозвали их timer-based events и повесили маленький скриптик в крон, который собсно, райзил эти самые евенты, в диспетчере их ловили слушающие их задачки, и прекрасно все продолжило работать с минимальным вмешательством в потроха системы.
 

MiksIr

miksir@home:~$
В _теории_ первый и второй варианты не отличаются. На практике первый гибче, ибо дает лигко менять "дельты" времени и сразу получать результат. Просто его нужно немного додумать, как был додуман второй вариант в том смысле, какие события когда писать и какими флагами отмечать, что бы выборка была простая.
 

korchasa

LIMB infected
Интересная задачка. Второй вариант действительно более красивый. Да и недостатки можно уменьшить, например, введя правило уникальности пользователь-событие-объект-идентификатор объекта.

Насчет данных для решения, ИМХО, как раз наоборот - в первом случае нам эти данные надо собирать, во втором большая часть из них у нас уже будет. В примере с тикетом и программистом, который его не посмотрел, у нас уже на руках будет тикет.

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

zerkms

TDD infected
Команда форума
MiksIr
вообще, первый вариант не легче.
use case варианта 1 (пусть у нас есть нано-CRM):

1. кроном вызывается какой-то код
2. он проходит по всем таскам и ищет те, которые долго не обновлялись
3. затем ищет в таблице логов, как давно мы уведомляли. если давнее недели - уведомляем и добавляем новую запись
4. если у нас уведомлять нужно не только по таскам, но и по юзерам, например - что пора сменить пароль согласно политике безопасности - то нужно ещё и по таблице юзеров пройтись, проверить кто когда менял пароль (а для этого нужно ещё и поле "дата смены пароля" завести), потом снова в лог, потом принять решение


а теперь тот же сценарий для варианта 2:
1. при добавлении таска - сразу планируется, когда нужно будет уведомить
2. планировщик вызывает задачи, которые нужно выполнить NOW() >= when_to_do_something
3. в таблицах у нас НЕ ДОБАВЛЯЮТСЯ дополнительные поля, потому что сразу после смены пароля юзером - мы в планировщик добавляем задание "напомнить о необходимости смены пароля в дату NOW() + месяц"

так что тут ещё вилами на воде писано, что просто, а что сложно.

-~{}~ 05.04.10 00:20:

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

AmdY

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

zerkms

TDD infected
Команда форума
AmdY
"легко" :)
кому легко? с таким подходом планировщик должен знать обо всех сущностях + обо всех событиях, которые могут быть применены к этим сущностям.
я уже не говорю о том, что сам запрос на выборку "по каким тикетам нужно выслать напоминание" будет весьма непростой (для субд. чисто визуально синтаксически - простой, да, а вот выполняться будет "медленно")...

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

-~{}~ 05.04.10 00:59:

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

а запрос у нас будет:
SELECT ..., MAX(logs.event_time) FROM users LEFT JOIN logs USING (user_id) GROUP BY (!!!!) user_id

набросок запроса уже более чем неприятный.
 

Fortop

Новичок
1. кроном вызывается какой-то код
2. он проходит по всем таскам и ищет те, которые долго не обновлялись
3. затем ищет в таблице логов, как давно мы уведомляли. если давнее недели - уведомляем и добавляем новую запись
4. если у нас уведомлять нужно не только по таскам, но и по юзерам, например - что пора сменить пароль согласно политике безопасности - то нужно ещё и по таблице юзеров пройтись, проверить кто когда менял пароль (а для этого нужно ещё и поле "дата смены пароля" завести), потом снова в лог, потом принять решение
? зачем 3 пункт? это совсем отдельная задача.
4й не понятен вовсе.

1. кроном запустились.
2. смотрим есть задача - выполнили ее, если надо реинициализровали.

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

-~{}~ 04.04.10 17:11:

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

флоппик

promotor fidei
Команда форума
Партнер клуба
Интересная задачка. Второй вариант действительно более красивый. Да и недостатки можно уменьшить, например, введя правило уникальности пользователь-событие-объект-идентификатор объекта.
Чего хорошего в лютом оверхеде в виде кучи событий, которые могут не произойти вообще ?
кому легко? с таким подходом планировщик должен знать обо всех сущностях + обо всех событиях, которые могут быть применены к этим сущностям.
Tell, don't ask. Планировщик знает лишь, кого, как и в какое время (возможно, и при каких условиях) нужно дернуть, что бы он отреагировал о событии. Это посредник, сам он не выполняет непосредственно работы. Причем потребовать от него "разбудить в нужное время" должен сам обьект, которому это требуется, т.к. он об этом знает лучше всех.

-~{}~ 04.04.10 21:22:

Если хочется сделать второй вариант "красивым" - нужно его доэволюционировать до нормального диспетчера событий, который будет организовывать подписку обьектов на события, и собсно, их уведомление. А кто будет создавать эти события - другие обьекты, или же таймер - абсолютно не важно. Причем "таймером" в этом случае вполне может выступать libevent демон. Но об этом нам расскажет 440hz на DevConf::pHP() =)
 

zerkms

TDD infected
Команда форума
2. смотрим есть задача - выполнили ее, если надо реинициализровали.
что такое "смотрим есть задача"? вариант 1 не подразумевает никаких задач в базе.

Планировщик знает лишь, кого, как и в какое время (возможно, и при каких условиях) нужно дернуть
откуда?

Причем потребовать от него "разбудить в нужное время" должен сам обьект, которому это требуется, т.к. он об этом знает лучше всех.
как? т.е. если у нас есть миллион тасков - нужно каждый дёрнуть и спросить "а не нужно ли выполнить чего?"?.

есть подозрение, что последние 2 поста выпадают из контекста.

напоминаю задачу:

есть issue tracker. нужно: в случае, если ticket висит на исполнителе больше недели:
- отправить исполнителю уведомление
- отправить начальнику исполнителя уведомление
подобные действия выполнять раз в неделю

аналогично:
если ticket висит на начальнике больше суток - напомнить ему, что нужно тикет кому-то назначить. повторять раз в сутки.

м?
 

флоппик

promotor fidei
Команда форума
Партнер клуба
как? т.е. если у нас есть миллион тасков - нужно каждый дёрнуть и спросить "а не нужно ли выполнить чего?"?.
прочти, пожалуйста, что я написал.
Планировщик знает лишь, кого, как и в какое время (возможно, и при каких условиях) нужно дернуть, что бы он отреагировал о событии.
А сообщить ему эти данные (читай, зарегестировать евент в этом воркфлоу) должен собсно, обьект
 

Fortop

Новичок
zerkms
а посмотреть тот же taskscheduler под win или cron?
Откуда они знают что запускать?

есть issue tracker. нужно: в случае, если ticket висит на исполнителе больше недели:
если ticket висит на начальнике больше суток
Вот они твои два task

Первый запускается раз в день.
Второй раз в час/несколько часов.
Оба реинициализируются.
 

zerkms

TDD infected
Команда форума
флоппик
прочти, пожалуйста, что я написал.
будь поласковее?

А сообщить ему эти данные (читай, зарегестировать евент в этом воркфлоу) должен собсно, обьект
так ты определись, к какому варианту ты тяготеешь, к 1 (когда нет ивентов), или ко второму - когда ивенты, есть, но при этом ты же сам и возмущался:
Чего хорошего в лютом оверхеде в виде кучи событий, которые могут не произойти вообще ?
 

AmdY

Пью пиво
Команда форума
если делается crm, то в любом случае придётся вводить логирование, а то ждут длинные разговоры (а почему этого никто не заметил, откуда выросло такое число). а сложные запросы - хм, но это же не хайлод, где выборки только по ключу делать можно и то не много. хотя учитывая возможность не стесняться количества запросов к базе, запросы можно и упростить.
 
Сверху