Как стать автором
Поиск
Написать публикацию
Обновить

Медленный regexp, умирающий Node.js


Наверное каждый слышал выражение, что если ты решаешь проблему с помощью регулярных выражение, то у тебя становится две проблемы.


Недавно и сам столкнулся с проблемой производительности регулярок на Node.js, и к чему это может привести.


В один прекрасный момент все инстансы сервиса на Node.js один за одним перестали отвечать на health-check, слать логи и метрики. Пришлось остановить эти контейнеры (мы запускаем Node.js в Docker) и запустить новые.


Начали разбираться.


С самого начала было понятно, что был заблокирован event loop Node.j какой-то очень длительной операцией в обработке запроса пользователя. Load balancer определял что этот инстанс нездоров и направлял трафик на другие. Туда приходил следующий злополучный запрос от пользователя и они умирали.


Что было странно, что никаких синхронных или блокирующих операций мы в обработке запроса не делали — ну, по крайней мере, нам так казалось. Логгер показывал, что последней операцией перед тем как процесс переставал отвечать была валидация email… с помощью регулярного выражения (да-да я знаю, что так делать нельзя, но все так делают :).


Запустили этот-же скрипт валидации емейла на локальной машине и прифигели — регулярка имела экспоненциальную сложность, время выполнения росло очень быстро при увеличении длины емейла. На строке длиной в ~40 символов нам не хватило терпения дождаться его окончания.


const email = 'some_very_long_invalid_email_that_slows_down_regexp';
const regexp = /^[a-zA-Z0-9][a-zA-Z0-9_\\.\\-\\+&]*@([a-zA-Z0-9]([a-zA-Z0-9]*[\\-]?[a-zA-Z0-9]+)*\\.)+[a-zA-Z]{2,10}$/;

if (!email.match(regexp)) {
    throw new Error('Invalid email')
}

Так как String.prototype.match() это блокирующая операция, то весь event loop ждал пока она завершится, и сервис не обрабатывал другие запросы.


Причем это не было особенность Node.js, эта же регулярка запущенная на Java давала такое же время выполнения. Но из-за мультипочности сервис на Java не умирал полностью, и поэтому последствия были не настолько драматичными, как в случае с Node.js.


Решение было простое: заменить регулярное выражение на другое, которое широко используется в других библиотеках (таких как email-validator или validator) и проверить, что время выполнения больше не зависит от длины емейла.


Для себя сделал следующие выводы:


  • Избегать использования регулярок, поскольку они блокируют event loop, а по их внешнему виду невозможно сказать, какая сложность у алгоритма. И как он себя поведет при больших входящих данных или данных специфической формы.
  • Если избавиться нельзя — то проверять время выполнения.

И самое важное — крайне желательно мониторить состояние event loop в Node.js. Подскажите в комментариях удобные инструменты для этого.

Теги:
Хабы:
Данная статья не подлежит комментированию, поскольку её автор ещё не является полноправным участником сообщества. Вы сможете связаться с автором только после того, как он получит приглашение от кого-либо из участников сообщества. До этого момента его username будет скрыт псевдонимом.