Cache Enhancement Documentation
The variant-linker cache system has been enhanced with LRU (Least Recently Used) eviction and optional persistent file-based caching. This document describes the new features and how to configure them.
Overview
The enhanced cache system provides:
- LRU Eviction: Prevents memory bloat by evicting least recently used items when cache reaches size limit
- Configurable Size Limits: Set maximum number of entries in memory cache
- TTL Support: Time-to-live for cache entries with automatic expiration
- Persistent Caching: Optional file-based caching for cross-session persistence
- Two-Tier Architecture: L1 (memory) and L2 (persistent disk) caching
- Backward Compatibility: Existing API remains unchanged
Configuration
Cache configuration is defined in config/apiConfig.json:
{
"cache": {
"memory": {
"maxSize": 100,
"ttl": 300000,
"sizeCalculation": {
"enabled": false
}
},
"persistent": {
"enabled": false,
"location": "~/.cache/variant-linker",
"ttl": 86400000,
"maxSize": "100MB"
}
}
}Memory Cache Options
maxSize: Maximum number of entries in memory cache (default: 100)ttl: Default time-to-live in milliseconds (default: 300000 = 5 minutes)sizeCalculation.enabled: Enable size-based eviction (currently disabled)
Persistent Cache Options
enabled: Enable/disable persistent caching (default: false)location: Directory for cache files (default:~/.cache/variant-linker)ttl: Default TTL for persistent cache (default: 86400000 = 24 hours)maxSize: Maximum total size of persistent cache (e.g., "100MB", "1GB")
API Usage
Backward Compatible API
The existing synchronous API remains unchanged:
const { setCache, getCache, clearCache, getCacheStats, hasCache } = require('./src/cache');
// Store data
setCache('key', data, ttl); // ttl is optional
// Retrieve data
const data = getCache('key'); // Returns null if not found or expired
// Check existence
const exists = hasCache('key');
// Clear cache
clearCache();
// Get statistics
const stats = getCacheStats();New Async API
For full two-tier cache functionality:
const {
getCacheAsync,
hasCacheAsync,
clearCacheAsync,
getComprehensiveCacheStats,
} = require('./src/cache');
// Async operations that check both memory and persistent caches
const data = await getCacheAsync('key');
const exists = await hasCacheAsync('key');
await clearCacheAsync();
// Comprehensive statistics
const fullStats = await getComprehensiveCacheStats();Advanced Usage
Access the cache manager directly for advanced operations:
const { getCacheManager } = require('./src/cache');
const cacheManager = getCacheManager();
// Direct tier access
const memoryCache = cacheManager.memoryCache;
const persistentCache = cacheManager.persistentCache; // null if disabled
// Full async API
await cacheManager.set('key', data, ttl);
const data = await cacheManager.get('key');
const exists = await cacheManager.has('key');
await cacheManager.delete('key');
await cacheManager.clear();
// Statistics
const stats = await cacheManager.getStats();
const config = cacheManager.getConfig();Cache Behavior
Two-Tier Architecture
When persistent caching is enabled:
- L1 (Memory): Fast access, limited size, shorter TTL
- L2 (Persistent): Slower access, larger capacity, longer TTL
Cache Flow
- Set: Data stored in both L1 and L2 (if enabled)
- Get: Check L1 first, fallback to L2, promote to L1 on hit
- Delete: Remove from both tiers
- Clear: Clear both tiers
LRU Eviction
When memory cache reaches maxSize:
- Least recently used item is evicted from memory
- Item remains in persistent cache (if enabled)
- Accessing evicted item promotes it back to memory
TTL Handling
- Each tier has independent TTL configuration
- Memory cache typically has shorter TTL (5 minutes)
- Persistent cache has longer TTL (24 hours)
- Expired items are automatically cleaned up
Performance Considerations
Memory Cache
- Very fast access (hash map lookup)
- Limited by
maxSizesetting - Use for frequently accessed data
Persistent Cache
- Slower access (file I/O)
- Larger capacity
- Use for cross-session persistence
Recommendations
- Memory Cache Size: Set based on available RAM and usage patterns
- Persistent Cache: Enable for batch processing workflows
- TTL Settings: Balance between performance and data freshness
- Cache Location: Use fast storage (SSD) for persistent cache
Monitoring
Basic Statistics
const stats = getCacheStats();
console.log(stats);
// {
// size: 50,
// maxSize: 100,
// calculatedSize: 0,
// ttl: 300000,
// itemCount: 50
// }Comprehensive Statistics
const fullStats = await getComprehensiveCacheStats();
console.log(fullStats);
// {
// memory: {
// size: 50,
// maxSize: 100,
// calculatedSize: 0,
// ttl: 300000,
// itemCount: 50
// },
// persistent: {
// enabled: true,
// location: "/home/user/.cache/variant-linker",
// totalFiles: 150,
// validEntries: 140,
// expiredEntries: 10,
// totalSize: 52428800,
// maxSize: 104857600,
// defaultTTL: 86400000
// }
// }Troubleshooting
Persistent Cache Issues
- Permission Errors: Ensure write access to cache directory
- Disk Space: Monitor available space in cache location
- File Corruption: Cache handles corrupted files gracefully
Performance Issues
- High Memory Usage: Reduce
memory.maxSize - Slow Access: Check persistent cache location performance
- Cache Misses: Verify TTL settings and data access patterns
Debug Output
Enable debug logging:
DEBUG=variant-linker:cache* node your-script.js
DEBUG=variant-linker:cache-manager node your-script.js
DEBUG=variant-linker:persistent-cache node your-script.jsMigration Guide
From Previous Version
The enhanced cache is fully backward compatible. No code changes required for existing functionality.
Enabling Persistent Cache
Update
config/apiConfig.json:json{ "cache": { "persistent": { "enabled": true } } }Optionally configure location and TTL:
json{ "cache": { "persistent": { "enabled": true, "location": "/path/to/cache", "ttl": 86400000, "maxSize": "500MB" } } }
Using New Async API
For applications needing persistent cache access:
// Before (sync, memory only)
const data = getCache('key');
// After (async, both tiers)
const data = await getCacheAsync('key');Examples
Persistent cache layout and migration
Current persistent entries live in <configured location>/entries-v1/ and have names vl-<sha256>.json, an ownership marker, and a key whose hash matches the filename. The byte budget, expiration cleanup, and clearCacheAsync() apply only to verified entries in this namespace. Unrelated files and malformed JSON are preserved.
Older releases wrote unprefixed <sha256>.json files directly into the configured location. These legacy files are not migrated, included in current statistics or the byte budget, or removed by current cleanup. They can continue to occupy disk space after an upgrade. A matching filename or JSON shape alone does not establish that the application owns a file, so there is no automatic legacy deletion.
To reclaim that space, stop applications using the old cache, confirm the exact old cache directory from your previous configuration, and back it up. Inspect the candidate files and their provenance before removing anything. Remove only files you have independently confirmed were created by Variant-Linker; if the directory was dedicated exclusively to its cache, you may archive that confirmed directory and let the application create a fresh one. Preserve unrelated files and the active entries-v1 directory. Legacy cache contents are disposable and can be reacquired from the annotation service.
Statistics are read-only snapshots. They do not create mutation locks or change the generation marker, and repeated reads do not invalidate another process's index. A refresh is published only after its complete scan observes a stable generation without an active writer. If a writer is active or a refresh fails, statistics return the last complete snapshot with an error field and increment maintenanceErrors; they do not report a partly rebuilt index as current.
Basic Usage
const { setCache, getCache } = require('./src/cache');
// Cache API response
setCache('variant-rs123', apiResponse, 600000); // 10 minute TTL
// Retrieve cached response
const cached = getCache('variant-rs123');
if (cached) {
console.log('Using cached data');
return cached;
}Batch Processing with Persistent Cache
const { getCacheAsync, getCacheManager } = require('./src/cache');
// Enable persistent cache in config first
const cacheManager = getCacheManager();
async function processVariants(variants) {
for (const variant of variants) {
// Check cache first (both memory and persistent)
let result = await getCacheAsync(variant);
if (!result) {
// Not in cache, process variant
result = await processVariant(variant);
// Cache for future use (24 hour TTL)
await cacheManager.set(variant, result, 86400000);
}
console.log(`Processed ${variant}:`, result);
}
}Cache Warming
const { getCacheManager } = require('./src/cache');
async function warmCache(commonVariants) {
const cacheManager = getCacheManager();
for (const variant of commonVariants) {
if (!(await cacheManager.has(variant))) {
const result = await processVariant(variant);
await cacheManager.set(variant, result);
console.log(`Warmed cache for ${variant}`);
}
}
}Demo Script
Run the included demo to see cache features in action:
node scripts/cache-demo.jsThis demonstrates:
- Basic cache operations
- LRU eviction behavior
- TTL functionality
- Persistent cache features (if enabled)
- Statistics collection
Testing
The cache system includes comprehensive tests:
# Run cache-specific tests
npx mocha test/cache.test.js
npx mocha test/persistent-cache.test.js
npx mocha test/cache-manager.test.js
# Run all tests
npm testTest coverage includes:
- LRU eviction behavior
- TTL expiration
- Persistent file operations
- Two-tier cache coordination
- Error handling and recovery
- Configuration validation