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

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.

Sumber asli frankenphp.dev Resmi Rangkuman ~7 menit baca

Intisari

  • Hindari musl (Alpine) di produksi. PHP lebih lambat di musl, terutama dalam mode thread-safe.
  • Setel num_threads berdasarkan memori: num_threads ร— memory_limit < memori tersedia.
  • Di kontainer, setel GOMEMLIMIT sesuai batas memori kontainer.
  • Kurangi operasi filesystem: try_files eksplisit, root eksplisit, matikan resolve_root_symlink.
  • Semua penyetelan PHP biasa tetap berlaku โ€” OPcache, autoloader teroptimasi, realpath cache.

Pilihan basis image

BasislibcUntuk produksi
dunglas/frankenphp (Debian)glibcYa โ€” ini yang disarankan
dunglas/frankenphp:alpinemuslHindari
Kompilasi sendiriglibc, level optimasi pilihanmuYa, 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
		}
	}
}
  1. Mulai dari num_threads = 2โ€“4ร— jumlah inti kalau bebannya banyak menunggu I/O.
  2. Pastikan num_threads ร— memory_limit lebih kecil daripada memori yang tersedia.
  3. Pakai max_threads supaya lonjakan tidak langsung berubah jadi antrean panjang.
  4. 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 dipantauKenapa
Utas sibuk vs totalKalau selalu mentok, kapasitasnya kurang
Restart workerSering restart = ada crash atau kebocoran
Memori prosesNaik terus = kebocoran
Durasi permintaan (p95, p99)Ekor yang panjang menandakan utas kehabisan
Kode status 5xxWorker yang mati muncul di sini

Daftar periksa produksi

  1. Image berbasis Debian (glibc), bukan Alpine.
  2. num_threads dan max_threads ditentukan dari uji beban, bukan default.
  3. GOMEMLIMIT disetel sesuai batas kontainer.
  4. OPcache aktif dengan validate_timestamps=0.
  5. try_files dan root eksplisit; aset statis dipisahkan.
  6. Log JSON ke stdout, level yang tepat โ€” bukan debug.
  7. API admin (port 2019) tidak terjangkau dari luar.
  8. 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.