1. Главная
  2. Форумы
  3. Техподдержка Drupal
  4. Решение проблем

Как сгенерировать много терминов таксономии?

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

Знаю, что можно модулем Feeds, но он очень долго делает, около 10000 записей в час, в моем случае это очень долго. Есть более быстрый способ? Там термин и всего 2 поля, просто их много и часто добавляются новые, удаляются старые. C Feeds это очень долго.

Комментарии

Аватар пользователя kosskren kosskren 4 апреля в 8:17

Быстрый способ это прямой импорт записей в базу данных из дампа. Мне как-то нужно было загрузить на сайт 1 миллиард записей. Стандартными средствами CMS это очень долго. Я загружал пачками по 60 миллионов, в среднем на пачку уходило 7 минут.
Не знаю на сколько вам такое подходит, тут есть свои тонкости по реализации.

Аватар пользователя Delewo Delewo 4 апреля в 12:36

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

Аватар пользователя kosskren kosskren 4 апреля в 15:13
1

Мануалов нет) Надо только создать дамп с данными для записи в базу данных. Дамп это просто текстовый файл в нужном формате. Для примера назовем файл import.sql. Пример содержимого в файле:

INSERT INTO `post_statistics` (`post_id`, `statistic_entitie_id`, `value`, `updated_at`) VALUES
(6001,1,3923,1775397071),
(6001,2,1975,1775397071),
(6001,3,3482,1775397071),
(6001,4,1636,1775397071),
(6001,5,1314,1775397071),
== много строк с данными ==
(7000,30,1494,1775461871);

В примере:
post_statistics - название таблицы, куда вставляются данные
post_id, statistic_entitie_id и т.д. - это столбцы в таблице
(6001,1,3923,1775397071) - вставляемые данные. Последовательность такая же, как последовательность столбцов вот тут (`post_id`, `statistic_entitie_id`, `value`, `updated_at`)

Как подготовить такой файл?
У вас наверное имеется CSV или ексель файл, откуда вы берете термины. На основании этого файла создайте import.sql. Если не программируете, то можно через нейронку создать простенький скрипт, который возьмет скажем CSV, переберет его и создаст import.sql.

Зальем import.sql в базу
1. Подключимся по ssh к серверу, где расположена база.
2. Подключимся к серверу баз данных используя логин и пароль от пользователя сервера баз данных

=> mysql -uИМЯ_ПОЛЬЗОВАТЕЛЯ
=> введем пароль

=> use post_statistics; (выбрали базу)
=> source /var/dump/import.sql (запустили импорт)

Аватар пользователя OldWarrior OldWarrior 4 апреля в 15:58

kosskren wrote: В примере:
post_statistics - название таблицы, куда вставляются данные

Я бы при этом упомянул, что в случае, когда:

Delewo wrote: Там термин и всего 2 поля

то эти 2 поля (если они кастомные/дополнительные, а не основные/базовые для термина) будут храниться в отдельных таблицах. Стало быть, по факту нужно манипулировать минимум тремя таблицами: таблицей терминов и двумя таблицами полей.

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

Аватар пользователя kosskren kosskren 4 апреля в 16:23

Да, все верно. Я по этому написал в первом ответе, что там есть тонкости)

Последнее ID термина можно посмотреть в админке. Для импорта делаем у первого термина ID = ID последнего термина в админке+1, а остальные по порядку.

Добавлю, что если для терминов автоматически создается URL при добавлении, то нужно будет добавлять запись в таблицу с URL. Или из админки пересоздать URL для терминов, после их импорта.

Аватар пользователя Delewo Delewo 4 апреля в 15:44

ivnish wrote: Сгенерировать или импортировать?

Цель - импортировать, а как это будет реализовано, т.е. сама сгенерирует или ей надо предварительно сгенерировать - без разницы. Сгенерировать могу сам. Сейчас только заметил, что заголовок не совсем корректный, конечно же - импортировать)

Аватар пользователя Delewo Delewo 8 апреля в 23:28

Там есть таблицы taxonomy_term_field_data и taxonomy_term_field_revision. Их надо обе заполнять или можно обойтись только taxonomy_term_field_data?

И еще вопрос: в таблице path_alias поле uuid, как его генерировать?

Аватар пользователя kosskren kosskren 9 апреля в 7:58
1

