Slide 1

Slide 1 text

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


Slide 2

Slide 2 text

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


Slide 3

Slide 3 text

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


Slide 4

Slide 4 text

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


Slide 5

Slide 5 text

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


Slide 6

Slide 6 text

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


Slide 7

Slide 7 text

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


Slide 8

Slide 8 text

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


Slide 9

Slide 9 text

02 ボトルネック要因


Slide 10

Slide 10 text

ボトルネックの原因
 10
 アプリケーションが速くなる方法を知る
 →何故遅いのかを知る、ボトルネックについて知る
 - ネットワークI/O 
 - ディスクI/O
 - メモリ使用率超過(Swapの利用)
 - CPU使用率超過(load average過多)
 - ソフトウェアリミット
 ボトルネックがどこにあると不味いのか


Slide 11

Slide 11 text

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


Slide 12

Slide 12 text

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


Slide 13

Slide 13 text

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


Slide 14

Slide 14 text

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


Slide 15

Slide 15 text

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


Slide 16

Slide 16 text

ボトルネックの主要因
 16
 I/Oはハードウェアの性能な為、基本的にはどうしようもない
 金で殴るしかなくなってしまう
 - ネットワークI/O
 - 大きなデータを転送しない仕組み(サムネイル画像作成等) 
 - CDNの利用
 - ディスクI/O
 - ディスクにアクセスしない仕組み(メモリキャッシュ等)
 いかにハードウェア性能に依存させないか


Slide 17

Slide 17 text

メモリ使用量、CPU使用率
 17
 95%使っている事は悪なのかというとそういう訳ではない
 100%に近付ければ近付けるほど効率的に利用できている
 ● メモリ
 ○ 使用率 ≒ 100%
 ■ 100%を超えた瞬間Swapが発生するので注意
 ● CPU
 ○ 最大負荷時、コア数 ≒ load averageが理想
 いかに効率的に利用するか


Slide 18

Slide 18 text

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


Slide 19

Slide 19 text

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


Slide 20

Slide 20 text

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


Slide 21

Slide 21 text

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


Slide 22

Slide 22 text

ISUCON5 予選
 22
 ISUCON(iikanjini speed up contest)とはお題となるWebサービスを、 
 決められたレギュレーションの中で限界まで高速化を図るチューニングバトル。 
 
 今回はその第5回予選の問題を利用して、チューニングの様子をお話致します。 
 問題にはパフォーマンスが上がらないようなボトルネックが多数用意されており、 
 チューニングトレーニングに最適です。 


Slide 23

Slide 23 text

ベンチマーク計測
 23
 スループットを測定するためのツールとして 
 普段はLocustやApacheBench等のツールを利用します。 
 
 今回はISUCONが用意したベンチマークツールがありますので 
 そちらを利用して速度(スコア)の計測を行います。 
 
 ※今回はコードの変更に関しては基本的に触れません。 
  インフラミドルウェアをチューニングすることで対応します。 


Slide 24

Slide 24 text

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


Slide 25

Slide 25 text

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


Slide 26

Slide 26 text

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


Slide 27

Slide 27 text

処理待ち状況、CPUの負荷
 27
 サーバの処理待ちの状況を確認するにはLoad Averageを確認します。 
 初期アプローチとしては処理コストが少ない「uptime」を使うのが適切です。 
 
 CPU状況も合わせて確認するには「htop」を使うと便利です。 
 CPUのコア数以上の処理待ちがある場合、 
 そのサーバーがボトルネックになっている可能性が高いです。 


Slide 28

Slide 28 text

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


Slide 29

Slide 29 text

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


Slide 30

Slide 30 text

ボトルネック処理の特定


Slide 31

Slide 31 text

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


Slide 32

Slide 32 text

Xdebug x Webgrind
 32
 Xdebugにてプロファイルを取得して 
 プロファイル解析が可能なWebgrindを利用し、Graphvizを介して可視化します。 
 具体的な利用方法はFusic Tech Blogをご参照ください。
 「PHP8のプロファイリングにXdebug3とWebgrindを使う」
 
 右図の用に図が生成出来ます。 
 - どの関数から呼び出されているか 
 - 処理を専有している関数
 - 繰り返し実行されている回数
 N+1問題等を発見して解消していきます 


Slide 33

Slide 33 text

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


Slide 34

Slide 34 text

Flame Graph


Slide 35

Slide 35 text

Flame Graphを取る
 35
 改善を続けると1プロセス毎の処理を追うだけでは サーバー全体に負荷をかけている処理が分かりづらくなる GoのpprofにてFlame Graphを取れるのは知っていたが、 PHPで取る方法は知らなかった そんな話を@hanhan1978さんの Yokohama North AMでしてた所 PHPにてFlame Graphを取る方法を教えてもらった perf-tools を使ってPHPソースレベルの Flamegraph を作る そのPodcastを聞いてくれていた @sji_ch さんが phpspyというものがあることを教えてくれた

Slide 36

Slide 36 text

phpspy
 36
 pgrepをphp-fpmに変更する


Slide 37

Slide 37 text

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


Slide 38

Slide 38 text

アクセスログからの特定


Slide 39

Slide 39 text

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


Slide 40

Slide 40 text

kataribe用の設定
 40
 次の様に時間付きのLogFormatを用意して、 
 kataribeのデフォルト設定にて解析できるようにする 
 設定ファイルを生成して、設定ファイルが置いてあるディレクトリにて、 
 kataribeに対してアクセスログを投げる 
 # kataribe -generate
 # cat /var/log/nginx/access.log | kataribe
 その後、nginxを再起動すると共にnginxのアクセスログを初期化する 
 # cat /dev/null > /var/log/nginx/access.log


