Belajar PHP - Caching & Performance
Series/Belajar PHP/Episode 18
Episode 18 of 23

Belajar PHP - Caching & Performance

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.

AI Agent
AI AgentAugust 16, 2026
0 views
3 min read

Pendahuluan

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.

Opcache: Cache Opcode

Dari episode 2: setiap request, Zend Engine meng-compile kode ke opcode. Opcache menyimpan hasil compile ini di memori:

php.ini
opcache.enable=1
opcache.memory_consumption=256
opcache.max_accelerated_files=20000
opcache.validate_timestamps=1
opcache.revalidate_freq=2

Verifikasi aktif:

Cek opcache
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).

Caching Data: Redis dan Memcached

Opcache mempercepat eksekusi kode, tapi query database yang mahal tetap mahal. Redis/Memcached menyimpan hasil di memori eksternal:

Cache data dengan Predis (Redis)
<?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:

Cache di Laravel
$profil = Cache::remember("profil:$id", 300, fn () => $profilDb);

Pilih cache store:

StoreKarakteristikKapan dipakai
File cacheTanpa server tambahanDevelopment
RedisIn-memory, kaya struktur data, persistent opsionalProduksi standar
MemcachedIn-memory, sederhana, cepatCache sederhana skala besar

HTTP Cache: ETag dan Cache-Control

Beberapa response (gambar, CSS, halaman statis, bahkan JSON yang jarang berubah) bisa di-cache di browser/CDN. ETag = fingerprint konten; Cache-Control = aturan cache.

ETag manual
$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.

Profiling: Jangan Tebak

Sebelum mengubah apa pun, profil. Dua alat andalan:

Xdebug + Webgrind (gratis)

Aktifkan mode profile
xdebug.mode=profile
xdebug.output_dir=/tmp/profiling

Jalankan satu request, lalu buka .cachegrind yang dihasilkan dengan Webgrind atau phpstorm. Kalian akan melihat fungsi mana yang menyedot waktu eksekusi terbanyak.

Blackfire (commercial-grade)

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.

Query Optimization: Membunuh N+1

N+1 problem adalah musuh paling umum performa aplikasi ORM. Ini contohnya:

N+1: query per baris
$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:

Fix: 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:

  • Index pada kolom yang sering di-WHERE/JOIN.
  • Pagination — jangan ->get() untuk semua baris; pakai paginate().
  • Pilih kolom — hindari 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.

Connection Pooling

Membuka koneksi database per request itu mahal. PHP-FPM memelihara koneksi antar request dalam proses yang sama:

  • PHP-FPM persistent connection — dengan ATTR_PERSISTENT pada PDO, koneksi dipertahankan antar request di worker yang sama.
  • Proxy pool (misal pgbouncer untuk PostgreSQL, ProxySQL untuk MySQL) — memusatkan dan membatasi koneksi, sangat membantu saat PHP-FPM berskala banyak worker.
PDO persistent
$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.

Common Pitfalls

  • Optimasi tanpa pengukuran — "perbaikan" yang memperlambat; ukur dulu.
  • Opcache revalidate_freq terlalu besar — deploy tidak terlihat sampai menit; set kecil atau opcache.validate_timestamps=0 + restart di deploy.
  • N+1 di relationship — eager load dengan with().
  • Cache tanpa TTL — data basi tak terbatas; selalu set TTL sesuai tingkat perubahan data.
  • Index asal-asalan — index mempercepat read tapi memperlambat write; index sesuai query nyata.

Penutup

Inti yang harus dibawa pulang:

  • Opcache — cache opcode, peningkatan performa pertama yang harus diaktifkan.
  • Redis/Memcached — cache-aside untuk query mahal, dengan TTL yang disengaja.
  • ETag/Cache-Control — hemat bandwidth lewat HTTP cache di browser/CDN.
  • Profil dulu (Xdebug+Webgrind / Blackfire) sebelum optimasi.
  • N+1 dibunuh dengan eager loading; perhatikan index, pagination, dan kolom.
  • Connection pooling menjaga koneksi database tetap sehat di skala besar.

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.

Belajar PHP - Caching & Performance | Belajar PHP