Хорошее замечание! Компонент 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-запроса.
Сайт начинал испытывать проблемы уже при ~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 от обычной нагрузки.
Хорошее замечание! Компонент
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-запроса.Сайт начинал испытывать проблемы уже при ~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 от обычной нагрузки.