/** * Descomplicar® Crescimento Digital * https://descomplicar.pt */ [], 'validation_results' => [], 'transform_data' => [], 'sync_results' => [] ]; private $pool_max_size = 50; // Streaming state management private $stream_state = [ 'total_processed' => 0, 'current_chunk' => 0, 'errors_encountered' => 0, 'memory_peak' => 0, 'checkpoints' => [] ]; // Performance tracking private $streaming_metrics = [ 'chunks_processed' => 0, 'gc_cycles_forced' => 0, 'memory_warnings' => 0, 'objects_pooled' => 0, 'objects_reused' => 0, 'stream_start_time' => 0, 'total_streaming_time' => 0 ]; // Database operations optimization private $db_ops; public function __construct() { parent::__construct(); // Initialize optimized database operations $this->db_ops = new OptimizedDatabaseOperations(); // Setup memory monitoring $this->initializeMemoryManagement(); // Configure PHP for optimal memory usage $this->optimizePhpConfiguration(); } /** * Initialize memory management system */ private function initializeMemoryManagement() { // Convert MB to bytes for PHP memory functions $this->memory_limit_bytes = $this->memory_limit_mb * 1024 * 1024; // Initialize streaming metrics $this->streaming_metrics['stream_start_time'] = microtime(true); // Set up memory monitoring $this->stream_state['memory_peak'] = memory_get_usage(true); // Register shutdown function for cleanup register_shutdown_function([$this, 'streamingCleanup']); } /** * Optimize PHP configuration for streaming operations */ private function optimizePhpConfiguration() { // Enable garbage collection if (function_exists('gc_enable')) { gc_enable(); } // Optimize memory settings if possible if (function_exists('ini_set')) { // Increase memory limit if current limit is too low $current_limit = ini_get('memory_limit'); if ($this->parseMemoryLimit($current_limit) < $this->memory_limit_bytes) { ini_set('memory_limit', $this->memory_limit_mb . 'M'); } // Optimize garbage collection ini_set('zend.enable_gc', '1'); // Optimize realpath cache ini_set('realpath_cache_size', '4096K'); ini_set('realpath_cache_ttl', '600'); } } /** * Parse memory limit string to bytes */ private function parseMemoryLimit($limit_string) { $limit_string = trim($limit_string); $last_char = strtolower($limit_string[strlen($limit_string)-1]); $limit_value = (int) $limit_string; switch($last_char) { case 'g': $limit_value *= 1024; // no break case 'm': $limit_value *= 1024; // no break case 'k': $limit_value *= 1024; } return $limit_value; } // ================================================= // STREAMING BULK OPERATIONS // ================================================= /** * Memory-optimized streaming bulk synchronization * * @param array $invoice_ids Invoice IDs to sync * @param array $options Sync options * @return array Comprehensive sync results */ public function streamingBulkSync($invoice_ids, $options = []) { $this->streaming_metrics['stream_start_time'] = microtime(true); try { // Initialize streaming session $this->initializeStreamingSession(count($invoice_ids), $options); // Process in memory-efficient chunks $chunks = array_chunk($invoice_ids, $this->chunk_size); $results = $this->initializeStreamingResults(); foreach ($chunks as $chunk_index => $chunk_invoice_ids) { $chunk_result = $this->processInvoiceChunkOptimized( $chunk_invoice_ids, $chunk_index, $options ); $this->mergeChunkResults($results, $chunk_result); // Memory management between chunks $this->performMemoryMaintenance($chunk_index); // Create checkpoint for recovery $this->createStreamingCheckpoint($chunk_index, $results); $this->streaming_metrics['chunks_processed']++; } // Finalize streaming session $this->finalizeStreamingSession($results); return $results; } catch (Exception $e) { $this->handleStreamingError($e, $invoice_ids, $options); throw $e; } } /** * Initialize streaming session */ private function initializeStreamingSession($total_count, $options) { $this->stream_state = [ 'total_invoices' => $total_count, 'total_processed' => 0, 'current_chunk' => 0, 'errors_encountered' => 0, 'memory_peak' => memory_get_usage(true), 'session_start' => microtime(true), 'checkpoints' => [], 'options' => $options ]; log_message('info', "StreamingInvoiceSyncService: Starting bulk sync of {$total_count} invoices"); } /** * Initialize streaming results structure */ private function initializeStreamingResults() { return $this->getFromPool('sync_results', [ 'total_invoices' => $this->stream_state['total_invoices'], 'processed' => 0, 'successful' => 0, 'failed' => 0, 'errors' => [], 'performance' => [ 'start_time' => microtime(true), 'chunks_processed' => 0, 'memory_usage' => [], 'gc_cycles' => 0 ], 'chunks' => [] ]); } /** * Process single chunk with optimization */ private function processInvoiceChunkOptimized($invoice_ids, $chunk_index, $options) { $chunk_start_time = microtime(true); $chunk_start_memory = memory_get_usage(true); $chunk_result = $this->getFromPool('sync_results', [ 'chunk_index' => $chunk_index, 'invoice_count' => count($invoice_ids), 'successful' => 0, 'failed' => 0, 'errors' => [], 'invoices' => [] ]); foreach ($invoice_ids as $invoice_id) { try { // Process single invoice with memory monitoring $invoice_result = $this->processInvoiceWithMemoryControl($invoice_id, $options); if ($invoice_result['success']) { $chunk_result['successful']++; } else { $chunk_result['failed']++; $chunk_result['errors'][] = $invoice_result['error']; } $chunk_result['invoices'][] = $invoice_result; // Update stream state $this->stream_state['total_processed']++; } catch (Exception $e) { $this->stream_state['errors_encountered']++; $chunk_result['failed']++; $chunk_result['errors'][] = $this->sanitizeErrorMessage($e->getMessage()); log_message('error', "StreamingSync: Error processing invoice {$invoice_id}: " . $e->getMessage()); } } // Calculate chunk performance metrics $chunk_result['performance'] = [ 'execution_time' => microtime(true) - $chunk_start_time, 'memory_used' => memory_get_usage(true) - $chunk_start_memory, 'memory_peak' => memory_get_peak_usage(true) ]; return $chunk_result; } /** * Process single invoice with memory control */ private function processInvoiceWithMemoryControl($invoice_id, $options) { $before_memory = memory_get_usage(true); try { // Call parent sync method $result = $this->sync_invoice($invoice_id, $options); // Monitor memory usage $after_memory = memory_get_usage(true); $memory_used = $after_memory - $before_memory; // Add memory usage to result $result['memory_used'] = $memory_used; // Check for memory issues if ($after_memory > ($this->memory_limit_bytes * $this->memory_warning_threshold)) { $this->handleMemoryWarning($after_memory, $invoice_id); } return $result; } catch (Exception $e) { return [ 'success' => false, 'invoice_id' => $invoice_id, 'error' => $this->sanitizeErrorMessage($e->getMessage()), 'memory_used' => memory_get_usage(true) - $before_memory ]; } } /** * Merge chunk results into main results */ private function mergeChunkResults(&$main_results, $chunk_result) { $main_results['processed'] += $chunk_result['invoice_count']; $main_results['successful'] += $chunk_result['successful']; $main_results['failed'] += $chunk_result['failed']; $main_results['errors'] = array_merge($main_results['errors'], $chunk_result['errors']); $main_results['chunks'][] = $chunk_result; $main_results['performance']['chunks_processed']++; $main_results['performance']['memory_usage'][] = $chunk_result['performance']['memory_peak']; } /** * Perform memory maintenance between chunks */ private function performMemoryMaintenance($chunk_index) { $current_memory = memory_get_usage(true); // Update memory peak if ($current_memory > $this->stream_state['memory_peak']) { $this->stream_state['memory_peak'] = $current_memory; } // Force garbage collection periodically if ($chunk_index % $this->gc_frequency === 0) { $this->forceGarbageCollection(); } // Clean object pools if memory is high if ($current_memory > ($this->memory_limit_bytes * $this->memory_warning_threshold)) { $this->cleanObjectPools(); } // Critical memory handling if ($current_memory > ($this->memory_limit_bytes * $this->memory_critical_threshold)) { $this->handleCriticalMemoryUsage($current_memory); } } /** * Force garbage collection and measure effectiveness */ private function forceGarbageCollection() { $before_memory = memory_get_usage(true); if (function_exists('gc_collect_cycles')) { $cycles_collected = gc_collect_cycles(); $this->streaming_metrics['gc_cycles_forced']++; $after_memory = memory_get_usage(true); $memory_freed = $before_memory - $after_memory; if ($memory_freed > 0) { log_message('debug', "GC freed {$memory_freed} bytes, collected {$cycles_collected} cycles"); } } } /** * Create checkpoint for streaming recovery */ private function createStreamingCheckpoint($chunk_index, $results) { $checkpoint = [ 'chunk_index' => $chunk_index, 'timestamp' => microtime(true), 'processed_count' => $this->stream_state['total_processed'], 'success_count' => $results['successful'], 'error_count' => $results['failed'], 'memory_usage' => memory_get_usage(true) ]; $this->stream_state['checkpoints'][] = $checkpoint; // Keep only last 5 checkpoints to save memory if (count($this->stream_state['checkpoints']) > 5) { array_shift($this->stream_state['checkpoints']); } } /** * Finalize streaming session */ private function finalizeStreamingSession(&$results) { $session_end_time = microtime(true); $total_session_time = $session_end_time - $this->stream_state['session_start']; // Flush any remaining database batches $this->db_ops->flushAllBatches(); // Calculate final performance metrics $results['performance']['total_time'] = $total_session_time; $results['performance']['memory_peak'] = $this->stream_state['memory_peak']; $results['performance']['gc_cycles'] = $this->streaming_metrics['gc_cycles_forced']; $results['performance']['invoices_per_second'] = $results['processed'] / max($total_session_time, 0.001); // Add streaming-specific metrics $results['streaming_metrics'] = $this->getStreamingMetrics(); log_message('info', "StreamingInvoiceSyncService: Completed bulk sync - " . "{$results['successful']} successful, {$results['failed']} failed, " . "Peak memory: " . round($this->stream_state['memory_peak'] / 1024 / 1024, 2) . "MB"); } // ================================================= // OBJECT POOL MANAGEMENT // ================================================= /** * Get object from pool or create new one */ private function getFromPool($pool_name, $default_value = []) { if (!isset($this->object_pools[$pool_name])) { $this->object_pools[$pool_name] = []; } $pool = &$this->object_pools[$pool_name]; if (!empty($pool)) { $object = array_pop($pool); $this->streaming_metrics['objects_reused']++; // Reset object to default state if (is_array($object)) { $object = array_merge($object, $default_value); } else { $object = $default_value; } return $object; } // Create new object $this->streaming_metrics['objects_pooled']++; return $default_value; } /** * Return object to pool */ private function returnToPool($pool_name, $object) { if (!isset($this->object_pools[$pool_name])) { $this->object_pools[$pool_name] = []; } $pool = &$this->object_pools[$pool_name]; if (count($pool) < $this->pool_max_size) { // Clear sensitive data before pooling if (is_array($object)) { unset($object['errors'], $object['error'], $object['sensitive_data']); } $pool[] = $object; } // Let object be garbage collected if pool is full } /** * Clean object pools to free memory */ private function cleanObjectPools($force_clean = false) { $cleaned_objects = 0; foreach ($this->object_pools as $pool_name => &$pool) { if ($force_clean) { $cleaned_objects += count($pool); $pool = []; } else { // Clean half of each pool $pool_size = count($pool); $to_remove = max(1, intval($pool_size / 2)); for ($i = 0; $i < $to_remove; $i++) { if (!empty($pool)) { array_pop($pool); $cleaned_objects++; } } } } if ($cleaned_objects > 0) { log_message('debug', "Cleaned {$cleaned_objects} objects from pools"); } } // ================================================= // MEMORY MONITORING AND HANDLING // ================================================= /** * Handle memory warning */ private function handleMemoryWarning($current_memory, $context = '') { $this->streaming_metrics['memory_warnings']++; $memory_mb = round($current_memory / 1024 / 1024, 2); $limit_mb = round($this->memory_limit_bytes / 1024 / 1024, 2); log_message('warning', "StreamingSync: Memory warning - {$memory_mb}MB used of {$limit_mb}MB limit" . ($context ? " (context: {$context})" : "")); // Trigger immediate cleanup $this->forceGarbageCollection(); $this->cleanObjectPools(); } /** * Handle critical memory usage */ private function handleCriticalMemoryUsage($current_memory) { $memory_mb = round($current_memory / 1024 / 1024, 2); log_message('error', "StreamingSync: Critical memory usage - {$memory_mb}MB - forcing aggressive cleanup"); // Aggressive cleanup $this->forceGarbageCollection(); $this->cleanObjectPools(true); // Clear any cached data if (method_exists($this, 'clearCaches')) { $this->clearCaches(); } // If still critical, consider reducing chunk size if (memory_get_usage(true) > ($this->memory_limit_bytes * $this->memory_critical_threshold)) { $this->chunk_size = max(5, intval($this->chunk_size / 2)); log_message('warning', "Reduced chunk size to {$this->chunk_size} due to memory pressure"); } } /** * Handle streaming errors with context */ private function handleStreamingError($exception, $invoice_ids, $options) { $error_context = [ 'total_invoices' => count($invoice_ids), 'processed_count' => $this->stream_state['total_processed'], 'current_chunk' => $this->stream_state['current_chunk'], 'memory_usage' => memory_get_usage(true), 'memory_peak' => $this->stream_state['memory_peak'], 'streaming_metrics' => $this->getStreamingMetrics() ]; log_message('error', 'StreamingInvoiceSyncService: Streaming error - ' . $exception->getMessage() . ' | Context: ' . json_encode($error_context)); } // ================================================= // PERFORMANCE MONITORING // ================================================= /** * Get streaming performance metrics */ public function getStreamingMetrics() { $total_time = microtime(true) - $this->streaming_metrics['stream_start_time']; return array_merge($this->streaming_metrics, [ 'total_streaming_time' => $total_time, 'memory_efficiency' => $this->calculateMemoryEfficiency(), 'processing_rate' => $this->stream_state['total_processed'] / max($total_time, 0.001), 'chunk_average_time' => $this->streaming_metrics['chunks_processed'] > 0 ? $total_time / $this->streaming_metrics['chunks_processed'] : 0, 'gc_efficiency' => $this->calculateGCEfficiency(), 'pool_efficiency' => $this->calculatePoolEfficiency() ]); } /** * Calculate memory efficiency */ private function calculateMemoryEfficiency() { $peak_mb = $this->stream_state['memory_peak'] / 1024 / 1024; $limit_mb = $this->memory_limit_bytes / 1024 / 1024; return max(0, 100 - (($peak_mb / $limit_mb) * 100)); } /** * Calculate garbage collection efficiency */ private function calculateGCEfficiency() { if ($this->streaming_metrics['chunks_processed'] === 0) { return 0; } $gc_frequency_actual = $this->streaming_metrics['chunks_processed'] / max($this->streaming_metrics['gc_cycles_forced'], 1); return min(100, ($this->gc_frequency / $gc_frequency_actual) * 100); } /** * Calculate pool efficiency */ private function calculatePoolEfficiency() { $total_objects = $this->streaming_metrics['objects_pooled'] + $this->streaming_metrics['objects_reused']; if ($total_objects === 0) { return 0; } return ($this->streaming_metrics['objects_reused'] / $total_objects) * 100; } /** * Get memory usage report */ public function getMemoryUsageReport() { return [ 'current_usage_mb' => round(memory_get_usage(true) / 1024 / 1024, 2), 'peak_usage_mb' => round(memory_get_peak_usage(true) / 1024 / 1024, 2), 'limit_mb' => $this->memory_limit_mb, 'usage_percentage' => round((memory_get_usage(true) / $this->memory_limit_bytes) * 100, 2), 'warnings_triggered' => $this->streaming_metrics['memory_warnings'], 'gc_cycles_forced' => $this->streaming_metrics['gc_cycles_forced'], 'pool_objects' => array_sum(array_map('count', $this->object_pools)) ]; } // ================================================= // CLEANUP AND DESTRUCTOR // ================================================= /** * Streaming cleanup */ public function streamingCleanup() { // Flush any pending database operations if ($this->db_ops) { $this->db_ops->flushAllBatches(); } // Clean all object pools $this->cleanObjectPools(true); // Final garbage collection $this->forceGarbageCollection(); // Log final streaming metrics if ($this->stream_state['total_processed'] > 0) { log_activity('StreamingInvoiceSyncService Final Stats: ' . json_encode($this->getStreamingMetrics())); } } /** * Destructor with cleanup */ public function __destruct() { $this->streamingCleanup(); parent::__destruct(); } }