taxonomy_term_field_revision это ревизии термина. По сути история изменения для термина. Предположу, что ее можно не заполнять, если не планируете восстанавливать какое-нибудь состояние у термина.

path_alias поле uuid, как его генерировать

Можно посмотреть в модуле, как uuid создается. Предпложу, что так:

<?php$uuid = \Drupal::service('uuid')->generate();?>

Еще можно так сделать, напрямую в дампе с данными.

INSERT INTO path_alias (uuid, path, alias, langcode, STATUS)
VALUES (UUID(), '/node/1', '/my-alias', 'ru', 1);
Аватар пользователя Delewo Delewo 11 апреля в 0:25

Генерация и тестовый импорт на 10 шт. проходят успешно, а когда заливаю настоящий дамп более 1 млн. записей, то получаю ошибку:

ERROR 2026 (HY000) at line 1 in file: /var/dump/dump.sql unexpected eof while reading

Никакого SSL у меня никогда не было, это локалхост на Debian. У меня MariaDB, ОЗУ достаточно, т.е. начинается импорт, в консоли вижу бегущие строки, типо дамп заливается, и когда доходит до последней строки, то ошибка - в базу ничего не заливается, ни одной строки. А 10 строк импорт - без проблем заливается. Может вы сталкивались с такой проблемой, как посоветуете ее решить?

Аватар пользователя kosskren kosskren 11 апреля в 9:55

Попробуйте принудительно отключить SSL через конфиг, на локальном хосте можно и без ssl обойтись. С MariaDB я не работал, но вроде как она по конфигу очень похожа ан MySQL.

[client]
skip-ssl = TRUE

или при подключении к серверу базы данных:
mariadb -u username -p --skip-ssl

Тут еще возможно что-то с самим файлом dump.sql. С подобной ошибкой не сталкивался.

Аватар пользователя Delewo Delewo 12 апреля в 4:00

Проблема все таки оказалась в нехватке ОЗУ, выделенной для виртуалки, заметил, когда открыл ее консоль. Увеличил и все нормально, 5 млн строк улетают за 2-3 минуты. А еще подскажите пожалуйста, как теперь правильно индексы обновить? Например, я залил строки в таблицу post_statistics, что теперь с ней делать?

Аватар пользователя kosskren kosskren 12 апреля в 7:48

Про какие индексы спрашиваете?

Если про индексы самих таблиц, то движок базы данных их автоматом пересчитывает при вставке данных в таблицу. При желании можно их пересоздать, вызвав последовательно команды: "DROP INDEX...", "CREATE INDEX..."

Аватар пользователя Delewo Delewo 14 апреля в 11:11

Я про то, чтобы теперь при обычном создании термина из админки, tid были бы по порядку, с учетом залитых строк. И еще вопрос: залил 20 млн. терминов в словарь, на сайте прогрузилось, закешировалось, открывается корректно и быстро. А в админку в этот словарь не могу войти, чтобы, например, посмотреть список и добавить новый. Сначала, то недостаток озу, время выполнения, то Apache, то Nginx, ну увеличил все что смог, озу 128 гб, теперь в логах сервера чисто, но не открывает админку, крутит часа 3 и сбрасывается, но повторюсь, в логах сервера уже чисто, в логах Друпал тоже. Остальные страницы открываются, в т.ч. админка...кроме словаря с 20 млн. терминами в админке. Даже не открывается страница редактирования термина, если сделать прямую ссылку, указав tid. Что можете посоветовать?

Аватар пользователя kosskren kosskren 14 апреля в 15:32
1

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

tid должен по порядку идти, это не от друпала зависит. В базе данных, столбце с tid это AUTO_INCREMENT. По этому при добавлении нового термина в админке и где угодно, у вас будет:
Новый tid = последний tid+1

И еще вопрос: залил 20 млн. терминов в словарь...

Надо смотреть, какой sql запрос выполняется. 20 млн строк это сильно))

Включите у сервера баз данных лог медленных запросов(slow_query_log=1) и поставьте время = 1 сек. к примеру. Это покажет, какой запрос тормозит.

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

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

Тут конкретику дать не могу, ситуация индивидуальна. Описал только общий план действий.

Аватар пользователя Delewo Delewo 14 апреля в 21:11

kosskren wrote: Надо смотреть, какой sql запрос выполняется. 20 млн строк это сильно))

