Mutex mengatasi tumpang tindih, tapi ada masalah saudaranya: task yang lambat membuat proses schedule:run berdiri diam menunggu. Selama lima menit penuh laporan berat berjalan, proses scheduler tidak bisa melayani apa pun — dan bila task berikutnya justru butuh mulai tepat waktu, jadwal bergeser diam-diam. Solusinya adalah runInBackground(): scheduler menyalakan task lalu langsung pulang. Pada chapter ini kita ukur perbedaannya dengan nyata, bedah perintah shell yang sebenarnya dijalankan, dan — sama pentingnya — pelajari batasannya, karena tidak semua jenis task boleh berjalan di background.
Default: schedule:run Menunggu #
Command report:generate kita simulasikan sebagai proses berat tiga detik yang menandai selesai ke sebuah berkas log:
namespace App\Console\Commands;
use Illuminate\Console\Command;
class GenerateReport extends Command
{
protected $signature = 'report:generate {--seconds=3 : Durasi simulasi pembuatan laporan (detik)}';
protected $description = 'Buat laporan mingguan (mensimulasikan proses berat)';
public function handle(): int
{
sleep((int) $this->option('seconds'));
file_put_contents(
storage_path('logs/report-demo.log'),
'laporan siap pada '.now()->format('H:i:s')."\n",
FILE_APPEND,
);
$this->info('Laporan selesai dibuat.');
return self::SUCCESS;
}
}Jadwalkan sebagai task foreground biasa, lalu ukur durasi schedule:run:
Schedule::command('report:generate --seconds=3')->everyMinute()->name('laporan-mingguan');Output yang Dihasilkan:
2026-10-13 20:24:12 Running ['artisan' report:generate --seconds=3] 3s DONE
⇂ laporan-mingguan
durasi_schedule_run: 3.44 detikAngka durasi diambil dari pengukuran time di sekitar perintah. Proses schedule:run sibuk 3,44 detik penuh — hampir seluruhnya menunggu sleep() di dalam command. Pada cron dengan interval satu menit, task 70 detik berarti pemanggilan menit berikutnya tertunda atau tumpang tindih.
runInBackground: Scheduler Langsung Pulang #
Tambahkan satu method, jalankan pengukuran yang sama:
Schedule::command('report:generate --seconds=3')
->everyMinute()
->name('laporan-mingguan')
->runInBackground();Output yang Dihasilkan:
2026-10-13 20:24:23 Running ['artisan' report:generate --seconds=3] in background 3.17ms DONE
⇂ laporan-mingguan
durasi_schedule_run: 0.22 detikScheduler kembali dalam 0,22 detik — task dinyalakan lalu ditinggalkan berjalan sendiri. Buktinya ada dua: berkas report-demo.log masih kosong sesaat setelah schedule:run selesai, dan baris laporan siap pada 20:24:26 baru muncul tiga detik kemudian — persis ketika proses background menyelesaikan tugasnya.
Apa yang Sebenarnya Dijalankan #
runInBackground() bukan magic framework — ia membangun satu perintah shell yang mengeksekusi task, lalu memberinya akhiran & agar shell meletakkannya di background. Berikut string perintah yang dihasilkan untuk task di atas, diambil langsung dari $event->buildCommand():
('/home/z/bin/php' 'artisan' report:generate --seconds=3 > '/dev/null' 2>&1 ; '/home/z/bin/php' 'artisan' schedule:finish "framework/schedule-4306b1477b9ed81fbcda0555bfb5dd25a7138406" "$?") > '/dev/null' 2>&1 &Ada dua peristiwa dalam satu baris shell: pertama command report:generate dijalankan dengan output dibuang (/dev/null); kedua, setelah selesai, schedule:finish dipanggil dengan exit code task ($?). Perintah schedule:finish inilah yang melepas mutex withoutOverlapping dan menjalankan callback penutup — jadi bookkeeping task background tetap tuntas meskipun scheduler sudah lama pergi.
Batasan Penting: Closure dan Job #
runInBackground() hanya berlaku untuk task berbasis proses — Schedule::command() dan Schedule::exec(). Closure yang mencoba memakainya langsung ditolak framework saat definisi jadwal dimuat:
Schedule::call(function () {
sleep(3);
})->everyMinute()->name('laporan-berat')->runInBackground();Output yang Dihasilkan:
RuntimeException
Scheduled closures can not be run in the background.
at vendor/laravel/framework/src/Illuminate/Console/Scheduling/CallbackEvent.php:104Batasan yang sama berlaku untuk Schedule::job() — job dijadwalkan sebagai callback, jadi ->job(...)->runInBackground() menolak dengan error identik. Kabar baiknya: task job memang tidak membutuhkan runInBackground(), karena job yang mengimplementasikan ShouldQueue didispatch ke antrean sekejap mata dan dikerjakan worker — scheduler tidak pernah menunggu pekerjaannya selesai. Artinya strateginya jadi jelas: pekerjaan berat ditulis sebagai command lalu diberi runInBackground(), atau — lebih baik — sebagai queue job yang dijadwalkan lewat Schedule::job().
Kapan Memakai yang Mana #
| Aspek | Foreground (default) | Background (runInBackground()) |
|---|---|---|
schedule:run menunggu selesai | Ya | Tidak |
| Jenis task yang didukung | Semua | command() dan exec() saja |
| Urutan antar-task dalam satu menit | Berurutan | Paralel, urutan tak dijamin |
| Kegagalan task menghambat task lain | Bisa | Tidak — proses terisolasi |
| Cocok untuk | Task cepat, ringan, berurutan | Task lambat, berat, independen |
Task yang menulis log singkat atau memanggil satu query ringan tidak perlu di-background — overhead proses baru justru lebih mahal dari eksekusinya. Gunakan background untuk pemrosesan file, agregasi lintas juta baris, atau pemanggilan API eksternal yang lambat.
Eksekusi tervalancar, tumpang tindih terkunci — tinggal soal terakhir: bagaimana kita tahu semua ini benar-benar berjalan tiap menit di production? Chapter Schedule Monitoring membahas output, log, heartbeat, dan alerting. Rujukan resminya ada di dokumentasi Task Scheduling .