PHPカンファレンス2020 レポート

PHPカンファレンス2020 レポート[後編]

12月12日(土⁠⁠、PHPカンファレンス2020が開催されました。PHPカンファレンスは今年20周年の節目を迎え、初のオンライン開催となりました。前編に続き、本稿ではその模様をお伝えしていきます。

竹澤有貴さん「事業のスケールアウトを支えるPHPで作る分散アーキテクチャ」

スターフェスティバルの竹澤有貴さんは、これまで大規模データを扱う業務に多く携わってきました。その中での経験を基に、事業を支える大規模アプリケーションをどのようなパターンで構築していくかについて話しました

「Event Sourcing」「CQRS」をキーワードに上げ、これらと同列に語られることの多いドメイン駆動設計についても紹介しました。

画像

ビジネスの成長と組織

アプリケーションは小さなアプリケーション、小さなチームから始まります。当初は意思疎通が取りやすいのでコードベースの統一も容易に取れますが、大きいチームになると、意思疎通もコードベースの統一も難しくなっていきます。やがて、パフォーマンスの悪いコードが量産されるようになります。

具体的な例として、N+1問題を挙げました。アプリケーションが小さいうちは顕在化しないものの、アプリケーションが大きくなってくると、これらの問題が顕在化してきます。その際、取ってしまいがちな対応として、垂直スケールやテーブルの非正規化が行われますが、これを成長途上のプロダクトで行ってしまうと後から切り札がなくなってしまうなど、サービスの成長に悪影響を及ぼすと述べました。

また、アプリケーションが大きくなるにつれて機能要件が困難になっていき、データ取得のコストもどんどん上がっていきます。

こうしている間にもビジネスは止まらず開発を進めていくしかなく、継ぎ足されるコードがどんどん増えていき、さまざまなサービスが絡みあう大きなサービスになっていきます。アプリケーションの負荷も高まっていきます。対症療法的にキャッシュ機構を導入してしまうと、サービス間のデータの整合性が徐々に取れなくなっていき、コードベースの修正だけでなくアーキテクチャの抜本的な改善が必要になると述べました。

大規模アプリケーションで顕在化する、データ設計に起因する問題は次の3点です。

  • 大量データのフルスキャンが走っている
  • INDEXが適切に設定されていないことによるパフォーマンス低下
  • テーブルがシンプルであるが故に複雑なクエリが走る
    ※具体例として、新しい機能を実現しようとした場合に他のテーブルをJOINしなければ実装できない場合を挙げました。

このような問題を多く抱えたアプリケーションでは、機能追加時のソースコード調査に長期間かかってしまったり、複雑なソースコードによるエンジニアのモチベーションダウンを招いてしまうこと、場合によっては実装する時間がかかりすぎるが故に機能追加が見送りになることを挙げました。このことは機会損失による競合優位性の低下を招き、ビジネスがスケールしなくなることに直結し、会社全体に悪影響を及ぼすことを指摘しました。

様々な可能性を考慮したデータ設計を初期のうちに考慮はできないとして、非正規形のテーブルによって対処することの危うさについても言及しました。非正規化テーブルでは、特定の要件だけにしか対応できないテーブルになりがちです。それ以外の要件に対応するのは難しく、仕様変更やサービス拡大が難しくなると述べました。

また、定期的にデータベースやアプリケーションのリファクタリングを行うことは労力的に難しくなります。高速または高性能なデータベースを使うなどの場合でも、ミドルウェアレベルではアプリケーションの複雑さを解決できないことに触れた上で、ミドルウェアの検討と合わせてアプリケーションの改善が必要であることを指摘しました。

DDD(ドメイン駆動設計)

要件分析を行わない設計では、誤った汎用化が行われがちです。そこで、DDDで用いられる手法である「境界づけられたコンテキストを意識したドメイン分割」を行うことになります。これにより、アプリケーション内のドメインを意識できる点、ドメイン内に存在するモデルの属性がどのようなものかを洗い出すことができると言います。また、各ドメインによって必要な読み込み/書き込みモデルを意識することで、要件の複雑に立ち向かえることも指摘しました。

後述するCQRSではこのドメインの知識を前提としており、ここの理解なしにはCQRSを実践できません。

CQRS

業務要件とデータ構造の差分は必ずあると述べた上で、どちらも分離できればシンプルな設計になると述べました。これに対抗する手段としてCQRSを挙げました。

読み込み/書き込みモデルによってデータベースを分離することが可能となるため、それぞれのモデルでパフォーマンスの出やすいデータベースを採用できます。

書き込みに強いデータベース
  • Cassandra
  • DynamoDB
読み込みに強いデータベース
  • 基本的にはDBMSで十分
  • LIKE検索に強いのはElastic Search、Solrなど

CQRSを導入するメリットとデメリットとして、次の点を挙げました。

CQRSを導入するメリット
  • 読み書きのデータベースを分離することによる耐障害性の向上
  • スケーラビリティの向上や、読み込み/書き込みモデルで効率の良いデータベース(RDBMS、NoSQL)を選択できる
  • Command/Queryの関心を徹底的に分離できる
  • 書き込み時の正規化を崩す必要がない
  • 読み込みのスケーラビリティを確保できる
CQRSを導入するデメリット
  • アプリケーションを構成する要素が増えるため、全体の設計が増える
    • Create、Update、Delete アプリケーション
    • Read アプリケーション
  • 扱うデータベースが増えるにつれて運用コストが増える
  • データベースが分離することにより、レプリケーションのタイムラグが増える

EventSourcing

CQRSでデータベースを分離する際、データベース間のデータ整合性を担保するために使います。データ更新(書き込み)のイベントをイベントストアに逐次記録して、再現するパターンです。

PHPの場合はMessage Brokerを導入する必要があり、次の要件を満たす必要があると話しました。

  • Pub/Subに対応していること
  • イベントストアに揮発性がないこと
  • スケーラブルであること

この要件を満たすものとして、竹澤さんはApatche Kafkaを推薦しています。

分散トランザクション

分散トランザクションについて、導入コストやシステムが複雑化するという問題から、基本的には導入することは勧めませんでした。

極力、1プロセスで複数データベースにまたがる更新処理を行わない対策を示した上で、どうしても避けられない場合はマイクロサービスアーキテクチャの1つであるSagaパターンを導入する方法を示しました。

例として、コレオグラフィとオーケストレーションの2種類の分散トランザクションの実装パターンを紹介しました。竹澤さんは「小規模な分散処理はPHPで実装できるものの、大規模になるにつれて別言語の採用を視野に入れていくことが重要」と述べ、すべてPHPで実装することは勧めず、分散処理に向いた言語を取り入れて実装する方法を勧めていました。

EventSourcingとCQRSの発展

近年の大規模データ処理向けアーキテクチャとして、⁠Lambdaアーキテクチャ」「Kappaアーキテクチャ」の具体例を挙げました。将来的に、 機械学習を導入していきたい場合にも柔軟に対応できます。

最後に竹澤さんは、⁠後からメンテナンスしやすいコードを書いていくために、実装だけではなくビジネスに関心を持つこと」⁠規模に応じて適材適所な技術を見極めていくこと」が大事だと結びました。

パネルディスカッション「ひさてるさんに聞け」

このセッションでは、プログラムの設計手法についての疑問を、初心者にも分かりやすい内容で議論しました。登壇したのは、司会の小山哲志さん、ゲストの田中ひさてるさんと、イアン・ブライソンさんの計3名です。田中さんは、 大阪でWebサービスの開発・保守に携わる傍ら、Software Design誌で「ちょうぜつエンジニアめもりーちゃん」を連載。イアン・ブライソンさんはBASEに勤務する社会人3年目エンジニアで、 設計の分野に関心があるとのことです。

画像

多くのソフトウェア開発者が突き当たる疑問を事前にTwitter等で集め、その疑問について取り上げました。

このセッションで取り上げられた疑問の一つに、 次のものがありました。

詳細設計のレビュー時に、メンテナンス性/拡張性の考慮不足について指摘を受けてしまう。設計手法やおすすめの本があれば教えてほしい(エンジニアに転職して2年目の方より)

この質問に関して、田中さんがご自身の経験を基に話をするところから議論が始まりました。

田中「私は、最近コードを書くよりレビューする機会が多いのですが(笑)

まず、質問者の方はレビューで指摘されたくない、合格点をもらいたいと考えて完璧を目指しているのではないでしょうか。もし、そうであればそんなことを考える必要はないです。

こちらとしては、コードを書いてもらえるだけでありがたいと考えており、ざっくりでもいいので、ぐいぐい進めてもらえると嬉しいです。その中で、コードを眺めて……ここは!というところを指摘しています。

イメージとしては、⁠時間差のあるペアプログラミング』をしているイメージを持ってもらえればと思います。⁠採点される!』ではなく『一緒にコードを書いている』というイメージを持って気楽にしてみてください。もし上司に指摘されるのを恐れているのであれば、自信を持ってください!」

(中略)

イアン「指摘されてやだな〜ではなくて、見つけてもらえてラッキー!という感じですか?」

小山「そうですね!」

イアン「指摘されると実力不足を痛感しますが、数ヶ月立って指摘されなくなると成長を感じますね」

この議論の中で、正解をもらおうと思う人と、自分で相手の問題を解いてあげようという気持ちの人では、後者のタイプが成長しやすいことも語られました。小山氏は、ソフトウェア開発が正解のない作業であり、常に考えながら改善する姿勢を求められる点に触れ、⁠常に1つの正解を求めると何重の意味でも失敗を重ねてしまう可能性がある」と述べていました。

KISSの法則を意識する

田中さんはソフトウェア設計を考える取り掛かりについて「KISSの法則というものを意識するといい」と話しました。

田中「⁠⁠Keep it short and simple』の略で、シンプルにしておきなさいという格言です。デザインの世界でもそうですが、シンプルなものは洗練されていることが多いです。⁠Less is more」⁠訳:少ないほうが豊かである)という言葉もあるように」

