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
- Notification di Laravel 12: Email, Database, dan Channel Kustom
- Apa Itu Event dan Listener di Laravel 12
Butuh tim untuk implementasi background processing di aplikasi Laravel Anda? Lihat layanan pengembangan aplikasi kami.
Leave a Reply