SEO и GEO

Модуль llms.txt 1.1: фоновая генерация и лечение 504

Модуль llms.txt 1.1: фоновая генерация и лечение 504

Третья часть истории о генераторе llms.txt для 1С-Битрикс. Первая — о замысле модуля, вторая — о подключении к живому каталогу с 25 доменами. Эта — о том, как мы упёрлись в пределы синхронного PHP, как наш модуль положил продакшен-сайт собственного кейса и что мы исправили в версии 1.1.0: фоновая генерация без таймаутов, живой прогресс-бар, автодобавление разделов и честный разбор инцидента.

Что сломалось: 504 на большом каталоге

Сценарий, знакомый каждому, кто гонял длинные операции из админки Битрикса: на тестовом каталоге из пяти разделов генерация занимала секунды. На живом каталоге — сотни разделов, тысячи товаров — нажимаешь «Сгенерировать», страница висит минуту и отдаёт 504 Gateway Timeout.

Корень честно инженерный, без мистики:

  1. Генерация шла одним синхронным POST-запросом из формы настроек.
  2. nginx между браузером и PHP ждёт ответа бэкенда ограниченное время (proxy_read_timeout). PHP честно работал — но дольше, чем nginx готов ждать.
  3. Виновник долгой работы — опция проверки ссылок: LinkValidator делал HTTP GET к каждой странице каталога с таймаутом 3 секунды. На каталоге в тысячи страниц это часы.

Вывод, который определил всю архитектуру 1.1.0: «просто AJAX с полосой» это не лечит. Полоса рисуется в браузере, а длинный HTTP-запрос всё равно падает по таймауту. Убирать надо сам длинный запрос.

Решение: фоновый CLI-процесс вместо длинного запроса

В 1.1.0 генерация вынесена в фоновый CLI-процесс. CLI работает без nginx-таймаута, поэтому 504 невозможен в принципе:

[Админка]  нажали «Сгенерировать»
   → AJAX start_gen: exec("php cli_gen.php docRoot siteId &") → ответ сразу
   → фон: Agent::generateForSite() пишет прогресс в файл
[Браузер]  каждые 1 с AJAX gen_status → читает файл → двигает полосу %
   → running=false → «Готово» или ошибка

Новые детали реализации:

  • cli_gen.php — минимальный CLI-бутстрап Битрикса в корне модуля: принимает корень сайта и ID сайта, поднимает ядро, запускает генерацию. В корень сайта не копируется — AJAX вызывает его по абсолютному пути.
  • lib/Progress.php — файловый счётчик в /upload/eu.llmtxtautomata/.progress_<LID>.json: {total, done, pct, stage, running, error, ts}. Есть защита от «зависшего» счётчика: если файл старше часа, запуск считается мёртвым.
  • Защита от параллельных запусков: повторный клик по «Сгенерировать» блокируется по флагу running в прогресс-файле.
  • Путь к php-cli определяется автоматически (command -v php/usr/bin/phpPHP_BINARY).
  • Fallback без exec(): если хостинг запретил exec в disable_functions, модуль переключается на прежний синхронный режим — с явным предупреждением, что на большом каталоге возможен таймаут. Для таких сайтов штатная рекомендация — авто-агент по cron: он и раньше работал в CLI, где таймаутов нет.

Прогресс-бар с честными процентами

Полоса в админке показывает не «искусственную» анимацию, а реальное состояние генератора:

  • тик прогресса — по каждому разделу маппинга, реально добавившему контент;
  • total считается как разделы × проходы × 2, где проходы — регионы Аспро плюс основной домен, а двойка — кураторский llms.txt и расширенный llms-full.txt;
  • рядом с процентами выводится текущая стадия — название раздела, который собирается прямо сейчас.

На каталоге из 14 разделов с 24 региональными поддоменами это сотни тиков: полоса двигается непрерывно, и видно, что процесс живой, а не завис.

«Добавить все разделы ИБ» одной кнопкой

Раньше маппинг «раздел → секция llms.txt» набивали руками: строку за строкой. В 1.1.0 в настройках появился блок «Добавить все разделы ИБ»:

  • вводите ID инфоблока → AJAX подтягивает список активных разделов с реальными названиями и сортировкой;
  • галочка «включая вложенные» — берём либо только корневые (DEPTH_LEVEL=1), либо всю глубину;
  • каждая строка предзаполнена: ID, заголовок H2, сортировка, лимиты; галочки «активна», «с элементами», «с описанием» уже стоят;
  • если разделов больше 200 — модуль попросит подтверждение.

Практическая рекомендация из тестов: либо 5 верхних разделов с вложенными, либо только конечные подразделы без родителей — родитель с включёнными вложенными соберёт смесь «остатков». Порядок секций задаёт поле «Сорт.».

Инцидент, о котором надо рассказать: self-DDoS

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

  1. LinkValidator делал HTTP GET с таймаутом 3 секунды к каждой странице своего же сайта.
  2. Расширенный режим llms-full.txt удваивал объём генерации.
  3. Раньше спасал таймаут nginx — запрос умирал, не успев навредить. Фоновый CLI без таймаута пробегал весь каталог до конца: сервер получал сотни самообращений и ложился.

Классический self-DDoS: защиту от внешней атаки сайт выдержал бы, а от собственного модуля — нет. Сайт подняли (остановка фонового процесса, рестарт php-fpm, снятие галочек), после чего зафиксировали архитектурные хотфиксы в 1.1.0:

  • llms-full.txt полностью пропускает HTTP-валидацию — контент для него проверяется по базе и тексту, без обхода страниц;
  • check_links валидирует только кураторский llms.txt, а не оба файла;
  • таймаут LinkValidator снижен с 3 до 2 секунд, дубликаты URL отбрасываются до валидации;
  • опция проверки ссылок выключена по умолчанию и снабжена красным предупреждением в интерфейсе.

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

