Belajar Swoole - Task Worker (CPU-bound Work)
Episode 11 of 26

Belajar Swoole - Task Worker (CPU-bound Work)

Mengeluarkan pekerjaan CPU-heavy dari worker utama dengan task worker: memakai task dan on Task untuk image processing, PDF generation, dan pengiriman email async, memahami task_worker_num, finish callback, dan task_enable_coroutine.

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

Pendahuluan

Setelah di episode 10 kita mengelola waktu dan proses — pada episode kali ini kita menangani masalah yang sering mengejutkan developer Swoole: pekerjaan CPU-heavy. Coroutine membuat I/O tidak memblokir worker, tetapi komputasi murni (loop panjang, kompresi, resize gambar, generate PDF) tetap memblokir — karena CPU sedang sibuk dan tidak ada event yang bisa "diputar".

Mengapa episode ini penting? Satu request yang melakukan resize gambar 5 detik akan membuat seluruh worker itu beku selama 5 detik — ratusan request lain ikut terlambat. Task worker adalah solusi arsitektural Swoole untuk masalah ini, dan wajib dikuasai sebelum aplikasi kalian masuk produksi.

Masalah: CPU-bound Work Memblokir Worker

Bandingkan dua jenis pekerjaan:

JenisPerilaku di workerKapan selesai
I/O (DB, HTTP)Coroutine menunda, worker lanjut tugas lainSaat data tiba
CPU (resize, PDF, hash)Worker macet total sampai selesaiHanya setelah komputasi rampung

Kode berikut membuat worker macet selama komputasi hash:

Contoh CPU-bound yang memblokir
$server->on('Request', function ($req, $res) {
    // Loop hash 3 detik — worker membeku, request lain menunggu!
    $hash = '';
    for ($i = 0; $i < 3000000; $i++) {
        $hash = hash('sha256', $hash . $i);
    }
    $res->end($hash);
});

Solusi: Task Worker

Task worker adalah pool proses terpisah yang khusus mengeksekusi tugas berat. Worker utama hanya mengirimkan tugas (instan, tidak memblokir) dan melanjutkan melayani request lain.

Konfigurasi task worker
$server = new Server('0.0.0.0', 9501);
$server->set([
    'worker_num'      => 4,   // worker utama
    'task_worker_num' => 4,   // task worker
]);
100%

Siklus Task: task → on(Task) → finish

Alur lengkapnya:

  1. Handler memanggil $server->task($data) — data dikirim ke task worker.
  2. Task worker mengeksekusi on('Task') dan mengembalikan hasil (return).
  3. Hasil dikirim kembali ke worker asal dan diproses di on('Finish').
Task worker lengkap
<?php
use Swoole\Server;
 
$server = new Server('0.0.0.0', 9501);
$server->set(['worker_num' => 4, 'task_worker_num' => 4]);
 
$server->on('Receive', function (Server $server, $fd, $reactorId, $data) {
    // Kirim tugas ke task worker, hasil di on('Finish')
    $server->task(['job' => 'resize', 'path' => $data]);
    $server->send($fd, "Tugas diterima, diproses di background\n");
});
 
$server->on('Task', function (Server $server, $taskId, $workerId, $data) {
    // Ini berjalan di TASK WORKER — aman untuk CPU-heavy
    $image = new Imagick($data['path']);
    $image->resizeImage(800, 800, Imagick::FILTER_LANCZOS, 1);
    $image->writeImage($data['path'] . '.webp');
    return 'resized'; // hasil dikembalikan
});
 
$server->on('Finish', function (Server $server, $taskId, $data) {
    echo "Task $taskId selesai: $data\n";
});
 
$server->start();

Perhatikan: on('Task') berjalan di proses task worker — jadi variabel state worker biasa tidak tersedia di sana. Data harus di-pass lengkap lewat parameter $data.

Task tanpa Menunggu Hasil (Fire-and-Forget)

Tidak semua tugas butuh hasil — kirim email, update cache, tulis log. Untuk pola fire-and-forget, cukup panggil task() dan abaikan on('Finish'):

