Skip to main content
Glama
joelmnz

Article Manager MCP Server

by joelmnz
test-performance-load.ts15.6 kB
#!/usr/bin/env bun /** * Performance and Load Testing * * Tests database performance with large datasets, validates embedding search performance, * and tests concurrent operations and connection pooling. * * Requirements: 6.3 */ import { databaseInit } from '../src/backend/services/databaseInit.js'; import { listArticles, searchArticles, readArticle, createArticle, updateArticle, deleteArticle } from '../src/backend/services/articles.js'; import { semanticSearch, hybridSearch, getDetailedIndexStats, rebuildIndex } from '../src/backend/services/vectorIndex.js'; import { databaseHealthService } from '../src/backend/services/databaseHealth.js'; const SEMANTIC_SEARCH_ENABLED = process.env.SEMANTIC_SEARCH_ENABLED?.toLowerCase() === 'true'; interface PerformanceResult { name: string; duration: number; operations: number; opsPerSecond: number; details?: any; } interface LoadTestResult { name: string; totalOperations: number; duration: number; opsPerSecond: number; concurrency: number; errors: number; successRate: number; } class PerformanceLoadTester { private results: PerformanceResult[] = []; private loadResults: LoadTestResult[] = []; private testArticles: any[] = []; async runAllTests(): Promise<void> { console.log('🚀 Performance and Load Testing'); console.log('===============================\n'); try { // Initialize database await this.initializeDatabase(); // Run performance tests await this.testDatabasePerformance(); await this.testLargeDatasetPerformance(); await this.testSearchPerformance(); // Run load tests await this.testConcurrentOperations(); await this.testConnectionPooling(); // Report results this.reportResults(); } catch (error) { console.error('❌ Performance test setup failed:', error); process.exit(1); } } private async initializeDatabase(): Promise<void> { console.log('🔄 Initializing database connection...'); try { await databaseInit.initialize(); console.log('✅ Database initialized\n'); } catch (error) { throw new Error(`Database initialization failed: ${error}`); } } private async testDatabasePerformance(): Promise<void> { console.log('--- Testing Database Performance ---'); // Test single article operations await this.measurePerformance('Single article creation', async () => { const timestamp = Date.now(); const article = await createArticle( `Performance Test Article ${timestamp}`, `# Performance Test Article ${timestamp}\n\nThis is a performance test article with some content.\n\n## Section 1\n\nContent here.`, 'Performance test' ); this.testArticles.push(article); return 1; // 1 operation }); await this.measurePerformance('Single article read', async () => { if (this.testArticles.length > 0) { await readArticle(this.testArticles[0].filename); } return 1; // 1 operation }); await this.measurePerformance('Single article update', async () => { if (this.testArticles.length > 0) { const timestamp = Date.now(); const updated = await updateArticle( this.testArticles[0].filename, `Updated Performance Test Article ${timestamp}`, this.testArticles[0].content + '\n\n## Updated Section\n\nUpdated content.', 'Performance test update' ); this.testArticles[0] = updated; } return 1; // 1 operation }); // Test batch operations await this.measurePerformance('Batch article creation (10 articles)', async () => { const timestamp = Date.now(); const promises = []; for (let i = 0; i < 10; i++) { promises.push(createArticle( `Batch Test Article ${timestamp}-${i}`, `# Batch Test Article ${timestamp}-${i}\n\nThis is batch article ${i} for performance testing.\n\n## Content\n\nSome content here.`, `Batch creation ${i}` )); } const articles = await Promise.all(promises); this.testArticles.push(...articles); return 10; // 10 operations }); await this.measurePerformance('List all articles', async () => { await listArticles(); return 1; // 1 operation }); await this.measurePerformance('Search articles', async () => { await searchArticles('Performance Test'); return 1; // 1 operation }); } private async testLargeDatasetPerformance(): Promise<void> { console.log('\n--- Testing Large Dataset Performance ---'); // Create a larger dataset await this.measurePerformance('Large batch creation (50 articles)', async () => { const timestamp = Date.now(); const batchSize = 10; const totalArticles = 50; let created = 0; for (let batch = 0; batch < totalArticles / batchSize; batch++) { const promises = []; for (let i = 0; i < batchSize; i++) { const articleIndex = batch * batchSize + i; promises.push(createArticle( `Large Dataset Article ${timestamp}-${articleIndex}`, `# Large Dataset Article ${timestamp}-${articleIndex}\n\nThis is a large dataset test article ${articleIndex}.\n\n## Section 1\n\nContent for article ${articleIndex}.\n\n## Section 2\n\nMore content here with some text to make it larger.\n\n### Subsection\n\nEven more content to simulate real articles.`, `Large dataset creation ${articleIndex}` )); } const batchArticles = await Promise.all(promises); this.testArticles.push(...batchArticles); created += batchSize; } return totalArticles; }); // Test performance with large dataset await this.measurePerformance('List articles (large dataset)', async () => { await listArticles(); return 1; }); await this.measurePerformance('Search articles (large dataset)', async () => { await searchArticles('Large Dataset'); return 1; }); // Test database health with large dataset await this.measurePerformance('Database health check (large dataset)', async () => { await databaseHealthService.performHealthCheck(); return 1; }); } private async testSearchPerformance(): Promise<void> { console.log('\n--- Testing Search Performance ---'); if (!SEMANTIC_SEARCH_ENABLED) { console.log('⚠️ Semantic search disabled, skipping semantic search performance tests'); return; } // Test semantic search performance await this.measurePerformance('Semantic search', async () => { await semanticSearch('performance test article content', 10); return 1; }); await this.measurePerformance('Hybrid search', async () => { await hybridSearch('performance test article content', 10); return 1; }); // Test index rebuild performance await this.measurePerformance('Index rebuild', async () => { await rebuildIndex(); return 1; }); // Test index stats performance await this.measurePerformance('Get index stats', async () => { await getDetailedIndexStats(); return 1; }); } private async testConcurrentOperations(): Promise<void> { console.log('\n--- Testing Concurrent Operations ---'); // Test concurrent reads await this.loadTest('Concurrent article reads', async () => { if (this.testArticles.length > 0) { const randomArticle = this.testArticles[Math.floor(Math.random() * this.testArticles.length)]; await readArticle(randomArticle.filename); } }, 20, 50); // 20 concurrent operations, 50 total // Test concurrent searches await this.loadTest('Concurrent article searches', async () => { await searchArticles('test'); }, 10, 30); // 10 concurrent operations, 30 total // Test concurrent creates await this.loadTest('Concurrent article creation', async () => { const timestamp = Date.now(); const random = Math.random().toString(36).substring(7); const article = await createArticle( `Concurrent Test ${timestamp}-${random}`, `# Concurrent Test ${timestamp}-${random}\n\nConcurrent creation test.`, 'Concurrent test' ); this.testArticles.push(article); }, 5, 15); // 5 concurrent operations, 15 total // Test mixed operations await this.loadTest('Mixed concurrent operations', async () => { const operations = ['read', 'search', 'list']; const operation = operations[Math.floor(Math.random() * operations.length)]; switch (operation) { case 'read': if (this.testArticles.length > 0) { const randomArticle = this.testArticles[Math.floor(Math.random() * this.testArticles.length)]; await readArticle(randomArticle.filename); } break; case 'search': await searchArticles('test'); break; case 'list': await listArticles(); break; } }, 15, 45); // 15 concurrent operations, 45 total } private async testConnectionPooling(): Promise<void> { console.log('\n--- Testing Connection Pooling ---'); // Test connection pool under load await this.loadTest('Connection pool stress test', async () => { // Perform multiple database operations to stress the connection pool await listArticles(); if (this.testArticles.length > 0) { const randomArticle = this.testArticles[Math.floor(Math.random() * this.testArticles.length)]; await readArticle(randomArticle.filename); } await searchArticles('pool test'); }, 25, 100); // 25 concurrent operations, 100 total // Test database health under connection stress await this.measurePerformance('Database health under connection stress', async () => { const healthPromises = []; for (let i = 0; i < 10; i++) { healthPromises.push(databaseHealthService.performHealthCheck()); } await Promise.all(healthPromises); return 10; }); } private async measurePerformance(name: string, operation: () => Promise<number>): Promise<void> { const startTime = Date.now(); try { const operations = await operation(); const duration = Date.now() - startTime; const opsPerSecond = operations / (duration / 1000); this.results.push({ name, duration, operations, opsPerSecond }); console.log(`✅ ${name}: ${duration}ms (${opsPerSecond.toFixed(2)} ops/sec)`); } catch (error) { console.log(`❌ ${name}: ${error instanceof Error ? error.message : String(error)}`); } } private async loadTest( name: string, operation: () => Promise<void>, concurrency: number, totalOperations: number ): Promise<void> { const startTime = Date.now(); let completed = 0; let errors = 0; try { const batches = Math.ceil(totalOperations / concurrency); for (let batch = 0; batch < batches; batch++) { const batchSize = Math.min(concurrency, totalOperations - completed); const promises = []; for (let i = 0; i < batchSize; i++) { promises.push( operation().then(() => { completed++; }).catch(() => { errors++; completed++; }) ); } await Promise.all(promises); } const duration = Date.now() - startTime; const opsPerSecond = totalOperations / (duration / 1000); const successRate = ((totalOperations - errors) / totalOperations) * 100; this.loadResults.push({ name, totalOperations, duration, opsPerSecond, concurrency, errors, successRate }); console.log(`✅ ${name}: ${totalOperations} ops in ${duration}ms (${opsPerSecond.toFixed(2)} ops/sec, ${successRate.toFixed(1)}% success)`); } catch (error) { console.log(`❌ ${name}: ${error instanceof Error ? error.message : String(error)}`); } } private reportResults(): void { console.log('\n📊 Performance Test Results'); console.log('==========================='); if (this.results.length > 0) { console.log('\n🔧 Single Operation Performance:'); this.results.forEach(result => { console.log(` ${result.name}: ${result.duration}ms (${result.opsPerSecond.toFixed(2)} ops/sec)`); }); } if (this.loadResults.length > 0) { console.log('\n🚀 Load Test Results:'); this.loadResults.forEach(result => { console.log(` ${result.name}:`); console.log(` Operations: ${result.totalOperations} (${result.concurrency} concurrent)`); console.log(` Duration: ${result.duration}ms`); console.log(` Throughput: ${result.opsPerSecond.toFixed(2)} ops/sec`); console.log(` Success Rate: ${result.successRate.toFixed(1)}%`); console.log(` Errors: ${result.errors}`); }); } // Performance analysis console.log('\n📈 Performance Analysis:'); const avgOpsPerSec = this.results.reduce((sum, r) => sum + r.opsPerSecond, 0) / this.results.length; console.log(` Average single operation performance: ${avgOpsPerSec.toFixed(2)} ops/sec`); const avgLoadOpsPerSec = this.loadResults.reduce((sum, r) => sum + r.opsPerSecond, 0) / this.loadResults.length; console.log(` Average load test performance: ${avgLoadOpsPerSec.toFixed(2)} ops/sec`); const avgSuccessRate = this.loadResults.reduce((sum, r) => sum + r.successRate, 0) / this.loadResults.length; console.log(` Average success rate: ${avgSuccessRate.toFixed(1)}%`); const totalArticlesCreated = this.testArticles.length; console.log(` Total test articles created: ${totalArticlesCreated}`); // Performance benchmarks console.log('\n🎯 Performance Benchmarks:'); if (avgOpsPerSec > 100) { console.log(' ✅ Single operation performance: Excellent (>100 ops/sec)'); } else if (avgOpsPerSec > 50) { console.log(' ✅ Single operation performance: Good (>50 ops/sec)'); } else if (avgOpsPerSec > 10) { console.log(' ⚠️ Single operation performance: Acceptable (>10 ops/sec)'); } else { console.log(' ❌ Single operation performance: Poor (<10 ops/sec)'); } if (avgSuccessRate > 95) { console.log(' ✅ Reliability: Excellent (>95% success rate)'); } else if (avgSuccessRate > 90) { console.log(' ✅ Reliability: Good (>90% success rate)'); } else if (avgSuccessRate > 80) { console.log(' ⚠️ Reliability: Acceptable (>80% success rate)'); } else { console.log(' ❌ Reliability: Poor (<80% success rate)'); } console.log('\n🎉 Performance and load testing completed!'); console.log(`Database handled ${totalArticlesCreated} articles and multiple concurrent operations successfully.`); } } // Main execution async function main() { const tester = new PerformanceLoadTester(); await tester.runAllTests(); } // Run tests if this script is executed directly if (import.meta.main) { main().catch(error => { console.error('❌ Performance test execution failed:', error); process.exit(1); }); }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/joelmnz/mcp-markdown-manager'

If you have feedback or need assistance with the MCP directory API, please join our Discord server