โ† Semua pembelajaran / Laravel Nol โ†’ Enterprise
Fase 8 ยท FrankenPHP & RoadRunner

RoadRunner di produksi

Halaman produksi RoadRunner berisi saran yang sangat konkret dan sebagian mengejutkan. Membacanya sebelum deploy pertama menghemat banyak jam debugging yang sulit.

Intisari

  • State dan memori tidak dibagi antar worker, tapi dibagi di dalam satu worker.
  • Tutup semua descriptor, terutama saat exception fatal.
  • gc_collect_cycles() menekan memori tapi memperlambat eksekusi secara signifikan โ€” pakai dengan hati-hati.
  • Koneksi keep-alive memberi peningkatan performa sekitar 40%.
  • Hindari image berbasis Alpine โ€” musl tidak sepenuhnya kompatibel dengan biner Go RoadRunner dan banyak ekstensi PHP.

State dan memori

Aturan dasarnya persis seperti yang kita bahas di materi jebakan state, dinyatakan ulang dengan tegas oleh dokumentasi RoadRunner: state tidak dibagi antar worker, tetapi dibagi di dalam satu worker. Dua konsekuensi praktisnya:

KonsekuensiArtinya
Tidak dibagi antar workerJangan andalkan cache dalam memori untuk konsistensi โ€” worker lain tidak melihatnya
Dibagi di dalam satu workerData permintaan sebelumnya bisa bocor ke permintaan berikutnya

Saran konkret dari dokumentasinya:

  1. Tutup semua descriptor โ€” terutama pada jalur exception fatal.
  2. Waspadai kebocoran memori; bersikap selektif terhadap komponen yang dipakai. Worker akan di-restart saat bocor, tapi lebih baik dirancang supaya tidak bocor sama sekali.
  3. Hindari polusi state โ€” jangan menyimpan data global atau data pengguna di memori.
  4. Koneksi database dan setiap pipa atau soket adalah titik kegagalan potensial.

Soal menutup koneksi database setiap iterasi: dokumentasinya menyebut ini sebagai cara mudah menangani koneksi yang bermasalah, sekaligus mengakui bahwa itu bukan solusi yang paling berperforma. Menutup dan membuka ulang koneksi setiap permintaan menghapus salah satu keuntungan terbesar worker mode. Pilihan yang lebih baik: biarkan koneksi tetap hidup, dan tangani koneksi yang putus dengan mekanisme reconnect yang sudah disediakan Laravel.

gc_collect_cycles()

// Di dalam loop worker, setelah setiap permintaan
gc_collect_cycles();

Dokumentasi RoadRunner memberi peringatan tegas di sini: memanggil gc_collect_cycles() setiap eksekusi memang menekan pemakaian memori, tapi memperlambat eksekusi secara signifikan. Jangan memasangnya sebagai kebiasaan. Pasang hanya kalau kamu benar-benar melihat memori menumpuk karena referensi melingkar โ€” dan ukur dampaknya terhadap throughput setelah memasangnya.

Menentukan jumlah worker

Sifat bebanPanduan resmi
UmumJumlah worker = jumlah utas CPU
Terikat I/OSebanyak yang muat di memori; worker itu murah
Terikat CPUPantau beban rata-rata, targetkan 90โ€“95%, sisakan untuk GC Go
Latensi hampir tetapHitung dari target throughput dan latensi per permintaan
worker โ‰ˆ throughput target (req/detik) ร— latensi rata-rata (detik)

Contoh: 200 req/detik ร— 0,05 detik = 10 worker (ditambah margin)

Jaringan dan koneksi

  1. Hubungkan worker lewat pipa; soket Unix sedikit lebih lambat.
  2. Jangan mendengarkan RPC di 0.0.0.0 kecuali di dalam Docker yang memang butuh.
  3. Keep-alive memberi peningkatan performa sekitar 40% โ€” pastikan lapisan di depan memakainya.
  4. Pakai endpoint health check saat berjalan di lingkungan cloud.

Angka 40% itu layak diperiksa di sisi load balancer-mu. Kalau ALB atau Nginx menutup koneksi setelah setiap permintaan, kamu membayar biaya pembukaan koneksi TCP untuk setiap permintaan โ€” dan kehilangan sebagian besar keuntungan yang tadi susah payah kamu kejar lewat worker mode.

Memori dan OPcache

; php.ini
memory_limit = 220M            ; 10โ€“20% di bawah max_worker_memory (256M)
opcache.enable = 1
opcache.enable_cli = 1         ; WAJIB โ€” worker adalah proses CLI
opcache.validate_timestamps = 0

Kenapa memory_limit harus di bawah max_worker_memory. Kalau PHP mencapai batasnya lebih dulu, kamu mendapat error fatal PHP di tengah permintaan pengguna. Kalau supervisor RoadRunner yang mencapainya lebih dulu, worker diganti dengan rapi setelah permintaan selesai. Urutan itu yang kamu inginkan.

Peringatan Alpine

Dokumentasi RoadRunner menyatakannya secara eksplisit: hindari image berbasis Alpine. Alpine memakai musl libc, yang tidak sepenuhnya kompatibel dengan biner Go RoadRunner dan banyak ekstensi PHP โ€” dan hasilnya bisa berupa ketidakstabilan runtime serta masalah produksi yang sulit didiagnosis. Pakai Debian, Ubuntu, atau image PHP resmi non-Alpine.

Perhatikan bahwa FrankenPHP memberi saran yang sama dengan alasan berbeda. Kalau kamu terbiasa memilih Alpine demi ukuran image, kedua runtime ini sama-sama meminta kamu berhenti melakukannya.

Menjalankan worker sebagai user lain

server:
  command: "php ./vendor/bin/roadrunner-worker"
  user: "www-data"       # RoadRunner harus dijalankan sebagai root untuk ini

Berguna di server tradisional. Di kontainer, pola yang lebih lazim adalah menjalankan seluruh kontainer sebagai pengguna tanpa hak istimewa sejak awal โ€” dan itulah yang kita lakukan di Dockerfile Fase 9.

Daftar periksa

  1. Image berbasis Debian atau Ubuntu, bukan Alpine.
  2. opcache.enable_cli=1 aktif.
  3. memory_limit di bawah max_worker_memory.
  4. exec_ttl disetel supaya permintaan menggantung tidak menahan worker.
  5. RPC tidak terekspos ke luar.
  6. Health check terpasang, keep-alive aktif di lapisan depan.
  7. Log dalam format JSON ke stdout.
  8. Jumlah worker dihitung dari memori kontainer, bukan dari jumlah inti host.

Latihan: jalankan uji beban dengan keep-alive aktif lalu dimatikan (ab -k versus ab), dan bandingkan throughput-nya. Angka yang kamu dapat adalah versi nyata dari klaim 40% di dokumentasi โ€” untuk aplikasimu sendiri.

Rangkuman ini sengaja dipangkas ke bagian yang dipakai di roadmap. Buka sumber aslinya saat kamu butuh detail lengkap atau referensi parameter.