小山「単機能なものは単機能に納めておいて、複雑なものは入れない。バグが入り込む余地がなくなりますからね。ソフトウェア設計では増やすより減らすことが難しいですから」

イアン「最初のほうはやりがちでした(笑⁠⁠ ここに追加すればいいじゃん、としてみたら後からごちゃごちゃになって」

YAGNIを意識する

次に、ソフトウェアの複雑さをなくしつつ一般化を意識する考え方として「YAGNI」について話しました。

田中「要は必要になるまで作るなということですね。変な値がくる可能性とかいろんなことを考え始めると、行数がめちゃめちゃ膨れ上がっていきます。コードが硬直していく原因になっていきます」

イアン「設計といえばいろんなことを考慮するってことと思ってましたが、そうではないんですね?」

小山「こんなこともあろうか、ということは8割くらいこないですね(笑⁠⁠」

田中「考えること自体が無駄ということですね。最近はリファクタリングの考え方が進んだり、CIの導入が進んできたので、壊しては作ることが簡単になりました。考える時間がもったいない。必要になってから作ればよいのです」

TDDは100%お勧めできる開発手法

最後に、⁠どう動くかではなく、どうあるべきか」を設計する具体的な方法として「TDD」について話しました。

田中「どう動くかではなく、どうあるべきかが設計のスタートラインです。テストを作ってどうあるべきかを定義して、テストにパスする実装を作りましょうという考えです。テストって、まずクラス名を決めるじゃないですか? この時点でどうあるべきクラスか、何のクラスであるかが決まるのですごくいいと思っています」

おすすめの本

エンジニア1年目、2年目におすすめの本として、次の本を紹介しました。

  • 『プリンシプルオブプログラミング⁠⁠:学習内容に迷っている際、次の学習内容を決める指針になるとのことでした。
  • 『クリーンコード⁠⁠:ミクロなレベルでコードの書き方を解説しているとのことです。
  • 『Clean Architecture⁠⁠:田中さんはPackage原則の章を勧めていました。イアンさんも章ごとが短く読みやすいと勧めていました。

他にも、某ロボットアニメでオブジェクト指向プログラミングの説明があったり、Active RecordとO/R Mapperの違いについての話に触れた後、議論は終了となりました。

富所亮さん「PHPのソースコードから理解するPreloadとJIT」

Webアプリケーションエンジニアの富所さんは、PHP7.4でサポートされたPreloadとPHP8でサポートされたJITについて、これらを比較しながら解説しました

画像

今回のセッションでは、次の3点について解説しました。

  • PreloadとJITを謎の技術にせず、仕組みを理解する
  • PreloadとJITではどのくらいの速度向上が見込まれるか
  • JITは本番稼働中のWebアプリケーション動作環境に導入する価値があるか

はじめに、PHPの高速化の歴史について話しました。

高速化の仕組みとしてXCache、APCなどが用いられた時期もありましたが、PHP5.5以降ははOPCacheがデファクトスタンダードになっています。

まれに本番環境でOPCacheが有効になっていないことが見受けられると指摘した上で、DockerHubで公式配布されているイメージにはOPCacheが導入されていないことがある点についても触れました。OPCacheが無効化されていて良いことはないため、必ず有効化するようにと述べていました。

次に、PHPのスクリプトが実行されるまでの過程について詳細な説明があり、その中でコンパイルの流れを取り上げました。字句解析、構文解析を経て生成されたOPCodeをZendVMが実行することでPHPのコードは実行されること、OPCacheではコンパイルの部分が省略されることから高速化を実現できると説明しました。

Autoload機構が存在する場合にはコンパイルが何回動くのか、実際のWebアプリケーションを模擬したサンプルコードを元に説明しました。次のような依存関係の場合にはコンパイル処理が4回動作することを示しました。

index.php (コンパイル)
    -> autoload.php (コンパイル)
        -> 依存クラスA.php (コンパイル)
        -> 依存クラスA.php (コンパイル)

続いて、OPCacheの有効化によりコンパイル結果がキャッシュされるとパフォーマンスにどのように影響するかを検証しました。Laravelでベンチマークしたところ、OPCacheの有効化により約6倍の処理速度が高速化しました。ソースコードをコンパイルしてOPCodeを生成するには大きなコストがかかるため、回数を削減するだけでここまでの処理速度の高速化が可能になったことに言及していました。

PHP7.4で追加されたPreloadで、さらなる高速化処理が可能となります。Preloadでは、サーバ起動時にpreload.phpで指定されたファイルのコンパイルが走り、OPCodeをメモリに展開します。これにより、外部ファイルを参照する場合にautoload.phpを一度読み込む必要がなくなるため、さらに高速化されます。セッションの中では、PHPのソースコードレベルでPrealoadの仕組みについても触れました。結果、Preloadを有効化した場合、OPCache単体と比較して約14%の高速化が実現していました。

そして、PHP8.0でJITがサポートされた点に触れ、JITの検証も行いました。OPCodeはZendVM上で実行されますが、JITの場合はネイティブコードを生成しCPUで実行することで最適化を行います。ZendVM上での実行速度よりもCPU上での実行速度が高速であるため、結果として実行速度が向上します。一見OPCodeをネイティブコードに変換するコストが発生するため、速度が遅くなるのではないかとの懸念を持つかもしれませんが、変換コストを合わせてもZendVM上での実行速度を上回って高速に動作するためカバーできると説明しました。ただしJITの最適化はクラスや関数単位で行われることから、JITの恩恵を最大限に受けるには元のソースの書き方についても考慮が必要になります。結果、OPCache+Preloadの場合と比較して2.5%〜5%の処理速度が向上しました。

これらの結果を踏まえ、富所さんは「JITについては仕組みを理解して、適切なアプリケーションに導入することが重要である」と述べながらも、 ⁠Webアプリケーションの場合には、ネットワークアクセスやデータベースアクセスのボトルネックが大きなウエイトを占めるため、JITで速度改善を図ることは有効な対策とはいえず、最優先で本番投入することはお勧めしない」と結びました。

清家史郎さん「PHP8はISUCONへの扉を開く鍵となるか」

Fusicの清家史郎さんは、ISUCONの利用言語にPHP8を採用できるかについて話しました

画像

ISUCONは、お題となるWebサービスをどれだけ高速化できるか競うチューニングバトルです。PHPはWebアプリケーションに広く浸透しているものの、ISUCONでの採用事例がかなり少ないようです。今年開催されたISUCON10では、PHPを採用したチームのうち本戦出場に勝ち進んだチームが1つしかありませんでした。しかし他に高速な言語がある中で、本線出場で1組勝ち進んだことはポジティブで希望の持てる事例であると紹介しました。

そしてPHP8がリリースされましたが、これがISUCONに参加するPHPerの武器になるのではないかと考えたそうです。

PHP8でISUCONに臨む壁

ISUCONの鉄則「推測するな、計測せよ」に則ってプロファイラーを導入した検証を試みた際の経験について話しました。清家さんの環境では、xdebug、blackfireをはじめとしたプロファイラーはJITを有効化するとSegmentation Faultで動作しなくなったようです。

100万個の要素をクイックソートでソートした場合の処理

PHP 7.3の場合、平均1467msですが、PHP8+JITの場合は平均400msと約3.6倍の速度改善が可能になりました。

ただし、通常のWebアプリケーションに導入するだけでは高速化は見込めないとのことです。データベースに接続するサンプルプログラムで同様に検証したところ、約5%の速度改善に止まりました。

PHPのJITは非常に強力な機能でPHPerの武器となりますが、清家さんは「銀の弾丸ではない」と述べていました。そして、⁠ISUCONで他の言語と戦えるのかという目線で見た場合、PHP8もGo言語に迫る勢いで高速化を実現している点に着目しており、PHP8をISUCONに使える可能性はあるため、PHP8でISUCONに優勝したい」と結びました。

イベントを終えて

PHP8が11月26日にリリースされたばかりということもあり、PHP8のセッションが盛り沢山でした。特に目玉機能であるJITに関して、各登壇者が詳細に説明していました。他にも、今回のカンファレンスを通じてPHPの新たな側面を知ることができたり、より深い理解を得ることができたりと、大変有意義な1日となりました。

今回は初のオンライン開催となりましたが、オフライン開催に無い良さとしてセッション中のDiscordが盛り上がったりと新しい形での盛り上がりを見せていました(各セッションの動画はYouTubeで閲覧できます⁠。

来年のPHPカンファレンスは10月2日、3日に開催される予定です。今回参加の機会を逃してしまい、この記事をご覧いただいている方が来年参加されるきっかけになれば大変嬉しく思います。

おすすめ記事

記事・ニュース一覧