Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Job progress #14

Open
loveorigami opened this issue Apr 23, 2019 · 25 comments
Open

Job progress #14

loveorigami opened this issue Apr 23, 2019 · 25 comments

Comments

@loveorigami
Copy link

loveorigami commented Apr 23, 2019

Роман, подскажите, пожалуйста, и возможно ли, реализовать следующую логику.
Есть задача. Выполняется она длительное время (10-15 мин.).

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

class MyJob
{
protected $progress;

public function execute()
{
   $this->progress = 0;
   // job start
  $this->progress = 25;
  // complex logic
  $this->progress = 75;
  // simple logic
  $this->progress = 100;
}

public function getProgress():int
{
  return $this->progress;
}

}

В yiisoft/yii2-queue/src/cli/Queue.php:113 есть такой участок кода с событием self::EVENT_WORKER_LOOP

    protected function runWorker(callable $handler)
    {
         //....

        $exitCode = null;
        try {
            call_user_func($handler, function () use ($loop, $event) {
                $this->trigger(self::EVENT_WORKER_LOOP, $event);
                return $event->exitCode === null && $loop->canContinue();
            });
        } finally {
            $this->trigger(self::EVENT_WORKER_STOP, $event);
            $this->_workerPid = null;
        }

        return $event->exitCode;
    }

Смогу ли я через воркер считать значение $progress задачи и записать его в монитор.
И как именно?

Спасибо.

@zhuravljov
Copy link
Owner

Нет в этом модуле прогресс выполнения заданий не предусмотрен.
Ну и EVENT_WORKER_LOOP не то, что стоило бы для этого использовать. Это событие происходит в основном процессе воркера в цикле опроса очереди. Т.е. после того, как выполнилась одна задача, но перед тем, как начнет выполняться следующая.

@Insolita
Copy link
Contributor

Insolita commented Apr 23, 2019

технически это реализовать можно либо на уровне самого джоба, (или базового класса джобов)

class MyJob implements JoиInterface
   {
        private $progress;
        
        public function execute($queue)
        {
              try{
                   $this->executeInternal();
              }catch(\Throwable $e){
                    $this->saveProgress(..);
                     throw $e;
             }
        }
   }

либо поведением которое добавит свойство прогресса и будет записывать его по событиям завершения/ошибки или еще когда но главная проблема в том, что довольно проблематично добавить какие-то свои записи в таблицу текущего мониторинга - это надо перекрывать модель, свои миграции...
Де-факто кейсы с необходимостью записи какой-то текущей стадии и/или отчета выполнения очень частые, и было бы неплохо чтоб модуль поддерживал какое-то произвольное поле summary типа json/text в зависимости от бд куда можно было писать что-то в процессе выполнения без допольнительных шаманств с перекрытием или доп.таблицами.

@loveorigami
Copy link
Author

loveorigami commented Apr 23, 2019

Спасибо за идею!
Новые поля пришлось добавит, т.к. решал //-но еще одну задачу:

Определенный экшен генерирует около 100-800 задач, по которым необходимо знать их суммарное выполнение и блокировать повторный запуск.
Например, есть некий список товаров от разных поставщиков - более 15000 наименований. На все эти товары необходимо сформировать документы приема, сгруппированные по поставщикам. Формирование 1 документа занимает ок. 10 сек. А их может быть от 100 до 800 (один документ на job - задачу).

С job-ом я пока размышляю на предмет событий. Чтоб обработку можно было добавить в сам монитор
https://github.com/zhuravljov/yii2-queue-monitor/blob/master/src/JobMonitor.php#L72

@zhuravljov
Copy link
Owner

Де-факто кейсы с необходимостью записи какой-то текущей стадии и/или отчета выполнения очень частые, и было бы неплохо чтоб модуль поддерживал какое-то произвольное поле summary типа json/text в зависимости от бд куда можно было писать что-то в процессе выполнения без допольнительных шаманств с перекрытием или доп.таблицами.

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

Я обычное если нужен прогресс чего-либо решаю вопрос на уровне проекта.

@loveorigami
Copy link
Author

loveorigami commented Apr 23, 2019

В моем случае, как раз таки это и нужно для отладки.

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

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

@Insolita
Copy link
Contributor

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

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

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

@zhuravljov
Copy link
Owner

Я у себя стараюсь проектировать архитектуру так, чтобы лонг-ранов не было. Лонг-ран, как процесс, превращается в лонг-ран, как перечень джобов, которые нужно выполнить. Каждая итерация - отдельный джоб. Тогда и прогрессе легко отследить по соотношению того, сколько джобов выполнено и сколько осталось. Тут отсылка к еще недоработанному в виде расширения yii2-queue-chain. И перезапуск фейлов легко организовать штатными средствами очереди.

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

@Insolita
Copy link
Contributor

Insolita commented Apr 23, 2019

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

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

@zhuravljov
Copy link
Owner

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

Ну да, эта идея и лежит в основе queue-chain:
https://github.com/zhuravljov/yii2-queue-chain/blob/master/src/StorageInterface.php
Считаем сколько заданий группы поставлено в очередь и сколько уже выполнено. А прогресс - это соотношение этих двух счетчиков.

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

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

@Insolita
Copy link
Contributor

Insolita commented Apr 24, 2019