Fire-and-forget email
$server->on('Request', function ($req, $res) use ($server) {
    $emailData = ['to' => 'user@example.com', 'body' => $req->post['message'] ?? ''];
    $server->task($emailData); // instan, tidak menunggu
    $res->end('Email sedang dikirim');
});
 
$server->on('Task', function (Server $server, $taskId, $workerId, $data) {
    send_email_smtp($data['to'], $data['body']); // lambat tapi di task worker
    return true;
});

Request selesai dalam milidetik; pengiriman email yang butuh detik tidak lagi menghalangi user.

task_enable_coroutine: Coroutine di Task Worker

Secara default task worker tidak punya coroutine context. Aktifkan task_enable_coroutine untuk memakai coroutine di dalam task — memungkinkan satu task worker menangani banyak task I/O-heavy secara parallel:

Task worker dengan coroutine
$server->set([
    'task_worker_num'      => 4,
    'task_enable_coroutine' => true,
]);
 
$server->on('Task', function (Server $server, Server\Task $task) {
    // $task->data — payload task
    // Di sini boleh pakai coroutine
    $resp = Coroutine\Http\get('https://api.render.example/v1/pdf');
    $task->finish($resp->getBody());
});

Dengan mode ini, bentuk callback berubah: parameter kedua adalah object Swoole\Server\Task, dan hasil dikirim via $task->finish().

Praktik: Async Email / Rendering via Task Pool

Contoh produksi gabungan — upload gambar lalu resize di task worker:

Async resize via task
<?php
use Swoole\Server;
 
$server = new Server('0.0.0.0', 9501);
$server->set([
    'worker_num'      => 4,
    'task_worker_num' => 8, // 8 task worker untuk resize
]);
 
$server->on('Receive', function (Server $server, $fd, $reactorId, $data) {
    $payload = json_decode($data, true);
    $server->task([
        'job'   => 'resize',
        'file'  => $payload['file'],
        'sizes' => [100, 300, 800],
    ]);
    $server->send($fd, 'OK');
});
 
$server->on('Task', function (Server $server, $taskId, $workerId, $data) {
    if ($data['job'] !== 'resize') {
        return;
    }
    foreach ($data['sizes'] as $size) {
        $img = new Imagick($data['file']);
        $img->resizeImage($size, $size, Imagick::FILTER_LANCZOS, 1);
        $img->writeImage(preg_replace('/\.(\w+)$/', "-$size.\$1", $data['file']));
        $img->clear();
    }
    return 'done';
});
 
$server->start();

Sekarang request upload selesai instan, dan semua resize berjalan di task worker — worker utama tetap responsif untuk request lain.

Common Pitfalls

MasalahPenyebabSolusi
Worker tetap macetCPU-heavy diproses di on('Request'), bukan on('Task')Pindahkan ke task
Task worker penuh, request menunggutask_worker_num terlalu kecil vs bebanNaikkan, atau antri via channel
on('Finish') tidak terpanggilTask melempar exception sebelum returnWrap try/catch dan selalu return
Data task hilangData non-serializable (resource) di-passHanya kirim data serializable (array/string)

Penutup

Pada episode 11 ini, kalian telah mengeluarkan pekerjaan berat dari worker utama.

Inti yang harus dibawa pulang:

  • CPU-bound work memblokir worker — coroutine tidak bisa membantu untuk komputasi murni.
  • Task worker: task_worker_num pool terpisah untuk pekerjaan berat.
  • Siklus: task()on('Task')returnon('Finish').
  • Fire-and-forget untuk email/notifikasi; task_enable_coroutine untuk coroutine di task worker.
  • Sesuaikan task_worker_num dengan beban; jangan menaruh CPU-heavy di handler request.

Di episode 12 selanjutnya, kita berbagi memory antar proses dengan aman: Table, Atomic & Lock — rate limiter dan online user counter tanpa database. Sampai jumpa di episode 12!

Belajar Swoole - Task Worker (CPU-bound Work) | Belajar Swoole