コロプラ・ベアーズ 面白いものを作りたい仲間が集まるベアーズ

コロプラのエンジニアブログ Vol.2【PHP 環境モダン化】
TEAM

コロプラのエンジニアブログ Vol.2【PHP 環境モダン化】

工藤 剛

2017年、コロプラに新卒入社。サーバーサイドエンジニアとして複数のスマホゲームの運用に携わった後、『白猫プロジェクト』のSREを担当。現在はサーバーサイドエンジニアとしてタイトルの運用や PHP のバージョンアップ対応などを横断して行っている。

サーバーサイドエンジニアの工藤と申します! 2017 年に新卒で入社し、『プロ野球バーサス』『白猫テニス(以下、白テニ)』の運用、『白猫プロジェクト(以下、白猫)』にて SRE (Site Reliability Engineer)などを担当してきました。

突然ですが、皆さん PHP を使っていますか? 昔はちょっと悪く言われることの多かった言語ですが、2015年12月にリリースされた PHP 7 以降の大幅な速度改善やよりモダンな言語機能の実装もあり、サーバーサイドの世界では今もなお圧倒的な地位を誇っています。
image8_300.png
コロプラでもゲームの API サーバーの実装でも主に PHP を採用しており、クイズRPG 魔法使いと黒猫のウィズ(以下、黒ウィズ)』や『白猫』、『白テニ』など様々なタイトルのサーバーが PHP で動いています。

精力的にバージョンアップが行われ、言語の改善や速度の高速化が行われている PHP ですが、運用中のゲームタイトルではなかなかバージョンアップが行いにくいのが実情です。特にコロプラでは基本的にメンテナンス期間を設けないようにしているため、 PHP のバージョンアップはより難しいものとなっています。

では、一度リリースしたタイトルの PHP のバージョンアップは行わないのでしょうか? 答えはもちろん NO です!セキュリティ上の懸念を解消したいのはもちろん、エンジニアなら新しいバージョンの PHP の恩恵を受けたいですよね?

今回は2020年3月におかげさまで7周年を迎えた『黒ウィズ』での PHP 5.6 (2014年8月リリース)から PHP 7.3(2018年12月リリース) へのバージョンアップの例を交えつつ、コロプラにおける PHP 環境モダン化の取り組みについてお話しさせていただきたいと思います。

『黒ウィズ』 のサーバー構成

ENGINEERBLOG_image_server_structure_tate_1_800.png
※ PvP: リアルタイム通信(リレー)サーバー 協力バトルで使用

上記は『黒ウィズ』のサーバー構成です。(今回あまり関係ないところについては省略してあります)。APP サーバーはゲームアプリケーションからの API 呼び出しを処理し、 Batch サーバーではランキング計算などの定時実行系の処理を行っています。

メンテナンスなしで PHP の移行を完了するため、今回は一時的に本番環境に PHP 5.6 と PHP 7.3 が混在する状態を作り、そこから PHP 5.6 の環境をサービスアウトすることで移行を行っていきました。

移行後のパフォーマンス

移行に関する細かなお話は一旦後回しにして、移行後にどれだけパフォーマンスが改善したかを見ていきます。
image10_800.png
image1_800.png
結果として CPU 使用率では 60% 減、バックエンドレイテンシ(ロードバランサまでのレスポンス時間)は平均 400ms から 170ms と大幅に高速化しました!

実際にゲームをプレイしていても、各所各所での "通信中......" の時間が短くなっていることを体感できるほどの効果がありました。サーバー負荷も低くなり、新たにジョインしたエンジニアも言語機能を活用でき、ユーザーさまにとってもより快適なゲーム体験を実現できる。なんて素晴らしい結果なのでしょうか!

全てが改善され誰もがハッピーに!これで一件落着......と一筋縄では行かないのがエンジニアリングの世界、バージョンアップには様々な苦難も伴いました。

問題その 1 ~PHP の互換性の問題 ~

PHP 5.6 から 7.3 までの間には、多くの改善が入ると同時に互換性の失われる変更も数多く行われました。ただしその多くはコードの修正によって解決できるもので、 PHPCompatibility 等の静的解析ツールを用いたり、実際にデバッグを行うことで修正できました。

しかし、コード修正だけでは解決できない問題もありました。特に厄介だったのが、 PHP 7.1 より srand() を始めとする乱数関連の実装がメルセンヌ・ツイスタ法を用いた乱数実装である mt_srand() 系のエイリアスとなった問題です。

『黒ウィズ』では、出題するクイズの順番を決める時にシード値を生成し、 srand() 関数でシーディングを行ってから shuffle() 関数で並び替えるようなロジックが各所で実装されていました。

