Senior Pomidor
0,0
рейтинг
7 ноября 2011 в 09:16

Разработка → Пример использования Batch

Допустим, необходимо проделать некую операцию с большим количеством node и времени выполнения скрипта не хватает.
В этом случае можно увеличить время выполнения скрипта следующим образом:
set_time_limit($time); // $time in seconds

Это, мягко говоря, не самое правильное решение.
В этом случае на много правильнее реализовать это через batch.


Пользоваться batch крайне просто. Сейчас приведу пример.

Допустим мы имеем массив из nid:
Copy Source | Copy HTML
  1. $nids = array(
  2.   0 => nid,
  3.   1 => nid,
  4.   …
  5.   n => nid,
  6. );


Также есть некая функция, которая работает с этим массивом.
Для примера просто будем загружать и сохранять node.

Copy Source | Copy HTML
  1. function batch_example_nodes_resave($nids = array()){
  2.   foreach ($nids as $nid){
  3.     if (is_numeric($nid)){
  4.       $node = node_load($nid);
  5.       node_save($node);
  6.     }
  7.   }
  8. }


Теперь описываем функцию с batch.
Будем делить массив $nids на части (по 5 эллементов) и отправлять в batch_example_nodes_resave()

Copy Source | Copy HTML
  1. function batch_example_nodes_resave_batch($nids = array()){
  2.   $operations = array();
  3.   while($nids){
  4.     $nids_part = array_splice($nids,  0, 5);
  5.     $operations[] = array('batch_example_nodes_resave', array($nids_part ));
  6.   }
  7.   $batch = array(
  8.     'title' => t('Resave nodes'),
  9.     'operations' => $operations,
  10.   );
  11.   batch_set($batch);
  12.   batch_process();
  13. }


Теперь достаточно передать наш массив в batch_example_nodes_resave_batch() и посмотреть, как все красиво работает :)

p.s. Прошу прощения за дабл пост, хабр чето вообще себя не важно чувствует…
Дмитрий @mrded
карма
48,5
рейтинг 0,0
Senior Pomidor
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Спецпроект

Самое читаемое Разработка

