Рейтинг:1

Как я могу исправить «Разрешенный размер памяти исчерпан» при завершении партии?

флаг cn

Пакет обработал все элементы, но вместо отображения сообщения о завершении я вижу ошибку «Разрешенный размер памяти 536870912 байт исчерпан».

Когда я отлаживаю код, я замечаю, что Drupal загружает каждый обработанный блок после пакетного завершения (ContentEntityBase->__construct). Я не могу понять, почему Drupal это делает.

Структура кода:

Это пользовательский модуль с классом формы и пакетными функциями в custom_module.module файл.При отправке формы модуль вызывает функцию создания пакета:

публичная функция submitForm (массив и $ форма, FormStateInterface $ form_state) {
  custom_module_make_batch();
}

Функция custom_module_build_batch получает идентификаторы пользовательских блоков (4000 и более) и генерирует пакет:

функция custom_module_make_batch()
{

  $ пакет = [];

  $items = get_blocks_ids();

  $batch = custom_module_generate_batch($items);
  пакет_набор ($ пакет);
}

функция custom_module_generate_batch($items)
{

  $операции = [];

  $operations_groups = array_chunk($items, 50);

  foreach ($operations_groups как $key => $operations_group) {
    $операции[] = [
      'пользовательский_модуль_batch_op',
      [$операционная_группа],
    ];
  }

  $ партия = [
    'операции' => $операции,
    'finished' => 'custom_module_batch_finished',
    'title' => 'Пользовательская партия',
    'init_message' => 'Пакетная обработка начинается.',
    'progress_message' => 'Обработано @current из @total частей.',
    'error_message' => 'В пакете произошла ошибка.',
  ];
  вернуть $пакет;
}

функция custom_module_batch_op($operations_group, &$context) {

  foreach ($operations_group as $key => $bid) {
  
    $block = \Drupal::service('entity.repository')->loadEntityByUuid('block_content', $bid);

    $block->field_name = $new_value;
    
    $блок->сохранить();
  }

}

функция custom_module_batch_finished ($ успех, $ результаты, $ операции)
{

  $messenger = \Drupal::messenger();
  если ($ успех) {
    // Здесь мы могли бы сделать что-то осмысленное с результатами.
    // Мы просто отображаем количество обработанных узлов...

    если (всего $) {
      $messenger->addMessage(t('Обработано результатов @count.', ['@count' => $total]));
    } еще {
      $messenger->addMessage(t('Нет элементов для переноса'));
    }

  } еще {
    // Произошла ошибка.
    // $operations содержит операции, которые остались необработанными.
    $ error_operation = сброс ($ операции);
    $мессенджер->добавитьсообщение(
      т(
        'Произошла ошибка при обработке @operation с аргументами: @args',
        [
          '@operation' => $error_operation[0],
          '@args' => print_r($error_operation[0], ИСТИНА),
        ]
      )
    );
  }
}
leymannx avatar
флаг ne
`$new_value` не определено. И лучше использовать `$block->set('field_MYFIELD', $new_value)`, чтобы установить значение или `$block->set('field_MYFIELD', [])`, чтобы очистить его.
Egor Elkin avatar
флаг cn
@leymannx спасибо, хорошо, я буду использовать $block->set() $new_value - это просто пример, это может быть массив: [ 'value' => 'Некоторые текс', 'формат' => 'обычный_текст', ];
apaderno avatar
флаг us
Кажется, что `get_blocks_ids()` загружает все элементы, что объясняет сообщение об ошибке. Пакетный обратный вызов выполняет запрос для получения необходимых элементов.Получение всех элементов и передача их в пакетах — это не то, как должна работать пакетная операция, поскольку пакетные операции выполняются, чтобы избежать использования всей доступной памяти и избежать тайм-аутов, вызванных тем, что PHP требует больше времени для обработки запроса, чем назначенный время.
Рейтинг:5
флаг us

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

$batch_builder = (новый BatchBuilder())
  ->setTitle(t('Удаление строк базы данных'))
  ->setFinishCallback('mymodule_finished_callback')
  ->addOperation('mymodule_delete_rows', []);

