FrankenPHP di produksi
FrankenPHP dengan setelan bawaan sudah baik. Perbedaan antara "baik" dan "cepat" ada pada beberapa setelan yang seluruhnya bisa kamu pasang dalam satu sore.
Intisari
- Hindari musl (Alpine) di produksi. PHP lebih lambat di musl, terutama dalam mode thread-safe.
- Setel
num_threadsberdasarkan memori:num_threads ร memory_limit < memori tersedia. - Di kontainer, setel
GOMEMLIMITsesuai batas memori kontainer. - Kurangi operasi filesystem:
try_fileseksplisit,rooteksplisit, matikanresolve_root_symlink. - Semua penyetelan PHP biasa tetap berlaku โ OPcache, autoloader teroptimasi, realpath cache.
Pilihan basis image
| Basis | libc | Untuk produksi |
|---|---|---|
dunglas/frankenphp (Debian) | glibc | Ya โ ini yang disarankan |
dunglas/frankenphp:alpine | musl | Hindari |
| Kompilasi sendiri | glibc, level optimasi pilihanmu | Ya, kalau butuh kendali penuh |
Ini bertentangan dengan kebiasaan umum, jadi perlu ditegaskan. Dokumentasi FrankenPHP menyatakan PHP lebih lambat saat ditautkan ke musl dibanding glibc โ terutama ketika dikompilasi dalam mode thread-safe (ZTS), yang justru merupakan syarat FrankenPHP. Selain itu ada sejumlah bug yang hanya muncul di musl. Kalau kamu memilih Alpine demi ukuran image, kamu membayarnya dengan performa di lingkungan yang padat utas. Untuk image yang lebih ramping atau lebih aman, pakai varian Debian yang diperkeras.
Utas dan worker
{
frankenphp {
num_threads 16
max_threads 48
worker {
file /app/public/worker.php
num 12
}
}
}
- Mulai dari
num_threads= 2โ4ร jumlah inti kalau bebannya banyak menunggu I/O. - Pastikan
num_threads ร memory_limitlebih kecil daripada memori yang tersedia. - Pakai
max_threadssupaya lonjakan tidak langsung berubah jadi antrean panjang. - Jangan menebak โ jalankan uji beban yang menyerupai lalu lintas nyata.
Runtime Go
GODEBUG=cgocheck=0 # sudah default di image resmi
GOMEMLIMIT=1750MiB # untuk kontainer berjatah 2 GiB
GOMEMLIMIT wajib disetel di kontainer. Tanpa itu, garbage collector Go tidak tahu ada
batas memori dan bisa menunda pembersihan sampai melewati jatah kontainer โ yang berakhir dengan proses dibunuh
kernel. Setel sekitar 85โ90% dari batas memori kontainer, sisakan ruang untuk PHP sendiri.
Mengurangi operasi filesystem
# Sebelum: php_server mencoba beberapa kemungkinan berkas tiap permintaan
php_server
# Sesudah: satu kemungkinan saja, dan root dinyatakan eksplisit
php_server {
try_files {path} index.php
root /app/public
resolve_root_symlink false
}
Menyebutkan root secara eksplisit memungkinkan nilainya di-cache. Sebaliknya, memakai
placeholder Caddy di dalam root atau env mematikan caching itu dan membawa
biaya yang nyata โ hindari di jalur yang sering dilalui.
# Paling hemat: pisahkan aset dari PHP berdasarkan path,
# supaya tidak ada pemeriksaan filesystem sama sekali untuk permintaan aplikasi
route {
@aset path /build/* /images/*
file_server @aset {
root /app/public
}
rewrite index.php
php {
root /app/public
}
}
Penyetelan PHP yang tetap berlaku
opcache.enable=1
opcache.enable_cli=1
opcache.memory_consumption=256
opcache.max_accelerated_files=20000
opcache.validate_timestamps=0
realpath_cache_size=4096K
realpath_cache_ttl=600
memory_limit=256M
composer install --no-dev --optimize-autoloader
php artisan optimize
Kolam utas untuk endpoint lambat
portal.test {
php_server {
root /app/public
worker index.php {
match /ekspor/*
num 1
max_threads 8
}
worker index.php {
match *
num 4
max_threads 32
}
}
}
Dokumentasinya sendiri menambahkan catatan yang layak diulang: endpoint yang sangat lambat sebaiknya dijadikan asinkron lewat antrean, bukan sekadar dikurung di kolam terpisah. Pemisahan kolam adalah pertahanan, bukan perbaikan.
Observability
{
servers {
metrics
}
}
| Yang perlu dipantau | Kenapa |
|---|---|
| Utas sibuk vs total | Kalau selalu mentok, kapasitasnya kurang |
| Restart worker | Sering restart = ada crash atau kebocoran |
| Memori proses | Naik terus = kebocoran |
| Durasi permintaan (p95, p99) | Ekor yang panjang menandakan utas kehabisan |
| Kode status 5xx | Worker yang mati muncul di sini |
Daftar periksa produksi
- Image berbasis Debian (glibc), bukan Alpine.
num_threadsdanmax_threadsditentukan dari uji beban, bukan default.GOMEMLIMITdisetel sesuai batas kontainer.- OPcache aktif dengan
validate_timestamps=0. try_filesdanrooteksplisit; aset statis dipisahkan.- Log JSON ke
stdout, level yang tepat โ bukandebug. - API admin (port 2019) tidak terjangkau dari luar.
- Health check yang tidak menyentuh database.
Latihan: jalankan uji beban dengan k6 atau ab pada konfigurasi bawaan, catat throughput
dan p95. Lalu setel num_threads, GOMEMLIMIT, dan pemisahan aset statis, jalankan
ulang, dan bandingkan. Perhatikan juga pemakaian memori โ throughput yang naik tapi memori mendekati batas
kontainer bukan kemenangan.
Rangkuman ini sengaja dipangkas ke bagian yang dipakai di roadmap. Buka sumber aslinya saat kamu butuh detail lengkap atau referensi parameter.