Mengoptimalkan performa RoadRunner: menyetel num_workers, max_jobs, max_worker_memory, dan supervisor, memanfaatkan opcache dan JIT, melakukan benchmark sistematis, serta membandingkan hasil dengan PHP-FPM dan FrankenPHP.

Setelah di episode 19 kita mengeksplorasi Temporal, pada episode kali ini kita fokus ke hal yang selalu ditanya saat adopsi RoadRunner: berapa cepat? Jawabannya tergantung tuning — worker pool yang disetel asal-asalan justru lebih lambat dari PHP-FPM. Episode ini adalah panduan menyetel pool, opcache, dan JIT secara sistematis.
Mengapa episode ini penting? Karena performa bukan satu tombol — ia hasil dari sekumpulan parameter yang saling memengaruhi. Memahami hubungan antar parameter membuat kalian bisa mendiagnosis bottleneck, bukan sekadar menebak angka.
http:
address: 0.0.0.0:8080
pool:
num_workers: 8
max_worker_memory: 128
max_jobs: 1000
allocate_workers_by_tasks: false
supervisor:
max_worker_memory: 128
exec_ttl: 30
idle_ttl: 60| Parameter | Efek performa | Cara menyetel |
|---|---|---|
num_workers | Seberapa banyak request diproses paralel | Mulai dari core CPU; naikkan sampai ada gain nyata |
max_jobs | Frekuensi restart worker (memori segar vs overhead restart) | Lebih kecil untuk aplikasi rawan leak; besar untuk stabil |
max_worker_memory | Kapan worker direstart karena bocor | Pantau baseline memory di metric |
allocate_workers_by_tasks | Spawn bertahap (hemat idle) atau penuh (siap beban) | true untuk traffic naik-turun; false bila butuh respons instan |
idle_ttl | Recycle worker idle | Kecil di dev (hemat resource), besar di prod |
Aturan penting: num_workers tidak otomatis = jumlah core. Worker PHP sering blocking (menunggu DB/API) — lebih banyak worker menyerap latensi tersebut, tapi menambah memory. Di sinilah benchmark berperan.
Worker persistent memberi kesempatan emas: bootstrap sekali, bytecode hangat. Aktifkan opcache di CLI worker:
opcache.enable_cli=1
opcache.memory_consumption=256
opcache.validate_timestamps=0
opcache.jit=tracing
opcache.jit_buffer_size=64M| Pengaturan | Efek |
|---|---|
opcache.enable_cli=1 | Bytecode PHP di-cache di worker CLI — wajib |
opcache.validate_timestamps=0 | Jangan cek ulang mtime tiap request (dev: 1) |
opcache.jit=tracing | Aktifkan JIT tracing (PHP 8+) untuk loop/CPU-bound |
opcache.jit_buffer_size=64M | Buffer JIT cukup besar |
Tip
Karena worker persistent, manfaat opcache/JIT terasa jauh lebih besar daripada di FPM: file di-compile sekali di awal worker, lalu semua request memakai bytecode hangat. JIT memberi gain signifikan pada kode CPU-bound (encryption, serialization).
Jangan benchmark dengan tebakan. Pakai tool beban HTTP seperti hey atau wrk:
./rr serve -c .rr.yaml &
hey -n 30000 -c 100 http://localhost:8080/api/healthLangkah metodis:
num_workers (4 → 8 → 16 → 32), catat hasil.worker_restarts_total dan memory (episode 12).Contoh template tabel hasil:
| num_workers | RPS | p95 (ms) | Memory total (MB) |
|---|---|---|---|
| 4 | 1200 | 45 | 210 |
| 8 | 2100 | 28 | 410 |
| 16 | 2400 | 24 | 820 |
| 32 | 2400 | 24 | 1600 |
Pada contoh ini, 16 adalah titik optimal — 32 hanya membuang memory.
Dengan data benchmark yang sama (satu worker, aplikasi sama, tanpa cache eksternal), pola umumnya:
| Aspek | PHP-FPM | RoadRunner | FrankenPHP |
|---|---|---|---|
| Model | Per-request bootstrap | Worker pool persistent | Thread persistent |
| Bootstrap sekali | Tidak | Ya | Ya |
| RPS (dengan framework berat) | Baseline (1x) | 2-4x lebih tinggi | 2-4x lebih tinggi |
| Memory footprint | Kecil per request | Sebanding jumlah worker | Sebanding jumlah thread |
| Fleksibilitas plugin | Tidak | Sangat luas | Terbatas |
Pola yang paling terasa: framework berat (Laravel/Symfony) mendapat gain terbesar karena bootstrap dihilangkan. Untuk skrip ringan tanpa framework, selisihnya kecil.
Warning
Angka benchmark antar-app server hanya bermakna bila metodologinya sama. Banyak artikel membandingkan "hello world" tanpa framework — hasilnya menyesatkan untuk keputusan production. Benchmark dengan aplikasi kalian sendiri, di mesin yang sama.
| Gejala | Kemungkinan penyebab | Aksi |
|---|---|---|
| RPS tidak naik walau worker ditambah | CPU/core habis, atau DB menjadi bottleneck | Profil CPU; cek query DB |
| Latensi p95 tinggi tapi p50 rendah | Beberapa worker terganggu (GC/restart) | Perbesar max_jobs, cek restart |
| Memory pool membengkak | Worker bocor | Kecilkan max_worker_memory, audit kode |
| Satu worker melayani lambat semua | Shared resource (DB connection pool) | Ukur di sisi DB |
Pada episode 20 ini, kalian telah belajar tuning performa.
Inti yang harus dibawa pulang:
opcache.enable_cli=1 + JIT: wajib untuk worker persistent.hey/wrk, cari plateau.Di episode 21 selanjutnya, kita belajar Docker & Kubernetes deployment — membangun image multi-stage PHP + binary RoadRunner, menyusun compose dan healthcheck, lalu deploy ke Kubernetes dengan HPA dan graceful shutdown SIGTERM. Sampai jumpa di episode 21!