batch_set($batch_builder->toArray());

функция mymodule_delete_rows(&$context) {
  $connection = \Drupal::database();

  если (пусто($context['песочница'])) {
    $context['песочница']['прогресс'] = 0;
    $context['sandbox']['current_id'] = 0;
    $context['sandbox']['max'] = $connection
      ->query('SELECT COUNT(DISTINCT [id]) FROM {example}')
      ->fetchField();
    $context['результаты'] = 0;
  }

  $лимит = 5;
  $результат = $соединение
    ->выбрать('пример')
    ->условие('id', $context['песочница']['current_id'], '>')
    ->orderBy('id')
    -> диапазон (0, $ предел)
    ->выполнить();

  foreach ($ результат как $ строка) {
    $context['песочница']['прогресс']++;
    $контекст['результаты']++;
    $context['sandbox']['current_id'] = $row->id;
    
    if (!empty($row->title) && is_numeric($row->title[0])) {
      $connection->удалить('пример')
        ->условие('id', $строка->id)
        ->выполнить()
    }
  }
  if ($context['sandbox']['progress'] != $context['sandbox']['max']) {
    $context['finished'] = $context['sandbox']['progress'] / $context['sandbox']['max'];
  }
}

function mymodule_finished_callback($успех,$результаты,$операции,$прошло) {
  если ($ успех) {
    $message = \Drupal::translation()->formatPlural($results, 'удалено @count строк.', '@count строк удалено.');
  }
  еще {
    $message = t('Завершено с ошибкой.');
  }
  \Drupal::messenger()->addMessage($message);
}

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

  • $контекст['завершено'] говорит Drupal прекратить вызывать обратный вызов пакетных операций, когда его значение равно 1 или $контекст['завершено'] не установлен
  • $контекст['результаты'] передается обратному вызову, вызываемому при выполнении пакетных операций, в качестве второго параметра
  • $контекст['сообщение'] это текстовое сообщение, отображаемое на странице прогресса

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

Справка

Egor Elkin avatar
флаг cn
«Кажется, что код использует всю разрешенную память, потому что он сначала загружает все объекты, а затем передает их идентификаторы обратному вызову операции. Это не то, как следует реализовывать пакетные операции». - нет, функция get_blocks_ids() получает идентификатор блока из БД, чтобы оптимизировать скорость и использование памяти - Пожалуйста, прочтите тему еще раз, я получаю сообщение "Разрешенный размер памяти 536870912 байт исчерпан" при пакетном завершении, когда скрипт обрабатывает все элементы. В любом случае, спасибо за ваши мысли @apaderno
apaderno avatar
флаг us
Тем не менее, память исчерпывается еще и потому, что вы загружаете идентификаторы сущностей и создаете операцию каждые 50 сущностей; с 4000 объектов это означает массив из 80 пакетных операций вместо одной пакетной операции.
Egor Elkin avatar
флаг cn
То же самое с одиночной пакетной операцией, я тестировал этот вариант. И снова: пакетная обработка всех элементов выполняется успешно, ошибка возникает при пакетной загрузке страницы.
apaderno avatar
флаг us
Если вы создаете массив, содержащий значение для каждого обрабатываемого элемента, это увеличит используемую память.
Egor Elkin avatar
флаг cn
Правильный.Это не решает мою проблему, но в любом случае уменьшите использование памяти - спасибо и ускорьте пакетную загрузку.
apaderno avatar
флаг us
К сожалению, ошибка *память исчерпана* появляется, когда код пытается выделить последний оставшийся байт доступной памяти, но код, потребляющий большую часть памяти, может быть кодом из другого модуля, включая модуль ядра Drupal. Учитывая код, показанный в вопросе, единственный возможный ответ: * Код неправильно реализует обратный вызов пакетной операции. * Он не может перечислить все возможные причины исчерпания памяти.
sonfd avatar
флаг in
Обратите внимание, что операция и завершенные обратные вызовы не могут находиться в файле mymodule.install (что вы можете попытаться сделать, потому что вы генерируете свой пакет из hook_install).

Ответить или комментировать

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