PHPUnit の Functions.php を自動でロードするやつ

下記↓の記事のコメントの通り、

PHPUnit には Functions.php というファイルが含まれていて、これを require するとアサーションなどがグローバル関数に登録されるので、次のようにテストを書くことができます。

<?php
// 普通こう書くけど、
$this->assertThat($actual, $this->equalTo($expect));

// Functions.php を読んどけばこうも書ける。
assertThat($actual, equalTo($expect));

とても便利なんですけど Functions.php は明示的に読む必要があります。

<?php
require_once __DIR__ . '/vendor/phpunit/phpunit/src/Framework/Assert/Functions.php';

しかもこれ、PHPUnit のバージョンによって違う位置にあります。

さらに、最近なら PHPUnit を Phar で実行することもあると思います。その場合は Phar ファイル名を元に読む必要があります。

<?php
require_once 'phar://phpunit-6.2.phar/phpunit/Framework/Assert/Functions.php';

こういうのが辛かったので、次のようにリフレクションでファイル名を特定するようにしていたのですが・・・PHPUnit 6 でクラス名が PEAR から PSR-4 のスタイルに変わったので、これも動かなくなりました。

<?php
require __DIR__ . '/../vendor/autoload.php';
$reflection = new ReflectionClass('PHPUnit_Framework_Assert');
require_once dirname($reflection->getFileName()) . '/Assert/Functions.php';

そもそも↑のようなスニペットをいちいちコピペするのも面倒臭くなってきたので Functions.php を自動的に読み込むやつを作りました。

次のように composer で追加しておくと、テストの実行時に自動的に Functions.php が読まれます。

composer require --dev ngyuki/phpunit-functions

これは便利。


MySQL で date_add/sub で年が 0000 になると 0000-00-00 が返る

MySQL の sql_modeNO_ZERO_IN_DATE,NO_ZERO_DATE とかを指定すると 0000-00-00 や、月や日が 00 などの日時の挿入を禁止できます。

がしかし、年が 0000 の場合の動きが謎いです。

MySQL のバージョンは次の通り。

select version();
/*-----------+
| version()  |
+------------+
| 5.7.19-log |
+-----------*/

SQL モードを指定します。

set session sql_mode = 'STRICT_ALL_TABLES,NO_ZERO_DATE,NO_ZERO_IN_DATE,ERROR_FOR_DIVISION_BY_ZERO';
select @@sql_mode;
/*--------------------------------------------------------------------------+
| @@sql_mode                                                                |
+---------------------------------------------------------------------------+
| STRICT_ALL_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO |
+--------------------------------------------------------------------------*/

データを入れます。想定通りの動きだと思います。月や日の 00 は無効ですが年の 0000 は有効です。

create table t (d date);
insert into t values ('0000-00-00'); /* ERROR 1292 (22007): Incorrect date value */
insert into t values ('0001-01-00'); /* ERROR 1292 (22007): Incorrect date value */
insert into t values ('0001-00-01'); /* ERROR 1292 (22007): Incorrect date value: */
insert into t values ('0000-01-01'); /* Query OK */
insert into t values ('0000-01-02'); /* Query OK */
insert into t values ('9999-12-30'); /* Query OK */
insert into t values ('9999-12-31'); /* Query OK */

なんということでしょう。

select d, date_add(d, interval 1 day) as `d+1` from t;
/*-----------+------------+
| d          | d+1        |
+------------+------------+
| 0000-01-01 | 0000-00-00 |
| 0000-01-02 | 0000-00-00 |
| 9999-12-30 | 9999-12-31 |
| 9999-12-31 | NULL       |
+------------+-----------*/

select d, date_sub(d, interval 1 day) as `d-1` from t;
/*-----------+------------+
| d          | d-1        |
+------------+------------+
| 0000-01-01 | 0000-00-00 |
| 0000-01-02 | 0000-00-00 |
| 9999-12-30 | 9999-12-29 |
| 9999-12-31 | 9999-12-30 |
+------------+-----------*/

9999-12-31 + 1 day が NULL なのは良いとして 0000-01-010000-01-02date_adddate_sub すると 0000-00-00 なりました。

下記の結果を見るに、比較はうまく動いているっぽい。