Чистота вывода

Параллельно закрыли накопившиеся шероховатости текста в обоих файлах:

  • пустые секции не пишутся — заголовок добавляется «оптимистично» и откатывается, если раздел не дал контента;
  • HTML-сущности декодируются (html_entity_decode + замена неразрывных пробелов) — в файлах больше нет &nbsp;;
  • Aspro-макросы срезаются: служебные подстановки вида #REGION_TAG_…# и #CITY# не попадают в текст;
  • обрезка анонсов — по границе слова с многоточием, а не посередине слова на 150-м символе;
  • список поддоменов заполняется сам: модуль читает инфоблок регионов Аспро и подставляет живой список — на кейсе это основной домен плюс 24 поддомена без ручного ввода.

Тест на живом каталоге

Финальный прогон — на том же магазине с региональной сетью (инфоблок 11, 24 поддомена):

  • генерация без 504, полоса с процентами работает;
  • оба файла собираются: llms.txt и llms-full.txt с описаниями до ~1000 символов;
  • пустых секций, &nbsp; и макросов в выводе нет, обрезка аккуратная;
  • php -l по всем изменённым файлам — чисто (проверяли в docker-контейнере php:8.1-cli);
  • пакет eu.llmtxtautomata-1.1.0.zip собран и готов к повторной подаче на модерацию Маркетплейса.

Кому это нужно

Модуль решает одну задачу — делает сайт на 1С-Битрикс читаемым для нейросетей: ChatGPT, Perplexity, Яндекс Нейро. Версия 1.1.0 добавляет то, чего не хватало взрослым каталогам:

  • большим магазинам — генерация любых объёмов без таймаутов и с наблюдаемым прогрессом;
  • сетям региональных сайтов на Аспро — автособор поддоменов и раздельные файлы по регионам;
  • компаниям без программиста в штате — настройка мышкой: все разделы добавляются кнопкой, cron-агент обновляет файлы сам.

Цена модуля — 4 500 ₽ единоразово через Маркетплейс 1С-Битрикс, установка под ключ — 5 000 ₽. Пока идёт модерация, напишите нам — поставим и настроим вручную. О том, как llms.txt встраивается в общую стратегию продвижения — в большом руководстве SEO для Bitrix, а готовность сайта к AI-поиску можно бесплатно проверить экспресс-аудитом GEO-индекса.

Достоверность: статья написана разработчиком модуля — eu.llmtxtautomata наш коммерческий продукт, это наш интерес. Факты инцидента и хотфиксов — из реального лога разработки и тестов на живом проекте.

Частые вопросы

Почему при генерации llms.txt возникала ошибка 504? Генерация шла одним синхронным POST-запросом из админки. На каталоге в сотни и тысячи разделов запрос длится дольше, чем nginx ждёт ответа от PHP (proxy_read_timeout), — и nginx возвращает 504 Gateway Timeout. В версии 1.1.0 генерация запускается фоновым CLI-процессом без nginx-таймаута, поэтому 504 невозможен в принципе.

Что нового в модуле eu.llmtxtautomata 1.1.0? Фоновая генерация через CLI с живым прогресс-баром в процентах, кнопка «Добавить все разделы ИБ» с автоподстановкой названий, лечение инцидента self-DDoS при проверке ссылок, чистый вывод без пустых секций и HTML-сущностей, автозаполнение списка поддоменов Aspro из инфоблока регионов.

Что такое self-DDoS при проверке ссылок? Опция проверки ссылок делала HTTP GET к каждой странице собственного сайта с таймаутом 3 секунды. На большом каталоге сервер получал сотни запросов сам к себе и ложился. В 1.1.0 llms-full.txt вообще не проходит HTTP-валидацию, проверка осталась только для кураторского llms.txt, таймаут снижен до 2 секунд, а опция по умолчанию выключена.

Модуль работает, если на хостинге запрещён exec()? Да. Если exec в disable_functions, модуль автоматически переключается на синхронный режим с предупреждением, что на очень большом каталоге возможен таймаут. Для таких сайтов рекомендуем генерацию авто-агентом по cron — это штатный механизм Битрикса для длинных задач.

Сколько стоит модуль и когда он появится в Маркетплейсе? Цена — 4 500 ₽ единоразово через Маркетплейс 1С-Битрикс, установка под ключ — 5 000 ₽. Версия 1.1.0 собрана и готовится к повторной подаче на модерацию; следите за обновлениями в блоге.


Об авторе. Евгений Урядов, ИП, занимается интеграциями на 1С-Битрикс с 2012 года. Автор модулей eu.weightsync (синхронизация веса и остатков между сайтом и CRM Битрикс24) и eu.llmtxtautomata (генератор llms.txt). Специализация — связка «Битрикс + современные фронтенды» и оптимизация сайтов под нейросетевые поисковики. Нашли неточность? Напишите на hello@automata.sale — поправим и укажем дату правки.


📞 +7 (906) 311-77-69 · ✉ hello@automata.sale · 💬 Telegram: @automatasale · 🌐 automata.sale

ИП Урядов Евгений Евгеньевич · ИНН 645112058391 · ОГРНИП 312645301900058 Работаем по России, Беларуси и Казахстану

🚀 Нужна помощь с сайтом или интернет-магазином?

Я работаю удалённо по всей России и СНГ. Узнайте цены и условия для вашего города:

Все регионы →