PHP

PHP5.3 -> PHP7.1にバージョンアップした話

概要

本記事はLIFULL Advent Calenderその2の16日目の記事となります。

今回は、先月運営しているサービスのPHPのバージョンアップを行ったので、
その進め方や結果について記します。
今回はPHP5.3から7.1にバージョンアップしました。

そういえば先日7.2がリリースされましたね。
http://php.net/archive/2017.php#id2017-11-30-1

インストール

公式ドキュメントをご覧ください。
http://php.net/manual/ja/install.php

バージョンアップとともにOPcacheという中間コードキャッシュの仕組みもインストールしました。
http://php.net/manual/ja/book.opcache.php

アプリケーションへの影響

バージョンアップに伴う影響の確認は、下記の公式のドキュメントを参考にします。
http://php.net/manual/ja/appendices.php
http://php.net/manual/ja/migration54.php
http://php.net/manual/ja/migration55.php
http://php.net/manual/ja/migration56.php
http://php.net/manual/ja/migration70.php
http://php.net/manual/ja/migration71.php

実際に受けた影響について

幸い今回のバージョンアップにおいては、そこまで影響を受けるコードがなかったので、
コードの変更は少量で済みました。
参考になるかはわかりませんが、具体的に影響を受けた変更と、その対応について記します。

カスタムセッションハンドラの戻り値の修正

session_set_save_handler関数などを使って、カスタムセッションハンドラを定義している場合、
戻り値でfalse/-1を返却しているとFatalErrorが発生します。
false/-1を返却するような場合は、何らかの異常系の場合の処理が多いと思いますが、
そういった場合でも空文字やtrueなどを返却するように修正しました。

http://php.net/manual/ja/migration70.incompatible.php#migration70.incompatible.other.fixes-custom-session-handler

無効なクラス名、etc..

以下にあるよう予約語をクラス名などに利用できなくなりました。利用していた箇所を全てリネームしました。
http://php.net/manual/ja/migration70.incompatible.php#migration70.incompatible.other.classes

ソートの動作変更

これは明確にドキュメントに記されていたわけではありませんでしたが、バージョンアップによりソートの挙動が変わっていたものがありました。

下記のようにrsortで渡した引数の並び順をただ逆順にしたいコードがありました。
PHP5.3では期待通り逆順になっていたのですが、PHP7.1では順序はそのままとなっておりました。

php53.php
$array = array('b', 'a');
rsort($array);
# array('a', 'b')
php71.php
$array = array('b', 'a');
rsort($array);
# array('b', 'a')
# 入れ替わっていない!???

そもそもrsortは配列を降順に並び変えるというものであり、PHP5.3の時点での挙動がバグだったと言えそうです。
http://php.net/manual/ja/function.rsort.php
array_reverseを利用するように修正しました。

このように明記されていない挙動の変化(バグの修正)にも注意を払わなくてはいけません。

検証

検証に関しては、

  1. 検証用サーバーの用意
  2. コード修正
  3. テストテストテスト

というような流れで進めるわけですが、ある程度工程を自動化したり、
ツールを利用することで、検証が楽になると思います。

検証用サーバーの用意

Ansible + Serverspec

サーバーの構築には構成管理ツールのAnsibleを利用しています。
https://www.ansible.com/

結合環境〜本番環境の構築を以前からansibleを利用して行っていたこともあり、
PHP7に関するサーバーの調達に関してもPHPに関する部分を差し替えるだけで用意できました。

Ansibleで構成したインフラのテストとして、Serverspecを作成しています。
http://serverspec.org/

Ansibleで構築しているならテストはいらないのでは?、という意見もありそうですが、
そもそもAnsibleの利用方法をミスって意図した設定なっていない場合や、
冪等性を担保しないような記述をしている場合もあるので、テストコードは別で用意しています。

このようにサーバーに関してもコード化しておくことで、
変更や作り直しに関するコストを下げることができます。

コード修正

コードの修正はマニュアル見ながら、デバッグしながら行っていくと思いますが、
静的解析ツールを利用して検証を行うこともできます。

php7cc

https://github.com/sstalle/php7cc
リポジトリ内のソースコードを解析し、php7非対応のコードを洗い出すことができます。

詳しい使い方などは下記記事などを参考にしていただければと思います。
https://qiita.com/su_mi/items/b9f06a81bdae40b84c61

phan

https://github.com/phan/phan
php7対応の静的解析ツール。
致命的なものはphp7ccで洗い出せるので、こちらは参考程度に。

詳しい使い方などは下記記事などを参考にしていただければと思います。
https://qiita.com/msmsny/items/46aaeda7e565cfc7ec48

テストする

このようなリポジトリ全体に及ぶようなテストを目視で全て確認するのは限界があります。
テストも下記のようなツールを用意して、ある程度自動化しています。

E2E

通常の開発時からE2Eテストを構築して運用しており、
サービス全体のウォークスルーテストを実施しています。
あらかじめ指定のURLリストを網羅的に探索し、ステータスコードなどをチェックしてくれます。

テストにはcapybaraを利用しています。
https://github.com/teamcapybara/capybara

これでテストを流すことで、致命的な問題が起きていないか、ある程度網羅的なテストが可能です。

html diff

上記とは別に、異なる2環境のhtmlのdiffを取れるようなスクリプトも開発しました。
php5.3とphp7.1の環境にそれぞれアクセスし、htmlのdiffが出ていないか網羅的にチェックします。

バージョンアップにより、エラーとまではならないものの、
不具合となっている箇所もこれで拾い上げることができます。

結果

バージョンアップ前後でのパフォーマンス比較等のデータを記載します。
PHP7にすることで爆速になるというもっぱらの噂ですが、果たして・・

レスポンスタイム

PHP7レスポンスタイム.png
バージョンアップしたサービスのTopページのレスポンスタイムですが、
真ん中のガクッと下がっている部分がリリースポイントです。
大体50msくらいは高速化したと言えるでしょうか。

ちなみに、上記はELBのログをQuickSightで表示したものになります。
QuickSightに関する小ネタも投稿したので、下記もよろしければどうぞ。
https://qiita.com/to_muu_mas/items/fffe1122a9583d4d6bca

CPU・メモリ使用率

PHP7CPU_メモリ使用率.png
レスポンスタイム以上に効果があったと思われるのが、CPU・メモリ使用率の減少です。
グラフの青がメモリ使用率。オレンジがCPU使用率です。
メモリ使用率で約10%、CPU使用率でも約3%の削減ができています。

これはOPcache導入の効果もあるのかもしれません。
以前からApcキャッシュは導入していましたが、切り替えによってこれだけのダイエットに成功しています。

感想

PHPのバージョンアップの進め方と結果について記しました。

弊社はマイクロサービス化を推進しておりますが、今回バージョンアップを行ったサービスも
巨大なリポジトリから脱却してマイクロサービス化したサービスの1つになります。

依存関係が複雑だったり、リポジトリがでかかったりすると、
こうした活動の難易度が非常に高くなるので、今回のバージョンアップでは
マイクロサービス化の恩恵を感じることができました。

また、今回記したようなインフラのコード化やテストの自動化を行っていると、
このような比較的規模の大きい変更に対するハードルが下がります。

実際このPHPのバージョンアップ後、2週間程度でAPIサーバーの
Rubyバージョンアップもリリースしています。
その話もまた機会があれば・・。