Разве 20 млн. это прям уж много? Как же тогда работают милиардные базы? Просто тут такое впечатление, что как только зашел в словарь, он начинает все термины перебирать и пока не переберет, то не отобразит страницу. Проблемы была и на 16 тысячах...как можно сделать, например, чтобы отображал в админке в списке постранично, например 20 терминов на страницу? В этом случае пусть их хоть триллион будет, должно ведь нормально открываться, не надо же все термины перебирать, чтобы отобразить 20 шт на странице. А по умолчанию Друпал, я вообще не понимаю сколько отображает и как именно, такое впечатление, что рандом. Опять же в публичной части сайта он эти 20 млн. минут за 10 отобразил, закешировал и вообще нет проблем.

Аватар пользователя kosskren kosskren 14 апреля в 22:37

Разве 20 млн. это прям уж много?

Не много, если все оптимально. Надо смотреть запрос к базе. Там же при построении списка терминов, идет формирование вложенности, сортировка. Помимо этого идет запрос к получению URL терминов. Может, что еще под капотом делается, я не изучал.

Аватар пользователя OldWarrior OldWarrior 15 апреля в 1:12

Delewo wrote: Просто тут такое впечатление, что как только зашел в словарь, он начинает все термины перебирать и пока не переберет, то не отобразит страницу.

Как обычные запросы из PHP выполняются к этой таблице? В смысле - не из Друпала? Например, попробовать получить первые 100 записей из этой таблицы из любого PHP-скрипта - есть разница по времени? Ну или хотя бы из того же phpMyAdmin, в нём есть встроенный клиент для произвольных запросов к таблице.

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

Delewo wrote: Проблемы была и на 16 тысячах...как можно сделать, например, чтобы отображал в админке в списке постранично, например 20 терминов на страницу?

А разве в админке не так? По-моему (но точно не помню), дефолтное кол-во терминов словаря - 100 шт на страницу, дальше бьёт по страницам.

PS. Кстати, есть ещё такое на орге (настройка через Драш на 500 терминов на страницу):

drush config:set taxonomy.settings terms_per_page_admin 500

Не проверял, если что.

Аватар пользователя kosskren kosskren 15 апреля в 9:49

Из интереса глянул, как формируется страница с терминами в админке друпала. Если честно удивлен, вот так получаем термины словаря:

<?php
$tree
= $this->storageController->loadTree($taxonomy_vocabulary->id(), 0, NULL, FALSE);
?>

$taxonomy_vocabulary->id() - идентификатор словаря
0 - строим дерево для всего словаря
NULL - вернуть все уровни дерева
FALSE - термин целиком не загружается, только данные из таблицы taxonomy_term_data (хоть тут спасибо)

Смысла нет смотреть SQL запрос. Мне кажется, вам памяти не хватает.

В среднем одна строка taxonomy_term_data (tid, vid, name, langcode и т.д.) занимает около 100–150 байт. Для 20 млн строк это примерно 2–3 ГБ "чистых" данных в памяти.

Форма с терминами:
web/core/modules/taxonomy/src/Form/OverviewTerms.php -buildForm()

Аватар пользователя Andruxa Andruxa 15 апреля в 10:17

А чему тут удивляться?
Таксономия - это сущность, в которой бай дизайн реализована иерархичность: у каждого термина есть id родителя, и loadTree рекурсивно строит дерево, запрашивая потомков для каждого узла-родителя.
Если в словаре 20M терминов - нуивот.

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

Аватар пользователя Delewo Delewo 15 апреля в 15:53

OldWarrior wrote: Как обычные запросы из PHP выполняются к этой таблице? В смысле - не из Друпала? Например, попробовать получить первые 100 записей из этой таблицы из любого PHP-скрипта - есть разница по времени? Ну или хотя бы из того же phpMyAdmin, в нём есть встроенный клиент для произвольных запросов к таблице.

Попробовал, все происходит вполне быстро на мой взгляд, 20000 записей, около 5 секунд.

Аватар пользователя Delewo Delewo 15 апреля в 16:10

kosskren wrote: Смысла нет смотреть SQL запрос. Мне кажется, вам памяти не хватает.

В среднем одна строка taxonomy_term_data (tid, vid, name, langcode и т.д.) занимает около 100–150 байт. Для 20 млн строк это примерно 2–3 ГБ "чистых" данных в памяти.

