Blog

MAMPをバージョンアップしたら、キャッシュ効きすぎ & 遅い。Keep Aliveで改善

ローカルでWordPress環境を作るのに、MAMPがお手軽なのでよく利用します。

今までPHPのバージョンが7.1だったのですが、WordPress自体の推奨が7.4になっているので、プラグインで非対応のものが出てきました。

そこで、MAMPのPHPをバージョンアップ(というか、MAMPのインストールページからMAMP自体を上書きインストール)し、PHPを7.4.2にしました。

すると、問題が2点発生

  • キャッシュが消えない
  • すごく遅い

このうち、2点目の「すごく遅い」にかなりハマりました。

キャッシュが消えない → OPcacheの更新頻度設定で解決

まず困ったのがこれです。ブラウザでキャッシュを切ってリロードしてもキャッシュが消えない。

これは、OPcacheによるものでした。

OPcacheは、PHPのアクセラレータ機能。PHP5.5.0から標準機能として取り込まれたもので、更新を確認する頻度が60秒になっているのが原因でした。

php.iniで設定を変えられます。php.iniの場所は、phpinfoでphp.iniの場所を調べられます。

http://localhost/MAMP/phpinfo.php

更新頻度(秒単位)はopcache.revalidate_freqです。ここを2に変更。

[OPcache]
zend_extension="/Applications/MAMP/bin/php/php7.4.21/lib/php/extensions/no-debug-non-zts-20190902/opcache.so"
  opcache.memory_consumption=128
  opcache.interned_strings_buffer=8
  opcache.max_accelerated_files=4000
  opcache.revalidate_freq=2
  opcache.fast_shutdown=1
  opcache.enable_cli=1

[xdebug]

(バージョンアップ以前の7.1の時にはもう入ってたということになるけど、もしかしたら無効になっていたのか…?不明…)

ローディングが遅い

キャッシュが切れるようになって、作業が捗るかと思いきや、遅い…。
かなり頭を悩ませました。

しかも、挙動がなにかおかしい。
キャッシュを切ってリロードすると、そのままの状態で4〜5秒ほどクロームのタブのローディングアイコンがくるくる周り、一度真っ白になってから次のページが表示されるんですが、表示されててもまだくるくる回り続けます。

トータルでローディングが終わるまで5〜6秒ほどかかります。

だいたいどのページでもそうなるのですが、果たしてこんなに遅かっただろうか…。
原因を探ることに。

以下、やってみたことです。

メモリの解放

ググって出てきた方法がこれ。

php.iniのmemory_limitでメモリを増やします。デフォルトは128Mになっていますが、これを無制限にしてみました。

memory_limit = -1

しかし私の場合は、変わりませんでした。

別のローカル環境(Docker)を作って、php.iniを比べる

Dockerを使い、まったく同じWordPressテーマでローカル環境を作ってみました。

ちなみに、php.iniは自分のMacに入っているものを流用しているので、Docker imageで生成されたものではありません。(ややこしいことをしてしまった…)

すると、速い。MAMPをバージョンアップする前は確かにこの速さだった!と改めて思いました。

<両者の環境>

Docker(速い)…PHPバージョン7.4.1、Apache/2.4.38 (Debian)、SSL使用なし
MAMP(遅い)…PHPバージョン7.4.21、Apache/2.4.48 (Unix)

、SSL使用なし

ではどこかに違いがあるはずだと、php.iniを上から順に違いを見ていき、合わせていきました。
(上述しましたがDocker側のphp.ini自体はPHP7.4のものではありません。)

結論を先に書くと、下記全て合わせてみても、速度は変わりませんでした。
一応備忘録を残します。(memory_limitは省く)

速い方(自分のphp.ini)と遅い方(MAMPのphp.ini)で、違ってたディレクティブとその説明

1、track_errors = Off。MAMPの方はコメントアウト
PHP 7.2.0 以降で非推奨になり、PHP 8.0.0 で削除されたものらしいです。

2、variables_order = "GPCS"。MAMPの方はコメントアウト
それぞれスーパーグローバル変数の作成をする定義をします。

E:Environment (環境変数)
G:Get
P:Post
C:Cookie (クッキー)
S:Server

GPCSは「環境変数以外」ということになります。

3、register_argc_argv = Off。

MAMPはOn
PHPが変数argvとargcを宣言するかどうかを指定します (これらにはGETの情報が格納されます)。
(よくわからない…)

4、enable_dl = Off

