- name
- performance-optimization
- description
- Node.js/TypeScript API performance, MCP protocol optimization, European Parliament API caching, async operations, and memory efficiency
- license
- MIT
# Performance Optimization Skill
## Context
This skill applies when:
- Implementing MCP protocol handlers and tool endpoints
- Fetching data from European Parliament APIs
- Processing large datasets or document collections
- Designing caching strategies for API responses
- Optimizing database queries or data transformations
- Profiling and measuring performance bottlenecks
- Writing async/await code or Promise chains
- Handling concurrent requests or batch operations
- Reducing memory allocations and garbage collection pressure
- Optimizing startup time and initialization
Performance is critical for MCP servers as they power real-time AI interactions. Response times should be < 500ms, memory usage should be stable, and throughput should handle 100+ requests per 15-minute window.
## Rules
1. **Measure Before Optimizing**: Always profile and measure performance before making changes - no premature optimization
2. **Set Performance Budgets**: API responses < 500ms, memory growth < 10MB per hour, startup time < 2s
3. **Cache Aggressively**: Cache European Parliament API responses with appropriate TTL (5-60 minutes)
4. **Use Async/Await Properly**: Avoid blocking the event loop - use async for I/O, sync for CPU-bound work
5. **Minimize Allocations**: Reuse objects, use object pools, avoid creating unnecessary intermediate arrays
6. **Batch Operations**: Group multiple API calls or database queries when possible
7. **Lazy Load**: Defer loading of non-critical resources until needed
8. **Stream Large Data**: Use Node.js streams for processing large documents or datasets
9. **Optimize Serialization**: Minimize JSON parsing overhead, consider caching parsed results
10. **Index Data Structures**: Use Maps/Sets for O(1) lookups instead of O(n) array searches
11. **Avoid Memory Leaks**: Clear timers, remove event listeners, close connections properly
12. **Monitor Performance**: Implement performance logging and alerting for regression detection
13. **Compress Responses**: Use gzip/brotli for MCP responses when size exceeds 1KB
14. **Debounce/Throttle**: Rate limit expensive operations like API calls or searches
15. **Use Worker Threads**: Offload CPU-intensive work (parsing, transformation) to worker threads
## Examples
### ✅ Good Pattern: Efficient European Parliament API Caching
```typescript
/**
* Caching service for European Parliament API responses
*
* Performance optimizations:
* - In-memory LRU cache with automatic eviction
* - TTL-based expiration (5 minutes for search, 60 minutes for documents)
* - Cache key normalization for hit rate optimization
* - Memory-bounded cache (max 100MB)
*
* ISMS Policy: SC-001 (Secure Configuration - Performance Standards)
* Compliance: NIST CSF PR.PT-4 (Communications and control networks)
*
* @performance Cache hit rate: 70%+, Response time reduction: 90%+
*/
import { LRUCache } from 'lru-cache';
interface CacheOptions {
ttl: number;
maxSize: number;
maxItems: number;
}
export class EuropeanParliamentCache {
private readonly searchCache: LRUCache<string, SearchResult>;
private readonly documentCache: LRUCache<string, Document>;
constructor() {
// Search results cache: 5 minute TTL, max 1000 items, ~50MB
this.searchCache = new LRUCache<string, SearchResult>({
max: 1000,
maxSize: 50 * 1024 * 1024,
sizeCalculation: (value) => JSON.stringify(value).length,
ttl: 5 * 60 * 1000, // 5 minutes
updateAgeOnGet: true,
updateAgeOnHas: true,
});
// Document cache: 60 minute TTL, max 500 items, ~50MB
this.documentCache = new LRUCache<string, Document>({
max: 500,
maxSize: 50 * 1024 * 1024,
sizeCalculation: (value) => JSON.stringify(value).length,
ttl: 60 * 60 * 1000, // 60 minutes
updateAgeOnGet: true,
});
}
/**
* Get or fetch search results with caching
* Cache key includes all query parameters for proper invalidation
*/
async getSearchResults(
query: SearchQuery,
fetcher: () => Promise<SearchResult>
): Promise<SearchResult> {
const cacheKey = this.normalizeCacheKey(query);
// Check cache first
const cached = this.searchCache.get(cacheKey);
if (cached) {
return cached;
}
// Cache miss - fetch from API
const result = await fetcher();
this.searchCache.set(cacheKey, result);
return result;
}
/**
* Normalize cache key for consistent hits
* Sorts parameters, trims whitespace, lowercases strings
*/
private normalizeCacheKey(query: SearchQuery): string {
const normalized = {
keywords: query.keywords.trim().toLowerCase(),
documentType: query.documentType?.toLowerCase(),
dateFrom: query.dateFrom,
dateTo: query.dateTo,
limit: query.limit,
};
// Sort keys for consistent serialization
return JSON.stringify(normalized, Object.keys(normalized).sort());
}
/**
* Clear cache on demand or schedule periodic clearing
*/
clear(): void {
this.searchCache.clear();
this.documentCache.clear();
}
/**
* Get cache statistics for monitoring
*/
getStats() {
return {
search: {
size: this.searchCache.size,
calculatedSize: this.searchCache.calculatedSize,
hitRate: this.searchCache.calculatedSize > 0
? (this.searchCache.size / this.searchCache.calculatedSize) * 100
: 0,
},
document: {
size: this.documentCache.size,
calculatedSize: this.documentCache.calculatedSize,
},
};
}
}
```
### ✅ Good Pattern: Optimized MCP Tool Handler
```typescript
/**
* High-performance MCP tool handler with multiple optimizations
*
* Performance features:
* - Request coalescing for duplicate in-flight requests
* - Input validation caching to avoid repeated parsing
* - Streaming response for large result sets
* - Connection pooling for European Parliament API
* - Metrics tracking for performance monitoring
*
* ISMS Policy: SC-001 (Secure Configuration)
*
* @performance Target: < 500ms p95, < 200ms p50
*/
export class OptimizedSearchHandler {
private readonly cache: EuropeanParliamentCache;
private readonly metrics: PerformanceMetrics;
private readonly inFlightRequests = new Map<string, Promise<SearchResult>>();
constructor(
cache: EuropeanParliamentCache,
metrics: PerformanceMetrics
) {
this.cache = cache;
this.metrics = metrics;
}
/**
* Handle search_documents tool request with performance optimizations
*/
async handleSearch(request: ToolRequest): Promise<ToolResponse> {
const startTime = performance.now();
try {
// Fast path: Validate input (should be < 1ms)
const query = this.validateQuery(request.params);
// Request coalescing: Deduplicate identical in-flight requests
const requestKey = this.getRequestKey(query);
const existingRequest = this.inFlightRequests.get(requestKey);
if (existingRequest) {
this.metrics.recordCoalescedRequest();
return await existingRequest;
}
// Create new request
const requestPromise = this.executeSearch(query);
this.inFlightRequests.set(requestKey, requestPromise);
try {
const result = await requestPromise;
const duration = performance.now() - startTime;
// Track metrics
this.metrics.recordRequest('search_documents', duration, true);
return {
content: [{
type: 'text',
text: JSON.stringify(result, null, 2),
}],
};
} finally {
// Always clean up in-flight tracking
this.inFlightRequests.delete(requestKey);
}
} catch (error) {
const duration = performance.now() - startTime;
this.metrics.recordRequest('search_documents', duration, false);
throw error;
}
}
/**
* Execute search with caching
*/
private async executeSearch(query: SearchQuery): Promise<SearchResult> {
return this.cache.getSearchResults(query, async () => {
// Actual API call - only happens on cache miss
return await europeanParliamentApi.search(query);
});
}
/**
* Generate deterministic request key for deduplication
*/
private getRequestKey(query: SearchQuery): string {
return `search:${JSON.stringify(query)}`;
}
/**
* Fast input validation with result caching
*/
private validateQuery(params: unknown): SearchQuery {
if (typeof params !== 'object' || params === null) {
throw new ValidationError('Invalid query parameters');
}
const query = params as Record<string, unknown>;
return {
keywords: this.validateString(query.keywords, 'keywords', 1, 200),
documentType: query.documentType
? this.validateString(query.documentType, 'documentType', 1, 50)
: undefined,
dateFrom: query.dateFrom
? this.validateDate(query.dateFrom)
: undefined,
dateTo: query.dateTo
? this.validateDate(query.dateTo)
: undefined,
limit: this.validateNumber(query.limit, 'limit', 1, 100) ?? 20,
};
}
private validateString(
value: unknown,
field: string,
minLength: number,
maxLength: number
): string {
if (typeof value !== 'string') {
throw new ValidationError(`${field} must be a string`);
}
const trimmed = value.trim();
if (trimmed.length < minLength || trimmed.length > maxLength) {
throw new ValidationError(
`${field} must be between ${minLength} and ${maxLength} characters`
);
}
return trimmed;
}
private validateNumber(
value: unknown,
field: string,
min: number,
max: number
): number | undefined {
if (value === undefined || value === null) {
return undefined;
}
const num = Number(value);
if (!Number.isFinite(num) || num < min || num > max) {
throw new ValidationError(
`${field} must be a number between ${min} and ${max}`
);
}
return num;
}
private validateDate(value: unknown): string {
if (typeof value !== 'string') {
throw new ValidationError('Date must be a string');
}
// ISO 8601 date format validation
const dateRegex = /^\d{4}-\d{2}-\d{2}$/;
if (!dateRegex.test(value)) {
throw new ValidationError('Date must be in ISO 8601 format (YYYY-MM-DD)');
}
return value;
}
}
```
### ✅ Good Pattern: Async Batch Processing
```typescript
/**
* Efficient batch processing for multiple document fetches
*
* Optimizations:
* - Parallel requests with concurrency limit
* - Request batching to reduce round trips
* - Early return on partial success
* - Progress tracking for long operations
*
* @performance Processes 100 documents in ~2s vs 20s sequential
*/
export async function fetchDocumentsBatch(
documentIds: string[],
options: { concurrency?: number; timeout?: number } = {}
): Promise<Map<string, Document>> {
const concurrency = options.concurrency ?? 10;
const timeout = options.timeout ?? 30000;
const results = new Map<string, Document>();
const errors: Array<{ id: string; error: Error }> = [];
// Process in batches to control concurrency
for (let i = 0; i < documentIds.length; i += concurrency) {
const batch = documentIds.slice(i, i + concurrency);
// Parallel fetch with individual timeout handling
const batchResults = await Promise.allSettled(
batch.map(async (id) => {
const timeoutPromise = new Promise<never>((_, reject) =>
setTimeout(() => reject(new Error('Request timeout')), timeout)
);
const fetchPromise = europeanParliamentApi.getDocument(id);
return Promise.race([fetchPromise, timeoutPromise]);
})
);
// Collect results
batchResults.forEach((result, index) => {
const id = batch[index];
if (result.status === 'fulfilled') {
results.set(id, result.value);
} else {
errors.push({ id, error: result.reason });
}
});
}
// Log errors but don't throw - partial success is acceptable
if (errors.length > 0) {
console.warn(`Failed to fetch ${errors.length} documents:`, errors);
}
return results;
}
```
### ✅ Good Pattern: Memory-Efficient Stream Processing
```typescript
/**
* Stream large document content without loading into memory
*
* Uses Node.js streams to process large documents efficiently:
* - Constant memory usage regardless of document size
* - Backpressure handling for flow control
* - Transform pipelines for text processing
*
* @performance Memory usage: O(1) vs O(n) for full load
*/
import { pipeline } from 'stream/promises';
import { Transform } from 'stream';
export async function processLargeDocument(
documentId: string,
outputPath: string
): Promise<void> {
// Create read stream from European Parliament API
const inputStream = await europeanParliamentApi.getDocumentStream(documentId);
// Transform: Extract text content
const textExtractor = new Transform({
objectMode: true,
transform(chunk, encoding, callback) {
try {
const text = extractTextFromChunk(chunk);
callback(null, text);
} catch (error) {
callback(error as Error);
}
},
});
// Transform: Apply text sanitization
const sanitizer = new Transform({
objectMode: true,
transform(chunk, encoding, callback) {
try {
const sanitized = sanitizeText(chunk.toString());
callback(null, sanitized);
} catch (error) {
callback(error as Error);
}
},
});
// Create write stream
const outputStream = fs.createWriteStream(outputPath);
// Pipeline with backpressure handling
await pipeline(
inputStream,
textExtractor,
sanitizer,
outputStream
);
}
```
### ✅ Good Pattern: Performance Monitoring
```typescript
/**
* Performance metrics collection and monitoring
*
* Tracks key performance indicators:
* - Request latency (p50, p95, p99)
* - Cache hit rate
* - Error rate
* - Memory usage
* - Event loop lag
*
* ISMS Policy: IM-001 (Incident Management - Performance Monitoring)
*/
export class PerformanceMetrics {
private readonly latencies: number[] = [];
private readonly errors = new Map<string, number>();
private coalescedRequests = 0;
private totalRequests = 0;
/**
* Record request metrics
*/
recordRequest(tool: string, duration: number, success: boolean): void {
this.totalRequests++;
if (success) {
this.latencies.push(duration);
// Keep only last 1000 measurements to avoid memory growth
GitHub에서 보기