X

Magento 2: Как работает индексация

Для решения одной из проблем, пришлось разобраться с индексацией в Magento 2. В этой статье расскажу о том, что мне удалось узнать.

Индекс и индексатор

В Magento 2 довольно сложная система хранения продуктов, категорий и связанных с ними данных, таких как цены товаров, цены групп товаров, скидки и т.д. Для того, чтобы ускорить выборку этих данных, используются индексные таблицы. Без индексации, пришлось бы все это рассчитывать "на лету", что значительно отразилось бы на производительности. Индексные таблицы представляют собой набор данных, оптимизированный для быстрой выборки. Такой подход позволяет заранее произвести все нужные расчеты, например учесть скидки и в дальнейшем использовать уже обработанные данные вместо того, чтобы делать это для каждого запроса.

За создание индексов отвечают индексаторы. По сути, это классы отвечающие за то, чтобы обработать данные и сохранить их в нужном формате.

  • Индекс = это таблица с оптимизированным для выборки набором данных.
  • Индексатор = класс который оптимизирует данные и записывает их в таблицу индекса

Способы обновления данных

Предусмотрено 2а способа обновления данных индекса

  • Полный реиндекс - используется когда необходимо пересчитать все данные индекса.
  • Частичный реиндекс - используется при обновлении конкретного значения индекса.

Теперь поговорим про каждый из способов подробнее.

Полный реиндекс

Для запуска полного реиндекса используется крон задача indexer_reindex_all_invalid, описанная в файле

/vendor/magento/module-indexer/etc/crontab.xml

<!-- vendor/magento/module-indexer/etc/crontab.xml -->

<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Cron:etc/crontab.xsd">
    <group id="index">
        <job name="indexer_reindex_all_invalid" instance="Magento\Indexer\Cron\ReindexAllInvalid" method="execute">
            <schedule>* * * * *</schedule>
        </job>
        ...
    </group>
</config>

Работает это так. Некоторые запускаемые процессы могут пометить весь индекс как "invalid". Делается это установкой статуса invalid в таблице indexer_state. Например, некоторый процесс поставит статус invalid для индексера catalog_product_flat

Magento 2: Статус invalid для индексера

В админке это будет выглядеть так

Magento 2: индексер atalog_product_flat

В случае, если magento крон установлен, то такой индекс будет обнаружен задачей indexer_reindex_all_invalid и отправлен на полную переиндексацию.

Возможности выполнить задачу indexer_reindex_all_invalid вручную средствами magento нет. Однако на помощь придет утилита N98-Magerun: https://github.com/netz98/n98-magerun2 . С ее помощью можно запустить эту задачу вот так:

php n98-magerun2.phar sys:cron:run indexer_reindex_all_invalid

а вот так посмотреть список всех доступных задач

php n98-magerun2.phar sys:cron:list

Другой способ запустить переиндексацию, это выполнить консольную команду указав конкретный индексатор

php bin/magento indexer:reindex catalog_product_flat

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

php bin/magento indexer:reindex

В результате вы получите сообщение, что все индексы были перестроены

Design Config Grid index has been rebuilt successfully in <time>
Customer Grid index has been rebuilt successfully in <time>
Category Products index has been rebuilt successfully in <time>
Product Categories index has been rebuilt successfully in <time>
Catalog Rule Product index has been rebuilt successfully in <time>
Product EAV index has been rebuilt successfully in <time>
Inventory index has been rebuilt successfully in <time>
Catalog Product Rule index has been rebuilt successfully in <time>
Stock index has been rebuilt successfully in <time>
Product Price index has been rebuilt successfully in <time>
Catalog Search index has been rebuilt successfully in <time>

Просмотреть статус индексов из консоли можно так

bin/magento indexer:status

результат

+----------------------+------------------+-----------+---------------------+---------------------+
| Title                | Status           | Update On | Schedule Status     | Schedule Updated    |
+----------------------+------------------+-----------+---------------------+---------------------+
| Catalog Product Rule | Reindex required | Save      |                     |                     |
| Catalog Rule Product | Reindex required | Save      |                     |                     |
| Catalog Search       | Ready            | Save      |                     |                     |
| Category Products    | Reindex required | Schedule  | idle (0 in backlog) | 2018-06-28 09:45:53 |
| Customer Grid        | Ready            | Schedule  | idle (0 in backlog) | 2018-06-28 09:45:52 |
| Design Config Grid   | Ready            | Schedule  | idle (0 in backlog) | 2018-06-28 09:45:52 |
| Inventory            | Ready            | Save      |                     |
| Product Categories   | Reindex required | Schedule  | idle (0 in backlog) | 2018-06-28 09:45:53 |
| Product EAV          | Reindex required | Save      |                     |                     |
| Product Price        | Reindex required | Save      |                     |                     |
| Stock                | Reindex required | Save      |                     |                     |
+----------------------+------------------+-----------+---------------------+---------------------+