MAMPはOn
PHPの動的ロード拡張機能をdl()で 仮想サーバー毎またはディレクトリ毎にオンまたはオフに変更することができます。
dlは実行時に PHP 拡張モジュールをロードする

5、extension系の記述がコメント
MAMPは下記記載あり

extension=imap.so
extension=gettext.so
extension=pgsql.so
extension=pdo_pgsql.so

6、pcre.jitがコメント。MAMPはpcre.jit=0
PCRE の just-in-time コンパイルを利用するかどうか。

7、mail.add_x_header = Off 

MAMPはmail.add_x_header = On
X-PHP-Originating-Script を追加します。 それはスクリプトの UID を含み、その後にファイル名が続きます。

8、sql.safe_mode = Off。MAMPは記載がない
PHP 7.2.0 以降は削除されています。

9、mysqlnd.collect_memory_statistics = Off。MAMPは mysqlnd.collect_memory_statistics = On
さまざまなメモリ統計情報の収集を有効にします。この設定項目は、MySQL Native Driver 統計情報 全体の中でメモリ管理に関する統計を有効にします。

10、session.gc_divisor = 1000。MAMPは session.gc_divisor = 100
デフォルトは100らしいです。
session.gc_divisorと session.gc_probabilityの組み合わせで すべてのセッションの初期化過程でgc(ガーベッジコネクション)プロセス も始動する確率を制御します。確率は gc_probability/gc_divisor で計算されます。例えば、1/100は各リクエスト毎に1%の確率でGCプロセスが 始動します。
(わからない…)

11、zend.assertions = -1。MAMPは zend.assertions = 1
assert() は PHP 7 で言語構造となり、expectation の定義を満たすようになりました。 すなわち、開発環境やテスト環境では有効であるが、運用環境では除去されて、まったくコストのかからないアサーションということです。

  • 1: コードを生成して実行する (開発モード)
  • 0: コードを生成するが、実行時には読み飛ばす
  • 1: コードを生成しない (運用モード)

つまりMAMPは開発モードが有効になってて、Dockerは運用モード

12、OPcachexdebug(こっちはコメントになってるが)の記述がない
MAMPは

[OPcache]
zend_extension="/Applications/MAMP/bin/php/php7.4.21/lib/php/extensions/no-debug-non-zts-20190902/opcache.so"
  opcache.memory_consumption=128
  opcache.interned_strings_buffer=8
  opcache.max_accelerated_files=4000
  opcache.revalidate_freq=2
  opcache.fast_shutdown=1
  opcache.enable_cli=1

[xdebug]
;zend_extension="/Applications/MAMP/bin/php/php7.4.21/lib/php/extensions/no-debug-non-zts-20190902/xdebug.so"

ここまですべてを合わせても解決しなかったので、php.iniでどうにかできる問題ではないということがわかりました。

クロームのデバッグツールNetworkタブで調べる

クライアントサイドの問題かもしれないと思い、デバッグツールで調べました。

すると、驚きの結果が。何度かやってみると、cssファイル、jsファイルの読み込みにものすごく時間がかかってました。

仮にこれらを読み込まないようにしてみたら、速くなりました。

そういえばブラウザのローディング時に、5秒くらい画面そのまま → 一回消えて次の画面になる というのは、この5秒間の間にCSSを読み込んで、レイアウトのために一旦白くなってレンダリング一瞬でしてた…みたいな感じなのかも…と仮説を立ててみました(言っててよくわかりませんが)

ちなみにDockerで作ったローカル環境の方は何も問題ありませんでした。こちらは1秒ちょっとで表示されます。

同じWordPressテーマの同じCSSファイルなのに、なんでこんなに差が出るのだろう…

PHPでもHTML/CSSでもないということは、怪しいのはApache。
…そう思って試しにMAMPの設定でNginxに切り替えてみたら、なんと1秒でローディングが終わりました。

解決策 Keep Alive

結論

apacheのhttpd.conf

KeepAlive Off

もしくは後述する

KeepAliveTimeout 1

を追加します。

Keep Aliveとは

持続的な接続(1回の接続で複数回の要求)を許可するかどうかを設定します。デフォルトはOnです。

Offにするということは、1回で複数回の要求を許可しないということになります。それだと逆に遅くなりそうですが、なぜOffで速くなったのでしょうか。

HTTPは、通常は1回の要求(リクエスト)ごとに接続が切断されます。
しかし、1つのWebページは複数ファイルで構成される場合がほとんどなので、1リクエストごとに接続を切っていたのでは効率が悪いです。

そこでKeep Aliveが登場しました。一度接続したら、ある条件を満たすまで接続を切断しないよう設定することができる様になりました。

