Validator Cache and Performance Tuning

Overview

schema-dsl compiles a schema into a reusable validation function. CacheManager stores those compiled results so the same schema does not need to be compiled repeatedly in high-frequency validation paths.

Most applications do not need to instantiate CacheManager directly. You can usually call validate(), validateAsync(), or new Validator() normally. Read this page when you need one of these tasks:

ScenarioWhat you configure or inspect
Forms, APIs, or batch jobs reuse the same schemasCache hit behavior and capacity
Your application creates many schema shapesmaxSize, TTL, or disabling cache
You need to explain performance changesgetStats() hit rate, misses, and entry count
You are building a framework adapterPassing a custom CacheManager into Validator

Core functions

  • ✅ LRU (least recently used) elimination strategy
  • ✅ TTL (time to live) expiration mechanism
  • ✅ Hit rate statistics
  • ✅ Configurable cache size
  • ✅ Automatically clear expired cache

quick start

import { CacheManager } from 'schema-dsl/pure';

//Create cache instance
const cache = new CacheManager({
  maxSize: 5000, //Default maximum cache size
  ttl: 0 // Default: never expires; LRU controls compiled schema lifecycle
});

// store cache
cache.set('user-schema', compiledValidator);

// Get cache
const validator = cache.get('user-schema');
if (validator) {
  console.log('cache hit');
} else {
  console.log('cache miss');
}

// View statistics
console.log(cache.getStats());

API reference

Constructor

new CacheManager(options)

Parameters:

Parametertypedefault valueDescription
options.maxSizenumber5000Maximum number of cache entries
options.ttlnumber0Cache lifetime in milliseconds; 0 means no time-based expiration
options.enabledbooleantrueWhether to enable caching
options.statsEnabledbooleantrueWhether to enable statistics

get(key)

Get cached value.

const value = cache.get('my-key');

Return Value:

  • Hit: Returns the cached value
  • Missed or expired: return null

Behavior:

  • Update access time (LRU)
  • Increase access count
  • Move entry to end of queue

set(key, value, ttl?)

Set cache value.

// Use default TTL
cache.set('key', value);

// Use custom TTL (5 minutes)
cache.set('key', value, 300000);

Behavior:

  • If maximum capacity is reached, automatically retire the oldest unused entries
  • Record creation time and access time

delete(key)

Delete cache entries.

const deleted = cache.delete('key');
console.log(deleted); // true or false

has(key)

Check cache exists (do not update access time).

if (cache.has('key')) {
  console.log('cache exists');
}

clear()

Clear all caches.

cache.clear();

getStats()

Get cache statistics.

const stats = cache.getStats();
console.log(stats);
// {
// hits: 150, // Number of hits
// misses: 30, // Number of misses
// evictions: 10, // Number of eliminations
// sets: 100, // Number of settings
// deletes: 5, // Number of deletes
// clears: 1, // Number of clears
// hitRate: '83.33', // Hit rate percentage string
// size: 80, // Current cache number
// maxSize: 5000, // maximum capacity
// enabled: true // Whether to enable caching
// }

resetStats()

Reset statistics.

cache.resetStats();

size()

Get the current number of cache entries.

console.log(`Current cache: ${cache.size()} items`);

Configuration options

cache size

// Small application (save memory)
const smallCache = new CacheManager({ maxSize: 50 });

// Large applications (higher performance)
const largeCache = new CacheManager({ maxSize: 500 });

TTL settings

// Short-term caching (5 minutes)
const shortCache = new CacheManager({ ttl: 5 * 60 * 1000 });

// Long term cache (24 hours)
const longCache = new CacheManager({ ttl: 24 * 60 * 60 * 1000 });

// Never expires
const permanentCache = new CacheManager({ ttl: 0 });

Disable caching

// Development environment may need to disable caching
const noCache = new CacheManager({ enabled: false });

Statistics

Hit rate analysis

function analyzeCachePerformance(cache) {
  const stats = cache.getStats();

  console.log('=== Cache performance analysis ===');
  console.log(`Number of hits: ${stats.hits}`);
  console.log(`Number of misses: ${stats.misses}`);
  console.log(`Hit rate: ${stats.hitRate}%`);
  console.log(`Cache usage: ${(stats.size / stats.maxSize * 100).toFixed(1)}%`);
  console.log(`Number of eliminations: ${stats.evictions}`);

  //Performance recommendations
  if (Number(stats.hitRate) < 50) {
    console.log('⚠️ The hit rate is low, consider increasing the cache size');
  }
  if (stats.evictions > stats.sets * 0.5) {
    console.log('⚠️ The elimination rate is high, consider increasing the cache size or TTL');
  }
}

Monitoring dashboard

function printCacheDashboard(cache) {
  const stats = cache.getStats();
  const hitRate = `${stats.hitRate}%`;
  const usage = (stats.size / stats.maxSize * 100).toFixed(1);

  console.log('┌─────────────────────────────┐');
  console.log('│ Cache Status Dashboard │');
  console.log('├─────────────────────────────┤');
  console.log(`│ Hit rate: ${hitRate.padStart(8)} │`);
  console.log(`│ Usage rate: ${usage.padStart(6)}% │`);
  console.log(`│ Current entry: ${String(stats.size).padStart(6)} │`);
  console.log(`│ Maximum capacity: ${String(stats.maxSize).padStart(6)} │`);
  console.log('└─────────────────────────────┘');
}

Cache boundaries

CacheManager stores compiled validators for repeated schema cache keys. It reduces repeated AJV compilation, but it does not make unlimited dynamic schema shapes free.

  • Stable schema structure + reused Validator instance: cache hits are expected.
  • new Validator() on every request: each instance starts with a new cache, so previous hits are lost.
  • A different schema shape on every request: cache misses and evictions are expected even with a large maxSize.

If low hit rate is caused by high-cardinality dynamic schemas, prefer bounding or reusing schema shapes. Increasing maxSize only helps when entries are likely to be reused.


best practices

1. Set the cache size appropriately

// Estimated based on Schema quantity
// If there are 50 different schemas, set 100 to have margin
const cache = new CacheManager({ maxSize: 100 });

2. Disable caching in the development environment

const cache = new CacheManager({
  enabled: process.env.NODE_ENV !== 'development'
});

3. Check performance regularly

setInterval(() => {
  const stats = cache.getStats();
  if (Number(stats.hitRate) < 80) {
    console.warn('cache hit rate is less than 80%');
  }
}, 60000);

4. Clear cache when Schema is updated

function updateSchema(name, newSchema) {
  //Update Schema
  schemas[name] = newSchema;

  //Clear related cache
  cache.delete(`schema:${name}`);
}

LRU elimination mechanism

When the cache reaches maximum capacity, the oldest unused entries are automatically evicted:

Cache operation sequence:
1. set('A', ...) → [A]
2. set('B', ...) → [A, B]
3. set('C',...) → [A, B, C] (reach maxSize=3)
4. get('A') → [B, C, A] (A is moved to the end)
5. set('D',...) → [C, A, D] (B is eliminated)


Corresponding sample file

Example entry: cache-manager.ts DESCRIPTION: Covers the actual behavior of set/get/has, LRU eviction, statistics reading, and resetStats().