FrankenPHP worker mode
Octane menyembunyikan detail ini darimu, tapi memahaminya membuat perilaku aneh di produksi jadi masuk akal — terutama soal apa yang direset dan apa yang tidak.
Intisari
- Skrip worker mem-bootstrap aplikasi sekali, lalu memanggil
frankenphp_handle_request()berulang. - Superglobal
$_GET,$_POST,$_SERVERdireset tiap permintaan — tapi$_ENVtidak. - Variabel statis, properti statis kelas, dan variabel global bertahan. Itulah sumber kecepatannya.
- Worker yang keluar dengan kode ≠ 0 di-restart dengan jeda menaik; terlalu sering gagal = server berhenti.
- Worker bisa dimuat ulang lewat API admin Caddy tanpa menjatuhkan lalu lintas.
Bentuk sebuah skrip worker
<?php
// public/worker.php — inilah yang dilakukan Octane untukmu
require __DIR__.'/../vendor/autoload.php';
$app = require __DIR__.'/../bootstrap/app.php';
$app->make(Kernel::class)->bootstrap(); // ← SEKALI SAJA
$handler = static function () use ($app) {
try {
// Dipanggil saat permintaan masuk. Superglobal sudah direset di sini.
$request = Request::capture();
$response = $app->handle($request);
$response->send();
$app->terminate($request, $response);
} catch (Throwable $e) {
// set_exception_handler hanya dipanggil saat skrip worker BERAKHIR,
// jadi exception wajib ditangkap di sini.
report($e);
http_response_code(500);
}
};
$maks = (int) ($_SERVER['MAX_REQUESTS'] ?? 0);
for ($n = 0; ! $maks || $n < $maks; ++$n) {
$lanjut = frankenphp_handle_request($handler);
gc_collect_cycles(); // kurangi peluang GC berjalan di tengah render halaman
if (! $lanjut) {
break;
}
}
Perhatikan blok try/catch di dalam handler. Di PHP biasa,
set_exception_handler() menangkap exception yang lolos. Di worker mode, handler itu baru terpanggil
saat seluruh skrip worker berakhir — bukan saat sebuah permintaan gagal. Exception yang tidak
ditangkap di dalam handler akan mematikan worker, bukan sekadar menggagalkan satu permintaan.
Perilaku superglobal
| Superglobal | Direset tiap permintaan? |
|---|---|
$_GET, $_POST, $_COOKIE | Ya |
$_FILES, $_REQUEST | Ya |
$_SERVER | Ya |
$_ENV | Tidak |
$_ENV tidak direset — dan itu punya konsekuensi keamanan. Apa pun yang kamu tulis ke
$_ENV selama sebuah permintaan akan tetap terlihat oleh permintaan berikutnya di utas yang sama.
Jangan pernah menaruh data khusus permintaan atau data sensitif di sana. Untuk konfigurasi, tetap lewat
config().
Sebelum panggilan pertama frankenphp_handle_request(), superglobal berisi nilai milik skrip worker
itu sendiri. Kalau kamu butuh nilai tersebut di dalam handler, salin dulu ke variabel dan bawa lewat
use — setelah loop dimulai, nilainya sudah tergantikan oleh nilai permintaan.
State apa yang bertahan
function penghitung(): int
{
static $n = 0;
return ++$n; // 1, 2, 3, … untuk setiap permintaan di utas ini
}
| Bertahan antar permintaan | Contoh |
|---|---|
| Variabel statis di fungsi/method | static $n = 0; |
| Properti statis kelas | Metrik::$peristiwa |
| Variabel global skrip worker | $app |
| Apa pun di memori di luar handler | Array cache, koneksi |
Ini bukan kebetulan atau cacat — inilah yang membuat worker mode cepat. Framework seperti Laravel Octane sudah mereset state milik framework; yang tersisa adalah state milik kodemu sendiri, dan itulah isi materi jebakan state sebelumnya.
Jumlah worker dan utas
{
frankenphp {
num_threads 16
worker {
file /app/public/worker.php
num 8
}
}
}
Secara default FrankenPHP menyalakan utas dan worker sebanyak dua kali jumlah inti CPU. Dokumentasinya secara eksplisit menyarankan mengubah nilai ini, dengan satu aturan yang layak dihafal:
num_threads × memory_limit < memori yang tersedia
Aturan itu menyelamatkanmu dari OOM kill. Dengan memory_limit=256M dan 32 utas, kamu
memesan sampai 8 GB — di kontainer berjatah 2 GB, itu berarti proses dibunuh saat lalu lintas naik, bukan
saat kamu mengujinya. Setel num_threads berdasarkan memori kontainer, bukan berdasarkan jumlah
inti mesin host.
max_threads untuk lonjakan
{
frankenphp {
num_threads 8 # selalu ada
max_threads 24 # boleh naik sampai sini saat dibutuhkan
}
}
Nilai auto akan memperkirakan batas berdasarkan memory_limit, tapi dokumentasinya
memperingatkan bahwa perkiraan itu bisa jauh terlalu rendah. Untuk beban produksi, tentukan angkanya sendiri
berdasarkan uji beban.
Kegagalan dan pemuatan ulang
# Muat ulang seluruh worker dengan rapi, lewat API admin Caddy
curl -X POST http://localhost:2019/frankenphp/workers/restart
| Kejadian | Perilaku FrankenPHP |
|---|---|
| Worker keluar dengan kode 0 | Dianggap normal, dijalankan ulang |
| Worker keluar dengan kode ≠ 0 | Restart dengan jeda menaik |
| Gagal terus dalam waktu singkat | Server berhenti: too many consecutive failures |
Berkas berubah (dengan --watch) | Restart otomatis — hanya untuk pengembangan |
Kegagalan beruntun yang menjatuhkan server sebenarnya perilaku yang baik. Sebuah typo pada kode bootstrap akan membuat worker mati seketika, berulang kali. Alih-alih berputar tanpa henti sambil menolak semua permintaan, FrankenPHP berhenti — sehingga orkestrator (ECS) melihat kontainernya tidak sehat dan menggagalkan deploy, bukan menerima lalu lintas dengan aplikasi yang rusak.
Latihan: tulis fungsi penghitung() di atas, panggil dari sebuah rute, jalankan Octane
dengan --workers=1, lalu muat halaman itu sepuluh kali dan amati angkanya naik. Restart lewat API
admin, dan lihat ia kembali ke satu.
Rangkuman ini sengaja dipangkas ke bagian yang dipakai di roadmap. Buka sumber aslinya saat kamu butuh detail lengkap atau referensi parameter.