180 lines
4.1 KiB
JavaScript
180 lines
4.1 KiB
JavaScript
/**
|
|
* Performance configuration for Phase 1 optimization
|
|
*/
|
|
|
|
export const PERFORMANCE_CONFIG = {
|
|
// Metadata cache TTL (5 minutes)
|
|
METADATA_CACHE_TTL: 5 * 60 * 1000,
|
|
|
|
// Preload nearby notes (number of notes before/after)
|
|
PRELOAD_NEARBY: 5,
|
|
|
|
// Pagination size for large vaults
|
|
PAGINATION_SIZE: 100,
|
|
|
|
// Maximum notes to load in metadata endpoint
|
|
MAX_METADATA_ITEMS: 10000,
|
|
|
|
// Enable performance logging
|
|
ENABLE_PERF_LOGGING: process.env.PERF_LOGGING === 'true',
|
|
};
|
|
|
|
/**
|
|
* Advanced in-memory cache for metadata with metrics and intelligent invalidation
|
|
*/
|
|
export class MetadataCache {
|
|
constructor(ttl = PERFORMANCE_CONFIG.METADATA_CACHE_TTL, maxSize = 10000) {
|
|
this.cache = new Map();
|
|
this.ttl = ttl;
|
|
this.maxSize = maxSize;
|
|
this.hits = 0;
|
|
this.misses = 0;
|
|
this.isLoading = false;
|
|
this.loadPromise = null;
|
|
}
|
|
|
|
/**
|
|
* Get cached metadata for a vault
|
|
*/
|
|
async getMetadata(vaultDir, loader) {
|
|
const now = Date.now();
|
|
const cacheKey = this.getCacheKey(vaultDir);
|
|
const cached = this.cache.get(cacheKey);
|
|
|
|
// Cache valide ?
|
|
if (cached && (now - cached.timestamp) < this.ttl) {
|
|
this.hits++;
|
|
return cached.data;
|
|
}
|
|
|
|
// Cache miss - recharger
|
|
this.misses++;
|
|
return await this.loadFreshMetadata(vaultDir, cacheKey, loader);
|
|
}
|
|
|
|
/**
|
|
* Charger les métadonnées fraiches
|
|
*/
|
|
async loadFreshMetadata(vaultDir, cacheKey, loader) {
|
|
// Éviter les chargements concurrents
|
|
if (this.isLoading && this.loadPromise) {
|
|
return this.loadPromise;
|
|
}
|
|
|
|
this.isLoading = true;
|
|
|
|
try {
|
|
this.loadPromise = loader();
|
|
const metadata = await this.loadPromise;
|
|
|
|
// Stocker en cache
|
|
this.cache.set(cacheKey, {
|
|
data: metadata,
|
|
timestamp: Date.now()
|
|
});
|
|
|
|
// Nettoyer le cache si trop gros
|
|
this.cleanupIfNeeded();
|
|
|
|
return metadata;
|
|
} finally {
|
|
this.isLoading = false;
|
|
this.loadPromise = null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Invalider le cache pour une voûte
|
|
*/
|
|
invalidate(vaultDir = null) {
|
|
if (vaultDir) {
|
|
const cacheKey = this.getCacheKey(vaultDir);
|
|
this.cache.delete(cacheKey);
|
|
} else {
|
|
this.cache.clear();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Générer une clé de cache unique
|
|
*/
|
|
getCacheKey(vaultDir) {
|
|
return `metadata_${vaultDir.replace(/[/\\]/g, '_')}`;
|
|
}
|
|
|
|
/**
|
|
* Nettoyer le cache si nécessaire (LRU simple)
|
|
*/
|
|
cleanupIfNeeded() {
|
|
if (this.cache.size > this.maxSize) {
|
|
// Supprimer les entrées les plus anciennes (LRU simple)
|
|
const entries = Array.from(this.cache.entries());
|
|
entries.sort((a, b) => a[1].timestamp - b[1].timestamp);
|
|
|
|
const toRemove = entries.slice(0, Math.floor(this.maxSize * 0.1));
|
|
toRemove.forEach(([key]) => this.cache.delete(key));
|
|
|
|
console.log(`[Cache] Cleaned up ${toRemove.length} old entries`);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Obtenir les statistiques du cache
|
|
*/
|
|
getStats() {
|
|
const total = this.hits + this.misses;
|
|
return {
|
|
size: this.cache.size,
|
|
hitRate: total > 0 ? (this.hits / total) * 100 : 0,
|
|
hits: this.hits,
|
|
misses: this.misses,
|
|
maxSize: this.maxSize,
|
|
ttl: this.ttl
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Réinitialiser les métriques
|
|
*/
|
|
resetStats() {
|
|
this.hits = 0;
|
|
this.misses = 0;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Performance logger
|
|
*/
|
|
export class PerformanceLogger {
|
|
static log(operation, duration, metadata = {}) {
|
|
if (!PERFORMANCE_CONFIG.ENABLE_PERF_LOGGING) {
|
|
return;
|
|
}
|
|
|
|
const level = duration > 1000 ? 'warn' : 'info';
|
|
const message = `[PERF] ${operation}: ${duration.toFixed(0)}ms`;
|
|
|
|
if (level === 'warn') {
|
|
console.warn(message, metadata);
|
|
} else {
|
|
console.log(message, metadata);
|
|
}
|
|
}
|
|
|
|
static mark(label) {
|
|
if (typeof performance !== 'undefined' && performance.mark) {
|
|
performance.mark(label);
|
|
}
|
|
}
|
|
|
|
static measure(label, startMark, endMark) {
|
|
if (typeof performance !== 'undefined' && performance.measure) {
|
|
try {
|
|
performance.measure(label, startMark, endMark);
|
|
} catch (e) {
|
|
// Ignore if marks don't exist
|
|
}
|
|
}
|
|
}
|
|
}
|