PHP 7.1 にて srand() 関数は mt_srand() 関数のエイリアスとなり、 shuffle() 関数は mt_srand() で生成された乱数テーブルを使用することになったことから、シード値が同一でも問題の並び順が同一にならないという問題が発生しました。
ENGINEERBLOG_image_random_issue_800.png
また、 PHP 7.1 未満の srand() を始めとする乱数生成系関数はより低レイヤーのライブラリである libc の実装に完全に依存しており、 OS の提供する libc が異なれば同一シード値であっても異なった結果となってしまいます。
random_issue_2_800.png
以下はGNU libc を用いた Debian ベース環境と musl-libc を用いた Alpine Linux ベースの PHP 5.6 にて同一のシード値で srand() を実行した例です。結果が異なっていることがわかります。
random_issue_3_800.png
移行はメンテナンスタイムなしで同 PHP 5.6 と 7.3 の環境が一時的に混在する形で行うため、このままではアクセスの度に問題の並び順が異なる(最悪不整合を起こす) というとんでもない状態になってしまいます。

そこで、 PHP 7.0 時点の PHP 向け各種 rand 関数、 GNU libc 環境での rand 関数とシード値レベルで互換性がある関数を提供する PHP 拡張を実装し、PHP 7.1 以降の場合は拡張側の関数を使うことで、互換性を担保することとしました。
sample_code_800.png
↑ 検証用コード

Debian (GNU libc) 環境でも Alpine Linux (musl-libc) 環境でも同一の結果となっていることが確認できます。
colopl_php70bc_test_800.png
前述の通り、互換関数は PHP 拡張として実装しているため、万が一リソースリークが発生した場合にはサーバーがクラッシュしかねません。社内の GitLab CI を用いて Valgrind を有効にした状態でテストを実行し、リソースリークが発生していないことを担保しています。
colopl_php70bc_ci_800.png

問題その 2 ~パッケージマネージャがない!~

『黒ウィズ』は運用開始から7周年を迎えたタイトルになりますが、7年という時間はとても長く、当時の PHP を取り巻く環境は今ほど洗練されていませんでした。今では当たり前のような存在になりつつあるパッケージマネージャの Composer も当時はまだ一般的ではなかったのです。そう、『黒ウィズ』にはパッケージマネージャが導入されていませんでした!

パッケージマネージャがないことによって、多くのライブラリはアプリケーションコードのリポジトリで管理されたり、 RPM 化された PEAR パッケージをインストールしていたりしました。特に RPM パッケージの管理はインフラチームの大きな負担になっていました。

そこでまず、PHP のバージョンアップに備えたモダン化の一つとしてパッケージマネージャである Composer を導入することとなりました。
composer_logo_300.png
Batch サーバー上で実行していた PHP スクリプト一つ一つの処理を確認しつつ、 Composer のオートローダーの読み込み処理を加えていき、 APP / Batch サーバーへのデプロイスクリプトにパッケージのインストール処理を組み込んでいくことで、なんとか導入が完了しました。

問題その 3 ~並行開発での互換性の担保~

ENGINEERBLOG_image_5_800.png
PHP のバージョンアップ作業を行っている間にも新イベントや新機能の開発は続いていきます。『黒ウィズ』の例ではありませんが、とあるタイトルにて移行期間中に PHP 7 系と互換のない形で新機能の実装が行われステージング環境にてエラーとなってしまうという問題が発生しました。

チームメンバーで今一度 PHP 7 系で互換性がなくなるポイントの整理や共有を行い、 PHPCompatibility 等の静的解析ツールで Jenkins を用いて継続的にチェックすることで、並行開発での問題に対処していきました。

まとめ

現在、コロプラで運用しているタイトルは『黒ウィズ』のみならず多くのタイトルが PHP のアップグレードを含むモダン化に積極的に取り組んでおり、ほぼ全てのタイトルで PHP 7.2 以上を使用しています。

移行作業を進めるにあたっては様々な困難が伴いましたが、無事完遂することができたのは各プロジェクトメンバー、インフラチーム、そしてエンジニアリング部自体の理解が得られる環境であったということが非常に大きいと思っています。

今回はコロプラにおけるサーバーサイド環境のモダン化の取り組みについてお話させていただきました。コロプラでは Kubernetes や Cloud Spanner を始めとする最新技術へのキャッチアップにとどまらず、既存タイトルの運用改善にも尽力しています。

大好きなタイトルをモダンな技術で開発し続けられる、いちエンジニアとしてワクワクしてくるものがあるのではないでしょうか?