From 30662b97d5ce11bf8772efaa9a4534be217edbae Mon Sep 17 00:00:00 2001 From: daengdeni Date: Wed, 28 May 2025 09:28:12 +0700 Subject: [PATCH] feat(webstatement): optimize data processing with batching strategy - Menambahkan konstanta `CHUNK_SIZE` untuk membatasi ukuran batch selama proses data. - Memperkenalkan properti `$entryBatch` untuk menyimpan batch data sementara sebelum disimpan ke database. - Mengubah metode `saveRecord` menjadi `addToBatch` untuk menambahkan record ke batch alih-alih langsung menyimpannya. - Menambahkan metode `saveBatch` untuk menyimpan batch data ke database secara efisien menggunakan `upsert`. - Menangani chunking dalam proses CSV untuk menghindari masalah memori dengan memproses data dalam batch. - Menambahkan logging pada setiap batch yang selesai diproses untuk melacak kemajuan pemrosesan. - Memastikan batch yang tersisa diproses setelah selesai membaca file CSV. - Menambahkan penanganan error saat menyimpan batch untuk mencegah reprocessing pada batch yang gagal memproses. - Otomatis menambahkan timestamp (`created_at` dan `updated_at`) pada setiap record sebelum dimasukkan ke batch. --- app/Jobs/ProcessStmtEntryDataJob.php | 60 +++++++++++++++++++++++++--- 1 file changed, 54 insertions(+), 6 deletions(-) diff --git a/app/Jobs/ProcessStmtEntryDataJob.php b/app/Jobs/ProcessStmtEntryDataJob.php index 5a3eaad..9970186 100644 --- a/app/Jobs/ProcessStmtEntryDataJob.php +++ b/app/Jobs/ProcessStmtEntryDataJob.php @@ -20,10 +20,12 @@ private const MAX_EXECUTION_TIME = 86400; // 24 hours in seconds private const FILENAME = 'ST.STMT.ENTRY.csv'; private const DISK_NAME = 'sftpStatement'; + private const CHUNK_SIZE = 1000; // Process data in chunks to reduce memory usage private string $period = ''; private int $processedCount = 0; private int $errorCount = 0; + private array $entryBatch = []; /** * Create a new job instance. @@ -61,6 +63,7 @@ set_time_limit(self::MAX_EXECUTION_TIME); $this->processedCount = 0; $this->errorCount = 0; + $this->entryBatch = []; } private function processPeriod() @@ -111,10 +114,23 @@ $headers = (new StmtEntry())->getFillable(); $rowCount = 0; + $chunkCount = 0; while (($row = fgetcsv($handle, 0, self::CSV_DELIMITER)) !== false) { $rowCount++; $this->processRow($row, $headers, $rowCount, $filePath); + + // Process in chunks to avoid memory issues + if (count($this->entryBatch) >= self::CHUNK_SIZE) { + $this->saveBatch(); + $chunkCount++; + Log::info("Processed chunk $chunkCount ({$this->processedCount} records so far)"); + } + } + + // Process any remaining records + if (!empty($this->entryBatch)) { + $this->saveBatch(); } fclose($handle); @@ -132,7 +148,7 @@ $data = array_combine($headers, $row); $this->cleanTransReference($data); - $this->saveRecord($data, $rowCount, $filePath); + $this->addToBatch($data, $rowCount, $filePath); } private function cleanTransReference(array &$data) @@ -144,15 +160,21 @@ } } - private function saveRecord(array $data, int $rowCount, string $filePath) + /** + * Add record to batch instead of saving immediately + */ + private function addToBatch(array $data, int $rowCount, string $filePath) : void { try { if (isset($data['stmt_entry_id']) && $data['stmt_entry_id'] !== 'stmt_entry_id') { - StmtEntry::updateOrCreate( - ['stmt_entry_id' => $data['stmt_entry_id']], - $data - ); + // Add timestamp fields + $now = now(); + $data['created_at'] = $now; + $data['updated_at'] = $now; + + // Add to entry batch + $this->entryBatch[] = $data; $this->processedCount++; } } catch (Exception $e) { @@ -161,6 +183,32 @@ } } + /** + * Save batched records to the database + */ + private function saveBatch() + : void + { + try { + if (!empty($this->entryBatch)) { + // Bulk insert/update statement entries + StmtEntry::upsert( + $this->entryBatch, + ['stmt_entry_id'], // Unique key + array_diff((new StmtEntry())->getFillable(), ['stmt_entry_id']) // Update columns + ); + + // Reset entry batch after processing + $this->entryBatch = []; + } + } catch (Exception $e) { + Log::error("Error in saveBatch: " . $e->getMessage()); + $this->errorCount += count($this->entryBatch); + // Reset batch even if there's an error to prevent reprocessing the same failed records + $this->entryBatch = []; + } + } + private function cleanup(string $tempFilePath) : void {