Памяти она жрет, конечно много, но у меня 128 ГБ, я мониторю память, ее хватает. Вот все таки в ошибках Nginx нашел строчку, видимо вчера проглядел. Она всего одна, когда все рушится:

[error] 1171#1171: *117 upstream prematurely closed connection while reading response header from upstream

Такие настройки поставил на время тестов:

proxy_connect_timeout 100000000s;
proxy_send_timeout 100000000s;
proxy_read_timeout 100000000s;

Может еще надо что-то подкрутить?

Ведь на самом сайте я вывел представление, которое тоже эти 20 млн обрабытывает. Вот он несколько минут думает (около 5-7), кеширует и все ок. Далее сайт с этим словарем и в целом летает, а админка лежит)

Я вчера пробовал вообще все дополнительные модули удалить, вроде Metatag и пр. безрезультатно. Модуль Optimize Database Tables также пробовал.

<?php
$tree = $this->storageController->loadTree($taxonomy_vocabulary->id(), 0, NULL, FALSE);
?>

Я предполагал, что именно так и происходит, весь словарь перебирается. Может хук какой решит проблему?

Даже когда подсовываешь прямую ссылку, вроде:

/taxonomy/term/123321/edit?destination=/admin/structure/taxonomy/manage/moyslovarik/overview

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

Аватар пользователя kosskren kosskren 15 апреля в 17:50
1

Может хук какой решит проблему?

Сомневаюсь, чет не могу ничего вспомнить.

Может еще надо что-то подкрутить?

У вас же php-fpm за обработку php отвечает?

Если да, то в конфиге пула можете увеличить request_terminate_timeout. В целом посмотрите параметры связанные с таймингами и буферами(на память не помню). Они все перечислены в конфиге.

В конфиге ngnix можно увеличить - proxy_buffer_size, proxy_buffers, proxy_busy_buffers_size, proxy_max_temp_file_size.

Так же в секции location, которая у вас php обрабатывает, поднимите fastcgi_read_timeout.

В конфиге php наверное уже увеличили max_execution_time.

Аватар пользователя Delewo Delewo 15 апреля в 16:17

Andruxa wrote: А чему тут удивляться?
Таксономия - это сущность, в которой бай дизайн реализована иерархичность: у каждого термина есть id родителя, и loadTree рекурсивно строит дерево, запрашивая потомков для каждого узла-родителя.
Если в словаре 20M терминов - нуивот.

Имхо, не тот инструмент выбран - я сомневаюсь, что эти 20M терминов организованы в хоть какую-нибудь иерархию.
А если там (скорее всего) плоски

У меня все один родительский термин и остальные 20 млн дочерние термины на одном уровне. Мне иерархия там не нужна. Единственно, что еще, возможно влияет - каждый из этих 20 млн терминов имеет по 2 связи с терминами из других словарей. Может какой хук существует, чтобы переопределить логику отображения для словаря, чтобы не перебирал все термины?

Аватар пользователя OldWarrior OldWarrior 15 апреля в 18:09
1

Delewo wrote: Может какой хук существует, чтобы переопределить логику отображения для словаря, чтобы не перебирал все термины?

Переопределить класс формы/контроллера для этого роута. Т.е. написать свой класс на основе класса из ядра, убрав из него всё ненужное. Только не хук, а подписка через RouteSubscriber (метод ::alterRoutes)Либо на другом кастомном роуте/адресе поставить свой класс для администрирования терминов, а штатный роут не трогать, раз там адище творится. ))

Delewo wrote: Даже когда подсовываешь прямую ссылку, вроде:

/taxonomy/term/123321/edit?destination=/admin/structure/taxonomy/manage/moyslovarik/overview

То он не сразу ее открывает, а опять же такое впечатление, что начинает все термины в словаре перебирать.

У меня ощущение, что тут скорее что-то битое в БД или в сущностях. Да и, если честно, эта механика с прямым импортом структур данных сущностей в таблицы БД кажется - ну, как бы так себе, несколько сомнительной. Друпал частенько карает (причём в неожиданных местах) за всевозможные велосипеды и обходы штатных механизмов API.

Аватар пользователя Delewo Delewo 16 апреля в 4:44

kosskren wrote: У вас же php-fpm за обработку php отвечает?

Если да, то в конфиге пула можете увеличить request_terminate_timeout. В целом посмотрите параметры связанные с таймингами и буферами(на память не помню). Они все перечислены в конфиге.

