Переиндексация и диагностика поиска
Поисковый индекс хранит подготовленную копию данных сайта. Эта копия ускоряет поиск, но может отстать от исходных объектов после сбоя, массового изменения или ошибки синхронизации. Обслуживание возвращает индексу актуальное состояние, а диагностика помогает найти участок, где документ пропал или изменился результат запроса.
Для восстановления модуль search повторно получает документы от модулей-источников и передает их выбранному поисковому движку. Модуль-источник владеет исходными объектами и формирует из них поисковые документы.
Масштаб работ влияет на доступность поиска. Одиночное обновление почти не затрагивает пользователей. Полная переиндексация сначала очищает индекс, поэтому до завершения прохода выдача остается пустой или неполной.
Выбрать масштаб переиндексации
Начинайте с самого узкого действия, которое устраняет причину сбоя. Такой выбор сокращает нагрузку и не перестраивает корректные части индекса.
|
Ситуация |
Действие |
API |
Влияние на работающий сайт |
|
Изменился один объект, его текст или теги |
Повторно проиндексировать документ |
|
Модуль обновляет одну запись |
|
Изменились только права одного объекта |
Заменить коды доступа |
|
Модуль не обрабатывает текст повторно |
|
Изменились только сайты или адреса одного объекта |
Заменить привязки к сайтам |
|
Для OpenSearch модуль заново передает документ в индексы сайтов |
|
Исходный объект удален |
Удалить документ |
|
Модуль удаляет запись. Внешний движок может отразить изменение после обновления своего индекса |
|
Массово изменились данные одного модуля |
Переиндексировать выбранный модуль |
|
Неполный проход сохраняет остальные источники |
|
Нужно найти и удалить устаревшие записи |
Запустить неполную переиндексацию |
|
Модуль обновляет документы и удаляет старые записи после успешного завершения |
|
Изменились настройка |
Запустить полную переиндексацию |
|
Модуль очищает индекс перед заполнением |
|
Индекс потерял целостность или его содержимому нельзя доверять |
Восстановить индекс полным проходом |
|
Поиск остается неполным до конца прохода |
Метод CSearch::ReindexModule($MODULE_ID, $bFull = false) находит обработчик OnReindex выбранного модуля и вызывает его в текущем процессе. При $bFull = true метод сначала удаляет прежние документы выбранного модуля. Обработчик передает поиску документы своего модуля. Параметры обработчика и состояние шага смотрите в статье Индексация собственного контента.
Метод ReindexModule() не повторяет вызов обработчика по возвращенной позиции. Для обработчика с ограниченной выборкой, включая пример на 1000 документов из соседней статьи, используйте CSearch::ReIndexAll() с положительным лимитом времени и ограничением MODULE_ID. Если обработчик возвращает позицию или false, метод ReindexModule() выходит до финального удаления устаревших записей. Это ограничение действует и после успешного завершения обработчика, который передает документы через объект обратного вызова.
Статические файлы принадлежат источнику main. Для них запускайте CSearch::ReIndexAll() с ограничением MODULE_ID = main. Метод CSearch::ReindexModule() обрабатывает только зарегистрированные обработчики OnReindex и не обходит дерево статических файлов.
Связи документов и различия между видами переиндексации показаны в статье Архитектура и поисковый индекс.
Проверить настройки перед обслуживанием
Настройки модуля определяют состав статических файлов, способ разбора слов, размер выдачи и стоимость запроса. Текущие значения находятся на странице Настройки > Настройки продукта > Настройки модулей > Поиск. Проверьте их, чтобы выбрать нужный масштаб обслуживания.
|
Настройка |
Тип и значения |
На что влияет |
Когда нужна переиндексация |
|
|
Строка с масками файлов, которые разделены точкой с запятой. По умолчанию: |
Разрешает статические файлы по маске |
После изменения повторно обработайте статические файлы |
|
|
Строка с масками исключений, которые разделены точкой с запятой. По умолчанию: |
Исключает каталоги и файлы из обхода |
После изменения повторно обработайте статические файлы |
|
|
Целое число — размер файла в килобайтах. Значение По умолчанию: без ограничения |
Исключает статический файл из индексации, если его размер превышает лимит |
После изменения повторно обработайте затронутые файлы |
|
|
Строка — имя свойства страницы с тегами. По умолчанию: |
Выбирает свойство статической страницы с тегами |
После изменения повторно обработайте статические файлы |
|
|
Целое число - длительность шага в секундах. Значение По умолчанию: |
Задает длительность одного шага переиндексации |
Не нужна |
|
|
Значение По умолчанию: |
Разрешает морфологический разбор в |
Не нужна |
|
|
Строка с дополнительными символами, которые нужно считать частью слова. По умолчанию: |
Добавляет символы, которые морфологический анализ встроенного движка Bitrix не считает разделителями слов |
После изменения выполните полный проход |
|
|
Значение По умолчанию: |
Передает морфологическую обработку новых и измененных документов встроенного движка Bitrix агенту |
Не нужна, но проверьте очередь агента |
|
|
Целое число — длительность одного запуска агента в секундах. По умолчанию: |
Ограничивает один запуск агента морфологии |
Не нужна |
|
|
Целое число — максимальное число документов в результате. По умолчанию: |
Ограничивает число документов в результате и некоторые выборки тегов |
Не нужна |
|
|
Целое число — объем текста в символах задается целым числом. Значение По умолчанию: |
Ограничивает объем текста, который модуль читает для подсветки результата |
Не нужна |
|
|
Значение По умолчанию: |
Ускоряет поиск встроенного движка Bitrix ценой менее точного ранжирования |
Не нужна. Модуль очищает данные для быстрого поиска после сохранения настройки |
|
|
Значение По умолчанию: |
Учитывает позиции слов при расчете ранга встроенным движком Bitrix на MySQL |
Не нужна |
|
|
Движок выбирается по коду: По умолчанию: |
Выбирает хранилище полнотекстовых данных и способ выполнения запроса |
Всегда нужен полный проход |
Маски и ограничение размера действуют только на физические файлы публичной части, то есть на файлы страниц сайта. Они не ограничивают документы, которые модули передают через событие OnReindex. Если индекс растет из-за данных модуля, уменьшение max_file_size не даст результата.
Отложенная морфология снижает работу внутри вызова CSearch::Index(), но создает промежуток между сохранением нового или измененного документа и появлением слов в индексе встроенного движка Bitrix. Если agent_stemming = Y, проверьте работу агента перед переиндексацией. Накопившаяся очередь может выглядеть как потеря документов.
Настройка max_body_size не сокращает текст во время индексации. Она ограничивает чтение тела для подготовки подсвеченного фрагмента. Для сокращения индекса уменьшайте состав документа в модуле-источнике и не передавайте служебный текст в BODY.
Анализатор встроенного движка Bitrix ограничивает длину уже подготовленного условия запроса значением 2000 символов. Морфологические варианты, группы и операторы могут увеличить условие сильнее исходной строки. В таком случае CSearch::Search() возвращает ошибку 4. Сократите число слов и групп, а точные признаки перенесите в поля фильтра.
Подготовить безопасный запуск
Полный проход затрагивает весь поиск. Подготовка нужна, чтобы отличить ожидаемую неполную выдачу от нового сбоя и не потерять состояние длительного процесса.
-
Сохраните настройки модуля, выбранного движка и внешнего сервиса. Они понадобятся для отката, если контрольные запросы покажут ухудшение выдачи.
-
Подготовьте контрольные запросы. Запишите пользователя, сайт, строку, фильтры, сортировку и ожидаемые документы.
-
Проверьте одиночную индексацию контрольного документа. Если источник формирует неверные поля, полный проход повторит ту же ошибку для всего набора.
-
Убедитесь, что все модули-источники зарегистрировали обработчики
OnReindex. Собственный модуль должен передавать документы через объект обратного вызова. Этот объект добавляет документ и возвращаетfalse, когда время шага закончилось. Обработчик должен остановиться и вернуть позицию продолжения. -
Проверьте доступность выбранного движка. Для внешнего сервиса заранее проверьте подключение, свободное место и права учетной записи. Иначе полный проход может остановиться после очистки индекса и оставить поиск без документов.
-
Выберите период низкой нагрузки. На время полного прохода ограничьте публичный поиск или покажите сообщение о технических работах.
-
Исключите параллельный запуск. Два процесса могут очистить или обновить один индекс в разном порядке.
-
Задайте шаг короче лимита веб-сервера и фонового процесса. Слишком большой шаг повышает риск принудительной остановки до сохранения состояния.
-
Подготовьте надежное хранилище состояния. Оно должно пережить перезапуск процесса и оставаться недоступным из публичной части.
-
Не отключайте обычную синхронизацию объектов без необходимости. Изменения, которые происходят во время длительного прохода, должны по-прежнему вызывать
CSearch::Index()или методы точечного обновления.
Перед переключением движка подготовьте откат и контрольные запросы по инструкции Выбор и настройка поискового движка. После переключения запустите полный проход, а при остановке продолжите его с сохраненным состоянием.
Запустить переиндексацию
Административная страница подходит для ручного обслуживания. Метод CSearch::ReIndexAll() нужен для очереди, служебного процесса или собственного интерфейса контроля.
Запустить проход в административном разделе
Откройте страницу Настройки > Поиск > Переиндексация под учетной записью с доступом к настройкам модуля search. Форма позволяет выбрать сайт, модуль и длительность шага для неполного прохода.
-
Оставьте опцию Переиндексировать только измененные включенной, если индекс сохранил целостность и нужно обновить измененные документы.
-
Ограничьте проход сайтом или модулем, если причина сбоя известна. Узкая область уменьшает число обращений к источникам.
-
Включите очистку данных подсказок, если изменились правила формирования фраз или область поиска. Модуль удалит поисковые фразы, кеш кодов доступа пользователей и частотные данные слов.
-
Снимите опцию Переиндексировать только измененные для полного прохода. В этом режиме ограничения по сайту и модулю недоступны, потому что модуль очищает общий индекс.
-
Запустите переиндексацию и дождитесь сообщения о завершении. Кнопка остановки прерывает последовательность шагов, а кнопка продолжения передает сохраненное состояние следующему запросу.
Полный проход очищает поисковые фразы, кеш кодов доступа пользователей и частотные данные слов вместе с индексом. Отдельная опция выполняет такую очистку в начале неполной переиндексации.
Если сайт использует модуль социальной сети, административная страница после завершения показывает отдельное предупреждение. Выполните указанную в нем индексацию из компонента социальной сети. Общий проход модуля search не заменяет этот шаг.
Выполнить один шаг через PHP
Метод CSearch::ReIndexAll() принимает четыре необязательных параметра.
-
$bFullожидает значениеbool, значение по умолчанию равноfalse. Значениеtrueочищает общий индекс перед первым шагом. Значениеfalseсохраняет записи и удаляет устаревшие документы только после завершения прохода. -
$max_execution_timeожидает значениеint, значение по умолчанию равно0. Передайте положительное число для пошагового режима. При нулевом или отрицательном значении метод выполняет проход до конца в одном вызове. Из переданного$NSон сохраняет только ограниченияSITE_IDиMODULE_ID. -
$NSожидает значениеarray, значение по умолчанию равно пустому массиву. Для первого шага передайте пустой массив или ограниченияSITE_IDиMODULE_IDв неполном режиме. Для продолжения передайте весь массив, который вернул предыдущий вызов. -
$clear_suggestожидает значениеbool, значение по умолчанию равноfalse. Значениеtrueочищает поисковые фразы, кеш кодов доступа пользователей и частотные данные слов в начале неполного прохода. Полный режим очищает эти данные независимо от параметра.
Начальный массив $NS поддерживает два ограничения.
-
SITE_IDимеет типstringи ограничивает обход статических файлов одним сайтом. Метод также передает его обработчикамOnReindex, чтобы они сузили свою выборку. -
MODULE_IDимеет типstringи выбирает обработчики одного модуля. Значениеmainвыбирает статические файлы.
Метод возвращает array|int. Массив означает, что работу нужно продолжить. Он может содержать следующие поля.
-
MODULEимеет типstringи указывает текущий модуль. Для статических файлов метод возвращаетmain. -
IDимеет типstring|intи хранит позицию внутри текущего источника. -
CNTимеет типintи содержит счетчик обработанных документов. -
CLEARсодержит строкуYи не дает повторить начальный этап. -
SESS_IDимеет типstringи связывает шаги неполного прохода. По этому идентификатору метод удаляет устаревшие записи после завершения. -
SITE_IDиMODULE_IDимеют типstring. Метод сохраняет их, если вы ограничили первый шаг.
Набор полей зависит от этапа. Передавайте массив следующему вызову целиком и не меняйте отдельные значения. Целое число означает, что проход завершился, и содержит итоговый счетчик.
Для одного шага передайте сохраненное состояние и положительный лимит времени. Следующий фрагмент показывает только вызов API, модуль search должен быть подключен.
// Первый шаг: неполный проход по одному модулю
$state = [
'SITE_ID' => 's1',
'MODULE_ID' => 'vendor.docs',
];
$result = \CSearch::ReIndexAll(false, 15, $state, false);
if (is_array($result))
{
// Сохранить весь массив и передать его как $state при следующем запуске
$state = $result;
}
else
{
// Проход завершен; результат содержит счетчик обработанных документов
$indexedTotal = (int)$result;
}
При следующем вызове загрузите сохраненный массив вместо начальных ограничений. Полный режим запускайте с первым аргументом true и пустым начальным состоянием: он очищает общий индекс и должен восстановить все источники.
CLI-сценарий с сохранением состояния в конце статьи добавляет запись JSON, блокировку параллельных заданий и статусы для планировщика.
Восстановить процесс после остановки
Состояние определяет безопасный способ продолжения. Сначала выясните, завершился ли последний вызов и сохранил ли вызывающий код возвращенный массив.
-
Продолжите процесс с тем же массивом
$NS, если метод вернул состояние. Неполный проход удалит устаревшие записи только в конце того же сеанса. -
Повторите текущий шаг с сохраненным состоянием после временной ошибки движка или базы данных. Успешно обработанные документы могут пройти через индексацию еще раз.
-
Запустите новый полный проход, если состояние полной переиндексации потеряно. Индекс уже мог быть очищен, поэтому частичное продолжение с новой позиции невозможно.
-
Запустите неполный проход заново, если его состояние потеряно. Старые записи останутся до успешного конца нового сеанса.
-
Переместите поврежденный JSON-файл из рабочего пути и сохраните его для диагностики. Затем начните проход заново. Для полного режима это единственный безопасный способ восстановить весь индекс, а неполный режим удалит старые записи после успешного завершения нового сеанса.
-
Не очищайте внутренние таблицы или индекс внешнего сервиса вручную. Такой способ обходит связанные данные, события и общий порядок восстановления.
-
Не меняйте движок во время незавершенного прохода. Сначала восстановите прежнюю конфигурацию или начните полный проход для окончательно выбранного движка.
Повторный вызов может еще раз обработать документ. Обработчик OnReindex должен читать исходные данные без побочных изменений и передавать документ с прежним ITEM_ID. Тогда CSearch::Index() обновит ту же запись и не создаст дубль.
О правилах обработчика, параметрах состояния и использовании объекта обратного вызова читайте в статье Индексация собственного контента. О последовательности событий при очистке читайте в статье События и расширение поиска.
Проверить результат
Успешный счетчик подтверждает завершение прохода, но не подтверждает качество каждого документа. Проверяйте индекс через CSearch::Search() или публичный компонент. Такой запрос учитывает движок, морфологию, сайт, фильтры и права текущего пользователя.
-
Найдите контрольный документ по уникальному слову из заголовка и текста.
-
Повторите запрос от имени администратора, пользователя с доступом и пользователя без доступа.
-
Проверьте каждый сайт документа. Результат должен вести на URL из соответствующей привязки
SITE_ID. -
Сравните поиск по релевантности и по дате. Зафиксируйте доступные поля
CUSTOM_RANK,RANKиDATE_CHANGEконтрольных результатов. -
Измените уникальное слово и дату исходного объекта. Обычная синхронизация должна удалить прежний вариант из выдачи и добавить новый.
-
Проверьте теги, быстрый поиск по заголовкам и подсказки, если проект использует эти механизмы.
-
Просмотрите журнал собственного процесса и состояние внешнего движка. Внешняя система может применить изменения не сразу.
Контрольный PHP-запрос и проверку полного цикла одного документа смотрите в статье Индексация собственного контента. Для оценки порядка, тегов и подсказок используйте чек-лист из статьи Ранжирование, теги и поисковые подсказки.
Найти причину сбоя
Диагностируйте поиск от источника к интерфейсу. Сначала проверьте документ и его связи. Затем переходите к запросу, движку, ранжированию и кешу.
|
Симптом |
Вероятная причина |
Как проверить |
Что сделать |
|
Известный документ не находится |
Источник не передал документ или сохранил прежнюю дату |
Найдите объект по уникальному слову и проверьте |
Повторно вызовите |
|
Документ видит администратор, но не видит пользователь |
Не совпали коды доступа |
Сравните |
Обновите права через |
|
Документ находится на одном сайте, но пропадает на другом |
Неверна привязка |
Повторите запрос отдельно для каждого сайта |
Исправьте сайты через |
|
Поиск вернул пустой набор без ошибки |
Фильтры, даты или права исключили все документы |
Зафиксируйте сайт, модуль, параметры, период активности и пользователя |
Уберите ограничения по одному и найдите условие, которое исключает документ |
|
Свойство |
Анализатор не разобрал запрос или движок вернул ошибку |
Запишите |
Исправьте строку запроса или восстановите движок |
|
Новый документ появляется с задержкой |
Включена отложенная морфология или внешний движок еще применяет изменение |
Проверьте |
Запустите агент, устраните очередь или дождитесь ожидаемого обновления движка |
|
Выдача показывает прежний текст |
Источник не изменил дату или не запустил синхронизацию |
Сравните исходный объект, |
Обновите документ и передайте актуальную дату. Для принудительной обработки используйте |
|
После неполного прохода остались удаленные документы |
Процесс не дошел до финальной очистки сеанса |
Проверьте, вернул ли последний шаг целое число |
Продолжите прежний сеанс или начните новый неполный проход |
|
После смены движка выдача пуста или неполна |
Полный проход не завершился или сервис недоступен |
Проверьте состояние шага, подключение и контрольные запросы |
Восстановите сервис и продолжите проход. При потере состояния начните полный проход заново |
|
Порядок результатов изменился |
Изменилась морфология, движок, сортировка или пользовательский вес |
Повторите запрос с прежним пользователем, сайтом и сортировкой |
Верните одинаковые условия и отдельно проверьте правила ранжирования |
|
Выдача содержит слабо связанные документы |
В |
Сравните подготовленный документ и результаты запроса без морфологии |
Исправьте состав документа и повторно проиндексируйте его. Настройки морфологии меняйте только после проверки контрольных запросов |
|
Переиндексация останавливается на одном модуле |
Обработчик |
Сравните поля |
Исправьте обработчик и продолжите с последним корректным состоянием |
|
Поиск работает медленно |
Область слишком широка или настройки увеличили объем обработки |
Измерьте запрос с |
Сузьте область, уменьшите результат и проверьте |
|
Облако тегов показывает прежние данные |
Компонент вернул сохраненный результат |
Временно передайте |
Обновите документ, затем очистите или дождитесь окончания кеша компонента |
|
В индексе появились дубли |
Источник изменил |
Сравните стабильную пару |
Верните стабильный ключ и удалите ошибочную запись через |
Коды ошибок анализатора встроенного движка Bitrix и правила обработки пустого запроса смотрите в статье Поисковые запросы через CSearch. О диагностике подключений и особенностях Sphinx, OpenSearch и полнотекстового поиска СУБД читайте в статье Выбор и настройка поискового движка.
Ограничить размер индекса и снизить нагрузку
Размер индекса растет вместе с числом документов, объемом TITLE и BODY, количеством сайтов, прав, тегов и дополнительных параметров. Внешний движок хранит полнотекстовое представление отдельно, поэтому общий расход диска включает его данные и связи модуля search.
Полный проход повторно читает каждый источник, подготавливает текст, сохраняет связи и обновляет движок. Встроенный движок Bitrix также рассчитывает морфологию. Не используйте полный проход как замену ежедневной синхронизации. Точечные методы устраняют небольшое расхождение быстрее и создают меньше нагрузки.
Для стабильной работы соблюдайте несколько правил.
-
Передавайте в
BODYтолько текст, который должен участвовать в поиске. Служебные фрагменты увеличивают индекс и добавляют в выдачу слабо связанные документы. -
Сохраняйте стабильную пару
MODULE_IDиITEM_IDдля каждого объекта, чтобы повторная индексация обновляла прежнюю запись и не создавала дубль. -
Ограничивайте статические файлы масками и размером, чтобы исключить из обхода служебные страницы и ненужные большие файлы.
-
Передавайте сайт и модуль в контрольных и фоновых запросах, чтобы ограничить область поиска и сравнивать результаты в одинаковых условиях.
-
Увеличивайте
max_result_sizeтолько после измерения времени и памяти, чтобы расширенная выдача не создала неприемлемую нагрузку. -
Включайте
use_word_distanceиuse_tf_cacheпосле сравнения скорости и порядка результатов, чтобы ускорение не привело к неприемлемому изменению выдачи. -
Делите длительную переиндексацию на шаги и сохраняйте состояние после каждого вызова, чтобы продолжить проход после остановки без повторной обработки всего набора.
-
Храните журналы времени шага, счетчика
CNT, полейMODULEиID. Эти данные показывают источник замедления без прямого чтения таблиц.
CLI-сценарий с сохранением состояния
Пример выполняет один шаг за один запуск CLI-сценария, то есть программы для командной строки. Перед запуском задайте три переменные окружения.
-
BITRIX_DOCUMENT_ROOT— абсолютный путь к корню установки с каталогом/bitrix. -
SEARCH_REINDEX_STATE_FILE— абсолютный путь к файлу состояния вне публичной части. Каталог должен существовать, а PHP-процессу нужны права на чтение и запись. Файловая система должна поддерживать атомарную замену файла черезrename(). -
SEARCH_REINDEX_LOCK_FILE— абсолютный путь к общему файлу блокировки вне публичной части. Используйте один и тот же путь для всех запусков этого сценария в пределах установки. Файл хранит идентификатор незавершенного задания и не позволяет чередовать его шаги с другим заданием.
Сценарий подключает файл prolog_before.php, чтобы инициализировать Bitrix Framework перед вызовом Loader::includeModule(). В примере s1 и vendor.docs служат образцами. Замените их идентификаторами своего сайта и модуля. При сохранении блока в отдельный PHP-файл добавьте в начало открывающий тег <?php.
Сценарий печатает JSON со статусом continue, done или locked. Поле indexedTotal содержит общий счетчик документов с начала прохода.
Планировщик должен повторять вызов после continue. Статус locked и код завершения 2 означают, что другой процесс выполняет шаг или общий индекс закреплен за другим незавершенным заданием. Повторите запуск после короткой ограниченной задержки, чтобы не создавать постоянную нагрузку во время длительной блокировки.
Ошибка дает код 1 и сообщение в стандартном потоке ошибок. Запишите сообщение в журнал и устраните причину перед повторным запуском.
Используйте отдельный файл состояния для каждого задания, чтобы шаги разных проходов не смешивались. Не меняйте путь к файлу, режим, область и очистку подсказок до завершения.
Файл блокировки координирует только процессы, которые запускают этот сценарий и используют общую файловую систему с согласованной межпроцессной поддержкой flock(). Переиндексация из административного раздела или другого кода не учитывает эту блокировку, поэтому не запускайте ее одновременно со сценарием.
После статуса done файл состояния сохраняет итоговый счетчик. Архивируйте или удалите файл перед повторным использованием того же пути, иначе сценарий вернет прежний результат без нового прохода.
Чтобы окончательно отменить зависшее задание, сначала убедитесь, что его процесс остановлен. Затем переместите файл состояния и очистите содержимое общего файла блокировки. Такой порядок не позволит другому процессу продолжить отмененное задание.
use Bitrix\Main\Loader;
function saveSearchReindexJob(string $stateFile, array $job): void
{
$stateDirectory = dirname($stateFile);
$jobJson = json_encode(
$job,
JSON_THROW_ON_ERROR | JSON_PRETTY_PRINT
);
$temporaryStateFile = tempnam(
$stateDirectory,
basename($stateFile) . '.'
);
if ($temporaryStateFile === false)
{
throw new \RuntimeException('Не удалось создать временный файл состояния');
}
try
{
if (file_put_contents($temporaryStateFile, $jobJson, LOCK_EX) === false)
{
throw new \RuntimeException('Не удалось записать временное состояние');
}
if (!rename($temporaryStateFile, $stateFile))
{
throw new \RuntimeException('Не удалось заменить файл состояния');
}
}
finally
{
if (is_file($temporaryStateFile))
{
unlink($temporaryStateFile);
}
}
}
function releaseSearchReindexJob($lockHandle): void
{
rewind($lockHandle);
if (!ftruncate($lockHandle, 0) || !fflush($lockHandle))
{
throw new \RuntimeException('Не удалось освободить индекс для других заданий');
}
}
function runSearchReindexStep(
string $stateFile,
string $lockFile,
bool $full,
int $stepSeconds,
bool $clearSuggest,
array $initialState
): array
{
if ($stepSeconds <= 0)
{
throw new \InvalidArgumentException('Длительность шага должна быть положительной');
}
if ($full && $initialState !== [])
{
throw new \InvalidArgumentException(
'Полную переиндексацию нельзя ограничивать сайтом или модулем'
);
}
$stateDirectory = dirname($stateFile);
if (!is_dir($stateDirectory) || !is_writable($stateDirectory))
{
throw new \RuntimeException('Каталог состояния недоступен для записи');
}
$lockDirectory = dirname($lockFile);
if (!is_dir($lockDirectory) || !is_writable($lockDirectory))
{
throw new \RuntimeException('Каталог файла блокировки недоступен для записи');
}
if ($stateFile === $lockFile)
{
throw new \InvalidArgumentException(
'Файл состояния и файл блокировки должны различаться'
);
}
// Заблокировать шаг и проверить владельца задания
$lockHandle = fopen($lockFile, 'c+');
if ($lockHandle === false)
{
throw new \RuntimeException('Не удалось открыть файл блокировки');
}
if (!flock($lockHandle, LOCK_EX | LOCK_NB))
{
fclose($lockHandle);
return [
'status' => 'locked',
'indexedTotal' => null,
];
}
try
{
$jobId = hash('sha256', $stateFile);
rewind($lockHandle);
$activeJobContents = stream_get_contents($lockHandle);
if ($activeJobContents === false)
{
throw new \RuntimeException('Не удалось прочитать файл блокировки');
}
$activeJobId = trim($activeJobContents);
if ($activeJobId !== '' && !hash_equals($activeJobId, $jobId))
{
return [
'status' => 'locked',
'indexedTotal' => null,
];
}
if ($activeJobId === '')
{
rewind($lockHandle);
if (!ftruncate($lockHandle, 0))
{
throw new \RuntimeException('Не удалось очистить файл блокировки');
}
$writtenBytes = fwrite($lockHandle, $jobId);
if ($writtenBytes !== strlen($jobId) || !fflush($lockHandle))
{
throw new \RuntimeException('Не удалось закрепить индекс за заданием');
}
}
// Продолжить прежнее задание или подготовить первый шаг
$state = $initialState;
if (is_file($stateFile))
{
$stateJson = file_get_contents($stateFile);
if ($stateJson === false)
{
throw new \RuntimeException('Не удалось прочитать состояние');
}
try
{
$job = json_decode(
$stateJson,
true,
512,
JSON_THROW_ON_ERROR
);
}
catch (\JsonException $exception)
{
throw new \RuntimeException(
'Файл состояния поврежден. '
. 'Сохраните его для диагностики и начните проход заново',
0,
$exception
);
}
if (
!is_array($job)
|| !array_key_exists('status', $job)
|| !array_key_exists('full', $job)
|| !array_key_exists('clearSuggest', $job)
|| !array_key_exists('scope', $job)
|| !array_key_exists('state', $job)
|| !array_key_exists('indexedTotal', $job)
|| !in_array($job['status'], ['continue', 'done'], true)
|| $job['full'] !== $full
|| $job['clearSuggest'] !== $clearSuggest
|| $job['scope'] !== $initialState
|| !is_array($job['state'])
|| !is_int($job['indexedTotal'])
)
{
throw new \RuntimeException('Состояние не соответствует запуску');
}
if ($job['status'] === 'done')
{
releaseSearchReindexJob($lockHandle);
return [
'status' => 'done',
'indexedTotal' => $job['indexedTotal'],
];
}
$state = $job['state'];
}
// Выполнить один ограниченный по времени шаг
$result = \CSearch::ReIndexAll(
$full,
$stepSeconds,
$state,
$clearSuggest
);
if (is_array($result))
{
// Сохранить состояние для следующего запуска
$indexedTotal = (int)$result['CNT'];
saveSearchReindexJob(
$stateFile,
[
'status' => 'continue',
'full' => $full,
'clearSuggest' => $clearSuggest,
'scope' => $initialState,
'state' => $result,
'indexedTotal' => $indexedTotal,
]
);
return [
'status' => 'continue',
'indexedTotal' => $indexedTotal,
];
}
$indexedTotal = (int)$result;
saveSearchReindexJob(
$stateFile,
[
'status' => 'done',
'full' => $full,
'clearSuggest' => $clearSuggest,
'scope' => $initialState,
'state' => [],
'indexedTotal' => $indexedTotal,
]
);
releaseSearchReindexJob($lockHandle);
return [
'status' => 'done',
'indexedTotal' => $indexedTotal,
];
}
finally
{
flock($lockHandle, LOCK_UN);
fclose($lockHandle);
}
}
$stateFile = getenv('SEARCH_REINDEX_STATE_FILE');
if (!is_string($stateFile) || $stateFile === '')
{
fwrite(STDERR, "Задайте SEARCH_REINDEX_STATE_FILE\n");
exit(1);
}
$lockFile = getenv('SEARCH_REINDEX_LOCK_FILE');
if (!is_string($lockFile) || $lockFile === '')
{
fwrite(STDERR, "Задайте SEARCH_REINDEX_LOCK_FILE\n");
exit(1);
}
$documentRoot = getenv('BITRIX_DOCUMENT_ROOT');
if (!is_string($documentRoot) || $documentRoot === '')
{
fwrite(STDERR, "Задайте BITRIX_DOCUMENT_ROOT\n");
exit(1);
}
$_SERVER['DOCUMENT_ROOT'] = rtrim($documentRoot, '/\\');
try
{
require $_SERVER['DOCUMENT_ROOT'] . '/bitrix/modules/main/include/prolog_before.php';
if (!Loader::includeModule('search'))
{
throw new \RuntimeException('Модуль search не установлен');
}
$full = false;
$stepSeconds = 15;
$clearSuggest = false;
$initialState = [
'SITE_ID' => 's1',
'MODULE_ID' => 'vendor.docs',
];
$progress = runSearchReindexStep(
$stateFile,
$lockFile,
$full,
$stepSeconds,
$clearSuggest,
$initialState
);
// Передать результат планировщику через стандартный вывод
echo json_encode($progress, JSON_THROW_ON_ERROR) . PHP_EOL;
exit($progress['status'] === 'locked' ? 2 : 0);
}
catch (\Throwable $exception)
{
fwrite(STDERR, $exception->getMessage() . PHP_EOL);
exit(1);
}
Для полной переиндексации задайте $full = true и $initialState = []. Не ограничивайте полный режим одним сайтом или модулем. Метод очищает общий индекс и должен восстановить все источники.
Не публикуйте такой сценарий как страницу без авторизации. Переиндексация меняет общий индекс и создает нагрузку на все источники. Запускайте код из доверенной очереди, административной команды или закрытого служебного процесса.
Надежное обслуживание начинается с узкого исправления и заканчивается контрольным запросом. Полную переиндексацию оставляйте для смены движка, изменения правил подготовки слов в индексе и восстановления после потери целостности. Настройка use_stemming меняет только разбор запроса и не требует полного прохода. Такой порядок сохраняет поиск доступным и помогает связать каждый симптом с конкретным этапом.