Тут

  • Title = название индекса
  • Status = статус индекса указывающий на то, требуется обновление или нет
  • Update On = тип частичного реиндекса (см. следующий раздел)
  • Schedule Status = Статус индексатора, указывающий на то, был ли он запущен или нет.
  • Schedule Updated = Время обновления Schedule Status

Просмотреть id индексаторов из консоли можно так

bin/magento indexer:info

выведет

design_config_grid                       Design Config Grid
customer_grid                            Customer Grid
catalog_category_product                 Category Products
catalog_product_category                 Product Categories
catalogrule_rule                         Catalog Rule Product
catalog_product_attribute                Product EAV
inventory                                Inventory
catalogrule_product                      Catalog Product Rule
cataloginventory_stock                   Stock
catalog_product_price                    Product Price
catalogsearch_fulltext                   Catalog Search

Подведем итоги по командам: php bin/magento <команда>

  • indexer:status = покажет какие  индексы требуют обновления
  • indexer:info = покажет id всех индексаторов
  • indexer:reindex = переиндексировать все индексы (все индексаторы будут запущены по очереди)
  • indexer:reindex <id-индексатора> = запустить конкретный индексатор

Частичный реиндекс

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

В Magento 2 существует два типа частичного реиндекса:

  • Update on Save = данные обновляются в момент сохранения изменений
  • Update by Schedule = измененные данные обновляются по расписанию

Для каждого из индексов, можно задать свой тип обновления. Например, индекс цен обновлять "по расписанию", а индекc категорий обновлять "в момент сохранения". Изменить эти настройки, можно в админке: Magento Admin > System > Tools > Index Management, затем отметить чекбоксами нужные индексы и выбрать нужный пункт в выпадающем списке "Actions"

Magento 2. Тип индексации

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

Update on Save - используется в случае, если при сохранении не будет необходимости обновлять большое кол-во элементов. Например, у вас 3 категории и 50 товаров, тогда смело можно использовать этот способ. Кроме того, данный способ позволяет практически моментально синхронизировать изменения с витриной.

Update on Schedule -  используется в случае, когда у вас огромное кол-во товаров или требуется перестроить индекс для большого кол-ва записей. Работает это так: id измененных записей накапливаются в отдельной таблице с постфиксом _cl, по расписанию запускается процесс индексации, который поочередно обновляет эти записи.

Конфигурация расписания Update on Schedule

Как вы уже знаете, индексы для которых указан тип индексации "Update on Schedule" будут запущены по расписанию.

Индексаторы запускаются в отдельной крон-группе: index. Настройки для данной группы задаются в админке, в разделе:

Magento Admin > Store > Configuration > Advanced > System > Cron (Scheduled Tasks) > Cron configuration options for group: index

Magento 2: Настройки крона для группы indexer

Расписание для индексаторов задается в файлe /vendor/magento/module-indexer/etc/crontab.xml

<!-- vendor/magento/module-indexer/etc/crontab.xml -->

<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Cron:etc/crontab.xsd">
    <group id="index">
        ... 
        <job name="indexer_update_all_views" instance="Magento\Indexer\Cron\UpdateMview" method="execute">
            <schedule>* * * * *</schedule>
        </job>
        <job name="indexer_clean_all_changelogs" instance="Magento\Indexer\Cron\ClearChangelog" method="execute">
            <schedule>0 * * * *</schedule>
        </job>
    </group>
</config>

Подробнее про настройки и то как работает планировщик можно прочитать тут: Magento 2: Планировщик задач

Тут мы видим 2 крона, связанных с частичным индексированием:

  • ndexer_update_all_views - индексирует записи из таблиц имя-таблицы_cl
  • indexer_clean_all_changelogs - очищает старые записи из таблиц имя-таблицы_cl

Прежде чем продолжать нам нужно разобраться с тем что такое MView и как это используется в Magento

MView

Mview это сокращение от Materialized Views или на русском Материализованное представление.

