Konfigurasi OPcache PHP di Production
PHP Performance DevOps #php #opcache #performance #php-fpm

Konfigurasi OPcache PHP di Production

A
Abd. Asis
6 min read
Bagikan:

Ada deployment yang berjalan sempurna, semua file sudah terupload, cache CDN sudah di-purge — tapi perilaku aplikasi tidak berubah. Request masih merespons seperti kode lama. Tidak ada error di log, tidak ada peringatan. Hanya diam.

Ini bukan bug di kode aplikasi. Ini adalah OPcache PHP yang bekerja persis seperti yang dikonfigurasi: menyimpan bytecode lama di memori dan tidak pernah memeriksa apakah ada file yang berubah. Konfigurasi yang optimal untuk performa di production bisa berubah menjadi perangkap saat proses deploy tidak mempertimbangkan perilaku cache ini.

Artikel ini membahas direktif-direktif OPcache yang paling berdampak, pola konfigurasi yang tepat untuk production, dan cara menghindari jebakan yang sering tidak terasa sampai masalah sudah terjadi.

Cara Kerja OPcache

PHP secara default melakukan proses yang sama untuk setiap request: membaca file .php dari disk, melakukan tokenisasi, parsing, dan kompilasi menjadi bytecode (opcode), lalu mengeksekusi bytecode tersebut. OPcache memotong proses ini dengan menyimpan hasil kompilasi ke shared memory sehingga request berikutnya langsung mengeksekusi bytecode tanpa harus melewati tahap parsing.

Keuntungannya signifikan — terutama untuk framework besar seperti Laravel atau Symfony yang memuat ratusan file PHP di setiap bootstrap. Tapi efisiensi ini datang dengan konsekuensi: PHP tidak lagi membaca file asli di disk setiap request. Jika konfigurasi tidak tepat, perubahan kode bisa diabaikan secara senyap.

Direktif Paling Kritis

validate_timestamps dan revalidate_freq

opcache.validate_timestamps menentukan apakah OPcache memeriksa apakah file PHP di disk sudah berubah. Nilai default-nya 1 (aktif), artinya OPcache akan memeriksa timestamp file secara berkala.

Seberapa berkala? Itulah fungsi opcache.revalidate_freq (dalam detik). Default-nya 2 detik, artinya OPcache memeriksa setiap 2 detik. Untuk production, nilai 60 atau lebih adalah umum karena kode tidak berubah sesering itu.

; production — periksa setiap 60 detik
opcache.validate_timestamps=1
opcache.revalidate_freq=60
; production agresif — tidak pernah periksa, performa maksimal
opcache.validate_timestamps=0

Ketika validate_timestamps=0, revalidate_freq sepenuhnya diabaikan. OPcache tidak akan pernah memeriksa perubahan file sampai ada yang secara eksplisit memanggil opcache_reset() atau PHP-FPM di-reload. Ini adalah konfigurasi performa tertinggi, tapi membutuhkan proses deploy yang sadar akan cache.

Jika menggunakan validate_timestamps=0, setiap deployment wajib diikuti dengan cache invalidation eksplisit. Tanpa itu, kode baru yang sudah ter-deploy tidak akan aktif sampai PHP-FPM di-restart.

max_accelerated_files

Direktif ini menentukan jumlah maksimum skrip PHP yang bisa masuk ke hash table OPcache. Default-nya 10000, tapi angka ini sering tidak cukup untuk project dengan dependensi banyak.

# Hitung jumlah file PHP di project
find /var/www/app -name "*.php" | wc -l

OPcache menggunakan bilangan prima terdekat yang lebih besar dari nilai yang dikonfigurasi. Misalnya nilai 20000 akan dibulatkan menjadi 32531. Jika nilai yang dikonfigurasi melebihi 1000000, OPcache secara diam-diam kembali ke default 10000 — perilaku yang bisa mengejutkan.

; untuk Laravel/Symfony dengan dependensi penuh
opcache.max_accelerated_files=32531

Ketika hash table penuh, skrip baru tidak di-cache. Aplikasi tetap berjalan tapi tanpa manfaat caching untuk skrip-skrip yang tidak masuk antrean. Tidak ada error, tidak ada peringatan.

memory_consumption dan interned_strings_buffer

opcache.memory_consumption menentukan total ukuran shared memory untuk OPcache dalam MB. Yang perlu dipahami: opcache.interned_strings_buffer (buffer untuk string yang di-deduplikasi) diambil dari dalam nilai memory_consumption, bukan tambahan di atasnya.

; dengan konfigurasi ini, hanya 96 MB tersisa untuk bytecode
opcache.memory_consumption=128
opcache.interned_strings_buffer=32

Untuk production dengan framework modern, nilai yang lebih realistis:

opcache.memory_consumption=256
opcache.interned_strings_buffer=16

Pantau memory_usage.used_memory di opcache_get_status() untuk melihat seberapa banyak yang sudah terpakai. Jika cache_full bernilai true, OPcache kehabisan memori.

Konfigurasi Production yang Direkomendasikan

Berikut konfigurasi yang umum digunakan untuk production dengan PHP 8.x dan framework modern:

; /etc/php/8.3/fpm/conf.d/10-opcache.ini

opcache.enable=1
opcache.memory_consumption=256
opcache.interned_strings_buffer=16
opcache.max_accelerated_files=32531
opcache.validate_timestamps=0
opcache.save_comments=1

; PHP 8+ — aktifkan JIT untuk workload CPU-intensive
; opcache.jit=tracing
; opcache.jit_buffer_size=128M

