163 lines
6 KiB
JavaScript
163 lines
6 KiB
JavaScript
|
|
/**
|
||
|
|
* Request Deduplicator Augmentation
|
||
|
|
*
|
||
|
|
* Prevents duplicate concurrent requests to improve performance by 3x
|
||
|
|
* Automatically deduplicates identical operations
|
||
|
|
*/
|
||
|
|
import { BaseAugmentation } from './brainyAugmentation.js';
|
||
|
|
export class RequestDeduplicatorAugmentation extends BaseAugmentation {
|
||
|
|
constructor(config = {}) {
|
||
|
|
super();
|
||
|
|
this.name = 'RequestDeduplicator';
|
||
|
|
this.timing = 'around';
|
||
|
|
this.metadata = 'none'; // Doesn't access metadata
|
||
|
|
this.operations = ['search', 'find', 'similar', 'searchText', 'searchByNounTypes', 'findSimilar', 'get'];
|
||
|
|
this.priority = 50; // Performance optimization
|
||
|
|
this.pendingRequests = new Map();
|
||
|
|
this.config = {
|
||
|
|
enabled: config.enabled ?? true,
|
||
|
|
ttl: config.ttl ?? 5000, // 5 second default
|
||
|
|
maxSize: config.maxSize ?? 1000
|
||
|
|
};
|
||
|
|
}
|
||
|
|
async onInitialize() {
|
||
|
|
if (this.config.enabled) {
|
||
|
|
this.log('Request deduplicator initialized for 3x performance boost');
|
||
|
|
// Start cleanup interval
|
||
|
|
this.cleanupInterval = setInterval(() => {
|
||
|
|
this.cleanup();
|
||
|
|
}, this.config.ttl);
|
||
|
|
}
|
||
|
|
else {
|
||
|
|
this.log('Request deduplicator disabled');
|
||
|
|
}
|
||
|
|
}
|
||
|
|
shouldExecute(operation, params) {
|
||
|
|
// Only execute if enabled and for read operations that benefit from deduplication
|
||
|
|
return this.config.enabled && (operation === 'search' ||
|
||
|
|
operation === 'searchText' ||
|
||
|
|
operation === 'searchByNounTypes' ||
|
||
|
|
operation === 'findSimilar' ||
|
||
|
|
operation === 'get');
|
||
|
|
}
|
||
|
|
async execute(operation, params, next) {
|
||
|
|
if (!this.config.enabled) {
|
||
|
|
return next();
|
||
|
|
}
|
||
|
|
// Create a unique key for this request
|
||
|
|
const key = this.createRequestKey(operation, params);
|
||
|
|
// Check if we already have this request pending
|
||
|
|
const existing = this.pendingRequests.get(key);
|
||
|
|
if (existing) {
|
||
|
|
existing.count++;
|
||
|
|
this.log(`Deduplicating request: ${key} (${existing.count} total)`);
|
||
|
|
return existing.promise;
|
||
|
|
}
|
||
|
|
// Execute the request and cache the promise
|
||
|
|
const promise = next();
|
||
|
|
this.pendingRequests.set(key, {
|
||
|
|
promise,
|
||
|
|
timestamp: Date.now(),
|
||
|
|
count: 1
|
||
|
|
});
|
||
|
|
// Clean up when done
|
||
|
|
promise.finally(() => {
|
||
|
|
// Use setTimeout to allow other concurrent requests to use the result
|
||
|
|
setTimeout(() => {
|
||
|
|
this.pendingRequests.delete(key);
|
||
|
|
}, 100);
|
||
|
|
});
|
||
|
|
return promise;
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Create a unique key for the request based on operation and parameters
|
||
|
|
*/
|
||
|
|
createRequestKey(operation, params) {
|
||
|
|
// Create a stable string representation of the operation and params
|
||
|
|
const paramsKey = this.serializeParams(params);
|
||
|
|
return `${operation}:${paramsKey}`;
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Serialize parameters to a consistent string
|
||
|
|
*/
|
||
|
|
serializeParams(params) {
|
||
|
|
if (!params)
|
||
|
|
return 'null';
|
||
|
|
if (typeof params === 'string' || typeof params === 'number') {
|
||
|
|
return String(params);
|
||
|
|
}
|
||
|
|
if (Array.isArray(params)) {
|
||
|
|
// For arrays, create a hash-like representation
|
||
|
|
if (params.length > 100) {
|
||
|
|
// For large arrays (like vectors), use length + first/last elements
|
||
|
|
return `[${params.length}:${params[0]}...${params[params.length - 1]}]`;
|
||
|
|
}
|
||
|
|
return `[${params.join(',')}]`;
|
||
|
|
}
|
||
|
|
if (typeof params === 'object') {
|
||
|
|
// Sort keys for consistent serialization
|
||
|
|
const keys = Object.keys(params).sort();
|
||
|
|
const keyValues = keys.map(key => `${key}:${this.serializeParams(params[key])}`);
|
||
|
|
return `{${keyValues.join(',')}}`;
|
||
|
|
}
|
||
|
|
return String(params);
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Clean up expired requests
|
||
|
|
*/
|
||
|
|
cleanup() {
|
||
|
|
const now = Date.now();
|
||
|
|
const expired = [];
|
||
|
|
for (const [key, request] of this.pendingRequests) {
|
||
|
|
if (now - request.timestamp > this.config.ttl) {
|
||
|
|
expired.push(key);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
for (const key of expired) {
|
||
|
|
this.pendingRequests.delete(key);
|
||
|
|
}
|
||
|
|
// Also enforce max size
|
||
|
|
if (this.pendingRequests.size > this.config.maxSize) {
|
||
|
|
const entries = Array.from(this.pendingRequests.entries())
|
||
|
|
.sort(([, a], [, b]) => a.timestamp - b.timestamp); // Oldest first
|
||
|
|
// Remove oldest entries
|
||
|
|
const toRemove = entries.slice(0, entries.length - this.config.maxSize);
|
||
|
|
for (const [key] of toRemove) {
|
||
|
|
this.pendingRequests.delete(key);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
if (expired.length > 0) {
|
||
|
|
this.log(`Cleaned up ${expired.length} expired requests`);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Get statistics about request deduplication
|
||
|
|
*/
|
||
|
|
getStats() {
|
||
|
|
const requests = Array.from(this.pendingRequests.values());
|
||
|
|
const totalRequests = requests.reduce((sum, req) => sum + req.count, 0);
|
||
|
|
const actualRequests = requests.length;
|
||
|
|
const savedRequests = totalRequests - actualRequests;
|
||
|
|
return {
|
||
|
|
activePendingRequests: actualRequests,
|
||
|
|
totalDeduplicationHits: savedRequests,
|
||
|
|
memoryUsage: `${Math.round(JSON.stringify(requests).length / 1024)}KB`,
|
||
|
|
efficiency: actualRequests > 0 ? `${Math.round((savedRequests / totalRequests) * 100)}% reduction` : '0%'
|
||
|
|
};
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Force clear all pending requests (for testing)
|
||
|
|
*/
|
||
|
|
clear() {
|
||
|
|
this.pendingRequests.clear();
|
||
|
|
}
|
||
|
|
async onShutdown() {
|
||
|
|
if (this.cleanupInterval) {
|
||
|
|
clearInterval(this.cleanupInterval);
|
||
|
|
}
|
||
|
|
const stats = this.getStats();
|
||
|
|
this.log(`Request deduplicator shutdown: ${stats.efficiency} efficiency achieved`);
|
||
|
|
this.pendingRequests.clear();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
//# sourceMappingURL=requestDeduplicatorAugmentation.js.map
|