В конфиге ngnix можно увеличить - proxy_buffer_size, proxy_buffers, proxy_busy_buffers_size, proxy_max_temp_file_size.

Так же в секции location, которая у вас php обра

До этого был Apache Handlers, вчера настроил PHP-FPM, долго крутил-вертел, но в итоге добился, чтобы ничего не вылетало. Но за 5 часов не открылся ни словарь, ни термин) Но не вылетело ничего, сам выключил) Свободной ОЗУ оставалось около 30 ГБ.

Аватар пользователя Delewo Delewo 16 апреля в 5:04

OldWarrior wrote: Переопределить класс формы/контроллера для этого роута. Т.е. написать свой класс на основе класса из ядра, убрав из него всё ненужное. Только не хук, а подписка через RouteSubscriber (метод ::alterRoutes)Либо на другом кастомном роуте/адресе поставить свой класс для администрирования терминов, а штатный роут не трогать, раз там адище творится. ))

Спасибо, буду как-то пробовать.

OldWarrior wrote: У меня ощущение, что тут скорее что-то битое в БД или в сущностях. Да и, если честно, эта механика с прямым импортом структур данных сущностей в таблицы БД кажется - ну, как бы так себе, несколько сомнительной. Друпал частенько карает (причём в неожиданных местах) за всевозможные велосипеды и обходы штатных механизмов API.

Возможно, конечно и битое, но это вынужденная мера, т.к. штатными механизмами API Друпала это очень долго, даже с помощью Drush - уже пробовал, долго. У меня бывают небольшие импорты на несколько тысяч, там я делаю с помощью Feeds в браузере или Feeds + Drush.

А так, вроде бы, импортировалось все корректно. По крайне мере все представления отображаются нормально и по прямой ссылке термины удаляются корректно. Сейчас вспомнил, я недавно импортировал с помощью Feeds и Drush около 7 млн терминов, так там тоже все висело, правда долго не разбирался, т.к. устал ждать эти 7 млн (а дальше все медленне и медленне было) и просто откатил все назад и начал настраивать прямой импорт.

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

1. Добавлять термины
2. Редактировать термины
3. Удалять термины

С удалением уже разобрался, осталось найти способ как добавлять и редактировать (но не через Feeds). Через Drush удаление не работает, пробовал. По прямой ссылке /admin/structure/taxonomy/manage/slovarik/add не добавляет (не открывает). Можете какие-то варианты подсказать?

Аватар пользователя OldWarrior OldWarrior 16 апреля в 15:46
1
Delewo wrote: С удалением уже разобрался, осталось найти способ как добавлять и редактировать (но не через Feeds). Через Drush удаление не работает, пробовал. По прямой ссылке /admin/structure/taxonomy/manage/slovarik/add не добавляет (не открывает). Можете какие-то варианты подсказать?


Так а какие тут могут быть варианты, кроме как писать свои формы хотя бы в простейшем виде? Поясните, что вы здесь имеете в виду.

Ну есть ещё надстройка над штатным модулем taxonomy - модуль Taxonomy Manager. У него собственный, и для некоторых случаев - более удобный, интерфейс добавления/редактирования/удаления сущностей терминов. Можно попытаться его проверить, вдруг с ним всё норм будет.

---

PS. По поводу этого:

Delewo wrote: У меня все один родительский термин и остальные 20 млн дочерние термины на одном уровне. Мне иерархия там не нужна.


Тогда вы обречены. Поскольку все 20 М терминов привязаны к одному верхнему и ПЕРВОМУ В СЛОВАРЕ термину - они всегда будут грузиться разом, не помогут никакие штатные разбиения на страницы. И по этой же причине Taxonomy Manager может ничем не обрадовать - у него есть специальный виджет (слева на скриншоте выше) для выборе текущей ветки. И подразумевается, что вложенные термины могут быть развёрнуты по отдельности и по мере необходимости. Но что произойдёт, когда вы развернёте свой единственный корневой термин - я думаю, понятно.

Тут нужно задуматься, видимо, о том, какая необходимость в первом (родительском) термине. Приводите уже словарь к flat-виду, это может решить сразу ряд проблем. В том числе - с разбиением на странице в админке. Если, конечно, причина только в этом, а не в повреждённой структуре таблицы или записей в БД.

Аватар пользователя OldWarrior OldWarrior 17 апреля в 15:26
1

