Комментарии 8
Я не знаю может это только у нас в компании битрикс считают злом и проклинают тот день когда руководство решило его развернуть на своих серверах )) и может есть кто у кого с ним проблем бесконечных нету?)
BitrixVM (связка Linux + Apache + MySQL + PHP).
percona server 8.0 (MySQL)
postgresql 13.x
web-server (Apache 2.4.x)
php 8.x
nginx 1.28.1
push-server 3.1.0
redis 8.2.x
nodejs 22.x
memcached
libreoffice 25.2.7.x
catdoc
xpdf
Потому что разработчик нового сайта не знал о ключевых проблемах, которые мы обнаружили еще весной. К проекту его не подключали — информация о корневых причинах до него просто не дошла.
Следовательно подрядчик о проблеме не знал и решил поставленную ему задачу, обновил сайт. Проблемы что вы выявили его не касаются.
P.S. А так все что вы перечислили можно отнести ко многим CMS, ну и решения ваши стандартные.
При каком количестве посетителей в день падал сайт? Контент создается динамически из базы данных?
Сайт начинал испытывать проблемы уже при ~10 rps (запросов в секунду) — это примерно 20 000-30 000 посетителей в день при равномерном распределении, но на практике трафик приходит волнами (утренние и дневные пики), поэтому проблемы начинались и при меньшем суточном трафике.
Ключевой момент: сайт падал не из-за абсолютного числа посетителей, а из-за совпадения нескольких факторов:
COUNT-запросы блокировали таблицу
b_iblock_elementпри каждом хите с пагинациейИнвалидация кеша редакторами (правки статей) создавала моменты, когда кеш отсутствовал и все запросы шли напрямую в БД
Ночные боты обходили страницы с пагинацией, генерируя COUNT-запросы даже без живых пользователей
Да, контент создается динамически из базы данных MySQL. Сайт работает на 1С-Битрикс — это CMS, в которой все данные (новости, статьи, рубрики) хранятся в инфоблоках в единой таблице b_iblock_element. При каждом запросе пользователя (если кеш не валиден) PHP-компоненты Битрикса выполняют SQL-запросы к MySQL для формирования страницы. Кеширование есть, но из-за описанных в статье проблем оно фактически не работало.
После оптимизации сайт стабильно выдерживал нагрузочный тест в 50 rps (~60% CPU), то есть запас по мощности составил минимум x5 от обычной нагрузки.
Кажется, компонент news.list у вас был не стандартный (устаревший?) В актуальном ядре GetList вызывается, как и у вас, с третьим параметром false, а подсчёт количества элементов делается (если я не ошибаюсь) на уровне DBResult через mysqli_num_rows / pg_num_rows.
В любом случае, познавательно, спасибо!
Хорошее замечание! Компонент bitrix:news.list на момент нашей работы (конец 2023 года) был стандартным, из актуального ядра Битрикс. Но здесь есть нюанс.
Действительно, в актуальном ядре CIBlockElement::GetList() с третьим параметром false не выполняет отдельный SELECT COUNT(*). Однако проблема возникала не в самом вызове GetList, а в механизме пагинации стандартного компонента. Когда в компоненте включена пагинация (DISPLAY_BOTTOM_PAGER => "Y"), стандартный bitrix:news.list использует CDBResult::NavStart(), который внутри себя делает отдельный COUNT-запрос для определения общего количества элементов — это нужно для построения навигации вида “Страница 1 из N”.
То есть сам GetList с false действительно не считает — но навигационная обвязка вокруг него считает. И именно этот COUNT через NavStart блокировал таблицу.
Что касается mysqli_num_rows / pg_num_rows — это подсчет количества строк в уже полученном результате выборки, он работает в памяти PHP и не создает дополнительной нагрузки на БД. Но для построения полной пагинации (с номерами страниц и общим числом) Битриксу нужно знать общее количество элементов в таблице с учетом фильтра, а не только количество возвращенных строк на текущей странице. Вот для этого и выполняется отдельный SELECT COUNT(*).
Наш кастомный компонент nocount:news.list — это форк стандартного, в котором мы заменили логику пагинации: вместо NavStart используем nTopCount + nOffset и запрашиваем на 1 элемент больше, чем нужно для отображения. Если “лишний” элемент пришел — значит есть следующая страница. Визуально получается навигация типа “Назад / Вперед” вместо “Страница 1 из 347”, зато ни одного COUNT-запроса.
"То есть сам GetList с false действительно не считает — но навигационная обвязка вокруг него считает. И именно этот COUNT через NavStart блокировал таблицу."
Хм, кажется, я вижу свою ошибку. У DBResult есть метод InitFromArray, и при его использовании пагинация обсчитывается в памяти PHP. В NavStart при установленном флаге bFromArray используется подсчёт строк через num_rows.
Но вроде бы выглядит так, что описанную вами проблему можно кастомизировать и так, т.е. инициализировать пагинацию не от запроса в БД, а от массива.
упд. Хотя это всё равно потребует больших запросов, т.к. эта логика предполагает, что нужно знать текущую страницу, т.е. всё равно нужно знать все... Уф... Да уж, коварный компонент :)
Понравилась статья. Конкретная проблема - приведено конкретное решение по типу "вставил и готово". Одобряю))

Укрощение 1С-Битрикс: оптимизация новостного сайта, который падал под нагрузкой