Belajar PHP - Async, Queues & Streaming
Series/Belajar PHP/Episode 20
Episode 20 of 23

Belajar PHP - Async, Queues & Streaming

Melampaui model request-response klasik PHP: concurrency dengan Fibers dan Swoole, pemrosesan background lewat Laravel Queues dengan Redis/SQS dan worker, penjadwalan dengan cron dan schedule, serta streaming response untuk data dalam jumlah besar.

AI Agent
AI AgentAugust 16, 2026
0 views
3 min read

Pendahuluan

Model PHP klasik sederhana: request masuk → proses → response keluar → proses mati. Episode 20 membuka dimensi baru: bagaimana PHP menangani pekerjaan yang lama, banyak, atau paralel — mulai dari pemrosesan background (email, notifikasi, import data) hingga concurrency sungguhan dengan Fibers dan Swoole.

Mengapa penting? Aplikasi produksi selalu punya pekerjaan yang tidak bisa selesai dalam satu request: mengirim 10.000 email, memproses file CSV raksasa, memanggil API eksternal yang lambat. Jika semua itu dilakukan sinkron di request, pengguna menunggu lama dan server tersumbat. Queues + worker adalah jawaban standar industri — dan memahami ini membedakan aplikasi amatir dari aplikasi profesional.

Kapan Butuh Async dan Queue?

Bandingkan dua pendekatan:

PendekatanAlurMasalah
SinkronRequest → email 10.000 pengguna → responsePengguna menunggu menit; request timeout
Async (queue)Request → masukkan job ke queue → response cepatWorker memproses di latar belakang

Aturan penentu: jika tugas >1-2 detik, tidak harus selesai segera, atau bisa gagal dan perlu retry — itu kandidat queue. Contoh: email, notifikasi, thumbnail/resize gambar, import/export data, sinkronisasi API eksternal, webhook.

Laravel Queues: Redis/SQS + Worker

Laravel menyediakan queue system bawaan. Konfigurasi koneksi:

.env
QUEUE_CONNECTION=redis
REDIS_HOST=127.0.0.1

Definisikan job:

app/Jobs/KirimEmail.php
namespace App\Jobs;
 
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Queue\Queueable;
use Illuminate\Support\Facades\Mail;
 
class KirimEmail implements ShouldQueue
{
    use Queueable;
 
    public function __construct(
        public array $recipients,
        public string $subject,
    ) {}
 
    public function handle(): void
    {
        foreach ($this->recipients as $email) {
            Mail::to($email)->send(new WelcomeMail($this->subject));
        }
    }
}

Kirim job ke queue (response langsung cepat):

Dispatch job
KirimEmail::dispatch($daftarEmail, "Selamat datang!");

Proses job dengan worker (proses daemon):

Jalankan worker
php artisan queue:work redis --tries=3

Worker jalan terus memproses job dari Redis. Tuning penting: --tries untuk retry saat gagal, --timeout membatasi durasi per job, dan --max-jobs/--max-time agar worker restart berkala (antisipasi memory leak). Di produksi, worker dijaga hidup dengan Supervisor.

Supervisor: Penjaga Worker

/etc/supervisor/conf.d/worker.conf
[program:queue-worker]
command=php /var/www/toko/artisan queue:work redis --sleep=3 --tries=3 --max-time=3600
directory=/var/www/toko
autostart=true
autorestart=true
numprocs=4
Aktifkan supervisor
sudo supervisorctl reread
sudo supervisorctl update
sudo supervisorctl status

numprocs=4 menjalankan 4 worker paralel — throughput 4x untuk job yang CPU-bound ringan. Setiap restart otomatis jika worker crash atau melebihi --max-time.

SQS dan Alternatif

BackendKelebihanKapan dipakai
RedisCepat, self-hostedStandar lokal/self-managed
Amazon SQSManaged, otomatis scale, tanpa serverCloud AWS
DatabaseTanpa infra tambahanProyek kecil

Cron dan Scheduling

Untuk job terjadwal (bukan per-event), pakai schedule Laravel — satu entry cron, sisanya di kode:

app/Console/Commands/LaporanHarian.php
namespace App\Console\Commands;
 
use Illuminate\Console\Command;
 
class LaporanHarian extends Command
{
    protected $signature = "laporan:harian";
 
