Проблемы с производительностью

Библиотека class-validator опирается на декораторы и метаданные TypeScript, что автоматически формирует слой валидации поверх обычных объектов JavaScript. Основная цена такого подхода заключается не в синтаксисе, а в том, что проверка выполняется во время выполнения, с использованием рефлексии и цепочек правил.

Ключевые источники затрат:

  • обращение к метаданным reflect-metadata
  • создание экземпляров классов DTO
  • рекурсивная проверка вложенных объектов
  • последовательное выполнение всех валидаторов без глобальной оптимизации

Каждый из этих элементов по отдельности не критичен, но в совокупности становится заметен при росте нагрузки.


Рефлексия и metadata-доступ

Механизм декораторов в class-validator тесно связан с reflect-metadata. При каждом вызове валидации происходит чтение описаний:

  • типов полей
  • применённых декораторов
  • настроек правил (например, @IsString, @Length, @IsOptional)

Эти данные не кэшируются в виде оптимизированного AST, а извлекаются через runtime API. В результате:

  • увеличивается количество обращений к объектам метаданных
  • создаются промежуточные структуры правил
  • растёт стоимость первой инициализации валидации для класса

Особенно заметно это на больших DTO, где десятки и сотни полей содержат декораторы.


Стоимость создания экземпляров DTO

Типичный сценарий использования предполагает преобразование plain object → class instance:

plain -> class-transformer -> DTO instance -> class-validator

Каждое такое преобразование добавляет нагрузку:

  • создаётся новый объект класса
  • копируются поля
  • применяются трансформации (если используется class-transformer)
  • инициируется цепочка валидации

В высоконагруженных API это превращается в скрытый overhead, особенно если входные данные большие (например, массивы сущностей).


Рекурсивная валидация и вложенные структуры

Одним из наиболее дорогих сценариев является использование вложенных DTO:

class User {
  @ValidateNested()
  profile: Profile;
}

Проблема заключается в том, что class-validator:

  • рекурсивно проходит по всем уровням вложенности
  • создаёт отдельные контексты валидации для каждого уровня
  • повторно извлекает метаданные для каждого класса

При глубокой структуре данных возникает эффект экспоненциального роста количества проверок, особенно если:

  • используются массивы объектов
  • присутствуют повторяющиеся типы DTO
  • включена глубокая валидация без ограничений

Валидация массивов и больших коллекций

Массивы объектов являются одним из самых дорогих случаев:

  • каждый элемент валидируется отдельно
  • для каждого элемента создаётся собственный контекст
  • вложенные валидаторы вызываются заново

Пример типичной проблемы:

@ValidateNested({ each: true })
items: Item[];

При тысячах элементов стоимость становится линейно растущей, но с большим коэффициентом из-за накладных расходов на каждый вызов валидатора.

Дополнительный фактор — отсутствие батчинга: библиотека не оптимизирует проверку массива как единой структуры.


Цепочки валидаторов и последовательное выполнение

Каждый декоратор в class-validator добавляет отдельное правило. Например:

@IsString()
@Length(5, 20)
@Matches(/^[a-z]+$/)
field: string;

Для одного поля выполняется последовательная цепочка проверок:

  • сначала IsString
  • затем Length
  • затем Matches

Особенность заключается в том, что:

  • проверки не объединяются
  • нет компиляции правил в единый predicate
  • каждое правило вызывает отдельную функцию

На больших DTO количество вызовов быстро увеличивается до тысяч операций.


Синхронная модель выполнения

class-validator в базовой конфигурации работает синхронно. Это означает:

  • отсутствует параллелизация
  • каждая проверка блокирует поток выполнения
  • нет распределения нагрузки между ядрами CPU

Даже если часть валидаторов могла бы выполняться независимо, библиотека сохраняет последовательный характер исполнения.


Условные правила и группы валидации

Использование групп (groups) и условий (@ValidateIf) добавляет дополнительную стоимость:

  • каждая проверка предваряется вычислением условия
  • метаданные групп фильтруются на лету
  • набор активных валидаторов пересобирается при каждом запуске

Пример проблемного сценария:

  • множество групп (create/update/admin)
  • пересекающиеся правила
  • динамическое включение/исключение валидаторов

Это приводит к дополнительным проходам по метаданным перед фактической проверкой.


Whitelist и forbidNonWhitelisted

Опции:

  • whitelist: true
  • forbidNonWhitelisted: true

выполняют дополнительную пост-обработку объекта:

  • сравнение всех ключей входного объекта с DTO
  • удаление лишних свойств или генерация ошибок
  • обход всех enumerable keys объекта

При больших payloads эта операция становится заметной, поскольку добавляет второй проход по структуре данных после основной валидации.


Перекрёстные зависимости class-transformer

Часто class-validator используется вместе с class-transformer. Это усиливает нагрузку:

  • сначала происходит трансформация типов
  • затем повторное чтение тех же данных валидатором
  • возможны двойные обходы структуры

Особенно затратны:

  • преобразование массивов
  • вложенные объекты с @Type
  • преобразования строк → числа / даты

Проблемы масштабирования на больших DTO

При увеличении количества полей и уровней вложенности наблюдаются следующие эффекты:

  • рост времени валидации почти линейно с коэффициентом overhead
  • увеличение потребления памяти из-за промежуточных объектов
  • рост GC pressure из-за множества краткоживущих структур

DTO с сотнями полей и сложной вложенностью становится узким местом даже при умеренном RPS.


Отсутствие компиляции правил в оптимизированный код

В отличие от систем вроде AJV, где JSON Schema компилируется в оптимизированные функции, class-validator:

  • не компилирует правила в единый execution plan
  • использует набор независимых функций
  • хранит метаданные в декларативном виде

Это делает библиотеку гибкой, но ограничивает производительность.


Типичные сценарии деградации

На практике проблемы проявляются в следующих случаях:

  • массовая валидация списков сущностей (bulk import API)
  • глубокие вложенные DTO (3+ уровня)
  • частое создание новых экземпляров классов
  • отсутствие ограничения на входные данные
  • использование большого числа декораторов на одном поле

Подходы к снижению нагрузки

Оптимизация обычно строится вокруг уменьшения количества работы, а не ускорения самой библиотеки:

  • сокращение глубины DTO
  • разбиение моделей на более плоские структуры
  • минимизация числа декораторов на поле
  • отключение whitelist при отсутствии необходимости
  • ограничение использования @ValidateNested
  • предварительная фильтрация данных до попадания в валидатор

Дополнительно используется стратегия частичной валидации, когда проверяются только изменённые поля, а не весь объект целиком.


Альтернативные модели валидации

В системах с высокими требованиями к производительности часто применяются альтернативы:

  • схемная валидация (например, через JSON Schema)
  • компилируемые валидаторы
  • ручная валидация критических участков
  • гибридные подходы (schema + minimal custom logic)

Причина перехода заключается в снижении runtime overhead и уменьшении количества рефлексивных операций.


Поведение при увеличении нагрузки

При росте количества запросов характерны следующие эффекты:

  • увеличение latency из-за синхронной природы выполнения
  • рост CPU usage пропорционально количеству DTO
  • ухудшение p95/p99 задержек из-за длинных цепочек валидаторов
  • неравномерная нагрузка при сложных вложенных структурах

Эти эффекты становятся особенно заметны при API, ориентированных на массовую обработку данных.