Посмотрел код Taxonomy Manager (ссылался на него постом выше). В общем, здесь есть некоторая надежда, что будет работать в вашем случае. Метод виджета дерева терминов использует обычный запрос сущностей ::entityQuery(), а не высокоуровневый ::loadTree(). Причём следом дополнительно строит обычный запрос SQL-запрос к таблице 'taxonomy_term_data'.

Метод работает с одной (текущей/выбранной в виджете) веткой словаря. В обоих запросах учитывается параметр $pager_size (т.е. кол-во терминов на страницу).

В финале метод грузит и возвращает найденные термины используя обычный ::loadMultiple()

https://git.drupalcode.org/project/taxonomy_manager/-/blob/2.0.x/src/Ele...

<?php
...
/**
* Load one single level of terms, sorted by weight and alphabet.
*/
public static function loadTerms($vocabulary, $parent = 0, $pager_size = -1) {
try {
$query = \Drupal::entityQuery('taxonomy_term')
->
accessCheck()
->
condition('vid', $vocabulary->id())
->
condition('parent', $parent)
->
sort('weight')
->
sort('name');
if (
$pager_size > 0) {
$query->pager($pager_size);
}
$tids = $query->execute();
return \
Drupal::entityTypeManager()->getStorage('taxonomy_term')->loadMultiple($tids);
}
catch (
QueryException $e) {
// This site is still using the pre-Drupal 8.5 database schema, where
// https://www.drupal.org/project/drupal/issues/2543726 was not yet
// committed to Drupal core.
// @todo Remove both the try/catch wrapper and the code below the catch-
// statement once the taxonomy_manager module only supports Drupal 8.5 or
// newer.
} $database = \Drupal::database();
if (
$pager_size > 0) {
$query = $database->select('taxonomy_term_data', 'td')->extend('Drupal\Core\Database\Query\PagerSelectExtender');
}
else {
$query = $database->select('taxonomy_term_data', 'td');
}
$query->fields('td', ['tid']);
$query->condition('td.vid', $vocabulary->id());
$query->join('taxonomy_term__parent', 'th', 'td.tid = th.entity_id AND th.parent_target_id = :parent', [':parent' => $parent]);
$query->join('taxonomy_term_field_data', 'tfd', 'td.tid = tfd.tid');
$query->orderBy('tfd.weight');
$query->orderBy('tfd.name');

if (

$pager_size > 0) {
$query->limit($pager_size);
}
$result = $query->execute(); $tids = [];
foreach (
$result as $record) {
$tids[] = $record->tid;
}

return \

Drupal::entityTypeManager()->getStorage('taxonomy_term')->loadMultiple($tids);
}
...
?>

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

Параметр $pager_size (кол-во терминов на страницу) копируется из элемента 'taxonomy_manager_pager_tree_page_size' массива настроек, который в свою очередь устанавливается в настройках модуля из предопределённых значений.

https://git.drupalcode.org/project/taxonomy_manager/-/blob/2.0.x/src/For...

<?php
...
$form['taxonomy_manager_pager_tree_page_size'] = [
'#type' => 'select',
'#title' => $this->t('Pager count'),
'#options' => [
25 => 25,
50 => 50,
75 => 75,
100 => 100,
150 => 150,
200 => 200,
250 => 250,
300 => 300,
400 => 400,
500 => 500,
1000 => 1000,
2500 => 2500,
5000 => 5000,
10000 => 10000,
],
'#default_value' => $config->get('taxonomy_manager_pager_tree_page_size'),
'#description' => $this->t('Select how many terms should be listed on one page. Huge page counts can slow down the Taxonomy Manager'),
];
...
?>
Аватар пользователя Delewo Delewo 21 апреля в 3:46

OldWarrior wrote: Посмотрел код Taxonomy Manager (ссылался на него постом выше). В общем, здесь есть некоторая надежда, что будет работать в вашем случае. Метод виджета дерева терминов использует обычный запрос сущностей ::entityQuery(), а не высокоуровневый ::loadTree(). Причём следом дополнительно строит обычный запрос SQL-запрос к таблице 'taxonomy_term_data'.