Вот снова нарисовалось - надо при фейле таска юзеру аккуратную ошибочку вывести... куда удобнее ее было бы класть в кастомное поле лога джоба, чем вставлять еще одно поле в модель-инициатора для текста ошибки (у нас связано с моделями).
Может, задумывалось оно как для отладки, но совсем с небольшим допилом оно приобретает дополнительный и весьма востребованный функционал, который заненадобностью можно и не использовать, нуллить значения по дефолту и все.. Может какой опрос на yii-шных ресурсах замутить? Мне почему-то кажется, что далеко не одна я такая извращенка и подобные возможности ожидают и другие... Или таки собраться и свой блек-джек мутить

@Insolita
Copy link
Contributor

https://github.com/zhuravljov/yii2-queue-monitor/blob/master/src/JobMonitor.php#L179 seems we can got result only for success execution. What about support special Exception type with additional result message for storing it as result for failed execution?

@zhuravljov
Copy link
Owner

@Insolita how it could be used?

@Insolita
Copy link
Contributor

class JobException extend \Exception
{
       public $result;
       public function __construct(string $message, array $result, int $code=0, $previous = null)
   {
          $this->result = $result;
          parent::__construct($message, $code, $previous);
     }
}
class MyJob implements JobInterface
   {    
        public function execute($queue)
        {
              try{
                   $this->executeInternal();
              }catch(\Throwable $e){
                     throw new JobException($e->getMessage(), ['some advanced data for result - like current step, user-friendly error, additional context.. '], 0, $e);
             }
        }
   }

@zhuravljov
Copy link
Owner

Если exception рассматривается как результат, то не проще ли обычным способом?

class MyJob implements JobInterface
{
    public function execute($queue)
    {
        try {
            return $this->executeInternal();
        } catch (\Throwable $e) {
            return ['some advanced data for result - like current step, user-friendly error, additional context.. '];
        }
    }
}

@Insolita
Copy link
Contributor

да но онож на retry не пойдет

@zhuravljov
Copy link
Owner

Верно, не пойдет. Я же и говорю про тот кейс, где исключение = результат.

@zhuravljov
Copy link
Owner

What about support special Exception type with additional result message for storing it as result for failed execution?

А стоит ли тогда создавать структурированное исключенние, если данные, которые туда будут передаваться, автоматика далее использовать не будет?
А если только для ручного контроля и анализа, то проще зашить их в message:

class JobException extends \Exception
{
    public function __construct(string $message, array $result, int $code=0, $previous = null)
    {
        $message .= "\n" . VarDumper::dumpAsString($result);
        parent::__construct($message, $code, $previous);
    }
}

@zhuravljov
Copy link
Owner

@Insolita кстати, на тему прогресса мысль пришла. Можно было бы JobInterface::execute() запускать как генератор. Тогда промежуточные этапы лонг-рана можно было бы во вне отправлять через yield.

@Insolita
Copy link
Contributor

Insolita commented Apr 25, 2019

А стоит ли тогда создавать структурированное исключенние, если данные, которые туда будут передаваться, автоматика далее использовать не будет?

Его как раз тогда можно будет залоггировать

имеется в виду что вместо https://github.com/zhuravljov/yii2-queue-monitor/blob/master/src/JobMonitor.php#L164

    public function afterError(ExecEvent $event)
    {
        $push = static::$startedPush ?: $this->getPushRecord($event);
        if (!$push) {
            return;
        }
        if ($push->isStopped()) {
            // Breaks retry in case is stopped
            $event->retry = false;
        }
        if ($push->last_exec_id) {
            ExecRecord::updateAll([
                'finished_at' => time(),
                'memory_usage' => static::$startedPush ? memory_get_peak_usage() : null,
                'error' => $event->error,
                'result_data' => $event->error instanceof JobException? $event->error->result:null,
                'retry' => $event->retry,
            ], [
                'id' => $push->last_exec_id
            ]);
        }
    }

А сейчас мы при ошибке в error получаем полный exception с трейсом который трудно препарировать, он норм для внтренней отладки, но его никому не покажешь

@Insolita
Copy link
Contributor

кстати, на тему прогресса мысль пришла.

Любопытно, но это на уровне queue надо разруливать...

@zhuravljov
Copy link
Owner

Его как раз тогда можно будет залоггировать

Смущает то, что JobException будет в неймспейсе мониторинга, и это спровоцирует пробрасывать связанность с мониторингом на уровень бизнес-логики. А хотелось бы оставаться в рамках концепции пассивного веб-интерфейса для наблюдения за очередями.

А сейчас мы при ошибке в error получаем полный exception с трейсом который трудно препарировать, он норм для внтренней отладки, но его никому не покажешь

Так его и не нужно препарировать. Если стоит такая задача, в которой нужна специфическая обработка отдельных типов исключений, лучше сделать отдельный обработчик для Queue::EVENT_AFTER_ERROR с сохранением промежуточных результатов в отдельное хранилище за пределами мониторинга.

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

@zhuravljov
Copy link
Owner

zhuravljov commented Apr 25, 2019

Если бы php допускал концепцию приватных классов то по задумке за пределами неймспейса yii\queue\monitor видно было только те классы, которые нужны для конфигурирования: Env, JobMonitor, WorkerMonitor, Module, а остальное, как в черном ящике, для внешнего использования не предусмотрено.

@Insolita
Copy link
Contributor

Insolita commented Apr 25, 2019

а если чекать исключение на предмет наличия property или метода getResultData ?

Вот сейчас я наконец поняла, что ты хочешь держать его с возможностью безболезненно отключить, или только в dev/staging подключать

@zhuravljov
Copy link
Owner

Вот сейчас я наконец поняла, что ты хочешь держать его с возможностью безболезненно отключить, или только в dev/staging подключать

Да, это ключевая идея. Что-то типа yii-debug, но для очередей.

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

@Insolita
Copy link
Contributor

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants