Производительность Hierarchical Select

Главные вкладки

Аватар пользователя kirill_dan kirill_dan 4 апреля 2015 в 22:46

Всем привет. Есть проблема. Что на локалке, что на ВПС, разницы нет. Проц I7 4200, Ram 16G, SSD.

Есть иерархическая таксономия: страны/регионы/города. Всего 150 тыс. записей. При добавлении новой ноды, когда происходит первая загрузка формы, и еще когда выбираем регион и начинает работать AJAX, то тормоза такие, что можно смело идти кофе готовить. Уходит по 20-50 секунд на то, чтобы подгрузить нужный регион с городами. Это полный пипец. Виджеты пробовал разные, толку от их замены ноль. Самое стабильное работает на HS, но тормоза просто жуть. Профайлер говорит, что подавляющее количество времени уходит на формирование родительских терминов и children терминов. 150 тыс. записей - это по сути полная фигня, но базу ложит так, что просто капец.

У кого есть опыт с такой проблемой, кто что может посоветовать? В качестве фронтэнда стоит nginx, апач пашет, как модуль fastcgi. Памяти под срипты выделено 1 Гиг. Настройки базы стоят с приличным выделением памяти. Я немного в замешательстве.
Сайт автора http://best-house.org

Комментарии

Аватар пользователя kirill_dan kirill_dan 4 апреля 2015 в 23:33

RxB wrote:
HS он такой, помноженный на ядрёную таксономию.
https://www.drupal.org/project/shs пробовали?

Пробовал. Он тоже уходит в даун, еще и бодрее чем HS. И еще два подобных виджета с drupal.org пробовал. Вся беда в том, что они используют taxonomy_get_tree. А после этого сервер уходит в полный ступор при таком количестве записей. Как бы свой виджет не пришлось писать, чего уж совсем не хочется, по причине того, что и без этого писанины дофига и больше.

Аватар пользователя Sky Cat Sky Cat 5 апреля 2015 в 2:28

Очевидно, что в данном случае таксономия не подходит совершенно. 150 тыс. терминов - это очень много. Особенно, если термины имеют иерархию. И если в случае словаря тэгов производительности хватает, то с деревом терминов начинаются проблемы.
Hierarchical Select вызывает функцию _hs_taxonomy_hierarchical_select_get_tree() (аналог taxonomy_get_tree) в цикле при каждом запросе (вроде бы, не уверен). В общем, как вы и пишете, полная засада.

Предложу несколько решений в порядке убывания производительности
1. Использовать Фиас (по крайней мере, для России). Смотрите сами: для областей, районов, населенных пунктов и улиц всего одна таблица. Каждый элемент, кроме верхнего, имеет поле parentguid, в котором хранится id вышестоящего элемента. В таком случае ваша задача сводится к одному простому запросу: SELECT all_children FROM {fias} WHERE parentguid = id вышестоящего элемента. Для объекта "Область" находим подчиненные объекты "Район", для объекта "Район" находим подчиненные объекты "Населенный пункт" и т. д. Для обратного построения иерархии можно использовать такую функцию:

  /**
   * Функция получает иерархию адреса от малого (дом, улица) к большому (область)
   * @param $guid
   * @param int $depth
   * @return array
   */
  public function getObjectFromGuidCycle($guid, $depth = -1) {
    static $children, $parents, $terms;
 
    $key = 'fias:address_tree:' . $guid;
 
    $redis = $this->connectRedis();
 
    if ($rs = $redis->get($key)) {
      $data = unserialize($rs);
    }
    else {
      $depth++;
 
      $sql = db_query("SELECT * FROM {d_fias_addrobj} WHERE aoguid = :aoguid AND actstatus = :actstatus", [
        ':aoguid' => $guid,
        ':actstatus' => 1,
      ]);
 
      foreach ($sql as $d) {
        $children['depth'][$d->parentguid][] = $d->aoguid;
        $parents['depth'][$d->aoguid][] = $d->parentguid;
        $terms['depth'][$d->aoguid] = $d;
      }
 
      $tree = array();
      if (!empty($parents['depth'][$d->aoguid])) {
        foreach ($parents['depth'][$d->aoguid] as $child) {
          $term = clone($terms['depth'][$d->aoguid]);
          $term->depth = $depth;
 
          unset($term->parentguid);
          $term->parents = $parents['depth'][$d->aoguid];
 
          $tree[] = $term;
          if (!empty($children['depth'][$child])) {
            $tree = array_merge($tree, Fias::getObjectFromGuidCycle($child, $depth));
          }
        }
      }
 
      $redis->set($key, serialize($tree), YARCOM_FIAS_CACHE_EXPIRE);
      $data = $tree;
 
    }
 
    return $data;
  }

Она для 8 друпала, но ее можно без труда адаптировать и к 7 версии.

2. Использовать Sphinx (или другой поисковик, который позволяет искать напрямую в базе). Собственно, Sphinx можно использовать вместе с первым пунктом. Получится как здесь, например: https://dadata.ru/suggestions/#demo
Если настроить Sphinx на существующую таксономию, то производительность уже скачкообразно вырастет.

3. Использовать Redis или Memcached для кеширования иерархических деревьев. Конечно, придется немного поковыряться в модуле Hierarchical Select (а конкретно в функции _hs_taxonomy_hierarchical_select_get_tree()), но результат будет виден уже после первого холодного запуска. В первый раз построится все дерево, затем данные будут браться их кеша по ключу. В качестве ключа можете использовать tid термина. Данный пункт очень хорошо себя показал в связке с первыми двумя. Но можно использовать и как отдельное решение.

4. Сделать собственную формочку выбора адресных объектов с быстрыми и легкими ajax-запросами. И еще можно сделать отдельную таблицу (таблицы), куда перенести всю адресную информацию. Прямые запросы к такой простой таблице будут намного быстрее taxonomy_get_tree() с кучей джойнов и проверок прав доступа к терминам.

5. Отключить встроенное кэширование Hierarchical Select (как бы странно это ни звучало). Помнится, у нас как раз была проблема с этим модулем именно из-за встроенного кэширования. Как хорошо, что мы от него избавились.
Я уже не помню точно, где в коде его отключать, но начать можете с этого файла: http://cgit.drupalcode.org/hierarchical_select/tree/hierarchical_select_...

Аватар пользователя Sky Cat Sky Cat 5 апреля 2015 в 2:30

В общем, посыл моего комментария простой: не используйте Hierarchical Select на таком количестве терминов. Такими темпами дойдете до того, что построение иерархии у вас будет по таймауту отваливаться. Да и пользователям ждать 20-30 секунд для выбора адреса вряд ли нравится.

Аватар пользователя kirill_dan kirill_dan 5 апреля 2015 в 14:58

Sky Cat wrote:
В общем, посыл моего комментария простой: не используйте Hierarchical Select на таком количестве терминов. Такими темпами дойдете до того, что построение иерархии у вас будет по таймауту отваливаться. Да и пользователям ждать 20-30 секунд для выбора адреса вряд ли нравится.

Спасибо за советы. Мне нравится пока вариант своей формы со своими ajax запросами. И так же вариант кэширования всей структуры словаря. Спасибо за очень развернутые советы. Буду бороться.

Аватар пользователя kirill_dan kirill_dan 5 апреля 2015 в 22:58

Sky Cat wrote:
Попробуйте для формы Select2. Очень гибкий и мощный скрипт для выпадающих списков и ajax.

Спасибо большое, попробую.

Глянул возможности библиотеки - это просто СУПЕР! Спасибо.

Аватар пользователя kirill_dan kirill_dan 5 апреля 2015 в 15:07

duozersk wrote:
Попробуйте заюзать https://www.drupal.org/project/taxonomy_edge - должно помочь с производительностью taxonomy_get_tree()

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

Аватар пользователя kirill_dan kirill_dan 5 апреля 2015 в 15:32