С этим Taxonomy Manager непонятная история случилась, я его несколько дней назад пробовал, он вначале даже через 5 минут загрузил большой словарик, но после того, как я сделал какие-то настройки, он больше не загружал ничего (просто полупустая страница). И даже маленькие словари уже не загружал, вообще ничего. При этом я даже восстановил старый снимок на виртуалке, и заново установил Taxonomy Manager - и он опять ничего не загрузил) В общем пока модуль удалил, надо еще терминов загрузить, потом с чистого листа его попробую. Может кеш не почистил где-то, а так модуль интересный, жаль только поработал недолго. Родительский термин действительно нужен, их там несколько, поэтому если только несколько flat словарей делать. А неужели если не будет родительского термина, то при заходе в админку будет грузить строго определенное количество и все будет нормально (если предположить, что БД в порядке)? Не думал, что родительский термин может так выйти боком...

Аватар пользователя OldWarrior OldWarrior 21 апреля в 22:05
1

Delewo wrote: С этим Taxonomy Manager непонятная история случилась, я его несколько дней назад пробовал, он вначале даже через 5 минут загрузил большой словарик, но после того, как я сделал какие-то настройки, он больше не загружал ничего (просто полупустая страница).

Тут ничего не могу сказать: у меня нет предположений. Однако, следует иметь в виду, что Taxonomy Manager для отображения дерева терминов использует AJAX-запросы (по крайней мере, на дочерних терминах - точно).

Delewo wrote: Родительский термин действительно нужен, их там несколько, поэтому если только несколько flat словарей делать. А неужели если не будет родительского термина, то при заходе в админку будет грузить строго определенное количество и все будет нормально (если предположить, что БД в порядке)? Не думал, что родительский термин может так выйти боком...

В случае с админкой таксономии вся проблемная соль здесь в, как справедливо указал выше kosskren, методе ::loadTree(). То есть, вызов этого метода с приведенным выше параметром 0 родителя и NULL глубины выхватывает полностью всё дерево терминов словаря и формирует огромный массив. И это уже начало проблемы в вашем случае. Поскольку никакое разбиение на страницы методом ::loadTree() не осуществляется. Дальше - контроллер формы полученный результат/массив из этого метода в цикле раскладывает для текущей страницы по родителям и дочерним веткам. Мутная и тяжёлая, конечно, механика. Я уже и забыл, что админкой формируется список терминов со вложенными дочками - почему-то считал, что система использует запросы типа ::entityQuery с каким-то постраничным лимитом только для top-level терминов, а уже потом для каждого родителя на ТЕКУЩЕМ страничном диапазоне строится дерево потомков именно отдельными запросами. Поэтому открытие kosskren меня тоже несколько удивило.

Однако, думаю, что, возможно, на это были какие-то особые причины.

Говоря об "излишнем" родительском термине я тут в первую очередь подразумеваю, что flat-список легче получить, обработать и РАЗБИТЬ НА НЕБОЛЬШИЕ СТРАНИЦЫ своими силами - в каком-то кастомном механизме. Например, даже на основе штатной механики Views. Или (если позволяет опыт) реализовав какую-то форму/контроллер для собственного интерфейса управления терминами.

Но и со стороны механики админки таксономии Друпала и неизбежной рекурсии в методе ::loadTree() - одноуровневый "плоский" словарь должен в теории обрабатываться быстрее. Поскольку при рекурсивных вызовах с большими массивами обработки - всё равно результат от обрабатываемой ветки при передаче параметров и при возврате таскается через машинный стек (т.е. на лету создаются динамические области памяти для возврата результата из текущего уровня рекурсии, в вашем случае - очень большие). И в каких-то случаях (например, операции присваивания результата другой переменной) могут одновременно (пусть и очень короткое время) существовать несколько копий этих данных. Как результат - если бы у вас первые 100 (или сколько там влезает на страницу админки) термины были верхнего уровня, то хоть что-то вы, возможно, смогли загрузить. А так - в вашем случае получается на первом же термине строится массив из 100500 дочерних терминов.

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

Аватар пользователя OldWarrior OldWarrior 21 апреля в 23:44

PS. Меня что-то заинтересовал прецедент. А что вообще за словарь-то такой, с 20М терминов? Откуда столько набрали? ) Тут в русском языке всего-то 0.5 ляма слов. Ну английский вроде, говорят, сейчас около ляма. Обычно с кол-вом терминов-тегов в Друпале проблем как бы нет.

Аватар пользователя Delewo Delewo 23 апреля в 3:03

