brainy/.recovery-workspace/dist-backup-20250910-141917/augmentations/batchProcessingAugmentation.js
David Snelling 8ff382ca3b chore: recovery checkpoint - v3.0 API successfully recovered
CRITICAL CHECKPOINT - DO NOT PUSH TO GITHUB

Recovery Status:
- Successfully recovered brainy.ts from compiled JavaScript
- All core v3.0 API methods functional (add, get, update, delete, relate, find, etc.)
- Neural subsystem intact (562KB embedded patterns, NLP working)
- Augmentation pipeline operational (20+ augmentations)
- HNSW clustering system complete
- Triple Intelligence compiled (needs constructor fix)
- Test suite validates functionality

Changes preserved:
- 898 files with changes from last 3 days
- 144,475 insertions
- All augmentation improvements
- All test coverage enhancements
- Complete v3.0 feature set

This is a LOCAL checkpoint only - contains recovered work after corruption incident.
Created backup in .backups/brainy-full-20250910-151314.tar.gz

Branch: recovery-checkpoint-20250910-151433
Date: Wed Sep 10 03:18:04 PM PDT 2025
2025-09-10 15:18:04 -07:00

669 lines
No EOL
27 KiB
JavaScript

/**
* Batch Processing Augmentation
*
* Critical for enterprise-scale performance: 500,000+ operations/second
* Automatically batches operations for maximum throughput
* Handles streaming data, bulk imports, and high-frequency operations
*
* Performance Impact: 10-50x improvement for bulk operations
*/
import { BaseAugmentation } from './brainyAugmentation.js';
export class BatchProcessingAugmentation extends BaseAugmentation {
constructor(config) {
super(config);
this.metadata = 'readonly'; // Reads metadata for batching decisions
this.name = 'BatchProcessing';
this.timing = 'around';
this.operations = ['add', 'addNoun', 'addVerb', 'saveNoun', 'saveVerb', 'storage'];
this.priority = 80; // High priority for performance
this.config = {
enabled: true,
adaptiveMode: true,
immediateThreshold: 1,
batchThreshold: 5,
maxBatchSize: 100,
maxWaitTime: 1000,
adaptiveBatching: true,
priorityLanes: 2,
memoryLimit: 100 * 1024 * 1024 // 100MB
};
this.batches = new Map();
this.flushTimers = new Map();
this.metrics = {
totalOperations: 0,
batchesProcessed: 0,
averageBatchSize: 0,
averageLatency: 0,
throughputPerSecond: 0,
memoryUsage: 0,
adaptiveAdjustments: 0
};
this.currentMemoryUsage = 0;
this.performanceHistory = [];
}
getManifest() {
return {
id: 'batch-processing',
name: 'Batch Processing',
version: '2.0.0',
description: 'High-performance batching for bulk operations',
longDescription: 'Automatically batches operations for maximum throughput. Essential for enterprise-scale workloads, achieving 500,000+ operations/second. Provides 10-50x performance improvement for bulk operations.',
category: 'performance',
configSchema: {
type: 'object',
properties: {
enabled: {
type: 'boolean',
default: true,
description: 'Enable batch processing'
},
adaptiveMode: {
type: 'boolean',
default: true,
description: 'Automatically decide when to batch operations'
},
immediateThreshold: {
type: 'number',
default: 1,
minimum: 1,
maximum: 10,
description: 'Operations count below which to execute immediately'
},
batchThreshold: {
type: 'number',
default: 5,
minimum: 2,
maximum: 100,
description: 'Queue size at which to start batching'
},
maxBatchSize: {
type: 'number',
default: 1000,
minimum: 10,
maximum: 10000,
description: 'Maximum items per batch'
},
maxWaitTime: {
type: 'number',
default: 100,
minimum: 1,
maximum: 5000,
description: 'Maximum wait time before flushing batch (ms)'
},
adaptiveBatching: {
type: 'boolean',
default: true,
description: 'Dynamically adjust batch size based on performance'
},
priorityLanes: {
type: 'number',
default: 3,
minimum: 1,
maximum: 10,
description: 'Number of priority processing lanes'
},
memoryLimit: {
type: 'number',
default: 104857600, // 100MB
minimum: 10485760, // 10MB
maximum: 1073741824, // 1GB
description: 'Maximum memory for batching in bytes'
}
},
additionalProperties: false
},
configDefaults: {
enabled: true,
adaptiveMode: true,
immediateThreshold: 1,
batchThreshold: 5,
maxBatchSize: 1000,
maxWaitTime: 100,
adaptiveBatching: true,
priorityLanes: 3,
memoryLimit: 104857600
},
minBrainyVersion: '2.0.0',
keywords: ['batch', 'performance', 'bulk', 'streaming', 'throughput'],
documentation: 'https://docs.brainy.dev/augmentations/batch-processing',
status: 'stable',
performance: {
memoryUsage: 'high',
cpuUsage: 'medium',
networkUsage: 'none'
},
features: ['auto-batching', 'adaptive-sizing', 'priority-lanes', 'streaming-support'],
enhancedOperations: ['add', 'addNoun', 'addVerb', 'saveNoun', 'saveVerb'],
ui: {
icon: '📦',
color: '#9C27B0'
}
};
}
async onInitialize() {
if (this.config.enabled) {
this.startMetricsCollection();
this.log(`Batch processing initialized: ${this.config.maxBatchSize} batch size, ${this.config.maxWaitTime}ms max wait`);
if (this.config.adaptiveBatching) {
this.log('Adaptive batching enabled - will optimize batch size dynamically');
}
}
else {
this.log('Batch processing disabled');
}
}
shouldExecute(operation, params) {
if (!this.config.enabled)
return false;
// Skip batching for single operations or already-batched operations
if (params?.batch === false || params?.streaming === false)
return false;
// Enable for high-volume operations
return operation.includes('add') ||
operation.includes('save') ||
operation.includes('storage');
}
async execute(operation, params, next) {
if (!this.shouldExecute(operation, params)) {
return next();
}
// Check if this should be batched based on system load
if (this.shouldBatch(operation, params)) {
return this.addToBatch(operation, params, next);
}
// Execute immediately for low-latency requirements
return next();
}
shouldBatch(operation, params) {
// ZERO-CONFIG INTELLIGENT ADAPTATION:
if (this.config.adaptiveMode) {
// CRITICAL WORKFLOW DETECTION: Never batch operations that break critical patterns
// 1. ENTITY REGISTRY PATTERN: Never batch when immediate lookup is expected
if (this.isEntityRegistryWorkflow(operation, params)) {
return false; // Must be immediate for registry lookups to work
}
// 2. DEPENDENCY CHAIN PATTERN: Never batch when next operation depends on this one
if (this.isDependencyChainStart(operation, params)) {
return false; // Must be immediate for noun → verb workflows
}
// Count pending operations in the current operation's batch (needed for write-only mode)
const batchKey = this.getBatchKey(operation, params);
const currentBatch = this.batches.get(batchKey) || [];
const pendingCount = currentBatch.length;
// 3. WRITE-ONLY MODE: Special handling for high-speed streaming
if (this.isWriteOnlyMode(params)) {
// In write-only mode, batch aggressively but ensure entity registry updates immediately
if (this.hasEntityRegistryMetadata(params)) {
return false; // Entity registry updates must be immediate even in write-only mode
}
return pendingCount >= 3; // Lower threshold for write-only mode batching
}
// Apply intelligent thresholds:
// 4. Single operations are immediate (responsive user experience)
if (pendingCount < this.config.immediateThreshold) {
return false; // Execute immediately
}
// 5. Start batching when multiple operations are queued
if (pendingCount >= this.config.batchThreshold) {
return true; // Batch for efficiency
}
// 6. For in-between cases, use smart heuristics
const currentLoad = this.getCurrentLoad();
if (currentLoad > 0.5)
return true; // Higher load = more batching
// 7. Batch operations that naturally benefit from grouping
if (operation.includes('save') || operation.includes('add')) {
return pendingCount > 1; // Batch if others are already waiting
}
return false; // Default to immediate for best responsiveness
}
// TRADITIONAL MODE: (for explicit configuration scenarios)
// Always batch if explicitly requested
if (params?.batch === true || params?.streaming === true)
return true;
// Batch based on current system load
const currentLoad = this.getCurrentLoad();
if (currentLoad > 0.7)
return true; // High load - batch everything
// Batch operations that benefit from grouping
return operation.includes('save') ||
operation.includes('add') ||
operation.includes('update');
}
/**
* SMART WORKFLOW DETECTION METHODS
* These methods detect critical patterns that must not be batched
*/
isEntityRegistryWorkflow(operation, params) {
// Detect operations that will likely be followed by immediate entity registry lookups
if (operation === 'addNoun' || operation === 'add') {
// Check if metadata contains external identifiers (DID, handle, etc.)
const metadata = params?.metadata || params?.data || {};
return !!(metadata.did || // Bluesky DID
metadata.handle || // Social media handle
metadata.uri || // Resource URI
metadata.external_id || // External system ID
metadata.user_id || // User ID
metadata.profile_id || // Profile ID
metadata.account_id // Account ID
);
}
return false;
}
isDependencyChainStart(operation, params) {
// Detect operations that are likely to be followed by dependent operations
if (operation === 'addNoun' || operation === 'add') {
// In interactive workflows, noun creation is often followed by verb creation
// Use heuristics to detect this pattern
const context = this.getOperationContext();
// If we've seen recent addVerb operations, this noun might be for a relationship
if (context.recentVerbOperations > 0) {
return true;
}
// If this is part of a rapid sequence of operations, it might be a dependency chain
if (context.operationsInLastSecond > 3) {
return true;
}
}
return false;
}
isWriteOnlyMode(params) {
// Detect write-only mode from context or parameters
return !!(params?.writeOnlyMode ||
params?.streaming ||
params?.highThroughput ||
this.context?.brain?.writeOnly);
}
hasEntityRegistryMetadata(params) {
// Check if this operation has metadata that needs immediate entity registry updates
const metadata = params?.metadata || params?.data || {};
return !!(metadata.did ||
metadata.handle ||
metadata.uri ||
metadata.external_id ||
// Also check for auto-registration hints
params?.autoCreateMissingNouns ||
params?.entityRegistry);
}
getOperationContext() {
const now = Date.now();
const oneSecondAgo = now - 1000;
let recentVerbOperations = 0;
let operationsInLastSecond = 0;
// Analyze recent operations across all batches
for (const batch of this.batches.values()) {
for (const op of batch) {
if (op.timestamp > oneSecondAgo) {
operationsInLastSecond++;
if (op.operation.includes('Verb') || op.operation.includes('verb')) {
recentVerbOperations++;
}
}
}
}
return { recentVerbOperations, operationsInLastSecond };
}
getCurrentLoad() {
// Simple load calculation based on pending operations
let totalPending = 0;
for (const batch of this.batches.values()) {
totalPending += batch.length;
}
return Math.min(totalPending / 10000, 1.0); // Normalize to 0-1
}
async addToBatch(operation, params, executor) {
return new Promise((resolve, reject) => {
const priority = this.getOperationPriority(operation, params);
const batchKey = this.getBatchKey(operation, priority);
const operationSize = this.estimateOperationSize(params);
// Check memory limit
if (this.currentMemoryUsage + operationSize > this.config.memoryLimit) {
// Memory limit reached - flush oldest batch
this.flushOldestBatch();
}
const batchedOp = {
id: `op_${Date.now()}_${Math.random()}`,
operation,
params,
executor, // Store the actual executor
resolver: resolve,
rejector: reject,
timestamp: Date.now(),
priority,
size: operationSize
};
// Add to appropriate batch
if (!this.batches.has(batchKey)) {
this.batches.set(batchKey, []);
}
const batch = this.batches.get(batchKey);
batch.push(batchedOp);
this.currentMemoryUsage += operationSize;
this.metrics.totalOperations++;
// Check if batch should be flushed immediately
if (this.shouldFlushBatch(batch, batchKey)) {
this.flushBatch(batchKey);
}
else if (!this.flushTimers.has(batchKey)) {
// Set flush timer if not already set
this.setFlushTimer(batchKey);
}
});
}
getOperationPriority(operation, params) {
// Explicit priority
if (params?.priority !== undefined)
return params.priority;
// Operation-based priority
if (operation.includes('delete'))
return 10; // Highest
if (operation.includes('update'))
return 8;
if (operation.includes('save'))
return 6;
if (operation.includes('add'))
return 4;
return 1; // Lowest
}
getBatchKey(operation, priority) {
// Group by operation type and priority for optimal batching
const opType = this.getOperationType(operation);
const priorityLane = Math.min(priority, this.config.priorityLanes - 1);
return `${opType}_p${priorityLane}`;
}
getOperationType(operation) {
if (operation.includes('add'))
return 'add';
if (operation.includes('save'))
return 'save';
if (operation.includes('update'))
return 'update';
if (operation.includes('delete'))
return 'delete';
return 'other';
}
estimateOperationSize(params) {
// Rough estimation of memory usage
if (!params)
return 100;
let size = 0;
if (params.vector && Array.isArray(params.vector)) {
size += params.vector.length * 8; // 8 bytes per float64
}
if (params.data) {
size += JSON.stringify(params.data).length * 2; // Rough UTF-16 estimate
}
if (params.metadata) {
size += JSON.stringify(params.metadata).length * 2;
}
return Math.max(size, 100); // Minimum 100 bytes
}
shouldFlushBatch(batch, batchKey) {
// Flush if batch is full
if (batch.length >= this.config.maxBatchSize)
return true;
// Flush if memory limit approaching
if (this.currentMemoryUsage > this.config.memoryLimit * 0.9)
return true;
// Flush high-priority batches more aggressively
const priority = this.extractPriorityFromKey(batchKey);
if (priority >= 8 && batch.length >= 100)
return true;
if (priority >= 6 && batch.length >= 500)
return true;
return false;
}
extractPriorityFromKey(batchKey) {
const match = batchKey.match(/_p(\d+)$/);
return match ? parseInt(match[1]) : 0;
}
setFlushTimer(batchKey) {
const priority = this.extractPriorityFromKey(batchKey);
const waitTime = this.getAdaptiveWaitTime(priority);
const timer = setTimeout(() => {
this.flushBatch(batchKey);
}, waitTime);
this.flushTimers.set(batchKey, timer);
}
getAdaptiveWaitTime(priority) {
if (!this.config.adaptiveBatching) {
return this.config.maxWaitTime;
}
// Adaptive wait time based on performance and priority
const baseWaitTime = this.config.maxWaitTime;
const performanceMultiplier = this.getPerformanceMultiplier();
const priorityMultiplier = priority >= 8 ? 0.5 : priority >= 6 ? 0.7 : 1.0;
return Math.max(baseWaitTime * performanceMultiplier * priorityMultiplier, 10);
}
getPerformanceMultiplier() {
if (this.performanceHistory.length < 10)
return 1.0;
// Calculate average latency trend
const recent = this.performanceHistory.slice(-10);
const average = recent.reduce((a, b) => a + b, 0) / recent.length;
// If performance is degrading, reduce wait time
if (average > this.metrics.averageLatency * 1.2)
return 0.7;
if (average < this.metrics.averageLatency * 0.8)
return 1.3;
return 1.0;
}
async flushBatch(batchKey) {
const batch = this.batches.get(batchKey);
if (!batch || batch.length === 0)
return;
// Clear timer
const timer = this.flushTimers.get(batchKey);
if (timer) {
clearTimeout(timer);
this.flushTimers.delete(batchKey);
}
// Remove batch from queue
this.batches.delete(batchKey);
const startTime = Date.now();
try {
await this.processBatch(batch);
// Update metrics
const latency = Date.now() - startTime;
this.updateMetrics(batch.length, latency);
// Adaptive adjustment
if (this.config.adaptiveBatching) {
this.adjustBatchSize(latency, batch.length);
}
}
catch (error) {
this.log(`Batch processing failed for ${batchKey}: ${error}`, 'error');
// Reject all operations in batch
batch.forEach(op => {
op.rejector(error);
this.currentMemoryUsage -= op.size;
});
}
}
async processBatch(batch) {
// Group by operation type for efficient processing
const operationGroups = new Map();
for (const op of batch) {
const opType = this.getOperationType(op.operation);
if (!operationGroups.has(opType)) {
operationGroups.set(opType, []);
}
operationGroups.get(opType).push(op);
}
// Process each operation type
for (const [opType, operations] of operationGroups) {
await this.processBatchByType(opType, operations);
}
}
async processBatchByType(opType, operations) {
// Execute batch operation based on type
try {
if (opType === 'add' || opType === 'save') {
await this.processBatchSave(operations);
}
else if (opType === 'update') {
await this.processBatchUpdate(operations);
}
else if (opType === 'delete') {
await this.processBatchDelete(operations);
}
else {
// Fallback: execute individually
await this.processIndividually(operations);
}
}
catch (error) {
throw error;
}
}
async processBatchSave(operations) {
// Try to use storage's bulk save if available
const storage = this.context?.storage;
if (storage && typeof storage.saveBatch === 'function') {
// Use bulk save operation
const items = operations.map(op => ({
...op.params,
_batchId: op.id
}));
try {
const results = await storage.saveBatch(items);
// Resolve all operations with actual results
operations.forEach((op, index) => {
op.resolver(results[index] || op.params.id);
this.currentMemoryUsage -= op.size;
});
}
catch (error) {
// Reject all operations on batch error
operations.forEach(op => {
op.rejector(error);
this.currentMemoryUsage -= op.size;
});
throw error;
}
}
else {
// Execute using stored executors with concurrency control
await this.processWithConcurrency(operations, 10);
}
}
async processBatchUpdate(operations) {
await this.processWithConcurrency(operations, 5); // Lower concurrency for updates
}
async processBatchDelete(operations) {
await this.processWithConcurrency(operations, 5); // Lower concurrency for deletes
}
async processIndividually(operations) {
await this.processWithConcurrency(operations, 3); // Conservative concurrency
}
async processWithConcurrency(operations, concurrency) {
const promises = [];
for (let i = 0; i < operations.length; i += concurrency) {
const chunk = operations.slice(i, i + concurrency);
const chunkPromise = Promise.all(chunk.map(async (op) => {
try {
// Execute using the stored executor function - REAL EXECUTION!
const result = await op.executor();
op.resolver(result);
this.currentMemoryUsage -= op.size;
}
catch (error) {
op.rejector(error);
this.currentMemoryUsage -= op.size;
}
})).then(() => { }); // Convert to void promise
promises.push(chunkPromise);
}
await Promise.all(promises);
}
// REMOVED executeOperation - no longer needed since we use stored executors
flushOldestBatch() {
if (this.batches.size === 0)
return;
// Find oldest batch
let oldestKey = '';
let oldestTime = Infinity;
for (const [key, batch] of this.batches) {
if (batch.length > 0) {
const batchAge = Math.min(...batch.map(op => op.timestamp));
if (batchAge < oldestTime) {
oldestTime = batchAge;
oldestKey = key;
}
}
}
if (oldestKey) {
this.flushBatch(oldestKey);
}
}
updateMetrics(batchSize, latency) {
this.metrics.batchesProcessed++;
this.metrics.averageBatchSize =
(this.metrics.averageBatchSize * (this.metrics.batchesProcessed - 1) + batchSize) /
this.metrics.batchesProcessed;
// Update latency with exponential moving average
this.metrics.averageLatency = this.metrics.averageLatency * 0.9 + latency * 0.1;
// Add to performance history
this.performanceHistory.push(latency);
if (this.performanceHistory.length > 100) {
this.performanceHistory.shift();
}
}
adjustBatchSize(latency, batchSize) {
const targetLatency = this.config.maxWaitTime * 5; // Target: 5x wait time
if (latency > targetLatency && batchSize > 100) {
// Reduce batch size if latency too high
this.config.maxBatchSize = Math.max(this.config.maxBatchSize * 0.9, 100);
this.metrics.adaptiveAdjustments++;
}
else if (latency < targetLatency * 0.5 && batchSize === this.config.maxBatchSize) {
// Increase batch size if latency very low
this.config.maxBatchSize = Math.min(this.config.maxBatchSize * 1.1, 10000);
this.metrics.adaptiveAdjustments++;
}
}
startMetricsCollection() {
setInterval(() => {
// Calculate throughput
this.metrics.throughputPerSecond = this.metrics.totalOperations;
this.metrics.totalOperations = 0; // Reset for next measurement
// Update memory usage
this.metrics.memoryUsage = this.currentMemoryUsage;
}, 1000);
}
/**
* Get batch processing statistics
*/
getStats() {
let pendingOperations = 0;
for (const batch of this.batches.values()) {
pendingOperations += batch.length;
}
return {
...this.metrics,
pendingBatches: this.batches.size,
pendingOperations,
currentBatchSize: this.config.maxBatchSize,
memoryUtilization: `${Math.round((this.currentMemoryUsage / this.config.memoryLimit) * 100)}%`
};
}
/**
* Force flush all pending batches
*/
async flushAll() {
const batchKeys = Array.from(this.batches.keys());
await Promise.all(batchKeys.map(key => this.flushBatch(key)));
}
async onShutdown() {
// Clear all timers
for (const timer of this.flushTimers.values()) {
clearTimeout(timer);
}
this.flushTimers.clear();
// Flush all pending batches
await this.flushAll();
const stats = this.getStats();
this.log(`Batch processing shutdown: ${this.metrics.batchesProcessed} batches processed, ${stats.memoryUtilization} peak memory usage`);
}
}
//# sourceMappingURL=batchProcessingAugmentation.js.map