しかし、Keep Aliveを有効にするとサーバ側は TCPコネクションを確立し続けなければならず、負荷が掛かります。

(nginxでは「イベント駆動」と呼ばれる方式でこのデメリットを克服しており、Apacheでも同様の方式を実装し始めているそうです。)

Keep AliveのOnでなぜ効率が悪くなったのか

上記のような仕組みのため、リクエストが終了しても接続が切断されない…というのが、ローディングが終わらない理由のようです。

リクエストが終了したクライアントに対して、いつまでも接続を維持しているという状態です。

そして、接続をどれだけ維持するかを設定するのがKeepAliveTimeout。デフォルトは5に設定されいているので、5秒間は終わらないことになります。ここで秒数の辻褄が合いました。

KeepAliveTimeoutを変更して秒数を短くする

早速、httpd.confKeepAlive Offを消してデフォルトのOnにし、KeepAliveTimeout1に設定。

KeepAliveTimeout 1

これで約1秒でローディングが完了しました。

Keep Aliveをオフにせず、無駄なローディングない…と思いましたが、

実は作っていたページは画像もほとんどなく、1秒以内に読み込めるページだったので、今回はKeep AliveをOffにすることにしました。

ちなみに、試しに0.8と設定してみましたが、KeepAliveTimeoutは秒数指定なので、1以下を設定するとMAMP起動時にエラーがでました。

Apache couldn't be started. Please check your MAMP installation and configuration.

あまり短い時間にしてもKeep Aliveの意味がないですしね…。これでいいと思います。

Apacheの設定ファイルの調べ方と新たな謎

DockerのApacheとMAMPのApacheがちょっと違う種類のもので、調べるのに一手間かかったので、それぞれの設定ファイルの調べ方をメモしておきます。

apacheの設定ファイルはRed Hat系とDebian系で違うらしいです。

DockerのApache

サーバーのバージョンを調べます。

apachectl -v
Server version: Apache/2.4.48 (Debian)

ということでした。設定ファイルは下記でした。

/etc/apache2/apache2.conf

keep Alive周りの設定はこうなっていました。

#
# Timeout: The number of seconds before receives and sends time out.
#
Timeout 300

#
# KeepAlive: Whether or not to allow persistent connections (more than
# one request per connection). Set to "Off" to deactivate.
#
KeepAlive On

#
# MaxKeepAliveRequests: The maximum number of requests to allow
# during a persistent connection. Set to 0 to allow an unlimited amount.
# We recommend you leave this number high, for maximum performance.
#
MaxKeepAliveRequests 100

#
# KeepAliveTimeout: Number of seconds to wait for the next request from the
# same client on the same connection.
#
KeepAliveTimeout 5

MAMPのApache

apachectl -v

サーバーのバージョン

Server version: Apache/2.4.46 (Unix)

設定ファイルは下記でした。

/Applications/MAMP/conf/apache/extra/httpd-default.conf

Keep Aliveの設定を確認しましたが、Dockerのものとまったく同じでした。

httpd-default.confはおいといて、httpd.confでKeepAliveをOffに変更しました。

/Applications/MAMP/conf/apache/httpd.conf

最終的には下記を追加しました。

KeepAlive Off

新たな謎。Keep AliveがOnなのに、ローディングが終わっているDocker側の環境

念の為、phpinfo()で確認しました。

Docker
Max Requests
Per Child: 0 - Keep Alive: on - Max Per Connection: 100
Timeouts
Connection: 300 - Keep-Alive: 5

MAMP
Max Requests
Per Child: 0 - Keep Alive: off - Max Per Connection: 100
Timeouts
Connection: 60 - Keep-Alive:

ここまできて、新たな謎が出てきました。

MAMPの環境が改善したのはいいけど、Docker側はなぜうまくいってるのか?
Keep Aliveの秒数設定は「KeepAliveTimeout 5」なので、こちらも5秒間くるくるまわっているはずじゃないのか…。

おそらく、別の要因が何かあるのではと思います。
そもそもKeep Aliveは、速度改善のために考え出されたもののはずなのに、接続が自動的に切断されないせいでブラウザのローディングがおわらない…こちらの方が違和感があります。

とりあえず今回は目的を果たしたので、ここまでにしましたが、まだまだ謎を残したままとなりました。

参考

https://atmarkit.itmedia.co.jp/ait/articles/0207/23/news002_2.html
https://www.linuxmaster.jp/linux_skill/2017/07/keepaliveapache.html

おすすめの記事 recommend blog

新着 new blog

github