opcache.save_comments=1 penting untuk framework yang menggunakan docblock sebagai metadata — Doctrine ORM, PHPUnit, dan beberapa komponen Symfony bergantung pada parsing docblock. Mematikannya akan menyebabkan error yang tidak jelas.

JIT (opcache.jit) memberikan manfaat signifikan hanya untuk kode CPU-intensive. Untuk web application tipikal yang sebagian besar waktunya dihabiskan di query database atau I/O, perbedaan performa JIT hampir tidak terasa.

Di PHP 8.4, default opcache.jit berubah dari tracing menjadi disable. Jika sebelumnya hanya mengatur opcache.jit_buffer_size untuk mengaktifkan JIT, upgrade ke PHP 8.4 akan mematikan JIT secara diam-diam. Solusinya: tambahkan opcache.jit=tracing secara eksplisit.

Konfigurasi Development

Di environment development, prioritasnya berbeda: kita ingin melihat perubahan kode segera, tanpa harus me-reload PHP-FPM setiap kali menyimpan file.

; development — perubahan terlihat di setiap request
opcache.enable=1
opcache.validate_timestamps=1
opcache.revalidate_freq=0
opcache.jit=off

revalidate_freq=0 membuat OPcache memeriksa timestamp file di setiap request — persis seperti PHP tanpa OPcache dari sisi visibility perubahan, tapi dengan overhead yang lebih kecil karena revalidasi lebih cepat dari recompile.

Invalidasi Cache Saat Deploy

Ini bagian yang paling sering diabaikan. opcache_reset() berfungsi untuk membersihkan seluruh in-memory cache, tapi ada satu perilaku yang tidak intuitif:

opcache_reset() yang dijalankan dari CLI tidak berpengaruh pada cache PHP-FPM.

CLI PHP dan PHP-FPM berjalan di proses terpisah dengan shared memory yang berbeda. Memanggil php artisan opcache:clear atau skrip CLI apapun tidak akan membersihkan cache yang digunakan oleh web server.

Untuk benar-benar membersihkan OPcache di production, ada dua pendekatan yang andal:

Pendekatan 1 — Reload PHP-FPM:

systemctl reload php8.3-fpm

Ini adalah cara paling bersih. PHP-FPM akan graceful restart — menunggu request yang sedang berjalan selesai, lalu me-restart worker dengan konfigurasi baru dan cache kosong.

Pendekatan 2 — HTTP endpoint untuk reset:

Buat endpoint yang hanya bisa diakses dari internal network atau dengan token:

// public/opcache-reset.php — akses via curl dari deployment script
if ($_SERVER['REMOTE_ADDR'] !== '127.0.0.1') {
    http_response_code(403);
    exit;
}

opcache_reset();
echo json_encode(['status' => 'ok', 'timestamp' => time()]);
# Di deployment script
curl -s http://127.0.0.1/opcache-reset.php

opcache_invalidate($filepath, true) bisa digunakan untuk invalidasi file spesifik tanpa harus reset seluruh cache. Berguna untuk deployment incremental, tapi perlu diingat: ia hanya menandai file untuk recompile, tidak langsung membebaskan memori dari bytecode lama.

Memantau Kondisi OPcache

opcache_get_status() memberikan gambaran lengkap kondisi cache saat ini:

$status = opcache_get_status(false); // false = tidak sertakan per-script info

echo "Cache penuh: " . ($status['cache_full'] ? 'ya' : 'tidak') . "\n";
echo "Hit rate: " . round($status['opcache_statistics']['opcache_hit_rate'], 2) . "%\n";
echo "OOM restarts: " . $status['opcache_statistics']['oom_restarts'] . "\n";
echo "Hash restarts: " . $status['opcache_statistics']['hash_restarts'] . "\n";

$mem = $status['memory_usage'];
$used_mb = round($mem['used_memory'] / 1024 / 1024, 1);
$free_mb = round($mem['free_memory'] / 1024 / 1024, 1);
echo "Memori: {$used_mb} MB dipakai, {$free_mb} MB bebas\n";

Sinyal yang perlu diperhatikan:

IndikatorKondisi NormalTindakan Jika Abnormal
cache_fullfalseTambah memory_consumption atau max_accelerated_files
opcache_hit_ratemendekati 100%Cek apakah ada skrip yang sering di-invalidasi
oom_restarts0Tingkatkan memory_consumption
hash_restarts0Tingkatkan max_accelerated_files
wasted_memoryrendahNormal jika sedikit; tinggi berarti banyak invalidasi tanpa eviction

Kesimpulan

OPcache yang dikonfigurasi dengan benar adalah salah satu optimisasi performa PHP paling efektif yang bisa dilakukan tanpa menyentuh satu baris pun kode aplikasi. Tapi konfigurasi yang salah — terutama validate_timestamps=0 tanpa proses deploy yang tepat — bisa menyebabkan situasi yang membingungkan di production. Memahami mekanisme di balik setiap direktif, dan memastikan proses deploy selalu diikuti cache invalidation yang eksplisit, adalah fondasi untuk memanfaatkan OPcache secara aman dan efektif.

Referensi

  1. 1PHP Manual: OPcache Configuration Directives
  2. 2PHP Manual: opcache_get_status()
  3. 3PHP Manual: Preloading PHP
  4. 4Symfony Documentation: Performance

Tentang Penulis

Abd. Asis

Abd. Asis

Software Developer dan Laravel Programmer dari Madura, Indonesia. Passionate tentang PHP, Laravel, dan teknologi web modern.

Komentar