🐅

PHP OPcache を正しく理解して最適化したい

に公開
2026/01/05

はじめに

PHP は、リクエストごとにソースコードをそのまま実行しているわけではありません。
実際には、パースや構文解析を経て生成された Opcode(Zend VM 用の中間命令)を実行しています。

OPcache は、この Opcode を共有メモリ上に保持し、再利用することで、
毎回発生するパースやコンパイル処理を省略し、PHP の実行性能を大きく向上させる仕組みです。

そのため、

  • OPcache が「何を」キャッシュしているのか
  • どの粒度(ファイル単位なのか、関数単位なのか)で保持されるのか

といった PHP 内部の実行フローを理解していないと、
OPcache の設定値やステータスの意味を正しく解釈することができません。

PHP-FPM 環境における OPcache のプロセスモデル

php-fpm 環境では、OPcache の共有メモリは master プロセス起動時に確保され、複数の worker プロセス間で共有されます。

master
 ├─ worker
 ├─ worker
 └─ worker

この構造のため、opcache.memory_consumption などのメモリ関連設定は プロセス起動時にのみ反映されます。稼働中に動的に増やすことはできず、設定変更後には php-fpm の再起動が必要です。

CLI と FPM では OPcache は完全に別物

CLI と PHP-FPM では、使用される php.ini も OPcache も 完全に分離されています。
そのため、CLI で確認した OPcache の状態や設定が、Web リクエスト側に反映されないケースは珍しくありません。

OS
 └─ PHP
     ├─ SAPI: cli
     │    └─ OPcache(CLI専用)
     └─ SAPI: fpm-fcgi
          └─ OPcache(Web用)

インフラ構成と OPcache メモリの関係

OPcache はディスクではなく RAM 上の共有メモリを使用するため、EC2 インスタンスや ECS タスクに割り当てられたメモリ量が、そのまま利用可能な上限となります。ECS や Fargate のようなコンテナ環境では、タスクごとに OPcache が分離されるため、スケールアウト時にはキャッシュが各タスクに分散されます。この特性を考慮せずに OPcache のメモリサイズを増やすと、アプリケーションプロセスが使用できるメモリを圧迫し、結果として OOM Kill を引き起こすリスクがあります。

OPcache 最適化の3つの観点

OPcache の状態は、「memory・files・strings」という3つの観点から判断できます。これらの状態はいずれも opcache_get_status() を使って確認可能であり、数値をもとにボトルネックを特定し、必要な設定だけを調整していくことが重要です。

memory の確認

$status = opcache_get_status(false)['memory_usage'];

Array
(
  [used_memory]   => 123456789
  [free_memory]   => 987654321
  [wasted_memory] => 1234567
  [current_wasted_percentage] => 1.23
)
key 意味
used_memory Opcode 等で使用中
free_memory 未使用
wasted_memory 断片化
current_wasted_percentage wasted率

wasted_memory(断片化)とは何か

wasted_memory は、一度使われたものの、再利用できなくなったメモリ領域を指します。
以下のような流れで断片化が発生します。

  • ある PHP ファイルの opcode がメモリ上に配置される
  • その PHP ファイルが更新され、再コンパイルされる
  • 元の領域が解放される
  • 新しい opcode が、元の領域より大きいサイズだった場合、その場所に収まらない
  • 結果として「空いているが使えない穴」が残る

この 使えない穴 が wasted_memory です。

一方、free_memory は まだ一度も使われていない連続したメモリ領域を指します。
「空いている」という点では同じでも、再利用できるかどうかが両者の違いです。

files(キャッシュファイル数) の確認

$status = opcache_get_status(false)['opcache_statistics'];

Array
(
  [num_cached_scripts] => 15842
  [max_cached_scripts] => 20000
  [hits] => 12345678
  [misses] => 12345
)
key 意味
num_cached_scripts 実キャッシュ数
max_cached_scripts 上限
hits / misses 効いているか

num_cached_scripts(実キャッシュ数)

num_cached_scripts は、現在 OPcache に格納されている PHP ファイルの数を表します。 ここで言う「PHP ファイル」には、以下のようなものがすべて含まれます。

  • require / include によって読み込まれたファイル
  • オートローダ経由でロードされたクラスファイル
  • フレームワーク内部で使用されている PHP ファイル

num_cached_scripts が増えるタイミング

num_cached_scripts が増加するのは、主に、以下のようなタイミングです。
単なるアクセス数の増加ではありません。

  • 初回アクセス時(まだ OPcache が空の状態)
  • 新しい PHP ファイルが 初めてロードされたとき

たとえば、別のエンドポイントや管理画面に初めてアクセスした際、
それまで使用されていなかったクラスやファイルが require されると、
その瞬間に num_cached_scripts が増加します。

strings(interned strings) の確認

$status = opcache_get_status(false)['interned_strings_usage'];

Array
(
  [buffer_size] => 33554432
  [used_memory] => 28765432
  [free_memory] => 4789000
  [number_of_strings] => 123456
)
key 意味
buffer_size 設定値
used_memory 使用中
free_memory 空き
number_of_strings 登録数

interned strings とは

interned strings は、同じ文字列を1度だけメモリに保存し、参照を使い回す仕組みです。
対象となるのは、「クラス名」「メソッド名」「定数名」「名前空間」などです。
フレームワークを使うと、これらの文字列は大量に発生します。

buffer_size

interned strings 用に確保された専用メモリの総量

number_of_strings

登録されている ユニークな文字列の数

number_of_strings が増えると used_memory が増え、free_memory が減少していきます。free_memory が極端に少ない場合は、opcache.interned_strings_buffer の増加を検討します。

Discussion