Tutorial Laravel 12 Job Batching: Implementasi, Progress, dan Error Handling

Bayangkan Anda perlu kirim email ke 5.000 pengguna sekaligus, atau proses 1.000 gambar setelah upload. Kalau dijalankan satu per satu lewat queue biasa, Anda tidak tahu kapan semuanya selesai, dan tidak bisa jalankan aksi “setelah semua beres”.

Job Batching di Laravel menyelesaikan masalah ini. Artikel ini membahas cara kerjanya, implementasi lengkap dengan contoh kode, dan cara handle error dalam batch.

Apa Itu Job Batching?

Job Batching memungkinkan Anda mengelompokkan beberapa job ke dalam satu batch, lalu mendefinisikan callback yang dijalankan:

  • Saat semua job berhasil (then)
  • Saat ada job yang gagal (catch)
  • Saat semua job selesai (berhasil atau gagal) (finally)

Setup: Membuat Tabel Batch

php artisan make:batches-table
php artisan migrate

Ini membuat tabel job_batches yang menyimpan status setiap batch.

Membuat Job yang Batchable

Job harus implement ShouldQueue dan gunakan trait Batchable:

<?php

namespace AppJobs;

use AppModelsUser;
use AppMailPromotionMail;
use IlluminateBusBatchable;
use IlluminateBusQueueable;
use IlluminateContractsQueueShouldQueue;
use IlluminateFoundationBusDispatchable;
use IlluminateQueueInteractsWithQueue;
use IlluminateSupportFacadesMail;

class SendPromotionEmail implements ShouldQueue
{
    use Batchable, Dispatchable, InteractsWithQueue, Queueable;

    public function __construct(
        private User $user
    ) {}

    public function handle(): void
    {
        // Cek apakah batch sudah di-cancel
        if ($this->batch()->cancelled()) {
            return;
        }

        Mail::to($this->user)->send(new PromotionMail());
    }
}

Mengirim Batch

use AppJobsSendPromotionEmail;
use IlluminateSupportFacadesBus;

$users = User::where('subscribed', true)->get();

$batch = Bus::batch(
    $users->map(fn ($user) => new SendPromotionEmail($user))->toArray()
)->then(function (Batch $batch) {
    // Dijalankan saat semua job berhasil
    Log::info("Batch selesai: {$batch->totalJobs} email terkirim.");
})->catch(function (Batch $batch, Throwable $e) {
    // Dijalankan saat ada job yang gagal
    Log::error("Batch error: {$e->getMessage()}");
})->finally(function (Batch $batch) {
    // Selalu dijalankan saat batch selesai (berhasil atau tidak)
    Cache::forget('promotion-batch-running');
})->name('Kirim Email Promosi')
  ->allowFailures()  // batch tetap lanjut meski ada job gagal
  ->dispatch();

// Simpan ID batch untuk monitor progress
return $batch->id;

Monitor Progress Batch

Ambil info batch berdasarkan ID:

use IlluminateSupportFacadesBus;

$batch = Bus::findBatch($batchId);

// Info yang tersedia
$batch->id;
$batch->name;
$batch->totalJobs;
$batch->pendingJobs;
$batch->failedJobs;
$batch->processedJobs();  // totalJobs - pendingJobs
$batch->progress();       // 0-100 persen
$batch->finished();       // apakah sudah selesai
$batch->cancelled();      // apakah di-cancel

Contoh endpoint API untuk polling progress:

Route::get('/batches/{batchId}/progress', function (string $batchId) {
    $batch = Bus::findBatch($batchId);

    if (!$batch) {
        return response()->json(['error' => 'Batch tidak ditemukan'], 404);
    }

    return response()->json([
        'progress'     => $batch->progress(),
        'total'        => $batch->totalJobs,
        'processed'    => $batch->processedJobs(),
        'failed'       => $batch->failedJobs,
        'finished'     => $batch->finished(),
    ]);
})->middleware('auth');

Cancel Batch

$batch = Bus::findBatch($batchId);
$batch->cancel();

Job yang belum diproses akan skip sendiri karena pengecekan $this->batch()->cancelled() di awal method handle().

Batch dengan Job Berantai

Anda bisa chain batch: jalankan batch kedua setelah batch pertama selesai:

Bus::batch([
    new ProcessImages($product),
    new GenerateThumbnails($product),
])->then(function (Batch $batch) use ($product) {
    // Setelah gambar dan thumbnail selesai, generate sitemap
    GenerateSitemap::dispatch();
})->dispatch();

Praktis: Batch untuk Impor CSV

Skenario umum: impor data dari file CSV besar.

<?php

namespace AppJobs;

use IlluminateBusBatchable;
use IlluminateBusQueueable;
use IlluminateContractsQueueShouldQueue;

class ImportCsvRow implements ShouldQueue
{
    use Batchable, Queueable;

    public function __construct(private array $row) {}

    public function handle(): void
    {
        if ($this->batch()->cancelled()) return;

        Product::updateOrCreate(
            ['sku'  => $this->row['sku']],
            ['name' => $this->row['name'], 'price' => $this->row['price']]
        );
    }
}

// Di controller
$rows  = CsvParser::parse($file);
$jobs  = collect($rows)->map(fn ($row) => new ImportCsvRow($row));

$batch = Bus::batch($jobs->toArray())
            ->name('Import Produk')
            ->allowFailures()
            ->dispatch();

return redirect()->route('imports.progress', $batch->id);

Baca Juga

Butuh tim untuk implementasi background processing di aplikasi Laravel Anda? Lihat layanan pengembangan aplikasi kami.

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *