Кэширование результатов

Природа вычислений и необходимость кэширования

Валидационные библиотеки, построенные на декларативных правилах, сталкиваются с повторяющимся выполнением одних и тех же проверок при изменении состояния формы. В Vest (JavaScript library) каждая проверка формируется как набор правил, которые могут зависеть от текущего значения поля, других полей формы или внешнего состояния. При частом обновлении UI это приводит к избыточным вычислениям.

Кэширование результатов в таком контексте выполняет функцию устранения повторных вычислений идентичных или эквивалентных проверок. Основная цель — сокращение времени повторного запуска валидации и стабилизация поведения при инкрементальных изменениях состояния.

Модель выполнения правил и точки повторного использования

Внутренне выполнение набора правил можно представить как последовательность операций:

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

При повторном запуске валидации часть этих операций может быть идентичной предыдущему прогону. Особенно это проявляется в следующих сценариях:

  • значение поля не изменилось
  • зависимые поля не изменились
  • набор правил не изменился
  • контекст выполнения (например, режим формы) остался тем же

В таких условиях кэширование может работать на нескольких уровнях: от результата отдельного правила до полного результата проверки поля.

Кэширование на уровне поля

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

Ключ кэширования формируется из:

  • имени поля
  • значения поля
  • версии набора правил
  • зависимых значений

Условие повторного использования результата:

  • совпадение всех входных параметров проверки

Если хотя бы один элемент изменился, кэш считается недействительным.

Такой подход особенно эффективен в формах с большим количеством независимых полей, где пользователь редактирует только одно поле за раз.

Кэширование на уровне правил

Каждое правило в Vest (JavaScript library) может рассматриваться как чистая функция: на вход подаётся значение и контекст, на выходе — результат валидности.

Это позволяет вводить более гранулярный кэш:

  • ключ: идентификатор правила + входное значение + контекст
  • значение: результат выполнения правила

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

Особенно полезно это для дорогих операций:

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

Дедупликация выполнения правил

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

Дедупликация устраняет это повторение:

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

Это снижает нагрузку на CPU и предотвращает дублирующие вычисления ошибок.

Инвалидация кэша

Ключевой аспект кэширования — корректная инвалидация. Ошибочная инвалидация приводит либо к устаревшим результатам, либо к избыточным вычислениям.

Основные стратегии инвалидации:

1. Инвалидация по изменению значения

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

2. Инвалидация по версии схемы

  • при изменении набора правил увеличивается версия
  • все старые кэшированные результаты становятся невалидными

3. Инвалидация по зависимостям

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

Зависимости между полями и каскадное кэширование

Валидационные схемы часто содержат зависимости:

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

В таких случаях кэширование должно учитывать каскадные эффекты. Если изменяется базовое поле, необходимо сбросить:

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

Для этого формируется граф зависимостей, где:

  • узлы — поля
  • рёбра — зависимости

Инвалидация распространяется по графу.

Кэширование результатов всей валидации

На верхнем уровне возможно кэширование полного результата useForm или аналогичной структуры.

Ключ включает:

  • состояние всех полей формы
  • версию схемы
  • внешние параметры (если есть)

Если состояние не изменилось, повторный запуск возвращает готовый результат:

  • список ошибок
  • статус валидности формы
  • агрегированные флаги

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

Оптимизация структур ключей кэша

Эффективность кэширования зависит от того, насколько быстро формируются и сравниваются ключи.

Используются стратегии:

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

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

Влияние асинхронных правил

Асинхронные проверки усложняют модель кэширования. Такие правила могут:

  • выполняться с задержкой
  • зависеть от внешних API
  • возвращать разные результаты при одинаковом входе

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

  • хранение статуса выполнения (pending, resolved, rejected)
  • тайм-ауты устаревания кэша
  • возможность принудительного обновления

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

Частичные пересчёты и ленивое кэширование

Вместо полного пересчёта всей формы используется стратегия частичного обновления:

  • пересчитывается только изменённое поле
  • остальные результаты берутся из кэша
  • зависимые поля обновляются выборочно

Ленивое кэширование означает, что результат сохраняется только после первого вычисления. Это уменьшает начальную нагрузку, но сохраняет ускорение при повторных обращениях.

Баланс между памятью и производительностью

Кэширование всегда требует компромисса:

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

В условиях больших форм чрезмерное кэширование может привести к росту потребления памяти. Поэтому используются ограничения:

  • ограничение размера кэша
  • вытеснение редко используемых записей
  • очистка при смене контекста формы

Итоговая модель поведения

Комбинация всех уровней кэширования формирует многослойную систему:

  • кэш правил
  • кэш полей
  • кэш формы
  • кэш зависимостей

Каждый слой отвечает за свою область и инвалидацию, что позволяет поддерживать высокую производительность при сложных и динамических формах в Vest (JavaScript library)