UNPKG

claude-flow-tbowman01

Version:

Enterprise-grade AI agent orchestration with ruv-swarm integration (Alpha Release)

1,866 lines (1,473 loc) 56 kB
# Batchtools Best Practices Guide ## Table of Contents 1. [Core Principles](#core-principles) 2. [Design Patterns](#design-patterns) 3. [Performance Best Practices](#performance-best-practices) 4. [Security Considerations](#security-considerations) 5. [Error Handling Strategies](#error-handling-strategies) 6. [Testing Best Practices](#testing-best-practices) 7. [Team Collaboration](#team-collaboration) 8. [Tool-Specific Guidelines](#tool-specific-guidelines) --- ## Core Principles ### When to Use Batch Operations vs Sequential **Use Batch Operations When:** - Processing multiple independent files or data items - Operations have similar resource requirements - Total processing time is a concern - Operations can fail independently without affecting others **Use Sequential Operations When:** - Operations have dependencies between steps - Resource constraints are tight - Order of execution matters - Debugging and monitoring individual steps is critical **Decision Tree:** ``` Is the operation independent? ├── Yes → Are there multiple items to process? │ ├── Yes → Are resources sufficient? │ │ ├── Yes → USE BATCH │ │ └── No → USE SEQUENTIAL WITH THROTTLING │ └── No → USE SEQUENTIAL └── No → USE SEQUENTIAL ``` ### Balancing Parallelism with Resource Constraints ```javascript // Good: Resource-aware batch processing const OPTIMAL_BATCH_SIZE = Math.min( os.cpus().length * 2, // CPU cores Math.floor(os.freemem() / (100 * 1024 * 1024)), // Available memory 10, // Hard limit ); // Bad: Unbounded parallelism const results = await Promise.all( files.map((file) => processFile(file)), // Could overwhelm system ); ``` ### Maintaining Code Readability **Best Practices:** 1. Use descriptive variable names for batch operations 2. Group related batch operations together 3. Add comments explaining parallelism decisions 4. Use helper functions to abstract complexity ```javascript // Good: Clear and readable const batchedFileReads = await readFilesInBatches(filePaths, { batchSize: 5, retryCount: 3 }); // Bad: Unclear intent const r = await Promise.all(fps.map((f) => rf(f))); ``` ### Error Handling Philosophy **Principles:** 1. **Fail gracefully**: Don't let one failure crash everything 2. **Collect all errors**: Gather comprehensive error information 3. **Provide context**: Include which operation failed and why 4. **Enable recovery**: Design for partial retries --- ## Design Patterns ### Common Batchtools Patterns #### 1. **Batch with Accumulator Pattern** ```javascript async function batchProcess(items, processor, options = {}) { const { batchSize = 5 } = options; const results = []; const errors = []; for (let i = 0; i < items.length; i += batchSize) { const batch = items.slice(i, i + batchSize); const batchResults = await Promise.allSettled(batch.map((item) => processor(item))); batchResults.forEach((result, index) => { if (result.status === 'fulfilled') { results.push(result.value); } else { errors.push({ item: batch[index], error: result.reason, }); } }); } return { results, errors }; } ``` #### 2. **Pipeline Pattern** ```javascript async function pipeline(items, stages, options = {}) { let current = items; for (const stage of stages) { const { results, errors } = await batchProcess(current, stage, options); if (errors.length > 0) { console.warn(`Stage ${stage.name} had ${errors.length} errors`); } current = results; } return current; } ``` #### 3. **Circuit Breaker Pattern** ```javascript class BatchCircuitBreaker { constructor(threshold = 0.5, resetTime = 60000) { this.threshold = threshold; this.resetTime = resetTime; this.failures = 0; this.successes = 0; this.state = 'closed'; this.nextAttempt = Date.now(); } async execute(batch, processor) { if (this.state === 'open' && Date.now() < this.nextAttempt) { throw new Error('Circuit breaker is open'); } const results = await Promise.allSettled(batch.map((item) => processor(item))); const failures = results.filter((r) => r.status === 'rejected').length; const successes = results.filter((r) => r.status === 'fulfilled').length; this.failures += failures; this.successes += successes; const failureRate = this.failures / (this.failures + this.successes); if (failureRate > this.threshold) { this.state = 'open'; this.nextAttempt = Date.now() + this.resetTime; } return results; } } ``` ### Anti-patterns to Avoid #### 1. **Unbounded Parallelism** ```javascript // Bad: Can overwhelm system const results = await Promise.all(thousandsOfFiles.map((file) => processLargeFile(file))); // Good: Controlled parallelism const results = await batchProcess(thousandsOfFiles, processLargeFile, { batchSize: 10, }); ``` #### 2. **Ignoring Partial Failures** ```javascript // Bad: Loses error information try { const results = await Promise.all(operations); } catch (error) { console.log('Something failed'); } // Good: Handles partial failures const results = await Promise.allSettled(operations); const succeeded = results.filter((r) => r.status === 'fulfilled'); const failed = results.filter((r) => r.status === 'rejected'); ``` #### 3. **Resource Leaks in Batch Operations** ```javascript // Bad: Doesn't clean up resources async function processBatch(files) { const handles = await Promise.all(files.map((file) => fs.open(file, 'r'))); // Process files... // Oops, forgot to close handles! } // Good: Ensures cleanup async function processBatch(files) { const handles = []; try { for (const file of files) { handles.push(await fs.open(file, 'r')); } // Process files... } finally { await Promise.all(handles.map((handle) => handle.close())); } } ``` ### Dependency Management in Parallel Workflows #### Dependency Graph Execution ```javascript class DependencyGraph { constructor() { this.nodes = new Map(); this.edges = new Map(); } addTask(id, task, dependencies = []) { this.nodes.set(id, task); this.edges.set(id, dependencies); } async execute() { const completed = new Set(); const results = new Map(); while (completed.size < this.nodes.size) { const ready = []; for (const [id, deps] of this.edges) { if (!completed.has(id) && deps.every((d) => completed.has(d))) { ready.push(id); } } if (ready.length === 0) { throw new Error('Circular dependency detected'); } const batchResults = await Promise.allSettled(ready.map((id) => this.nodes.get(id)())); ready.forEach((id, index) => { completed.add(id); results.set(id, batchResults[index]); }); } return results; } } ``` ### State Management in Concurrent Operations #### Shared State Manager ```javascript class ConcurrentStateManager { constructor() { this.state = new Map(); this.locks = new Map(); } async withLock(key, operation) { // Wait for existing lock while (this.locks.has(key)) { await new Promise((resolve) => setTimeout(resolve, 10)); } // Acquire lock this.locks.set(key, true); try { return await operation(this.state); } finally { // Release lock this.locks.delete(key); } } async batchUpdate(updates) { const results = []; for (const { key, operation } of updates) { results.push(this.withLock(key, (state) => operation(state))); } return Promise.all(results); } } ``` --- ## Performance Best Practices ### Optimal Batch Sizes for Different Operations | Operation Type | Recommended Batch Size | Rationale | | ---------------- | ------------------------ | ---------------------------------------------- | | File Reads | 5-10 | I/O bound, prevents file descriptor exhaustion | | File Writes | 3-5 | Ensures data integrity, prevents corruption | | API Calls | 10-20 | Network bound, respects rate limits | | CPU-intensive | CPU cores × 2 | Maximizes CPU utilization | | Memory-intensive | Available RAM / Task RAM | Prevents OOM errors | ### Dynamic Batch Sizing ```javascript class AdaptiveBatcher { constructor(initialSize = 5) { this.batchSize = initialSize; this.performanceHistory = []; } async processBatch(items, processor) { const startTime = Date.now(); const batch = items.slice(0, this.batchSize); const results = await Promise.allSettled(batch.map((item) => processor(item))); const duration = Date.now() - startTime; const throughput = batch.length / (duration / 1000); this.performanceHistory.push({ size: this.batchSize, throughput }); this.adjustBatchSize(); return results; } adjustBatchSize() { if (this.performanceHistory.length < 3) return; const recent = this.performanceHistory.slice(-3); const avgThroughput = recent.reduce((sum, r) => sum + r.throughput, 0) / 3; // Increase if performance is good if (avgThroughput > this.batchSize * 0.8) { this.batchSize = Math.min(this.batchSize + 2, 20); } // Decrease if performance is poor else if (avgThroughput < this.batchSize * 0.5) { this.batchSize = Math.max(this.batchSize - 2, 1); } } } ``` ### Resource Pooling and Management #### Connection Pool Example ```javascript class ResourcePool { constructor(factory, maxSize = 10) { this.factory = factory; this.maxSize = maxSize; this.available = []; this.inUse = new Set(); this.waiting = []; } async acquire() { // Return available resource if (this.available.length > 0) { const resource = this.available.pop(); this.inUse.add(resource); return resource; } // Create new resource if under limit if (this.inUse.size < this.maxSize) { const resource = await this.factory(); this.inUse.add(resource); return resource; } // Wait for available resource return new Promise((resolve) => { this.waiting.push(resolve); }); } release(resource) { this.inUse.delete(resource); if (this.waiting.length > 0) { const resolve = this.waiting.shift(); this.inUse.add(resource); resolve(resource); } else { this.available.push(resource); } } async withResource(operation) { const resource = await this.acquire(); try { return await operation(resource); } finally { this.release(resource); } } } ``` ### Caching Strategies for Batch Operations #### LRU Cache for Batch Results ```javascript class BatchCache { constructor(maxSize = 100) { this.cache = new Map(); this.maxSize = maxSize; } getCacheKey(items) { return items.map((item) => (typeof item === 'object' ? JSON.stringify(item) : item)).join('|'); } async batchProcess(items, processor, options = {}) { const { useCache = true, ttl = 300000 } = options; const cacheKey = this.getCacheKey(items); // Check cache if (useCache && this.cache.has(cacheKey)) { const cached = this.cache.get(cacheKey); if (Date.now() - cached.timestamp < ttl) { return cached.results; } } // Process batch const results = await Promise.allSettled(items.map((item) => processor(item))); // Update cache if (useCache) { this.cache.set(cacheKey, { results, timestamp: Date.now(), }); // Evict oldest if over limit if (this.cache.size > this.maxSize) { const firstKey = this.cache.keys().next().value; this.cache.delete(firstKey); } } return results; } } ``` ### Monitoring and Profiling Guidelines #### Performance Monitor ```javascript class BatchPerformanceMonitor { constructor() { this.metrics = { totalOperations: 0, successfulOperations: 0, failedOperations: 0, totalDuration: 0, batchSizes: [], }; } async monitorBatch(batch, processor) { const startTime = Date.now(); const startMemory = process.memoryUsage().heapUsed; const results = await Promise.allSettled(batch.map((item) => processor(item))); const duration = Date.now() - startTime; const memoryDelta = process.memoryUsage().heapUsed - startMemory; // Update metrics this.metrics.totalOperations += batch.length; this.metrics.successfulOperations += results.filter((r) => r.status === 'fulfilled').length; this.metrics.failedOperations += results.filter((r) => r.status === 'rejected').length; this.metrics.totalDuration += duration; this.metrics.batchSizes.push(batch.length); // Log if performance degrades const avgDuration = duration / batch.length; if (avgDuration > 1000) { console.warn(`Slow batch operation: ${avgDuration}ms per item`); } if (memoryDelta > 50 * 1024 * 1024) { console.warn(`High memory usage in batch: ${(memoryDelta / 1024 / 1024).toFixed(2)}MB`); } return results; } getReport() { const avgBatchSize = this.metrics.batchSizes.reduce((a, b) => a + b, 0) / this.metrics.batchSizes.length; const successRate = this.metrics.successfulOperations / this.metrics.totalOperations; const avgDuration = this.metrics.totalDuration / this.metrics.totalOperations; return { totalOperations: this.metrics.totalOperations, successRate: `${(successRate * 100).toFixed(2)}%`, avgDuration: `${avgDuration.toFixed(2)}ms`, avgBatchSize: avgBatchSize.toFixed(2), }; } } ``` --- ## Security Considerations ### Safe Parallel File Operations #### File Locking for Concurrent Access ```javascript class FileLockManager { constructor() { this.locks = new Map(); } async withFileLock(filePath, operation, options = {}) { const { timeout = 30000, retryInterval = 100 } = options; const lockFile = `${filePath}.lock`; const startTime = Date.now(); // Try to acquire lock while (this.locks.has(filePath)) { if (Date.now() - startTime > timeout) { throw new Error(`Timeout acquiring lock for ${filePath}`); } await new Promise((resolve) => setTimeout(resolve, retryInterval)); } // Set lock this.locks.set(filePath, { pid: process.pid, timestamp: Date.now(), }); try { // Write lock file await fs.writeFile(lockFile, JSON.stringify(this.locks.get(filePath))); // Execute operation return await operation(); } finally { // Release lock this.locks.delete(filePath); try { await fs.unlink(lockFile); } catch (error) { // Lock file might already be removed } } } async batchFileOperation(files, operation) { return Promise.all(files.map((file) => this.withFileLock(file, () => operation(file)))); } } ``` ### Preventing Race Conditions #### Atomic Operations Pattern ```javascript class AtomicBatchOperations { async atomicWrite(filePath, content) { const tempPath = `${filePath}.tmp.${process.pid}.${Date.now()}`; try { // Write to temporary file await fs.writeFile(tempPath, content, { flag: 'wx' }); // Atomic rename await fs.rename(tempPath, filePath); } catch (error) { // Cleanup on failure try { await fs.unlink(tempPath); } catch {} throw error; } } async atomicBatchWrite(fileContents) { const tempFiles = []; try { // Write all temp files first for (const { path, content } of fileContents) { const tempPath = `${path}.tmp.${process.pid}.${Date.now()}`; tempFiles.push({ temp: tempPath, final: path }); await fs.writeFile(tempPath, content, { flag: 'wx' }); } // Atomic rename all await Promise.all(tempFiles.map(({ temp, final }) => fs.rename(temp, final))); } catch (error) { // Cleanup all temp files on failure await Promise.allSettled(tempFiles.map(({ temp }) => fs.unlink(temp))); throw error; } } } ``` ### Secure Credential Handling in Batch Operations #### Credential Manager ```javascript class SecureCredentialManager { constructor() { this.credentials = new Map(); this.accessLog = []; } setCredential(key, value, options = {}) { const { expiresIn = 3600000 } = options; // 1 hour default this.credentials.set(key, { value, expiresAt: Date.now() + expiresIn, accessed: 0, }); // Schedule cleanup setTimeout(() => this.credentials.delete(key), expiresIn); } getCredential(key, requester) { const cred = this.credentials.get(key); if (!cred) { this.logAccess(key, requester, false); return null; } if (Date.now() > cred.expiresAt) { this.credentials.delete(key); this.logAccess(key, requester, false); return null; } cred.accessed++; this.logAccess(key, requester, true); return cred.value; } logAccess(key, requester, success) { this.accessLog.push({ timestamp: new Date().toISOString(), key: key.substring(0, 4) + '****', // Partial key for security requester, success, }); // Rotate log if too large if (this.accessLog.length > 1000) { this.accessLog = this.accessLog.slice(-500); } } async batchOperationWithCredentials(operations, credentialKeys) { const requester = `batch-${Date.now()}`; // Validate all credentials exist const missingCreds = credentialKeys.filter((key) => !this.getCredential(key, requester)); if (missingCreds.length > 0) { throw new Error(`Missing credentials: ${missingCreds.join(', ')}`); } // Execute operations const results = await Promise.allSettled( operations.map((op, index) => { const creds = credentialKeys.reduce((acc, key) => { acc[key] = this.getCredential(key, requester); return acc; }, {}); return op(creds); }), ); return results; } } ``` ### Audit Logging for Parallel Processes #### Comprehensive Audit Logger ```javascript class BatchAuditLogger { constructor(logPath) { this.logPath = logPath; this.buffer = []; this.flushInterval = setInterval(() => this.flush(), 5000); } async logBatchOperation(operation, items, results) { const entry = { timestamp: new Date().toISOString(), operation: operation.name || 'anonymous', itemCount: items.length, duration: results.duration, success: results.success.length, failed: results.failed.length, user: process.env.USER || 'unknown', pid: process.pid, memory: process.memoryUsage(), errors: results.failed.map((f) => ({ item: f.item, error: f.error.message, })), }; this.buffer.push(entry); if (this.buffer.length >= 100) { await this.flush(); } } async flush() { if (this.buffer.length === 0) return; const entries = this.buffer.splice(0, this.buffer.length); const logContent = entries.map((e) => JSON.stringify(e)).join('\n') + '\n'; await fs.appendFile(this.logPath, logContent); } async auditedBatchProcess(items, processor, metadata = {}) { const startTime = Date.now(); const results = { success: [], failed: [] }; const batchResults = await Promise.allSettled(items.map((item) => processor(item))); batchResults.forEach((result, index) => { if (result.status === 'fulfilled') { results.success.push({ item: items[index], result: result.value }); } else { results.failed.push({ item: items[index], error: result.reason }); } }); results.duration = Date.now() - startTime; await this.logBatchOperation(processor, items, results); return results; } destroy() { clearInterval(this.flushInterval); this.flush(); } } ``` --- ## Error Handling Strategies ### Graceful Degradation Patterns #### Progressive Degradation ```javascript class ProgressiveDegradation { constructor(strategies) { this.strategies = strategies; // Ordered from best to worst } async execute(item) { let lastError; for (const strategy of this.strategies) { try { return await strategy.execute(item); } catch (error) { lastError = error; console.warn(`Strategy ${strategy.name} failed, trying next...`); } } throw new Error(`All strategies failed. Last error: ${lastError.message}`); } async batchExecute(items) { return Promise.allSettled(items.map((item) => this.execute(item))); } } // Example usage const fileProcessor = new ProgressiveDegradation([ { name: 'FastProcessor', execute: async (file) => { // Try fast in-memory processing const content = await fs.readFile(file); return processInMemory(content); }, }, { name: 'StreamProcessor', execute: async (file) => { // Fall back to streaming for large files return processStream(fs.createReadStream(file)); }, }, { name: 'ChunkedProcessor', execute: async (file) => { // Last resort: process in small chunks return processInChunks(file, { chunkSize: 1024 }); }, }, ]); ``` ### Retry Mechanisms for Batch Operations #### Exponential Backoff with Jitter ```javascript class BatchRetryManager { constructor(options = {}) { this.maxRetries = options.maxRetries || 3; this.baseDelay = options.baseDelay || 1000; this.maxDelay = options.maxDelay || 30000; this.jitter = options.jitter || 0.1; } calculateDelay(attempt) { const exponentialDelay = Math.min(this.baseDelay * Math.pow(2, attempt), this.maxDelay); const jitterRange = exponentialDelay * this.jitter; const jitter = (Math.random() - 0.5) * 2 * jitterRange; return exponentialDelay + jitter; } async retryOperation(operation, context = {}) { let lastError; for (let attempt = 0; attempt <= this.maxRetries; attempt++) { try { return await operation(); } catch (error) { lastError = error; if (attempt < this.maxRetries) { const delay = this.calculateDelay(attempt); console.log(`Retry ${attempt + 1}/${this.maxRetries} after ${delay}ms`); await new Promise((resolve) => setTimeout(resolve, delay)); } } } throw new Error(`Failed after ${this.maxRetries} retries: ${lastError.message}`); } async batchRetry(items, processor) { const results = await Promise.allSettled( items.map((item) => this.retryOperation(() => processor(item), { item })), ); return { successful: results.filter((r) => r.status === 'fulfilled').map((r) => r.value), failed: results.filter((r) => r.status === 'rejected').map((r) => r.reason), }; } } ``` ### Partial Failure Recovery #### Checkpoint and Resume Pattern ```javascript class CheckpointBatchProcessor { constructor(checkpointPath) { this.checkpointPath = checkpointPath; this.processed = new Set(); } async loadCheckpoint() { try { const data = await fs.readFile(this.checkpointPath, 'utf8'); this.processed = new Set(JSON.parse(data)); } catch (error) { // No checkpoint exists yet this.processed = new Set(); } } async saveCheckpoint() { await fs.writeFile(this.checkpointPath, JSON.stringify([...this.processed])); } async processBatchWithCheckpoints(items, processor, options = {}) { const { batchSize = 10, saveInterval = 100 } = options; await this.loadCheckpoint(); const remaining = items.filter((item) => !this.processed.has(item.id)); const results = { successful: [], failed: [] }; let processedCount = 0; for (let i = 0; i < remaining.length; i += batchSize) { const batch = remaining.slice(i, i + batchSize); const batchResults = await Promise.allSettled(batch.map((item) => processor(item))); batchResults.forEach((result, index) => { const item = batch[index]; if (result.status === 'fulfilled') { results.successful.push({ item, result: result.value }); this.processed.add(item.id); processedCount++; } else { results.failed.push({ item, error: result.reason }); } }); // Save checkpoint periodically if (processedCount >= saveInterval) { await this.saveCheckpoint(); processedCount = 0; } } // Final checkpoint save await this.saveCheckpoint(); return results; } async retry() { // Load checkpoint and find failed items await this.loadCheckpoint(); const allItems = await this.getAllItems(); const failed = allItems.filter((item) => !this.processed.has(item.id)); return this.processBatchWithCheckpoints(failed, this.processor); } } ``` ### Transaction-like Behavior in Batch Operations #### Two-Phase Commit Pattern ```javascript class BatchTransaction { constructor() { this.operations = []; this.preparedOps = []; } add(operation, rollback) { this.operations.push({ operation, rollback }); } async prepare() { this.preparedOps = []; for (const { operation, rollback } of this.operations) { try { const prepared = await operation.prepare(); this.preparedOps.push({ operation, rollback, prepared, }); } catch (error) { // Rollback any prepared operations await this.rollback(); throw new Error(`Prepare failed: ${error.message}`); } } return true; } async commit() { const results = []; let commitIndex = 0; try { for (const { operation, prepared } of this.preparedOps) { const result = await operation.commit(prepared); results.push(result); commitIndex++; } return results; } catch (error) { // Partial commit occurred, rollback committed operations await this.partialRollback(commitIndex); throw new Error(`Commit failed at operation ${commitIndex}: ${error.message}`); } } async rollback() { const rollbackResults = await Promise.allSettled( this.preparedOps.map(({ rollback, prepared }) => rollback(prepared)), ); const failed = rollbackResults.filter((r) => r.status === 'rejected'); if (failed.length > 0) { console.error(`${failed.length} rollback operations failed`); } } async partialRollback(fromIndex) { const toRollback = this.preparedOps.slice(0, fromIndex); await Promise.allSettled(toRollback.map(({ rollback, prepared }) => rollback(prepared))); } async execute() { try { await this.prepare(); return await this.commit(); } catch (error) { await this.rollback(); throw error; } } } // Example usage const transaction = new BatchTransaction(); // Add file operations files.forEach((file) => { transaction.add( { prepare: async () => { const backup = await fs.readFile(file); return { file, backup }; }, commit: async ({ file }) => { await fs.writeFile(file, newContent); }, }, async ({ file, backup }) => { await fs.writeFile(file, backup); }, ); }); await transaction.execute(); ``` --- ## Testing Best Practices ### Unit Testing Parallel Code #### Testing Batch Operations ```javascript describe('BatchProcessor', () => { let processor; beforeEach(() => { processor = new BatchProcessor(); }); it('should process all items successfully', async () => { const items = [1, 2, 3, 4, 5]; const mockProcessor = jest.fn().mockResolvedValue('processed'); const results = await processor.processBatch(items, mockProcessor); expect(mockProcessor).toHaveBeenCalledTimes(5); expect(results.successful).toHaveLength(5); expect(results.failed).toHaveLength(0); }); it('should handle partial failures', async () => { const items = [1, 2, 3, 4, 5]; const mockProcessor = jest .fn() .mockResolvedValueOnce('success') .mockRejectedValueOnce(new Error('fail')) .mockResolvedValueOnce('success') .mockRejectedValueOnce(new Error('fail')) .mockResolvedValueOnce('success'); const results = await processor.processBatch(items, mockProcessor); expect(results.successful).toHaveLength(3); expect(results.failed).toHaveLength(2); }); it('should respect batch size limits', async () => { const items = Array(100) .fill(0) .map((_, i) => i); const mockProcessor = jest.fn().mockResolvedValue('processed'); const concurrentCalls = []; mockProcessor.mockImplementation(async (item) => { const currentCalls = mockProcessor.mock.calls.length; concurrentCalls.push(currentCalls); await new Promise((resolve) => setTimeout(resolve, 10)); return 'processed'; }); await processor.processBatch(items, mockProcessor, { batchSize: 5 }); // Check that no more than 5 operations run concurrently const maxConcurrent = Math.max(...concurrentCalls); expect(maxConcurrent).toBeLessThanOrEqual(5); }); }); ``` #### Testing Race Conditions ```javascript describe('Concurrent Operations', () => { it('should handle concurrent writes safely', async () => { const fileManager = new ConcurrentFileManager(); const filePath = '/tmp/test-file.txt'; // Simulate concurrent writes const writes = Array(10) .fill(0) .map((_, i) => fileManager.atomicWrite(filePath, `content-${i}`)); await Promise.all(writes); // Verify file exists and has valid content const content = await fs.readFile(filePath, 'utf8'); expect(content).toMatch(/content-\d/); // Verify no temp files left behind const tempFiles = await glob(`${filePath}.tmp.*`); expect(tempFiles).toHaveLength(0); }); it('should prevent data corruption with locks', async () => { const lockManager = new FileLockManager(); const counter = { value: 0 }; // Concurrent increments const operations = Array(100) .fill(0) .map(() => lockManager.withLock('counter', async () => { const current = counter.value; await new Promise((resolve) => setTimeout(resolve, 1)); counter.value = current + 1; }), ); await Promise.all(operations); expect(counter.value).toBe(100); }); }); ``` ### Integration Testing Strategies #### End-to-End Batch Testing ```javascript describe('Batch Processing Integration', () => { let testDir; let processor; beforeEach(async () => { testDir = await fs.mkdtemp('/tmp/batch-test-'); processor = new IntegratedBatchProcessor(); }); afterEach(async () => { await fs.rm(testDir, { recursive: true }); }); it('should process file batch end-to-end', async () => { // Setup test files const files = await Promise.all( Array(20) .fill(0) .map(async (_, i) => { const path = `${testDir}/file-${i}.txt`; await fs.writeFile(path, `content ${i}`); return path; }), ); // Process batch const results = await processor.processFiles(files, { batchSize: 5, transform: (content) => content.toUpperCase(), }); // Verify results expect(results.successful).toHaveLength(20); // Verify files were transformed for (let i = 0; i < 20; i++) { const content = await fs.readFile(`${testDir}/file-${i}.txt`, 'utf8'); expect(content).toBe(`CONTENT ${i}`); } }); it('should recover from partial failures', async () => { const checkpointPath = `${testDir}/checkpoint.json`; const processor = new CheckpointBatchProcessor(checkpointPath); // Create files, some will fail const files = await Promise.all( Array(10) .fill(0) .map(async (_, i) => { const path = `${testDir}/file-${i}.txt`; // Make some files unreadable if (i % 3 === 0) { await fs.writeFile(path, 'content'); await fs.chmod(path, 0o000); } else { await fs.writeFile(path, `content ${i}`); } return { id: `file-${i}`, path }; }), ); // First run - will have failures const firstRun = await processor.processBatchWithCheckpoints(files, async (file) => { const content = await fs.readFile(file.path, 'utf8'); return content.toUpperCase(); }); expect(firstRun.failed.length).toBeGreaterThan(0); // Fix permissions for (let i = 0; i < 10; i += 3) { await fs.chmod(`${testDir}/file-${i}.txt`, 0o644); } // Retry - should only process failed items const retry = await processor.retry(); expect(retry.successful.length).toBe(firstRun.failed.length); }); }); ``` ### Performance Testing Guidelines #### Load Testing Batch Operations ```javascript class BatchLoadTester { constructor() { this.metrics = []; } async runLoadTest(config) { const { itemCount, batchSizes, processor, warmupRuns = 3, testRuns = 10 } = config; const items = Array(itemCount) .fill(0) .map((_, i) => ({ id: i, data: `test-data-${i}`, })); const results = {}; for (const batchSize of batchSizes) { console.log(`Testing batch size: ${batchSize}`); // Warmup runs for (let i = 0; i < warmupRuns; i++) { await this.runBatch(items, processor, batchSize); } // Test runs const runMetrics = []; for (let i = 0; i < testRuns; i++) { const metrics = await this.runBatch(items, processor, batchSize); runMetrics.push(metrics); } results[batchSize] = this.analyzeMetrics(runMetrics); } return results; } async runBatch(items, processor, batchSize) { const startTime = Date.now(); const startMemory = process.memoryUsage(); const batchProcessor = new BatchProcessor({ batchSize }); const results = await batchProcessor.processBatch(items, processor); const endTime = Date.now(); const endMemory = process.memoryUsage(); return { duration: endTime - startTime, throughput: items.length / ((endTime - startTime) / 1000), memoryDelta: endMemory.heapUsed - startMemory.heapUsed, successRate: results.successful.length / items.length, batchSize, }; } analyzeMetrics(metrics) { const durations = metrics.map((m) => m.duration); const throughputs = metrics.map((m) => m.throughput); const memoryDeltas = metrics.map((m) => m.memoryDelta); return { avgDuration: durations.reduce((a, b) => a + b) / durations.length, minDuration: Math.min(...durations), maxDuration: Math.max(...durations), avgThroughput: throughputs.reduce((a, b) => a + b) / throughputs.length, avgMemoryDelta: memoryDeltas.reduce((a, b) => a + b) / memoryDeltas.length, successRate: metrics[0].successRate, }; } } // Example usage const tester = new BatchLoadTester(); const results = await tester.runLoadTest({ itemCount: 10000, batchSizes: [1, 5, 10, 20, 50, 100], processor: async (item) => { // Simulate work await new Promise((resolve) => setTimeout(resolve, 10)); return item.data.toUpperCase(); }, }); console.log( 'Optimal batch size:', Object.entries(results).sort(([, a], [, b]) => b.avgThroughput - a.avgThroughput)[0][0], ); ``` ### Debugging Batch Operations #### Batch Operation Debugger ```javascript class BatchDebugger { constructor(options = {}) { this.enableBreakpoints = options.enableBreakpoints || false; this.logLevel = options.logLevel || 'info'; this.traces = []; } async debugBatch(items, processor, options = {}) { const { batchSize = 5, breakOn = [] } = options; const results = { successful: [], failed: [] }; for (let i = 0; i < items.length; i += batchSize) { const batch = items.slice(i, i + batchSize); const batchId = `batch-${i / batchSize}`; this.log('info', `Processing ${batchId} with ${batch.length} items`); // Check for breakpoint if (this.enableBreakpoints && breakOn.includes(batchId)) { await this.breakpoint(batchId, batch); } const batchResults = await this.processBatchWithTrace(batch, processor, batchId); // Collect results batchResults.forEach((result, index) => { if (result.status === 'fulfilled') { results.successful.push(result.value); } else { results.failed.push({ item: batch[index], error: result.reason, trace: this.traces.find((t) => t.itemId === batch[index].id), }); } }); } return results; } async processBatchWithTrace(batch, processor, batchId) { return Promise.allSettled( batch.map(async (item) => { const trace = { itemId: item.id, batchId, startTime: Date.now(), steps: [], }; try { const instrumentedProcessor = this.instrumentProcessor(processor, trace); const result = await instrumentedProcessor(item); trace.endTime = Date.now(); trace.duration = trace.endTime - trace.startTime; trace.status = 'success'; trace.result = result; this.traces.push(trace); return result; } catch (error) { trace.endTime = Date.now(); trace.duration = trace.endTime - trace.startTime; trace.status = 'error'; trace.error = { message: error.message, stack: error.stack, }; this.traces.push(trace); throw error; } }), ); } instrumentProcessor(processor, trace) { return new Proxy(processor, { apply: async (target, thisArg, args) => { trace.steps.push({ type: 'function-call', timestamp: Date.now(), args: args.map((arg) => this.sanitizeForLog(arg)), }); try { const result = await target.apply(thisArg, args); trace.steps.push({ type: 'function-return', timestamp: Date.now(), result: this.sanitizeForLog(result), }); return result; } catch (error) { trace.steps.push({ type: 'function-error', timestamp: Date.now(), error: error.message, }); throw error; } }, }); } sanitizeForLog(value) { if (typeof value === 'object' && value !== null) { return JSON.stringify(value).substring(0, 100) + '...'; } return value; } async breakpoint(batchId, batch) { console.log(`\nBREAKPOINT: ${batchId}`); console.log('Batch items:', batch); console.log('Press any key to continue...'); await new Promise((resolve) => { process.stdin.once('data', resolve); }); } log(level, message) { const levels = ['debug', 'info', 'warn', 'error']; if (levels.indexOf(level) >= levels.indexOf(this.logLevel)) { console.log(`[${level.toUpperCase()}] ${message}`); } } getTraceReport() { const report = { totalTraces: this.traces.length, successful: this.traces.filter((t) => t.status === 'success').length, failed: this.traces.filter((t) => t.status === 'error').length, avgDuration: this.traces.reduce((sum, t) => sum + t.duration, 0) / this.traces.length, slowestItems: this.traces .sort((a, b) => b.duration - a.duration) .slice(0, 5) .map((t) => ({ itemId: t.itemId, duration: t.duration, stepCount: t.steps.length, })), }; return report; } } ``` --- ## Team Collaboration ### Code Review Guidelines for Batch Operations #### Batch Operation Code Review Checklist ```markdown ## Batch Operation Review Checklist ### Architecture & Design - [ ] Is batch processing the right approach for this use case? - [ ] Are batch sizes appropriately configured? - [ ] Is there proper resource management (memory, file handles, connections)? - [ ] Are dependencies between operations properly handled? ### Error Handling - [ ] Are all Promise.all replaced with Promise.allSettled where appropriate? - [ ] Is there proper error aggregation and reporting? - [ ] Can the operation recover from partial failures? - [ ] Are errors properly typed and descriptive? ### Performance - [ ] Has the batch size been tested for optimal performance? - [ ] Is there proper backpressure handling? - [ ] Are resources pooled appropriately? - [ ] Has memory usage been profiled? ### Security - [ ] Are file operations atomic where necessary? - [ ] Is there proper access control for parallel operations? - [ ] Are credentials handled securely in batch contexts? - [ ] Is there audit logging for sensitive operations? ### Testing - [ ] Are there unit tests for both success and failure cases? - [ ] Has concurrent operation been tested? - [ ] Are there integration tests for the full workflow? - [ ] Has performance been benchmarked? ### Documentation - [ ] Is the batch operation's purpose clearly documented? - [ ] Are configuration options explained? - [ ] Are error scenarios documented? - [ ] Is there a runbook for operations? ``` ### Documentation Standards #### Batch Operation Documentation Template ```javascript /** * Processes multiple files in parallel batches with automatic retry and checkpointing. * * @description * This function handles large-scale file processing with the following features: * - Automatic batching to prevent resource exhaustion * - Checkpoint/resume capability for long-running operations * - Exponential backoff retry for transient failures * - Comprehensive error reporting and audit logging * * @param {Array<FileItem>} files - Array of file items to process * @param {ProcessorFunction} processor - Function to process each file * @param {BatchOptions} options - Configuration options * * @param {number} [options.batchSize=10] - Number of files to process concurrently * @param {number} [options.maxRetries=3] - Maximum retry attempts per file * @param {string} [options.checkpointPath] - Path to store checkpoint data * @param {boolean} [options.continueOnError=true] - Whether to continue processing on errors * * @returns {Promise<BatchResult>} Results including successful and failed operations * * @example * const results = await batchProcessFiles(files, compressFile, { * batchSize: 5, * maxRetries: 3, * checkpointPath: './checkpoint.json' * }); * * @throws {BatchProcessError} When all retries are exhausted * @throws {CheckpointError} When checkpoint operations fail * * @performance * - Memory: O(batchSize) - Only holds active batch in memory * - Time: O(n/batchSize) - Processes in parallel batches * - Optimal batch size: 2 * CPU cores for CPU-bound, 10-20 for I/O-bound * * @since 2.0.0 */ async function batchProcessFiles(files, processor, options = {}) { // Implementation } ``` ### Version Control Best Practices #### Commit Message Format for Batch Operations ``` <type>(<scope>): <subject> <body> <footer> Types: - feat: New batch operation feature - fix: Bug fix in batch processing - perf: Performance improvement - refactor: Code restructuring - test: Test additions/modifications - docs: Documentation updates Example: feat(batch): Add adaptive batch sizing for file operations - Implement dynamic batch size adjustment based on performance metrics - Add memory pressure detection to prevent OOM errors - Include circuit breaker for failing operations - Batch size now adjusts between 1-20 based on throughput Performance impact: - 40% improvement in large file batch processing - Reduced memory usage by 60% under load - Better handling of mixed file sizes Closes #123 ``` ### Knowledge Sharing Strategies #### Batch Operations Runbook Template ````markdown # Batch Operation Runbook: [Operation Name] ## Overview Brief description of what this batch operation does and when to use it. ## Prerequisites - Required permissions - System requirements - Dependencies ## Configuration ```yaml batchSize: 10 # Optimal for standard workload maxRetries: 3 # Handles transient failures timeout: 300000 # 5 minutes per batch checkpointEnabled: true ``` ```` ## Normal Operation ### Starting the Process ```bash npm run batch:process -- --config=production.json ``` ### Monitoring - Check logs at: `/var/log/batch-processor/` - Metrics dashboard: `http://metrics.internal/batch-ops` - Key metrics to watch: - Throughput (items/second) - Error rate (should be < 1%) - Memory usage (should be < 80%) ## Troubleshooting ### Common Issues #### High Error Rate 1. Check recent changes to data format 2. Verify external service availability 3. Review error logs for patterns #### Performance Degradation 1. Check batch size configuration 2. Monitor resource utilization 3. Look for lock contention #### Memory Issues 1. Reduce batch size 2. Enable streaming mode 3. Check for memory leaks ### Recovery Procedures #### From Checkpoint ```bash npm run batch:resume -- --checkpoint=./checkpoint.json ``` #### Manual Retry ```bash npm run batch:retry -- --failed-items=./failed.json ``` ## Escalation - Primary: @batch-ops-team - Secondary: @platform-team - Emergency: PagerDuty rotation ```` --- ## Tool-Specific Guidelines ### File Operations (Read, Write, Edit) #### Batch Read Operations ```javascript // Best Practice: Controlled parallel reads with resource limits async function batchReadFiles(filePaths, options = {}) { const { batchSize = 5, encoding = 'utf8' } = options; const fileHandles = new Map(); try { const results = []; for (let i = 0; i < filePaths.length; i += batchSize) { const batch = filePaths.slice(i, i + batchSize); const batchResults = await Promise.allSettled( batch.map(async (filePath) => { const handle = await fs.open(filePath, 'r'); fileHandles.set(filePath, handle); const content = await handle.readFile({ encoding }); handle.close(); fileHandles.delete(filePath); return { filePath, content }; }) ); results.push(...batchResults); } return results; } finally { // Ensure all handles are closed for (const handle of fileHandles.values()) { await handle.close().catch(() => {}); } } } ```` #### Batch Write Operations ```javascript // Best Practice: Atomic writes with rollback capability async function batchWriteFiles(fileWrites, options = {}) { const { batchSize = 3, atomic = true } = options; const tempFiles = []; try { // Write to temp files first for (let i = 0; i < fileWrites.length; i += batchSize) { const batch = fileWrites.slice(i, i + batchSize); await Promise.all( batch.map(async ({ path, content }) => { const tempPath = `${path}.tmp.${Date.now()}`; tempFiles.push({ temp: tempPath, final: path }); await fs.writeFile(tempPath, content, { flag: 'wx' }); }), ); } // Atomic rename all at once if (atomic) { await Promise.all(tempFiles.map(({ temp, final }) => fs.rename(temp, final))); } return { success: true, count: fileWrites.length }; } catch (error) { // Cleanup temp files on error await Promise.allSettled(tempFiles.map(({ temp }) => fs.unlink(temp))); throw error; } } ``` ### Search Operations (Grep, Glob) #### Batch Grep Operations ```javascript // Best Practice: Parallel search with result streaming async function batchGrep(pattern, directories, options = {}) { con