Отвечаю сам себе и тем, кому интересно. Смысл модуля taxonomy_edge в том, чтобы оптимизировать вывод терминов таксономии в блоках меню и т.п. Добиваются они этого тем, что вводят промежуточную таблицу с более эффективным хранением иерархии и переопределения функции в модуле таксономии на свою, чтобы не вызывать taxonomy_get_tree. В моем конкретном случае это мне не годиться. Так как у меня тупняк таксономии наступает при выводе ее в зависимых полях, а не при полном вываливании в меню или блок.

Аватар пользователя kirill_dan kirill_dan 5 апреля 2015 в 15:17

duozersk wrote:
Попробуйте заюзать https://www.drupal.org/project/taxonomy_edge - должно помочь с производительностью taxonomy_get_tree()

Обратил внимание, что модуль создан Berlingske Media (Датский Медиа холдинг, уже Бельгийский). У меня там друг работает. Так они там в иерархии миллионы записей используют. Но у них там все построено на шестерке и такой трэш в решениях и коде, что разработчик, который привык строить грамотную архитектуру и писать простой код, сходит с ума очень быстро!

Аватар пользователя duozersk duozersk 5 апреля 2015 в 20:05

Хм... вы вроде как сами говорили, что у вас тупняки начинаются, когда зовется taxonomy_get_tree() - и предложенный модуль как раз и должен ускорять эту функцию (так как патч добавляет в самое её начало вызов taxonomy_edge_get_tree() - http://cgit.drupalcode.org/taxonomy_edge/tree/taxonomy-7.12.patch?h=7.x-1.x) - и какая разница откуда она зовется, из блоков, меню, или из виджетов филдов. Или я что-то не так понимаю?

Аватар пользователя Sky Cat Sky Cat 5 апреля 2015 в 21:02

Если уж на то пошло, то убрав кусок кода ниже из taxonomy_get_tree() и его клонов (taxonomy_edge_get_tree_optimized(), _hs_taxonomy_hierarchical_select_get_tree() и т. д.), мы упростим запрос и он должен выполняться значительно быстрее. Естественно, нужно понимать, для чего это нужно и какие последствия могут быть. Но запросы точно быстрее станут. И чем больше объем обрабатываемых терминов, тем более заметен будет эффект.

->addTag('translatable')->addTag('term_access')

Аватар пользователя kirill_dan kirill_dan 5 апреля 2015 в 22:37

Sky Cat wrote:
Если уж на то пошло, то убрав кусок кода ниже из taxonomy_get_tree() и его клонов (taxonomy_edge_get_tree_optimized(), _hs_taxonomy_hierarchical_select_get_tree() и т. д.), мы упростим запрос и он должен выполняться значительно быстрее. Естественно, нужно понимать, для чего это нужно и какие последствия могут быть. Но запросы точно быстрее станут. И чем больше объем обрабатываемых терминов, тем более заметен будет эффект.

->addTag('translatable')->addTag('term_access')

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

Аватар пользователя kirill_dan kirill_dan 5 апреля 2015 в 22:44

duozersk wrote:
Хм... вы вроде как сами говорили, что у вас тупняки начинаются, когда зовется taxonomy_get_tree() - и предложенный модуль как раз и должен ускорять эту функцию (так как патч добавляет в самое её начало вызов taxonomy_edge_get_tree() - http://cgit.drupalcode.org/taxonomy_edge/tree/taxonomy-7.12.patch?h=7.x-1.x) - и какая разница откуда она зовется, из блоков, меню, или из виджетов филдов. Или я что-то не так понимаю?

Дело в том, что построением иерархической таксономии в добавлении ноды занимается модуль HS. Именно через его виджет и его функционал и происходит построение связных списков. Данный модуль использует много какие функции для своей работы. Он достаточно большой и "толстый". И вопрос там не только в одной функции taxonomy_get_tree(). Там целый набор "геморройных" функций.

P.S. И честно говоря, я не ярый сторонник кастомного патча ядра. Я за то, чтобы ядра вообще не касаться, а весь функционал строить исключительно на независимых расширениях. В том же Берлингское Медиа все ядро стоит на костылях. Все на заплатках, патчах и хаках. Вьюсы они вообще не используют. Все на собственных запросах. Не дай бог обновят ядро и весь их конгломерат рухнет.