Достигая ограничений памяти PHP, перебирая результаты Drupal 8 EntityQuery. Как мне его опустить? - PullRequest
0 голосов
/ 22 января 2019

У меня есть конечная точка API D8, которая запрашивает определенный тип контента, применяет любые необязательные условия, преобразует результат в JSON и возвращает клиенту.Я обновил ограничение памяти PHP до 512M, и все еще работаю с ним.В Drupal всего 1500 записей, так что на самом деле не должно быть никаких причин, почему это так плохо (341 КБ на запись ?!).Если я просто продолжаю накачивать память, чтобы заставить ее работать, визуализированный JSON составляет менее 2 МБ.

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

Я предпринял несколько попыток сохранить его, например, пакетирование запросов, рефакторинг в функции и явный вызов gc_collect_cycles, но ничего не изменилось.

Как сохранить потребление памяти при итерациипо результатам Drupal EntityQuery?

  protected function get() {
    echo "memory (start): " . memory_get_usage() . "\n<br>";

    //some setup and validation

    $query = $this->build_query($params);
    echo "memory (build_query): " . memory_get_usage() . "\n<br>";

    $results = $query->execute();
    echo "memory (execute): " . memory_get_usage() . "\n<br>";

    $items = [];

    $chunk_size = 50;
    $chunks = array_chunk(array_values($results), $chunk_size);
    echo "memory (chunk): " . memory_get_usage() . "\n<br>";

    foreach ($chunks as $chunk) {
      $items = array_merge($items, $this->load_nodes($chunk));
      echo "memory (chunk loaded): " . memory_get_usage() . "\n<br>";
    }
    echo "memory (all loaded): " . memory_get_usage() . "\n<br>";

    $response = [ 'results' => $items ];
    return new ResourceResponse($response);
  }

  protected function load_nodes($ids) {
    $items = [];
    $nodes = node_load_multiple($ids);
    foreach ($nodes as $node) {
      $items[] = $this->transform($node); 
    }
    return $items;
  }

  protected function transform($array) {
    $new = [
      "field1" => $array['field1'],
      "field2" => $array['field2'],
      //... for about 30 more fields, with some processing/manipulation ...
    ];
    return $new;
  }

И вывод в отношении эхо-памяти:

память (начало): 28297032
память (build_query): 29984168
память (выполнение): 31004048
память (чанк): 31083864
память (чанк загружен): 42175976
память (чанк загружен): 50447792
память (загруженный чанк): 57609344
память (загруженный чанк): 66762688
память (загруженный чанк): 74555712
память (загруженный чанк): 86663016
память (загруженный чанк): 98514192
память (загруженный чанк): 110908336
память (загруженный чанк): 122792592
память (загруженный чанк): 134651328
память (загруженный чанк): 145622512
память (загруженный чанк): 156546072
память (загруженный чанк): 167805352
память (загруженный чанк): 178617040
память (загруженный чанк): 190400936
память (загруженный чанк): 201246256
память (загруженный чанк): 212387384
память (чанк загружен): 223756088
память (чанк загружен): 234898632
память (загруженный чанк): 246125624
память (загруженный чанк): 257136304
память (загруженный чанк): 268205304
память (загруженный чанк): 278744896
память (загруженный чанк): 289693184
память (загруженный чанк): 300491840
память (загруженный чанк): 310564624
память (загруженный чанк): 321204064
память (загруженный чанк): 333842760
память (загруженный чанк): 343723672
память (загруженный блок): 344960728
память (все загружено): 344960728

Не должно ли потребление памяти оставаться постоянным при каждой итерации load_nodes какGC очищает старые ссылки?

Вы заметите, что моя конечная точка заканчивается только с 344 МБ.Фактически ошибка генерируется где-то в ядре Drupal.Поскольку я хочу сохранить максимальную память PHP на уровне 128 МБ, мне все еще нужно освободить часть памяти.

Ответы [ 2 ]

0 голосов
/ 24 января 2019

Я думаю, что у вас неправильное представление о сборщике мусора в PHP.

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

Вы также можете проверить, как отключить некоторые кеши в drupal, это может немного помочь вам в зависимости от состояния кэша, используемого drupal

0 голосов
/ 24 января 2019

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

Из документации Drupal 8:

функция node_load_multiple

Загружает объекты узлов из базы данных.

Эта функция должна использоваться всякий раз, когда вам нужно загрузить более одного узел из базы данных. Узлы загружаются в память и не будут требует доступа к базе данных, если загружается снова во время того же запроса страницы. [источник]

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

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


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

$nodes = node_load_multiple($ids, NULL, TRUE);

Надеюсь, это поможет:)


EDIT:

Хммм, кажется, мы были на правильном пути, пытаясь сбросить кеш, но нам придется попробовать другой подход к его сбросу. Этот подход извлечен из устаревшей функции node_load().

Путь к классам в Drupal для метода альтернативного сброса кэша:

\Drupal::entityManager()->getStorage('node')->resetCache(array('NID'));

Исправленный скрипт будет выглядеть примерно так:

$query = \Drupal::entityQuery('node')
     ->condition($params);

$results = $query->execute();

$nids = array_keys($results);

foreach ($nids as $nid) {
    $node = \Drupal\node\Entity\Node::load($nid);

    // Do stuff with loaded node, ex:
    // print $node->title->value;

    // Now reset the cache with the legacy reset cache
    \Drupal::entityManager()->getStorage('node')->resetCache(array($nid));
}
...