Комментарии (16)

  • +3
    Много. Информативно. С примерами. Дабл пост не нужен был и вовсе.
  • +1
    Спасибо, полезная информация
  • +3
    Callback операции в качестве последнего аргумента должен принимать по ссылке переменную &$context.

    В принципе нет нужды как у Вас через цикл создавать отдельную операцию для каждой ноды.
    Корректнее будет передать в операцию весь массив а в конце выполнения операции устанавливать значение $context['finished'] равное количеству обработанных нод деленному на общую длину массива. Прогресс можно сохранять в массиве $context['sandbox']. Операция будет автоматически вызываться до тех пор пока $context['finished'] не примет в качестве значения единицу.

    Такой подход лучше
    1. С точки зрения идеологии и эстетики: т.к. в батче по хорошему должна быть 1 колбэк — 1 операция (За очень редким исключением. Допустим если надо выполнить операцию А потом Б а потом сново А например)
    2. С точки зрения производительности. Батч сохраняет последовательность операций в БД. А теперь представьте что у Вас рабочая ситуация и ноды достаются из БД в количестве 100500+ штук. Вот у Вас и запишется в ячейку 100500+ операций. А на самом деле нужно записать только 1.
    • +1
      А можно пример? А то с батч не работал, ваши слова звучат умно, но не совсем понятно :(
  • +1
    Надо учесть, что для batch в браузере должна быть открыта страница с запущенной задачей, что не всегда приемлемо, в этом случае лучше юзать очереди.
    Пример нужен более подробный, у вас очень кратко.
    Основная мысль batch — постоянный вызов обработчика для работы над списком задач. То есть нужно показать как брать и сохранять состояния.
    Приведу пример с рабочего проекта (сокращённый), импорт юзеров: pastebin.com/GHkVsaJi
  • 0
    Всегда указывайте версию, даже если не было изменений в api.
  • 0
    Очень нелогичный пример кмк.

    В данном случае лучше было не плодить батч-операции, а сохранять смещение в контексте и использовать одну операцию, в которой смотреть номер шага и брать $nid по смещению (в большинстве случаев это будет SELECT… LIMIT x, y). Разные операции обычно используются для разных действий. Например, resave_node и resave_users — это 2 операции, и каждая операция выполняется в несколько шагов.
    • 0
      3 коммент сверху :)
    • 0
      >сохранять смещение в контексте и использовать одну операцию, в которой смотреть номер шага и брать $nid по смещению
      Можно по подробнее, пожалуйста?
      • +1
        Представьте что у Вас есть некоторый запрос $sql через который вы получаете массив nid с которыми надо что-то сделать. Тогда перед установкой батча Вы делаете count query к этому запросу получая объем выборки $total. Передаете его в качестве аргумента в операцию.

        В самой операции делаете свой запрос $sql с LIMIT $a,$a+$k где
        $a — то сколько вы нод уже обработали (отступ)
        $k — сколько нод вы хотите обрабатывать за одну итерацию (так чтобы итерация занимала приемлимое для Вас время, например 10 секунд)

        Потом идет что-то типо такого блока
        if ($a + $k >= $total) {
          $context['finished'] = 1;
        }
        else {
        // Тут сохраняем отступ и достаем его потом в начале следущей итерации
          $context['sandbox']['offset'] = $a + $k;
        // Эта штука показывает прогресс. Принимает значения [0;1].
          $context['finished'] = ($a + $k) / $total;
        }


        Таким образом операция будет выполняться пока $context['finished'] не станет 1 при этом сообщая батчу о прогрессе после каждой итерации. Обратите внимание на то, что $context['sandbox'] не переходит между операциями.
  • 0
    А есть примеры/ссылки на саму технику (чтобы реализовать самому такую технологию)? Уж очень не охота в исходниках копаться.
    • +2
      Концепция довольно проста.
      Когда вы устонавливаете задание для батч, задание помещается в БД.
      Затем происходит переход на страницу с JavaScript кодом который начинает делать последовательные асинхронные запросы на сервер (в друпал к странице example.com/batch) передавая в качестве одного из параметров ID задания.
      Сервер выполняет часть этого задания (одну операцию или одну одну итерацию) и возвращает назад либо сообщение о том что перация выполнена либо процент выполнения.

      Таким образом вся магия заключается в том, что большое задание разбивается на несколько этапов и соотвествено несколько запросов, позволяя избежать при этом превышения максимального времени выполнения скрипта.
      Минус всего этого заключается в том, что при закрытии страничке в браузере с выполняющимся заданием работа механизма прекращается т.к. некому становиться посылать запросы на сервер. Этот эффект можно немного смягчить, если спланировать задание так, чтобы его результат постепенно сохранялся в БД (допустим помечать обработанные ноды, чтобы ни не попали в следущее задание). Тогда можно будет когда нужно прервать задание, а потом безболезненно и без потери результатов запустить его заново.
      • 0
        Спасибо, но интересует именно теоретическая основа данной технологии. Т.е. как, например, применить ее к процессу упаковки большого числа файлов? Добавлять по одному? А если попадется 1 большой файл, который не удастся запаковать в один присест?
        • 0
          В том то и суть, что если попадается задача, которую нельзя разбить на этапы, подход более чем бесполезен.
          Конкретно про упаковку файлов могу сказать, что в данном случае чаще всего просто пишется специальная програмка упаковщик. Задание для нее можете заносить в БД через сайт, а програмка сама оттуда будет его получать.
          Например подобный подход используется на Drupal.org для создания релизов модулей. С некоторой частотой (для дев релизов 2 раза в день, для стабильных релизов раз в 5 минут) программа паковщик проходится по git репозиториям модулей и создает скачиваемый архив.

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.