Материализо́ванное представле́ние — физический объект базы данных, содержащий результат выполнения запроса.
Материализованные представления позволяют многократно ускорить выполнение запросов, обращающихся к большому количеству (сотням тысяч или миллионам) записей, позволяя за секунды (и даже доли секунд) выполнять запросы к терабайтам данных. Это достигается за счет прозрачного использования заранее вычисленных итоговых данных и результатов соединений таблиц. Предварительно вычисленные итоговые данные обычно имеют очень небольшой объем по сравнению с исходными данными. Целостность данных в материализованных представлениях поддерживается за счёт периодических синхронизаций или с использованием триггеров.
Впервые появились в СУБД Oracle.

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

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

MySQL не имеет поддержки Materialized View, поэтому вместо виртуальных таблиц используются реальные. В Magento это индексные и flat таблицы.

Как работает обновление данных

Прежде всего, нужно пойти в админки и выбрать для определенного индекса режим работы "Update by Schedule"

Magento 2 индекс Update by schedule

В момент изменения режима работы, в базе данных будет создана таблица с постфиксом _cl (у нас это catalog_product_flat_cl), а так же будут добавлены тригеры, отвечающие за то, чтобы сохранять change log-и, или проще говоря идентификатор того, что определенное значение в таблице было обновлено. Тригеры навешиваются на CRUD операции в базе, поэтому добавляется сразу несколько тригеров. В случае, если сущность является EAV, то тригеры будет сгенерированы для каждой из участвующих таблиц. Т.е. в нашем примере, будет добавлено 27 тригеров, для 3х операций (INSERT, UPDATE, DELETE) в 9 таблицах:

catalog_product_entity
catalog_product_entity_datetime
catalog_product_entity_decimal
catalog_product_entity_gallery
catalog_product_entity_int
catalog_product_entity_media_gallery_value
catalog_product_entity_text
catalog_product_entity_tier_price
catalog_product_entity_varchar

Просмотреть тригеры можно такой командой

SHOW TRIGGERS FROM <db-name>;

пример вывода

Magento 2 тригеры

Если просмотреть запросы, мы заметим что все что делают эти тригеры, это добавление id сущности в таблицу с постфиксом _cl. Т.е. каждый раз при обновлении записи, в таблицу с постфиксом _cl будет добавлено новое значение. Если посмотреть структуру таких таблиц, то увидим что там сохраняется всего два значения version_id и entity_id. Поле version_id - является primary key с автоинкрементом, entity_id - id сущности которая поменялась.

Magento 2: Структура cl таблицы

Подведем промежуточный итог: при включении режима обновления индексов "Update by schedule" создается таблица с постфиксом _cl, а так же добавляются тригеры на изменения данных сущности, которые наполняют эту таблицу. Как только мы обновляем какой-то товар в админке, в таблицу catalog_product_flat_cl добавляется новое значение.

Далее, нам нужно обратить внимание на таблицу mview_state, которая содержит информацию о том какие индексы работают в режиме "Update by schedule", их статус и главное, последнее обработанное значение из _cl таблицы в поле version_id.

Magento 2: Таблица mview_state

Теперь мы знаем что у нас есть catalog_product_flat_cl таблица где собраны id товаров которые обновились, а так же что в mview_state сохраняется последняя версия из этой таблицы, по которой изменения были внесены в индексную таблицу. Звучит запутанно, поэтому, лучше приведу пример, как это обновление работает.

  • Каждую минуту запускается magento планировщик (про планировщик написано тут)
  • Планировщик запускает индексатор, который называется indexer_update_all_views
  • Этот индексатор, перебирает поочередно все записи имеющие mode=enabled из таблицы mview_state
  • Далее он берет из этой таблицы mview_state.version_id и ищет в таблице catalog_product_flat_cl все version_id которые больше этого значения.
  • Таким образом индексатор узнает какие записи обновились с момента последней обработки, а соответственно их нужно обновить в индексе
  • Производит обновление индексов для этих записей
  • Обновляет значение mview_state.version_id , на последнее обработанное
  • Завершает работу

Как видите, все построено достаточно просто, по сути у нас есть список записей который были обновлены, и маркер последней обработанной индексатором записи. Берем все записи которые больше этого маркера, обновлем их и перезаписываем маркер.

Если присмотреться к списку шагов, вы не найдете шага с удалением обработанных записей из cl таблицы. Действительно, индексатор indexer_update_all_views не удаляет записи, которые он обработал, только меняет version_id на последний в таблице mview_state. За очистку старых данных в cl таблице отвечает другая задача, а именно indexer_clean_all_changelogs.

Объявления обоих индексаторов и связанных с ними классов находятся в файле: /vendor/magento/module-indexer/etc/crontab.xml

Заключение

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

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

Полезнае ссылки

Категории: Magento