select d, d < '0000-01-02', d > '0000-01-01' from t;
/*-----------+------------------+------------------+
| d          | d < '0000-01-02' | d > '0000-01-01' |
+------------+------------------+------------------+
| 0000-01-01 |                1 |                0 |
| 0000-01-02 |                0 |                1 |
| 9999-12-30 |                0 |                1 |
| 9999-12-31 |                0 |                1 |
+------------+------------------+-----------------*/

どうやら date_adddate_sub による演算の結果、年が 0000 になると 0000-00-00 になってしまうっぽい。

select date_add('0000-12-30', interval 1 day) as `0000-12-30 + 1`,
       date_add('0000-12-31', interval 1 day) as `0000-12-31 + 1`,
       date_sub('0001-01-01', interval 1 day) as `0001-01-01 - 1`,
       date_sub('0001-01-02', interval 1 day) as `0001-01-02 - 1`;
/*---------------+----------------+----------------+----------------+
| 0000-12-30 + 1 | 0000-12-31 + 1 | 0001-01-01 - 1 | 0001-01-02 - 1 |
+----------------+----------------+----------------+----------------+
| 0000-00-00     | 0001-01-01     | 0000-00-00     | 0001-01-01     |
+----------------+----------------+----------------+---------------*/

西暦1年の前年は0年ではなく紀元前1年?なのなら 0000 という年は無効な日付になるので結果が 0000-00-00 とかになるのも判らなくもないけど・・それなら date_adddate_sub の入力に与えられたときも結果は 0000-00-00 になるべきな気がするし NO_ZERO_IN_DATE で弾かれてほしい気もする。

あとこれ、テーブルへの格納ではなくて date_adddate_sub による演算に対して発生しているので sql_mode は関係なく発生するようです。

KeepAlive On な Apache+mod_php で HTTP/1.0 クライアントに HTTP/1.1 を返すとタイムアウトを待ってしまう

  • PHP 7.1.7
  • Apache 2.4.10
  • ApacheBench 2.3
  • zend-expressive 2.0.3
  • zend-expressive-skeleton 2.0.3
  • zend-diactoros 1.4.0
  • zend-stratigility 2.0.1

Docker で Apache+mod_php を実行して、

docker run --rm -p 8888:80 -v "$PWD:/var/www/html" php:apache

header 関数で HTTP/1.1 を指定するコードを配置して、

<?php
header('HTTP/1.1 200 OK');

次のように ab します。

ab -c 1 -n 1 http://localhost:8888/

すると、なんか異様なスコアになります(1回のリクエストに5秒もかかってる)。

