Meningkatkan kecepatan aplikasi PHP: mengaktifkan dan mengukur Opcache, caching data dengan Redis/Memcached, HTTP cache berbasis ETag dan Cache-Control, lalu profiling dengan Xdebug/Blackfire, menumpas query N+1, dan memahami connection pooling untuk skala produksi.

Aplikasi yang benar (episode 17) tapi lambat tidak akan dipakai orang. Episode 18 menjawab pertanyaan kedua paling penting setelah keamanan: bagaimana membuat aplikasi PHP cepat. Di sini kita membahas caching di empat lapis — opcode, data, HTTP, dan query — plus cara mengukur sebelum mengoptimalkan.
Mengapa penting? Performa bukan hiasan: setiap 100ms latensi tambahan menurunkan konversi dan retensi pengguna, dan biaya server bisa berkali lipat lebih mahal jika query tidak efisien. Namun prinsip pertama optimasi adalah ukur dulu — optimasi tanpa data adalah tebakan. Kita mulai dari hal yang paling berdampak dengan biaya terendah.
Dari episode 2: setiap request, Zend Engine meng-compile kode ke opcode. Opcache menyimpan hasil compile ini di memori:
opcache.enable=1
opcache.memory_consumption=256
opcache.max_accelerated_files=20000
opcache.validate_timestamps=1
opcache.revalidate_freq=2Verifikasi aktif:
php -i | rg "opcache.enable"Opcache bisa memangkas waktu eksekusi PHP hingga 30-50% untuk aplikasi yang banyak file. Ini tuning pertama yang harus dilakukan di server produksi. Pastikan validate_timestamps dan revalidate_freq diatur agar perubahan kode saat deploy terdeteksi (episode 19).
Opcache mempercepat eksekusi kode, tapi query database yang mahal tetap mahal. Redis/Memcached menyimpan hasil di memori eksternal:
<?php
declare(strict_types=1);
use Predis\Client;
$redis = new Client(["host" => "127.0.0.1", "port" => 6379]);
function ambilProfil(Client $redis, int $id): array
{
$kunci = "profil:$id";
$dariCache = $redis->get($kunci);
if ($dariCache !== null) {
return json_decode($dariCache, true);
}
// mahal: query database
$profil = queryProfilDariDatabase($id);
// simpan 5 menit (300 detik)
$redis->setex($kunci, 300, json_encode($profil));
return $profil;
}Pola cache-aside: cek cache → miss → query → simpan cache dengan TTL. Di Laravel, pola ini satu baris:
$profil = Cache::remember("profil:$id", 300, fn () => $profilDb);Pilih cache store:
| Store | Karakteristik | Kapan dipakai |
|---|---|---|
| File cache | Tanpa server tambahan | Development |
| Redis | In-memory, kaya struktur data, persistent opsional | Produksi standar |
| Memcached | In-memory, sederhana, cepat | Cache sederhana skala besar |
Beberapa response (gambar, CSS, halaman statis, bahkan JSON yang jarang berubah) bisa di-cache di browser/CDN. ETag = fingerprint konten; Cache-Control = aturan cache.
$body = json_encode($data);
$etag = '"' . md5($body) . '"';
if (($_SERVER["HTTP_IF_NONE_MATCH"] ?? "") === $etag) {
header("HTTP/1.1 304 Not Modified");
exit;
}
header("ETag: $etag");
header("Cache-Control: private, max-age=300");
echo $body;Di Laravel, Cache-Control bisa diset lewat middleware atau cache()->header() untuk response berubah. Konsepnya: server hemat bandwidth dan CPU karena konten yang tidak berubah tidak perlu dikirim ulang penuh.
Sebelum mengubah apa pun, profil. Dua alat andalan:
xdebug.mode=profile
xdebug.output_dir=/tmp/profilingJalankan satu request, lalu buka .cachegrind yang dihasilkan dengan Webgrind atau phpstorm. Kalian akan melihat fungsi mana yang menyedot waktu eksekusi terbanyak.
Blackfire memberi flamegraph lengkap: waktu, memori, dan I/O per fungsi, plus rekomendasi. Sangat berguna untuk tim, tapi Xdebug+Webgrind sudah cukup untuk belajar dan proyek kecil.
Aturan profiling: ukur sebelum → optimasi satu hal → ukur lagi. Jika tidak lebih cepat, jangan pertahankan "optimasi" itu.
N+1 problem adalah musuh paling umum performa aplikasi ORM. Ini contohnya:
$orders = Order::query()->take(20)->get();
foreach ($orders as $order) {
echo $order->user->nama; // ⛔ 1 query per order!
}Hasilnya 1 query + 20 query tambahan (satu per order). Solusi: eager loading:
$orders = Order::query()->with("user")->take(20)->get();
foreach ($orders as $order) {
echo $order->user->nama; // ✅ 2 query total
}Cara mendeteksi: SELECT yang jumlahnya naik mengikuti jumlah baris (perhatikan log query), atau pakai Laravel Debugbar / DB::listen(). Optimasi lain:
WHERE/JOIN.->get() untuk semua baris; pakai paginate().SELECT * bila hanya butuh 3 kolom.Warning
Cache yang salah lebih buruk dari tidak ada cache. Jangan meletakkan TTL terlalu lama untuk data yang sering berubah — pengguna akan melihat data basi. Dan jangan pernah meletakkan data per-user di cache global tanpa namespace (user:{$id}), karena bisa bocor antar pengguna.
Membuka koneksi database per request itu mahal. PHP-FPM memelihara koneksi antar request dalam proses yang sama:
ATTR_PERSISTENT pada PDO, koneksi dipertahankan antar request di worker yang sama.pgbouncer untuk PostgreSQL, ProxySQL untuk MySQL) — memusatkan dan membatasi koneksi, sangat membantu saat PHP-FPM berskala banyak worker.$pdo = new PDO($dsn, $user, $pass, [
PDO::ATTR_PERSISTENT => true,
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
]);Catatan: persistent connection + transaction bisa menyimpan state lintas request — pastikan kode menutup transaksi dengan benar (commit/rollBack) di akhir request.
revalidate_freq terlalu besar — deploy tidak terlihat sampai menit; set kecil atau opcache.validate_timestamps=0 + restart di deploy.with().Inti yang harus dibawa pulang:
Di episode 19 selanjutnya aplikasi kalian naik ke panggung dunia: Deployment & DevOps — setup produksi PHP-FPM + Nginx, tuning process manager, konfigurasi environment, lalu CI/CD dengan GitHub Actions, Docker, dan monitoring error dengan Sentry.