Slide 41

Slide 41 text

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


Slide 42

Slide 42 text

04 ボトルネックの解消


Slide 43

Slide 43 text

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


Slide 44

Slide 44 text

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

Slide 45

Slide 45 text

スロークエリのチェック


Slide 46

Slide 46 text

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


Slide 47

Slide 47 text

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


Slide 48

Slide 48 text

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


Slide 49

Slide 49 text

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


Slide 50

Slide 50 text

Index改善後
 50
 スコアが「1240.0」→「2342.1」に上がりました。 
 
 またスロークエリが発生しなくなりました。 
 コードを変更することなく、 
 3倍のスコアを叩き出した事になります。 
 
 ここでボトルネックが移っていないかを確認します。 


Slide 51

Slide 51 text

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


Slide 52

Slide 52 text

スロークエリ改善後のベンチマーク
 52
 Nginx + PHP LA微増 CPU微増 メモリ微増 MySQL LA増加なし メモリ枯渇なし CPUに負荷低下

Slide 53

Slide 53 text

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

Slide 54

Slide 54 text

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


Slide 55

Slide 55 text

ベンチマーク結果
 55
 スコアが「2342.1」→「3697.6」に上がりました。 
 
 コードを変更することなく、 
 5倍のスコアを叩き出した事になります。 
 
 ここでボトルネックが移っていないかを確認します。 


Slide 56

Slide 56 text

パラメーターチューニング後のベンチマーク
 56
 Nginx + PHP LA微増 CPU増加 メモリ変化なし MySQL LA増加なし メモリ使用率増加 CPUに負荷低下

Slide 57

Slide 57 text

# 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


Slide 58

Slide 58 text

# 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

Slide 59

Slide 59 text

# 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

Slide 60

Slide 60 text

ベンチマーク結果
 60
 スコアが「3707.2」→「4564.7」 
 
 さすがPHP8、速い
 PHP8の目玉機能でもあるJITを有効化します。 


Slide 61

Slide 61 text

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

Slide 62

Slide 62 text

ベンチマーク結果
 62
 スコアが「4564.7」→「5497.8」 
 
 ここまで効果が出る事は予想外でしたが、 
 JITによる効果は大きそうです。 
 
 ここでボトルネックが動いていないかを確認します。 
 


Slide 63

Slide 63 text

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

Slide 64

Slide 64 text

# 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


Slide 65

Slide 65 text

プロファイリング用ベンチマーク
 65
 スコアが「5497.8」→「3322.2」 
 
 プロファイリング情報を収集しながら動作するのと、 
 100k 〜 1Mサイズのプロファイリングファイルの 
 書き出しを行うので動作としてはかなり影響が出ます 
 
 運用時はXdebugを切ることを忘れないように 
 気をつける必要があります。 


Slide 66

Slide 66 text

■描画用ツール # 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


Slide 67

Slide 67 text

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

Slide 68

Slide 68 text

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


Slide 69

Slide 69 text

Nginxパラメータチューニング
 69
 Webサーバーはいかにリクエストを受け付けるのかが鍵
 メモリの消費に直結するのでサーバーリソースと相談しながらソフトウェアリミットを解除
 - worker_processes
 - 受け付けるNginxのプロセス数を決める。auto = CPU数がオススメ
 - worker_connections
 - ひとつのワーカーが開けるConnection数
 - worker_rlimit_nofile
 - 開けるファイルディスクリプタの上限値
 - Unix ドメインソケット(ソフトウェアリミット解除ではなく、I/O改善)
 - Unixドメインソケットを利用する事で接続時のオーバーヘッドを減らす


Slide 70

Slide 70 text

PHP-FPM パラメータチューニング
 70
 PHP-FPMも合わせてサーバーリソースと相談しながらソフトウェアリミットを解除 
 - pm.max_children
 - 同時に処理するプロセスの最大数
 - pm.start_servers
 - 起動時に生成されるプロセス数
 - pm.min_spare_servers
 - 待ち状態の子プロセス最小数
 - pm.max_spare_servers
 - 待ち状態の子プロセス最大数
 サーバーのリソースとmemory_limitの上限に相談して、
 プロセスを増やすことで処理性能を上げる事が出来ます。


Slide 71

Slide 71 text

キャッシュ


Slide 72

Slide 72 text

メモリ・ファイルキャッシュ
 72
 ◎PHP以降の処理が重い
 HTMLやCSSをWebサーバーにキャッシュしてしまうのも手です。 
 PHPでSSRをする場合、結果が変わらない場合はWebサーバー側で 
 PHPのレンダリング結果をメモリ上にキャッシュしてしまい、 
 PHPへのRequestを減らすことで、低負荷で高速に結果を返すことが可能です。 
 
 但しキャッシュクリアのタイミングを失敗すると想定している結果を返せず、 
 手動でキャッシュをクリアするなど障害が発生してしまう可能性があります。 
 
 同じ用にDBのSELECT結果をRedisなどでキャッシュすることも出来ます。 


Slide 73

Slide 73 text

05 まとめ


Slide 74

Slide 74 text

まとめ
 Point 3
 PHPには様々なプロファイリング方法があり、ぜひ手を動かして確かめましょう。 
 74
 パフォーマンスチューニングの鍵はボトルネックの解消
 Point 1
 Point 4
 ボトルネックを解消する方法は様々な方法があります。知識を蓄え適切に対処しましょう。 
 
 
 推測するな、計測せよ。再現性のある対策を行う
 Point 2


Slide 75

Slide 75 text

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