Requests per second:    0.20 [#/sec] (mean)
Time per request:       5011.751 [ms] (mean)
Time per request:       5011.751 [ms] (mean, across all concurrent requests)

header 関数をコメントアウトしたり、

<?php
//header('HTTP/1.1 200 OK');

HTTP/1.0 を指定したり、

<?php
header('HTTP/1.0 200 OK');

Connection: close を指定したり、

<?php
header('HTTP/1.1 200 OK');
header('Connection: close');

Aapche Bench で KeepAlive を有効にすれば、

ab -k -c 1 -n 1 http://localhost:8888/

それっぽい結果になります。

Requests per second:    560.85 [#/sec] (mean)
Time per request:       1.783 [ms] (mean)
Time per request:       1.783 [ms] (mean, across all concurrent requests)

原因

Apache Bench はデフォで HTTP/1.0 なリクエストを送ります。なのでレスポンスの最後はサーバの方からクローズされることを期待します。

一方、php が header 関数で HTTP/1.1 を指定すると、Apache は元のリクエストが 1.0 だったとしても 1.1 を応答し、しかも KeepAlive On なら KeepAlive も有効になります。そのため KeepAliveTimeout で指定された秒数まで接続しっぱなしになります。

Apache Bench はサーバからのアクティブクローズを待つので KeepAliveTimeout の秒数が経過するまでリクエストが終わらなくなります。

Docker Hub の php:apache のイメージは KeepAlive ONKeepAliveTimeout 5 です。

curl だと発生しない

curl で HTTP/1.0 を指定してもこの現象は発生しません。

time curl -i -0 http://localhost:8888/
HTTP/1.1 200 OK
Date: Sun, 30 Jul 2017 08:54:12 GMT
Server: Apache/2.4.10 (Debian)
X-Powered-By: PHP/7.1.7
Content-Length: 0
Content-Type: text/html; charset=UTF-8


real    0m0.012s
user    0m0.003s
sys     0m0.004s

curl はリクエストを 1.0 で送ってもレスポンスが 1.1 なら 1.1 のクライアントとして振る舞うからだと思います。ので、Content-Length を元にレスポンスの終わりを判断できます。

なお、php のコードを次のようにすると、

<?php
header('HTTP/1.1 200 OK');
flush();

curl の応答は次のようになります。

time curl -i -0 http://localhost:8888/
HTTP/1.1 200 OK
Date: Sun, 30 Jul 2017 08:57:05 GMT
Server: Apache/2.4.10 (Debian)
X-Powered-By: PHP/7.1.7
Vary: Accept-Encoding
Transfer-Encoding: chunked
Content-Type: text/html; charset=UTF-8

real    0m0.014s
user    0m0.002s
sys     0m0.005s

この場合は Transfer-Encoding: chunked なので、チャンクサイズ 0 でレスポンスの終端が判断できます。

zend-expressive

この現象、zend-expressive を Docker Hub の php:apache で動かして ab したときのスコアがおかしなことになって気づきました。

zend-expressive で使われている zend-diactoros のレスポンスオブジェクトは普通に作るとリクエストの内容に依らず 1.1 で固定のためです。

パイプラインの最初のほうでリクエストのバージョンを元にレスポンスのバージョンを変更するミドルウェアを登録しておけば解決します。

// pipeline.php

use Psr\Http\Message\ServerRequestInterface;
use Interop\Http\ServerMiddleware\DelegateInterface;

$app->pipe(function (ServerRequestInterface $request, DelegateInterface $delegate) {
    return $delegate->process($request)->withProtocolVersion($request->getProtocolVersion());
});

nginx+php-fpm では発生しない

ちなみに nginx+php-fpm の構成では発生しませんでした。

nginx の場合は php-fpm が 1.1 を返してきたとしても、リクエストのバージョンが 1.0 なら Connection: close を付与したうえでサーバからアクティブクローズするようです。

結局どこが悪いの?

現代の PHP は http_response_code があるのでレスポンスコードを指定するためだけにプロトコルバージョンまで付けて header 関数を使うべきでは無いと思いますけれども、zend-diactoros は header 関数でステータスコードを指定しています。ただ、PSR-7 の ResponseInterfacegetProtocolVersion/withProtocolVersion を持っているので、レスポンスの送信時にプロトコルバージョンも指定するのは至って自然な気がします。

なので、zend-expressive でレスポンスのプロトコルバージョンがリクエストのプロトコルバージョンに基いていないのがダメなのだろうか・・・?

でも、HTTP/1.0 のリクエストに対して HTTP/1.1 のレスポンスを返すこと自体は合法なようです。

ただ、サーバが「おれ HTTP/1.1 でも大丈夫だよ」とクライアントに伝えるためであって、KeepAlive などの HTTP/1.1 の機能は有効にするべきではない、気がする、ので、Apache が悪いような気もする。

あるいは zend-expressive が(あるいは mod_php が) HTTP/1.0 なリクエストに対しては Connection :close でレスポンスを返すべき?

SNMP v3 の engineID とかのメモ

man snmpcmd とか man snmpd.conf とか man snmptrapd.conf とか tcpdump とかで試行錯誤で推測した内容からのメモ。全然見当違いの可能性もある。


SNMP v3 のエージェントはエージェントを一意に識別するための engineID を設定する必要がある。engineID は不変で別のエージェントの engineID と競合してはならない。

Net-SNMP では engineID は未設定だとマシンのホスト名に対して見つかる最初の IP アドレスが使用される、と日本語の man にはるけど、英語の man では最初の起動時にシステム時刻と乱数に基づいて生成されるとなっている。

生成済の engineID を削除して再生成を何度か試したところ毎回異なる値になったので、英語の man のが正しいと思われる。engineIDType の設定で日本語の man と同じように生成することもできると思う。

生成された値は /var/lib/net-snmp/snmpd.conf に記録される。

SNMPv3 で snmpwalk すると、最初はユーザーや engineID は空でリクエストが送信され、エージェントからは .1.3.6.1.6.3.15.1.1.4.0=225 のような値が応答される。この OID は SNMP-USER-BASED-SM-MIB::usmStatsUnknownEngineIDs.0 という名前で、225 という値は engineID が不明なためにドロップされたパケットの数、らしい、MIB ファイルの記述によると。確かに実行するたびに値がインクリメントされている。

この応答が返ってくるとき、一緒に engineID も付いて返ってくる。snmpwalk は(つまりマネージャーは)この engineID を使って目的の OID の値を取得するためにもう一度リクエストする。

つまり、SNMPv3 を使う場合は engineID を取得するために1往復分余分にパケットがやり取りされている。もちろん engineID は不変という前提なので、一度取得した engineID をキャッシュしておけば必要ない、ただし snmpwalk はそんなのキャッシュしてないので毎回1往復余分にパケットをやり取りする。

一方、snmptrap コマンドでトラップを送る場合、snmpd の初回起動時に生成された engineID のとは別の engineID が生成されて、その engineID を付けてトラップが送信される。

ここで生成された値は /var/lib/net-snmp/snmpapp.conf に記録される。

トラップの場合、送信元がエージェントで送信先がマネージャーなので、エージェントはマネージャーが自分の engineID を知っているという前提でトラップを投げる。つまり、snmpwalk(で送られる GetRequest)のときのように1往復余分にやりとりしたりせず engineID が違ってればパケットが無視されておしまい。

snmptrapd で v3 のトラップを受けるためには、送信元の snmptrapengineID を調べて、その値を元に送信先の snmptrapd でユーザーを作成する必要がある。

たぶん、ネットワーク機器とかの組み込みの SNMP なら GetRequest に応答する engineID とトラップの engineID は同じ?なのかな??なのでマネージャーは GetRequest したときの engineID を記憶しておいてトラップを受信したときに照合することができる?のだろうか??マネージャーが Net-SNMP だと無理そう?だけど??

Net-SNMP の snmpdsnmptrap だと別々の engineID が生成されることがあるのでそういうのはできない。

v3 snmptrap とかでググると、適当な engineID をベタ書きで snmptrap コマンドの引数にしている例が多いのだけど、本当ならエージェントの /var/lib/net-snmp/snmpapp.conf に記録された engineID を使ってマネージャーの snmptrapd を設定するのが正しい?ような気がする??


2ヶ月ぐらい前に社内に書いてたメモからのコピペ。

うーん、結局のところよく判っていない。いろいろあって実案件でこの知識使うこともなくなったので、詳しくは調べていない。

というかなんでこんなニッチなことを調べる必要に迫られたのか・・なにエンジニアなのかわからなくなるわ。

Windows の SNMP サービスから取れる情報

Windows 10 で SNMP サービスを有効にして取ってみました。概ね下記のような情報が取れます。

  • SNMPv2-MIB::system
    • sysDescr とか sysUpTime とか
  • IF-MIB::interfaces
    • ネットワークインタフェース
  • RFC1213-MIB::ip
  • RFC1213-MIB::icmp
  • TCP-MIB::tcp
  • UDP-MIB::udp
    • ip とか icmp とか tcp とか udp とか
  • SNMPv2-MIB::snmp
    • SNMP パケット関係のメトリクスとか
  • HOST-RESOURCES-MIB::host
    • ホストのいろいろな情報(代表的なものを抜粋)
    • hrSystemUptime
    • hrSystemProcesses
    • hrStorageTable
      • hrStorageType
      • hrStorageDescr
      • hrStorageSize
      • hrStorageUsed
    • hrProcessorTable
      • hrProcessorLoad
  • IF-MIB::ifMIB
    • interfaces と似たような情報?
  • IPV6-MIB::ipv6MIB
    • ipv6 とか
  • SNMPv2-SMI::enterprises.77.1

トラフィックは IF-MIB::interfaces から普通に取れます。ただ、コントロールパネルのアダプタの設定には表示されないものがたくさんあがってきてるので、目的のインタフェースを特定するのが難しそう。

メモリ使用量とディスク使用量は HOST-RESOURCES-MIB::hrStorageTable から取れます。メモリもディスクも混在しているので hrStorageType を見て区別する必要があります。

CPU使用率は HOST-RESOURCES-MIB::hrProcessorLoad で CPU ごとに取れます。Load とあるけど MIB を読むと下記の通りだったので、ロードアベレージではなく CPU 使用率のはずです。ググるとロードアベレージだと思っている人が多そうでした。

The average, over the last minute, of the percentage of time that this processor was not idle. Implementations may approximate this one minute smoothing period if necessary.

このプロセッサがアイドル状態でなかった時間のパーセンテージの平均値。 実装は、必要であれば、この1分間のスムージング期間に近似することがあります。 Power by Google Translate

ロードアベレージは素ではとれなさそう・・ググると snmp-informant をインストールして取れるようにする例を見かけます。


2ヶ月ぐらい前に社内に書いてたメモからのコピペ。

いろいろなフレームワークのリクエスト/レスポンスクラスの実装

今日日の PHP のフレームワークでリクエスト/レスポンスクラスがどのように実装されているか調べたメモ。

PSR-7 の実装は zend-diactoros がほぼデファクトかと思ってたけど独自に実装されていることもあった(slim とか cake とか)。


社内に書いてたメモからのコピペ。PSR-7/PSR-15 で開発するときに PSR-7 の実装をどうしようかと思って調べたもの。

わりとメジャーなフレームワークで抜けてるものあるけど(Fuelとか)どういう基準で選定したかは・・・忘れた。

シンボリックリンクを用いたアトミックデプロイと opcache と realpath cache

これまで PHP のアプリケーションのデプロイは rsync でどべーとコードを撒いていました。が、それだと新旧のコードが混在するし Capistrano とかはデフォでシンボリックリンク切り替えでアトミックなデプロイになっているし、周回遅れな感じもしますが今後は似たような方法でデプロイしたいと思います。

releases/ ディレクトリの中にリリースタグでディレクトリを掘ってコードを配置して current を最新のリリースのディレクトリへのシンボリックリンクにします。そして Apache や Nginx でドキュメントルートを current の中の公開用のディレクトリに設定します(/path/to/app/current/public とか)。

/path/to/app/
  releases/
    20161213/
    20161224/
    20170101/
  current -> releases/20170101

なお、世間では(Capistrano では)リリース日時をディレクトリにしているようですけど、本番環境とか検証環境とかで同じバージョンなのに異なるディレクトリ名になるのもわかりにくいかな、と思うので、リリースタグ(Git のタグ)を使います。

新しいバージョンをデプロイする時は、新しいリリースタグでディレクトリを掘ってコードを rsync で撒いて、

/path/to/app/
  releases/
    20161213/
    20161224/
    20170101/
    20170112/ # new release
  current -> releases/20170101

current のシンボリックリンクの宛先を変更します。

/path/to/app/
  releases/
    20161213/
    20161224/
    20170101/
    20170112/
  current -> releases/20170112 # change symlink destination

シンボリックリンクのアトミックな入れ替えの ln -sfn vs mv -Tf で書いたように、アトミックにシンボリックリンクを切り替えれば新旧のコードが混在することなくデプロイが可能です。

が、しかし、PHP ではこのデプロイ方法の問題もよく知られています・・・

opcache と realpath cache が絡み合っていて、実際に試すと非常に不可解な動作になったりします。

いくつかの解決方法はあるのですが・・・とりあえず以下の選択肢は辛そうなので却下。

  • Blue Green Deployment
    • 構成からして考え直さなければならない
    • AWS の案件とかなら考えてもいいかも
  • mod_realdoc を使う (apache)
    • なんか辛そう

Apache を graceful restart

デプロイでシンボリックリンクを切り替えたあとに Apache を graceful restart します。

これなら opcache も realpath cache も間違いなくクリアされます。一番無難な方法です。

Nginx で $realpath_root

Nginx+PHP-FPM なら、Nginx の方で realpath に解決済のパスを渡すことで簡単に解決出来るらしいです。

fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;

これなら、シンボリックリンクを切り替えると PHP-FPM には PHP のコードは別のパスとして渡されるので、キャッシュが云々の問題はなくなります。

Apache でも mod_realdoc を使えば出来ますがね・・・

なお、この方法には後述の「キャッシュクリアしない場合の別の問題」の問題もあります。

opcache をクリアするページを呼ぶ

次のような opcache をクリアするページを作成しておいて、デプロイ後に curl でこのページを呼ぶ、という方法が考えられます。

<?php
opcache_reset();

なお、php の cli で opcache_reset() 実行しても意味がありません、opcache は sapi ごとの共有メモリにあるので cli と mod_php では別管理です。

で、一見この方法でうまくいきそうな気がしますが・・実際に試したところ realpath キャッシュが有効だとなんとも形容し難い動きになりました。

下記のように realpath キャッシュもクリアするようにしてもダメです。

<?php
opcache_reset();
clearstatcache(true);

opcache は共有メモリを用いて apache のプロセス間で共有されているのに対して、realpath キャッシュはプロセス単位なので、これだけだと複数の apache のワーカープロセスの realpath キャッシュをすべてクリアすることができないから・・なのかもしれない。

なので、この方法を用いる場合は realpath キャッシュは無効にしておくのが無難なようです。

realpath_cache_size = 0

opcache のタイムスタンプを毎回チェック

realpath キャッシュを無効にすれば、この問題は opcache だけの問題になります。その場合(mod_php なら)、opcache を明示的にクリアしなくても opcache によってファイルのタイムスタンプのチェックが行われる際に新しいファイルに置き換わります。

opcache のタイムスタンプのチェックは opcache.revalidate_freq に設定した秒数ごとに行われます。

この数秒の間、新旧のコードが混在することが妥協できるなら明示的にキャッシュをクリアする必要は無いし、妥協できないのであれば、0 を指定すればリクエストの都度チェックされるようになるので、下記のように設定すれば opcache をクリアしなくてもシンボリックリンクを切り替えたタイミングでアトミックにコードが差し替わります。

realpath_cache_size = 0
opcache.revalidate_freq = 0

ただし、この設定だとリクエストの都度 require/include で読まれるすべてのファイルの realpath が解決されて、かつ、タイムスタンプのチェックが行われるようになります。

それが問題にならない場合は、この設定が一番なにも考えなくても良い無難な設定となるでしょう。

ただ、この方法には後述の「キャッシュクリアしない場合の別の問題」の問題もあります。

キャッシュクリアしない場合の別の問題

opcache にしても realpath cache にしても、キャッシュのサイズには上限があります。

realpath_cache_size = 16K
opcache.memory_consumption = 64M
opcache.max_accelerated_files = 2000

この上限に達したとき、古いキャッシュをパージして空き容量を確保する・・・なんてことはなく、単純に新しくキャッシュされなくなる、だけです。

つまり、上限に達するとそれ以降のファイルはキャッシュされなくなります。

realpath cache のデフォルトは現代のフレームワークだと少なすぎる気もするので適当に増やしておくと良いかもしれません。

一方で opcache はデフォルト値で十分なような気もします。

が、シンボリックリンク切り替えによるアトミックなデプロイしていると、新旧のコードが両方ともキャッシュとして残るので、キャッシュを明示的にクリアしない限り、キャッシュがどんどん肥大化します。

いずれは共有メモリのサイズかファイル数のどちらかの上限に達して、新しくデプロイしたコードが一切キャッシュされなくなってしまうことがあります。

そのため、opcache_get_status() を監視して一定値を超えたら手動でキャッシュをクリアするように運用しているところもあるそうです。

もっとも、Apache なら大抵の場合は logrotate のために毎日 restart なり graceful restart なりしていると思うので、あんまり関係ないと思いますが。

まとめ

Apache を graceful restart するのが一番簡単で問題も少ないと思います。

この場合は graceful restart したときにコードが切り替われば良いので opcache.revalidate_freq とか realpath_cache_ttl とかはかなり大きめでも良いと思います。

むしろ opcache.validate_timestamps = 0 にして、opcache によるタイムスタンプのチェックを行わないようにしても良いかもしれません。その場合、Apache を graceful restart しない限り、いつまでたってもコードは新しいものに反映されなくなります。

また、php ファイルの require/include に限って言えば opcache は realpath cache の前段のキャッシュのように振る舞います。つまり、require/include したときに opcache にヒットすれば realpath の解決は行われません。

realpath cache が利くのはほとんど php コードの require/include だと思うので(PHPUnit は死ぬほど realpath() を呼んでた気がするけど)、opcache が有効なら realpath cache を無効にしてもあんまり性能は劣化しません(むしろ性能がよくなることもある?という話も聞いた)。


昨年の終わりぐらいに社内用に書いていたメモからのコピペ。

今のところ、Apache をおもむろに graceful restart しても大丈夫な感じのシステムなので、opcache.revalidate_freqrealpath_cache_ttl は大きめに設定して、デプロイ後は graceful restart してます。