Иногда значительное снижение производительности сайта может быть вызвано
причинами, на первый взгляд не способными создать какой-либо проблемы. Одной из
таких причин являются правила для модуля mod_rewrite в файлах .htaccess
веб-сервера Apache. Принципиальное существование такой проблемы легко
объясняется тем очевидным фактом, что задавая регулярное выражение, фактически
Вы неявно задаете алгоритм поиска данных в строке, и этот алгоритм может
оказаться иногда существенно неоптимальным.
С одним из таких примеров мы столкнулись на своем хостинге. Весьма заметное
снижение производительности и увеличение нагрузки возникло из-за единственного
правила в файле .htaccess:
RewriteRule ^(([a-z0-9A-Z]+/?)*)(_([a-z0-9A-Z_]+))?$ inner.php?pathstring=$1&urlparams=$3
Исследование показало, что время поиска с увеличением длины строки растет
экспоненциально, а основную проблему создают два вложенных шаблона без
ограничения длины поиска. Более того, проблемы с производительностью возникают,
когда строка не удовлетворяет регулярному выражению.
Для иллюстрации снижения производительности на
тестовой машине был запущен веб-сервер с файлом .htaccess, в котором было
определено правило, приведенное ниже. Это правило — упрощенная версия
приведенного выше примера. На нем становится очевидным, что собой представляют
вложенные шаблоны без ограничения длины.
RewriteRule ^(a+)*$ test.html [L]
Затем с использованием Apache benchmarking tool (программы “ab”) была замерена
зависимость скорости отдачи станицы от длины строки с запросом. Команда
выглядела следующим образом:
ab -g result.dat -q -n 1000 http://hostname/aaa..aa1 > result.txt
Количество символов “a” в запросе увеличивалось от 1 до 80. График,
иллюстрирующий зависимость среднего времени ответа веб-сервера от длины
запроса, приведен ниже (обратите внимание, шкала Y логарифмическая).

Видно, что на участке от 1 до 20-22 символов зависимость имеет экспоненциальный
характер, после чего время отклика остается примерно постоянным в районе
1.0-1.2 секунд. Дальнейшее исследование показало, что прекращение роста связано
с внутренним ограничителем библиотеки libpcre, используемой в Apache: при
превышении количества итераций определенного порога поиск по регулярному
выражению прекращается с сообщением об ошибке. Значение по-умолчанию для
данного порога составляет десять миллионов итераций, и может быть изменено
перекомпиляцией библиотеки.
Нелишне подчеркнуть, что правила, добавленные в .htaccess, проверяются при
каждом запросе к веб-серверу, и если для отрисовки Вашей страницы требуется
10-20 запросов, то эта проблема будет проявлять себя 10-20 раз на страницу.
Важно также отметить, что указанная проблема не является проблемой веб-сервера
Apache или модуля mod_rewrite. В действительности это проблема алгоритмическая,
и может воспроизводиться всюду, где используются регулярные выражения. Кроме
того, наличие ограничителя - это характерная особенность библиотеки libpcre.
Ее для работы с регулярными выражениями используют многие приложения, в
частности названный выше Apache, а также PHP. Однако другие реализации
регулярных выражений (в частности, реализация в Python), подобных ограничителей
не используют. Это значит, что добавив такую строку в собственный код на
Python, Вы получите чисто экспоненциальную зависимость времени выполнения от
длины строки, и неограниченные возможности по управлению производительностью
вашего сервера злоумышленниками.
Избегайте вложенных шаблонов с неограниченной длиной.
Если Вы не уверены в безопасности собственного выражения, проведите тест
производительности, воспользовавшись, например, утилитой pcretest
Обратитесь к документации, например, к man pcreperform
В качестве дополнительного средства увеличения производительности используйте
файлы .htaccess с директивой “RewriteEngine Off”, в каталогах, применение
mod_rewrite для файлов внутри которых не предполагается (например, в каталогах
с картинками).