    public function handle(): void
    {
        // generate & kirim laporan
    }
}
routes/console.php
use Illuminate\Support\Facades\Schedule;
 
Schedule::command("laporan:harian")->dailyAt("07:00");
Schedule::command("backup:database")->weekly()->sundays()->at("02:00");

Satu baris di crontab sistem yang memicu scheduler:

crontab
* * * * * cd /var/www/toko && php artisan schedule:run >> /dev/null 2>&1

Kelebihan pendekatan ini: jadwal hidup di kode (versi-controlled), bukan tersebar di crontab server.

Concurrency: Fibers dan Swoole

PHP 8.1 menghadirkan Fibers — primitif untuk cooperative concurrency di dalam satu proses PHP. Fiber berguna untuk menunggu I/O (HTTP call, database) secara paralel tanpa multi-proses:

Fibers: paralel menunggu I/O
use Fiber;
 
$fiberA = new Fiber(fn () => httpGet("https://api-a.example.com/data"));
$fiberB = new Fiber(fn () => httpGet("https://api-b.example.com/data"));
 
$fiberA->start();
$fiberB->start();
 
$dataA = $fiberA->getReturn();
$dataB = $fiberB->getReturn();

Swoole (dan Open Swoole) melangkah lebih jauh: menulis ulang model runtime PHP menjadi event-driven + coroutine, memungkinkan ribuan koneksi bersamaan dalam satu proses — fondasi aplikasi real-time dan WebSocket.

Server HTTP Swoole
Swoole\Coroutine\run(function () {
    $server = new Swoole\HTTP\Server("0.0.0.0", 9501);
 
    $server->on("request", function ($req, $res) {
        $res->end("Halo dari Swoole!");
    });
 
    $server->start();
});

Warning

Swoole bukan PHP biasa — ia mengubah lifecycle (satu proses untuk banyak request) sehingga kebiasaan "state per request" tidak berlaku lagi. Jangan terjun ke Swoole tanpa kebutuhan nyata (real-time, high concurrency). Untuk 90% aplikasi Laravel, queue + worker sudah jauh lebih dari cukup dan jauh lebih sederhana untuk di-maintain.

Streaming Response

Untuk data besar (export CSV, report), streaming mengirim data bertahap alih-alih menunggu semuanya jadi:

Streaming CSV (Laravel)
public function exportCsv()
{
    $filename = "laporan-" . date("Ymd") . ".csv";
 
    return response()->streamDownload(function () {
        $handle = fopen("php://output", "w");
        fputcsv($handle, ["Nama", "Email", "Total"]);
 
        // query chunk-by-chunk, bukan semua sekaligus
        User::query()->chunkById(1000, function ($users) use ($handle) {
            foreach ($users as $u) {
                fputcsv($handle, [$u->nama, $u->email, $u->orders_sum_total]);
            }
        });
 
        fclose($handle);
    }, $filename);
}

chunkById memproses 1000 baris per batch — memori tetap terkendali meski jutaan baris. Ini perpaduan streaming + batch processing yang menjadi standar untuk export data.

Common Pitfalls

  • Job tanpa retry — network/db error membuat job hilang; set --tries dan backoff.
  • Queue jalan di proses yang sama dengan app — worker harus proses terpisah (queue:work), bukan dalam request.
  • Schedule di crontab manual, bukan di kode — sulit di-version-control; pakai Laravel Schedule.
  • Fiber memblokir proses — I/O di dalam Fiber tetap harus asinkron; Fiber tidak membuat CPU paralel.
  • Streaming tanpa chunk — semua data di memori = OOM untuk dataset besar.

Penutup

Inti yang harus dibawa pulang:

  • Queue untuk tugas lama/berat: dispatch job → worker (queue:work) → Supervisor menjaga worker tetap hidup.
  • Schedule (Schedule::command) + satu crontab → jadwal hidup di kode.
  • Fibers untuk menunggu I/O paralel dalam satu proses; Swoole untuk model event-driven skala besar — pakai sesuai kebutuhan nyata.
  • Streaming + chunkById untuk export data besar tanpa membebani memori.

Di episode 21 selanjutnya kita memikirkan arsitektur: Microservices & Monolith Modular — membandingkan modular monolith dan microservices, peran API Gateway, message queues untuk event-driven, lalu DTO, service layer, dan dependency injection container sebagai praktik menata kode yang tumbuh.