Save 37% off PRO during our Black Friday Sale! »

PHPWebアプリケーションパフォーマンスチューニングの勘所〜なぜアプリケーションは速くなるのか〜 / Why PHP Applications are Faster

E7151ab8219e76672f7a7691dd2c88e6?s=47 shiro seike
October 02, 2021

PHPWebアプリケーションパフォーマンスチューニングの勘所〜なぜアプリケーションは速くなるのか〜 / Why PHP Applications are Faster

E7151ab8219e76672f7a7691dd2c88e6?s=128

shiro seike

October 02, 2021
Tweet

Transcript

  1. PHPWebアプリケーションパフォーマンスチューニングの勘所
 〜なぜアプリケーションは速くなるのか〜
 PHP Conference Japan 2021
 2021.10.2
 清家史郎
 1


  2. 速いは正義、アプリケーションは速くあるべきです
 インターネットの高速化やハードウェア性能の向上により
 アプリケーションはよりパフォーマンスを求められるようになっています
 PHPの力を最大限発揮し、より高速なインターネット体験を提供しましょう
 2


  3. 自己紹介
 清家 史郎
 @seike460
 - ID
 - GitHub:seike460
 - Twitter:@seike460


    - Work at
 - 株式会社 Fusic (フュージック) 
 技術開発本部/技術開発第一部門 
 - チームリーダー/エバンジェリスト 
 プリンシパルエンジニア
 - Skill
 - PHP/Go/AWS
 - Community
 - Fukuoka.php Organizer 
 - PHPカンファレンス2018 - 2021 
 3

  4. Agenda
 4
 1. パフォーマンスを上げるには
 2. ボトルネック要因
 3. ボトルネックを見つける
 4. ボトルネックの解消


    5. まとめ

  5. 01 パフォーマンスを上げるには


  6. なぜパフォーマンチューニングをするのか
 6
 時は2021年、世界のインターネット高速化は留まるところを知りません。
 コンピュータの性能向上、ネットワーク回線自体の高速化も相まって、
 システムが遅いことは利用率にそのまま影響を与える致命的になりうる要素です。
 
 この時代に沿ったアプリケーションのパフォーマンスを出すことは
 システムとして実現すべき使命の一つであると言えます。


  7. ハイパフォーマンスであることの定義
 7
 • スループット
 単位時間当たりの処理能力
 一般的に1秒間に返せるリクエストを表すrps(Request Per Second)が用いられます
 • レイテンシー


    リクエストからレスポンスまでに生じる通信の遅延時間
 一般的に単純な時間であるmsが用いられます

  8. スループットを上げるためには
 8
 パフォーマンスを下げる原因となる処理能力が低いポイント
 「ボトルネック」を探し、改善することが鍵となります。
 今回は「ボトルネックはなぜ発生するのか?」に着目してみます。
 - どのサーバー?
 - プログラムの問題?インフラの問題?
 -

    ディスク?メモリ?CPU?

  9. 02 ボトルネック要因


  10. ボトルネックの原因
 10
 アプリケーションが速くなる方法を知る
 →何故遅いのかを知る、ボトルネックについて知る
 - ネットワークI/O 
 - ディスクI/O
 -

    メモリ使用率超過(Swapの利用)
 - CPU使用率超過(load average過多)
 - ソフトウェアリミット
 ボトルネックがどこにあると不味いのか

  11. ネットワークI/O
 11
 Webアプリケーションの表示における上で、
 クライアントやサーバー間の通信によるInput/Outputのこと
 - ブラウザ <-> Webサーバー
 - PHPサーバー

    <-> DBサーバー
 - サーバー <-> ロードバランサー

  12. ディスクI/O
 12
 アプリケーションの要求に応じて、
 サーバーディスクに対するInput/Outputのこと
 - HTML/JavaScript/CSSなどのWebページ構成ファイル
 - プログラムによるサーバーへのファイルの読み書き
 - データベースに対するデータファイルの読み書き


  13. メモリ使用率超過(Swapの利用)
 13
 アプリケーションが計算に使用する領域で高速
 メモリ使用量を超過するとディスク上で計算する(Swap領域)
 - PHPのプロセスが利用するメモリ x プロセス数
 - データベースのプロセスが利用するメモリ

    x コネクション数

  14. CPU使用率超過(load average過多)
 14
 実際にアプリケーションが計算を行う頭脳
 CPUの使用率が100%を定常的に超えるとLoad Average過多
 - PHPの実処理
 - データベースの実処理


  15. ソフトウェアリミット
 15
 ソフトウェアによる設定の制限
 ハードウェアに過剰な負荷を与えない予防線
 - ulimitによるリソース制限
 - NginxのWorker Connection、worker_rlimit_nofile
 -

    php-fpmのpm.max_children等
 - PostgreSQL、MySQLのmax_connections

  16. ボトルネックの主要因
 16
 I/Oはハードウェアの性能な為、基本的にはどうしようもない
 金で殴るしかなくなってしまう
 - ネットワークI/O
 - 大きなデータを転送しない仕組み(サムネイル画像作成等) 
 -

    CDNの利用
 - ディスクI/O
 - ディスクにアクセスしない仕組み(メモリキャッシュ等)
 いかにハードウェア性能に依存させないか

  17. メモリ使用量、CPU使用率
 17
 95%使っている事は悪なのかというとそういう訳ではない
 100%に近付ければ近付けるほど効率的に利用できている
 • メモリ
 ◦ 使用率 ≒ 100%


    ▪ 100%を超えた瞬間Swapが発生するので注意
 • CPU
 ◦ 最大負荷時、コア数 ≒ load averageが理想
 いかに効率的に利用するか

  18. ボトルネックはどこにあるのか
 18
 構成要素のボトルネックを探し出し、正しく理解して対応することが必要
 経験を積んでいくと「この現象はこれっしょ」という推測が立つようになる
 この長年の勘に頼ることは再現性がなく不確実
 - Webサーバ?
 - PHP?
 -

    DB?

  19. 推測するな、計測せよ
 ボトルネックを特定するまで推測で行動しない
 計測した「データ」を元に原因を特定して、対策を講じる
 19


  20. 長年の勘ではなく、理論に従う
 20
 信頼のおけるデータを正しく理解し、理論的な対策を行い再現性を確保する
 そのようなパフォーマンス改善には次のステップを踏む必要があります
 - 現在の状況を把握するために、計測を行う
 - 計測で集まったデータを元に考察して、原因を特定する
 - 特定した原因に対して、正しく改善する


    愚直にこのサイクルを回す事が重要

  21. 03 ボトルネックを見つける


  22. ISUCON5 予選
 22
 ISUCON(iikanjini speed up contest)とはお題となるWebサービスを、 
 決められたレギュレーションの中で限界まで高速化を図るチューニングバトル。 


    
 今回はその第5回予選の問題を利用して、チューニングの様子をお話致します。 
 問題にはパフォーマンスが上がらないようなボトルネックが多数用意されており、 
 チューニングトレーニングに最適です。 

  23. ベンチマーク計測
 23
 スループットを測定するためのツールとして 
 普段はLocustやApacheBench等のツールを利用します。 
 
 今回はISUCONが用意したベンチマークツールがありますので 
 そちらを利用して速度(スコア)の計測を行います。

    
 
 ※今回はコードの変更に関しては基本的に触れません。 
  インフラミドルウェアをチューニングすることで対応します。 

  24. サーバーの情報
 24
 今回はAWSのM5.large
 vCPU : 2cpu
 メモリ:8GiB
 を以下の分類で割り振りました
 - ベンチマークサーバ


    - Web-PHPサーバ
 - DBサーバ

  25. サーバ負荷による
 ボトルネックサーバーの特定


  26. 負荷の確認
 26
 スループットの閾値に到達した際に閾値を決定づけるボトルネックを探します
 ボトルネックになってるサーバを探す際に各サーバのリソース状況を参照します
 
 - 処理待ち状況、CPUの負荷
 - メモリの負荷
 -

    ディスクの負荷

  27. 処理待ち状況、CPUの負荷
 27
 サーバの処理待ちの状況を確認するにはLoad Averageを確認します。 
 初期アプローチとしては処理コストが少ない「uptime」を使うのが適切です。 
 
 CPU状況も合わせて確認するには「htop」を使うと便利です。 


    CPUのコア数以上の処理待ちがある場合、 
 そのサーバーがボトルネックになっている可能性が高いです。 

  28. メモリの負荷
 28
 メモリを100%使い切っている場合、スワップメモリを利用することになります。 
 スワップメモリは、ディスクをメモリ領域として確保して利用する為、 
 実質ディスクI/Oを発生させる事になります。 
 ディスクは非常に低速なデータ領域なのでボトルネックになる可能性が高いです。 


    
 「htop」や「vmstat」でスワップメモリの状況を確認出来ます。 

  29. ディスクI/Oの状況
 29
 ディスクはデータの読み書きをする上で、一番低速な機器となります。 
 ディスクI/Oが大きい時は、パフォーマンス劣化の原因になっている事が多いです。 
 「vmstat」でディスクの読み込み、書き込みを確認する事が出来ます(bi / bo) 


    「pidstat -d」を利用することで、プロセスごとのI/Oも確認出来ます 

  30. ボトルネック処理の特定


  31. PHPのパフォーマンス測定
 31
 PHPに原因がありそうな事がわかった場合、 
 どの関数で問題が発生しているのかをプロファイラーを用いて確認します。 
 
 OSSになっているXdebugが比較的気軽に導入することが出来ます。 
 


    費用に余裕がある場合は、DatadogやBlackfire等の 
 SaaSを利用することも選択肢にあがります。 

  32. Xdebug x Webgrind
 32
 Xdebugにてプロファイルを取得して 
 プロファイル解析が可能なWebgrindを利用し、Graphvizを介して可視化します。 
 具体的な利用方法はFusic Tech

    Blogをご参照ください。
 「PHP8のプロファイリングにXdebug3とWebgrindを使う」
 
 右図の用に図が生成出来ます。 
 - どの関数から呼び出されているか 
 - 処理を専有している関数
 - 繰り返し実行されている回数
 N+1問題等を発見して解消していきます 

  33. RDBでのパフォーマンス測定
 33
 PHPプロファイリングにて時間がかかっているのがRDB操作の関数であったり、 
 DBサーバのリソース枯渇している場合、RDBにボトルネックがあることがわかります 
 
 スロークエリなどを元に、原因となるSQLを特定し 
 単一のSQLが原因になっているのであれば実行計画を見ていきます。

    
 DB設計ミスなのか、Index設定ミスなのか、 
 もしくは必要なリソースをRDB割り振れていないのかを判断していきます。 

  34. Flame Graph


  35. Flame Graphを取る
 35
 改善を続けると1プロセス毎の処理を追うだけでは サーバー全体に負荷をかけている処理が分かりづらくなる GoのpprofにてFlame Graphを取れるのは知っていたが、 PHPで取る方法は知らなかった そんな話を@hanhan1978さんの Yokohama

    North AMでしてた所 PHPにてFlame Graphを取る方法を教えてもらった perf-tools を使ってPHPソースレベルの Flamegraph を作る そのPodcastを聞いてくれていた @sji_ch さんが phpspyというものがあることを教えてくれた
  36. phpspy
 36
 pgrepをphp-fpmに変更する


  37. Flame Graphから俯瞰して見る
 37
 アクセス全体から負荷が見れることが可能


  38. アクセスログからの特定


  39. Webサーバーのアクセスログ解析
 39
 アクセスログを解析することで、よりシステムを深く計測する事が出来ます。 
 
 @matsuuさんのkataribeを利用してカジュアルにアクセスログを解析を行えます。 
 
 kataribeだけでなく、ISUCONの環境をかんたんに作れる AWSのAMIや、


    ansible、vagrantを公開してくれてる神です。

  40. kataribe用の設定
 40
 次の様に時間付きのLogFormatを用意して、 
 kataribeのデフォルト設定にて解析できるようにする 
 設定ファイルを生成して、設定ファイルが置いてあるディレクトリにて、 
 kataribeに対してアクセスログを投げる 


    # kataribe -generate
 # cat /var/log/nginx/access.log | kataribe
 その後、nginxを再起動すると共にnginxのアクセスログを初期化する 
 # cat /dev/null > /var/log/nginx/access.log

  41. 集計した結果から対策を講じる
 41


  42. 04 ボトルネックの解消


  43. ベンチマーク結果
 43
 差分を明確化するため、
 チューニングをしていない状況でベ ンチマークを実施します。
 
 スコアとしては
 「754.6」でした。
 
 更にスコアを伸ばすことを


    目標にチューニングします。

  44. 初期のベンチマーク
 44
 Nginx + PHP ほぼ負荷なし MySQL メモリ枯渇なし LA増加なし CPUに過負荷

  45. スロークエリのチェック


  46. スロークエリ設定
 46
 デフォルトではスロークエリは表示されない為、 
 以下の設定を投入してMySQLを再起動し、遅いクエリを炙り出して行きます。 


  47. 実行計画
 47
 炙り出されたスロークエリに対して、実行計画を取ります。 
 
 Indexが使えていない様子が確認出来ましたので、 
 Indexを貼る事で解消しないかを考えます。 
 


    WHEREの対象になっている、「one」と「another」にIndexを貼ります。 

  48. 実行計画改善後
 48
 Index設定後の実行計画も変わっており、 
 まだ改善が必要ではありますがIndexが利用できている事が確認出来るので、 
 一度ベンチマークを実行して、スコアが向上するかを確認します。 


  49. ベンチマーク結果
 49
 スコアが「754.6」→「1240.0」に上がりました。 
 先程のチューニングが効いた事になります。 
 
 この時点でもまだスロークエリは発生しているので、 
 スロークエリが出なくなるまでIndexの調整をして、

    
 再度ベンチマークを行います。 

  50. Index改善後
 50
 スコアが「1240.0」→「2342.1」に上がりました。 
 
 またスロークエリが発生しなくなりました。 
 コードを変更することなく、 
 3倍のスコアを叩き出した事になります。

    
 
 ここでボトルネックが移っていないかを確認します。 

  51. RDBに対するオススメ発表
 51
 PHPer が知るべき MySQL クエリチューニング まみーさん(@mamy1326)
 MySQLとインデックスとPHPer -PHPが本職でもMySQLを手懐けるために- yoku0825さん(@yoku0825)


  52. スロークエリ改善後のベンチマーク
 52
 Nginx + PHP LA微増 CPU微増 メモリ微増 MySQL LA増加なし

    メモリ枯渇なし CPUに負荷低下
  53. ディスクI/Oのチェック
 53
 V ベンチマーク実行時に I/Oの増加を確認 メモリ使用率が高くないので、 DBにもっとメモリを有効活用させる

  54. MySQLチューニング
 54
 主にメモリを限界まで利用する様に
 パラメーターをチューニング
 
 この状態でベンチマークを実施します。


  55. ベンチマーク結果
 55
 スコアが「2342.1」→「3697.6」に上がりました。 
 
 コードを変更することなく、 
 5倍のスコアを叩き出した事になります。 
 


    ここでボトルネックが移っていないかを確認します。 

  56. パラメーターチューニング後のベンチマーク
 56
 Nginx + PHP LA微増 CPU増加 メモリ変化なし MySQL LA増加なし

    メモリ使用率増加 CPUに負荷低下
  57. # cd /usr/local/src # wget https://www.php.net/distributions/php-8.0.6.tar.gz # tar zxf php-8.0.6.tar.gz

    # cd php-8.0.6 # ./configure --enable-fpm --with-mysqli --enable-mbstring --with-pdo-mysql=mysqlnd # make -j2 # make install Installing shared extensions: /usr/local/lib/php/extensions/no-debug-non-zts-20200930/ Installing PHP CLI binary: /usr/local/bin/ Installing PHP CLI man page: /usr/local/php/man/man1/ Installing PHP FPM binary: /usr/local/sbin/ Installing PHP FPM defconfig: /usr/local/etc/ Installing PHP FPM man page: /usr/local/php/man/man8/ Installing PHP FPM status page: /usr/local/php/php/fpm/ ~省略〜 Installing PDO headers: /usr/local/include/php/ext/pdo/ # php -v PHP 8.0.6 (cli) (built: May 29 2021 06:49:02) ( NTS ) Copyright (c) The PHP Group Zend Engine v4.0.6, Copyright (c) Zend Technologies OPCahceが読み込まれてないので読み込ませる PHP8のインストール
 57

  58. # php -i | grep ini Configuration File (php.ini) Path

    => /usr/local/lib # vim /usr/local/lib/php.ini OPcacheの有効化
 58
 # php -v PHP 8.0.6 (cli) (built: May 29 2021 06:49:02) ( NTS ) Copyright (c) The PHP Group Zend Engine v4.0.6, Copyright (c) Zend Technologies with Zend OPcache v8.0.6, Copyright (c), by Zend Technologies
  59. # vim /etc/systemd/system/isuxi.php.service PHP-FPMの入れ替え
 59
 # systemctl restart isuxi.php.service Warning:

    isuxi.php.service changed on disk. Run 'systemctl daemon-reload' to reload units. # systemctl daemon-reload # systemctl restart isuxi.php.service
  60. ベンチマーク結果
 60
 スコアが「3707.2」→「4564.7」 
 
 さすがPHP8、速い
 PHP8の目玉機能でもあるJITを有効化します。 


  61. # vim /usr/local/lib/php.ini JITの有効化
 61
 # systemctl restart isuxi.php.service

  62. ベンチマーク結果
 62
 スコアが「4564.7」→「5497.8」 
 
 ここまで効果が出る事は予想外でしたが、 
 JITによる効果は大きそうです。 
 


    ここでボトルネックが動いていないかを確認します。 
 

  63. PHP8入れ替え後のベンチマーク
 63
 Nginx + PHP LA変化なし CPU低減 メモリ変化なし MySQL LA増加なし

    メモリ使用率増加 CPUに負荷増加
  64. # cd /usr/local/src # wget https://xdebug.org/files/xdebug-3.0.4.tgz # tar zxf xdebug-3.0.4.tgz

    # cd xdebug-3.0.4 # phpize # ./configure --enable-xdebug # make -j2 # make install ~ 省略 ~ | NOTE: Please disregard the message | You should add "extension=xdebug.so" to php.ini | that is emitted by the PECL installer. This does not work for | Xdebug. | +----------------------------------------------------------------------+ Xdebugのインストール
 64

  65. プロファイリング用ベンチマーク
 65
 スコアが「5497.8」→「3322.2」 
 
 プロファイリング情報を収集しながら動作するのと、 
 100k 〜 1Mサイズのプロファイリングファイルの

    
 書き出しを行うので動作としてはかなり影響が出ます 
 
 運用時はXdebugを切ることを忘れないように 
 気をつける必要があります。 

  66. ▪描画用ツール # apt-get install graphviz ▪webgrind # cd /usr/local/src #

    git clone https://github.com/jokkedk/webgrind.git # php -S 0.0.0.0:8080 index.php PHP 8.0.6 Development Server (http://0.0.0.0:8080) started webgrindの設定
 66

  67. 関数コールの描画
 67
 db_executeによる、SQLの実行が52回も行われており、 get_userという関数がtemplatesファイルから50回も呼ばれている。 こちらの処理を見直して SQL発行を低減する事で、 処理の改善が見込める事が考察出来ます。 ※実際の改善はPHPの処理変更になるので割愛します。

  68. Webサーバー
 ソフトウェアリミット


  69. Nginxパラメータチューニング
 69
 Webサーバーはいかにリクエストを受け付けるのかが鍵
 メモリの消費に直結するのでサーバーリソースと相談しながらソフトウェアリミットを解除
 - worker_processes
 - 受け付けるNginxのプロセス数を決める。auto = CPU数がオススメ


    - worker_connections
 - ひとつのワーカーが開けるConnection数
 - worker_rlimit_nofile
 - 開けるファイルディスクリプタの上限値
 - Unix ドメインソケット(ソフトウェアリミット解除ではなく、I/O改善)
 - Unixドメインソケットを利用する事で接続時のオーバーヘッドを減らす

  70. PHP-FPM パラメータチューニング
 70
 PHP-FPMも合わせてサーバーリソースと相談しながらソフトウェアリミットを解除 
 - pm.max_children
 - 同時に処理するプロセスの最大数
 -

    pm.start_servers
 - 起動時に生成されるプロセス数
 - pm.min_spare_servers
 - 待ち状態の子プロセス最小数
 - pm.max_spare_servers
 - 待ち状態の子プロセス最大数
 サーバーのリソースとmemory_limitの上限に相談して、
 プロセスを増やすことで処理性能を上げる事が出来ます。

  71. キャッシュ


  72. メモリ・ファイルキャッシュ
 72
 ◎PHP以降の処理が重い
 HTMLやCSSをWebサーバーにキャッシュしてしまうのも手です。 
 PHPでSSRをする場合、結果が変わらない場合はWebサーバー側で 
 PHPのレンダリング結果をメモリ上にキャッシュしてしまい、 
 PHPへのRequestを減らすことで、低負荷で高速に結果を返すことが可能です。

    
 
 但しキャッシュクリアのタイミングを失敗すると想定している結果を返せず、 
 手動でキャッシュをクリアするなど障害が発生してしまう可能性があります。 
 
 同じ用にDBのSELECT結果をRedisなどでキャッシュすることも出来ます。 

  73. 05 まとめ


  74. まとめ
 Point 3
 PHPには様々なプロファイリング方法があり、ぜひ手を動かして確かめましょう。 
 74
 パフォーマンスチューニングの鍵はボトルネックの解消
 Point 1
 Point

    4
 ボトルネックを解消する方法は様々な方法があります。知識を蓄え適切に対処しましょう。 
 
 
 推測するな、計測せよ。再現性のある対策を行う
 Point 2

  75. ご清聴いただきありがとうございました
 Thank You We are Hiring !
 https://recruit.fusic.co.jp/