Search
Write a publication
Pull to refresh

Медленный 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. Подскажите в комментариях удобные инструменты для этого.

Tags:
Hubs:
You can’t comment this publication because its author is not yet a full member of the community. You will be able to contact the author only after he or she has been invited by someone in the community. Until then, author’s username will be hidden by an alias.