OldWarrior wrote: Собственно, поэтому я и говорил выше о том, что если нет нужды в корневом термине (и дереве в целом)- возможно рациональнее будет от него вообще отказаться.

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

Аватар пользователя OldWarrior OldWarrior 23 апреля в 13:37
1

Думаю, в вашем случае (поскольку всё равно всё делаете через прямой импорт в БД) самый короткий путь - просто массово заменить ID родителей дочерних терминов на 0 в таблице. Есть такая таблица в Друпале - taxonomy_term__parent, хранящая в том числе отношения "дочка-родитель" всех терминов, вот в ней массово проставить 0 для поля parent_target_id, но с указанием словаря, к которому относятся термины.

Например, в том же phpMyAdmin составить массовый UPDATE-запрос к таблице как-то так:

UPDATE `taxonomy_term__parent` SET `parent_target_id`='0' WHERE `bundle`=`ваше_машинное_имя_словаря`

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

После всего обязательно очистить кеш Друпала: drush cr

Аватар пользователя Delewo Delewo 23 апреля в 3:41

OldWarrior wrote: И в каких-то случаях (например, операции присваивания результата другой переменной) могут одновременно (пусть и очень короткое время) существовать несколько копий этих данных. Как результат - если бы у вас первые 100 (или сколько там влезает на страницу админки) термины были верхнего уровня, то хоть что-то вы, возможно, смогли загрузить.

Еще одна странность...когда я открыл этот большой словарь в Taxonomy Manager (это было всего 1 раз), то термины там были совсем из другого словаря. Я как бы удивился, закрыл и попробовал еще раз открыть и уже не получилось. В тот другой словарь термины были залиты ранее модулем Feeds. Но перепутать я ничего не мог (добавить не в тот словарь при генерации дампов). Во фронтенде все работает корректно, открываются списки и количество показывается все правильно, уже залито более 200М, модуль OptimizeDB тоже никаких ошибок не находит. Возможно, это как раз про "несколько копий этих данных"?

Аватар пользователя OldWarrior OldWarrior 23 апреля в 14:06
1

Нет, это не тот случай. Я выше писал про случай с рекурсией, когда все 20М терминов существуют в виде переменной где-то внутри тела функции ::loadTree() (т.е. когда вы используете штатную админку таксономии). Но методы модуля Taxonomy Manager не используют рекурсивный метод ::loadTree() (я уже приводил код выше), стало быть, замеченный вами баг не из этой оперы.

Грубо говоря (если уж говорим об этом), я писал про случаи операций, когда, например:

<?php
$a
= my_self_function(...params); // (например, рекурсивный вызов самого себя с присвоением результата).
?>

или даже

<?php
$a
= $b;
?>

на практике (на машинном уровне) при присвоении значения (=) создаются в один момент времени области памяти и для $a и для $b с одними и теми же данными. И на какой-то момент времени все 20М имеют две копии - у источника и у получателя, что ещё более увеличивает запрошенные ресурсы. Это обычная механика машинных операций, тут как бы ничего не сделаешь. И в PHP во втором случае обе переменные будут существовать до конца выполнения тела локального метода или скрипта (если глобальные), если им принудительно не присвоить другое значение (часто делают $a = $b = NULL для принудительной очистки памяти "здесь и сейчас", после чего ещё также unset ($a, $b)).

Правда, если переменная-источник является указателем, то механика несколько иная.

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

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

Если же в словаре всего один уровень терминов (т.е. все top-level), то в методе ::loadTree() рекурсии не происходит.

Аватар пользователя Delewo Delewo 25 апреля в 3:53

OldWarrior wrote: Думаю, в вашем случае (поскольку всё равно всё делаете через прямой импорт в БД) самый короткий путь - просто массово заменить ID родителей дочерних терминов на 0 в таблице.

Отлично, есть надежда, что все будет работать. Как доделаю, обязательно попробую удалить родительский термин. К тому времеми общее количество терминов будет около миллиарда)

Логотип drupal.ru

Содержимое сайта публикуется на условиях CreativeCommons Attribution-ShareAlike 3.0 или более поздней версии. Программные коды в тексте статей — на условиях GNU GPL v2 или более поздней версии.

Документация по совместной работе с репозиторием drupal.ru – https://docs.drupal.ru/code

Drupal – торговая марка Дриса Бёйтарта

12+

AltStyle によって変換されたページ (->オリジナル) /