Производительность

Производительность валидатора JSON Schema в Ajv определяется не только скоростью выполнения проверок, но и тем, насколько эффективно происходит подготовка схем, их компиляция и повторное использование. Основной принцип заключается в том, что дорогая операция компиляции должна выполняться один раз, после чего результат используется многократно.

Ajv реализует подход «compile once, validate many times», при котором схема преобразуется в оптимизированную функцию JavaScript. Эта функция выполняется значительно быстрее, чем интерпретация схемы на лету.

Ключевые факторы, влияющие на скорость:

  • стоимость компиляции схемы;
  • количество повторных вызовов валидатора;
  • сложность JSON Schema;
  • использование дополнительных ключевых слов (format, pattern, allOf, oneOf);
  • конфигурация экземпляра Ajv.

Компиляция схем и её влияние на скорость

Внутренняя архитектура Ajv основана на генерации JavaScript-функций из JSON Schema. При вызове compile() происходит:

  1. Разбор схемы;
  2. Построение внутреннего AST;
  3. Генерация оптимизированного JS-кода;
  4. Создание функции-валидатора.

Этот процесс относительно дорогой, но выполняется один раз на схему.

import Ajv from "ajv";

const ajv = new Ajv();
const validate = ajv.compile({
  type: "object",
  properties: {
    id: { type: "number" }
  }
});

validate({ id: 1 });

После компиляции вызов validate() становится прямым выполнением функции без анализа схемы.


Повторное использование экземпляра Ajv

Создание нового экземпляра Ajv для каждой проверки приводит к потере кэша скомпилированных схем и повторной компиляции.

Оптимальная модель:

  • один экземпляр Ajv на приложение или модуль;
  • повторное использование compile() и validate().
const ajv = new Ajv();

// используется повторно
const v1 = ajv.compile(schema1);
const v2 = ajv.compile(schema2);

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


Кэширование скомпилированных валидаторов

Ajv автоматически кэширует схемы по $id. Это позволяет повторно получать уже скомпилированные функции без повторной генерации кода.

const schema = {
  $id: "user.schema.json",
  type: "object",
  properties: {
    name: { type: "string" }
  }
};

ajv.addSchema(schema);

const validate = ajv.getSchema("user.schema.json");

Преимущество такого подхода:

  • отсутствие повторной компиляции;
  • ускорение загрузки модулей схем;
  • уменьшение потребления памяти при масштабных проектах.

Опции, влияющие на производительность

Некоторые настройки Ajv напрямую влияют на скорость выполнения валидаторов.

allErrors

По умолчанию Ajv может прекращать проверку после первой ошибки. Включение allErrors: true приводит к полной проверке всех полей, что снижает производительность.

new Ajv({ allErrors: true });

Использование этой опции оправдано только при необходимости полного отчёта об ошибках.


strict mode

Strict-режим добавляет дополнительные проверки корректности схемы и данных.

new Ajv({ strict: true });

Он увеличивает время компиляции и валидации, но снижает риск ошибок в схемах.


coerceTypes

Автоматическое приведение типов требует дополнительной логики во время выполнения:

new Ajv({ coerceTypes: true });

Каждое приведение типа увеличивает стоимость проверки, особенно на больших массивах данных.


removeAdditional

Удаление лишних полей из объекта выполняется в процессе валидации:

new Ajv({ removeAdditional: "all" });

Эта операция изменяет входные данные и добавляет накладные расходы, так как требует модификации объекта.


Быстрые пути выполнения и ранний выход

Ajv оптимизирует выполнение схем, используя short-circuit evaluation — прекращение проверки при первой критической ошибке.

Особенно эффективно это работает при:

  • строгих типах (type);
  • обязательных полях (required);
  • простых схемах без вложенных allOf и anyOf.

Однако включение allErrors полностью отключает этот механизм, заставляя валидатор проходить всю схему.


Предварительная генерация кода (codegen)

Ajv способен генерировать оптимизированный JavaScript-код, который можно использовать вне рантайма.

Подход позволяет:

  • снизить нагрузку на приложение;
  • исключить компиляцию на старте;
  • ускорить cold start в serverless-окружениях.

Генерация происходит через compile() и последующее использование .toString() у функции-валидатора.


Standalone-валидация

Standalone-схемы позволяют вынести валидатор в отдельный модуль без зависимости от Ajv во время выполнения.

Преимущества:

  • минимизация runtime-зависимостей;
  • более быстрый старт приложения;
  • предсказуемая производительность.

Такой подход особенно полезен в микросервисах и edge-окружениях.


Асинхронная компиляция

При использовании асинхронных форматов или ссылок на внешние схемы компиляция может выполняться асинхронно:

const validate = await ajv.compileAsync(schema);

Асинхронность влияет только на этап подготовки, но не на скорость самой валидации. Однако при большом количестве внешних $ref задержка компиляции становится заметной.


Работа с большими схемами

Большие JSON Schema с глубокой вложенностью и множеством комбинаций (oneOf, anyOf, allOf) существенно снижают производительность.

Оптимизационные подходы:

  • разделение схем на модули;
  • использование $ref вместо дублирования;
  • уменьшение глубины вложенности;
  • избегание избыточных union-типов.

Особенно дорогостоящими являются конструкции:

  • oneOf с множеством вариантов;
  • сложные patternProperties;
  • регулярные выражения в больших количествах.

Регулярные выражения и format-валидация

Проверка строк через pattern и format — один из наиболее затратных процессов.

{
  type: "string",
  pattern: "^[a-zA-Z0-9]+$"
}

Оптимизации:

  • избегание сложных regex;
  • использование простых проверок длины перед pattern;
  • замена format на кастомные быстрые функции.

Кастомные форматы

Ajv позволяет регистрировать пользовательские форматы:

ajv.addFormat("hexColor", /^[0-9A-Fa-f]{6}$/);

Производительность кастомных форматов выше, если они реализованы через:

  • регулярные выражения без backtracking;
  • простые функции без аллокаций;
  • минимальную ветвистость.

Типичные узкие места

На практике снижение производительности чаще всего связано с:

  • повторной компиляцией схем;
  • отсутствием кэширования валидаторов;
  • чрезмерным использованием oneOf;
  • включённым allErrors;
  • глубокими объектными структурами;
  • регулярными выражениями с высокой сложностью.

Интеграция с bundlers и runtime

В средах с Webpack, Vite или esbuild важную роль играет tree-shaking и размер бандла.

Оптимизации:

  • импорт только нужных модулей Ajv;
  • исключение dev-режима в production;
  • предварительная компиляция схем.

В serverless-средах критичен cold start, поэтому standalone-схемы и предкомпиляция дают заметный прирост скорости запуска.


Практики снижения нагрузки на валидатор

  • использование одного экземпляра Ajv на сервис;
  • компиляция схем при старте приложения;
  • отказ от динамического создания схем в runtime;
  • минимизация количества anyOf и oneOf;
  • строгая структура JSON Schema без избыточных ветвлений;
  • перенос сложной логики из схем в бизнес-логику.

Поведение при масштабировании

При росте числа схем и валидируемых объектов основное влияние начинает оказывать не сама проверка, а управление жизненным циклом валидаторов:

  • кэширование становится критическим;
  • компиляция должна быть вынесена из горячего пути;
  • повторное использование функций валидатора снижает нагрузку линейно относительно числа вызовов.

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