Slide 1

Slide 1 text

PHPWebアプリケーション
 パフォーマンスチューニング
 PHPerKaigi 2021
 2021.3.27
 清家史郎
 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 
 - Personal
 - PHPカンファレンス福岡2020 幻の実行委員長 
 - Fukuoka.php Organizer 
 - 46fm パーソナリティ 
 3


Slide 4

Slide 4 text

Agenda
 4
 1. PHPWebアプリケーション構成要素 
 2. 計測
 3. パフォーマンス改善
 4. まとめ
 5. Appendix


Slide 5

Slide 5 text

01 PHPWebアプリケーション
 構成要素


Slide 6

Slide 6 text

PHPWebアプリケーション構成要素
 6
 一般的なWebアプリケーションの構成は大きく分類して 
 以下の用に分類される事が多く、それぞれに対してパフォーマンスを改善を行います 
 
 - Webサーバ
 - アプリケーションサーバ
 - DBサーバー


Slide 7

Slide 7 text

Webサーバ
 7
 ウェブブラウザに対して、HTMLや画像などの表示を提供したり、 
 アプリケーションサーバへのリバースプロキシを受け持ちます 


Slide 8

Slide 8 text

アプリケーションサーバー
 8
 Webサーバからのリクエストに応じて処理を行い、 
 DBサーバとやり取りをしてレスポンスを構築する 


Slide 9

Slide 9 text

DBサーバー
 9
 リレーショナルデータベース(RDB) 
 PHPからの要求に応じて、データ登録、更新、削除、参照を行います 
 ※ストレージとしてRDB以外を選択することもあります 


Slide 10

Slide 10 text

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


Slide 11

Slide 11 text

ボトルネック
 11
 パフォーマンスを下げる原因となる処理能力が低いポイント 
 様々なボトルネックがあります
 - Webサーバパラメータ
 - PHPコードの問題
 - DB設計ミス
 経験を積んでいくと「この現象はこれっしょ」という推測が立つようになる 
 この長年の勘に頼ることは再現性がなく不確実 


Slide 12

Slide 12 text

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


Slide 13

Slide 13 text

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


Slide 14

Slide 14 text

02 計測


Slide 15

Slide 15 text

サーバ負荷の確認


Slide 16

Slide 16 text

スループット測定
 16
 スループットを測定するためのツールとして今回はLocustを利用します。 
 Python製ではありますが、少しづつ負荷を上昇させることが出来るため、 
 そのWebアプリケーションのスループットの閾値を測定することが出来ます。 


Slide 17

Slide 17 text

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


Slide 18

Slide 18 text

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


Slide 19

Slide 19 text

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


Slide 20

Slide 20 text

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


Slide 21

Slide 21 text

ボトルネック処理の特定


Slide 22

Slide 22 text

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


Slide 23

Slide 23 text

Xdebug x Webgrind
 23
 Xdebugにてプロファイルを取得して 
 プロファイル解析が可能なWebgrindを利用し、Graphvizを介して可視化します。 
 具体的な利用方法はこちらの記事をご確認ください。
 
 すると右図の用に図が生成出来ます。 
 - どの関数から呼び出されているか 
 - 処理を専有している関数
 - 繰り返し実行されている回数
 
 N+1問題等を発見して解消していきます 


Slide 24

Slide 24 text

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


Slide 25

Slide 25 text

計測、考察、対策
 25
 このようにサーバのリソース計測したり、プロファイラを利用することで 
 WebサーバやPHP、RDBの状態を正しく認識します。 
 
 現状を正しく認識したら原因について考察し、対策を実施します。 
 
 対策後、新たなボトルネックが判明したら、再度計測を行い繰り返し改善します。 


Slide 26

Slide 26 text

03 パフォーマンス改善


Slide 27

Slide 27 text

DB


Slide 28

Slide 28 text

実行計画①
 28
 ◎あるSELECT SQLの実行が遅い場合
 EXPLAIN(ANALYZE)をSELECTの前に付けて、実行計画を取ります。
 - Seq Scan … 全レコード探索
 - Index Scan … Indexとテーブルに交互にアクセスして探索
 - Bitmap Scan … メモリ上に展開されたBitmapを使ってAndOrの探索
 - Index Only Scan … Indexのみからデータを探索(条件有)
 意図したIndexが利用できているかを確認します。


Slide 29

Slide 29 text

実行計画②
 29
 テーブル結合にも注目します。
 Nested Loop … 外部テーブルをスキャンして、内部テーブルに結合 
 Indexの概念があるので、意図したIndexが効いてるかを考慮 
 Merge Join … 外部テーブル、内部テーブルのキーをソートして結合 
 Indexがないテーブルではコストが大きい 
 Hash Join … メモリ上にハッシュテーブルを作成してハッシュを比べて結合 
 メモリ上にハッシュテーブルが収まれば高速に結合 


Slide 30

Slide 30 text

非正規化
 30
 必要以上にJoinが発生してしまう場合などは、 
 正規化しているテーブルの非正規化も検討を行います。 
 
 データの追加・更新・削除が発生しないテーブルに関して、 
 非正規化が有効で、Joinを減らすことでSQLのコストを下げることが出来ます。 


Slide 31

Slide 31 text

パラメータチューニング
 31
 ◎DBサーバのリソースが有向活用出来ていない場合 
 CPUの使用率は高いが、メモリの使用率が低い場合などは、 
 RDBにメモリを十分に与える事で解決する可能性があります。 
 
 メモリを利用するソートやジョインが高速化され、 
 不必要なディスクI/Oを発生させずに結果を返すことが出来ます。 
 ディスクにデータを書き込むタイミングを調節することで、 
 ディスクI/Oを減らす事が出来ます。 
 
 RDBパラメータチューニングの内容はここでは話しきれないので、 
 Appendixを是非ご覧ください


Slide 32

Slide 32 text

Source、Replica
 32
 ◎DBサーバ複数台構成が組める場合 
 データ更新のディスクのI/Oが原因でSELECTが遅くなってしまう可能性があります。 
 
 そもそもの設計の話になりますが複数台構成を取り、データの更新はSourceに行い、 
 データの参照はReplicaに行うことでデータの参照を遅延させない事が出来ます。 


Slide 33

Slide 33 text

PHP


Slide 34

Slide 34 text

PHPバージョンアップ
 34
 ◎問答無用でパフォーマンスアップの可能性 
 PHPは日々進歩しています。
 PHPバージョンアップにより高速化する可能性は十分にあります。 
 SwooleTwitterアカウント (@php_swoole)より引用


Slide 35

Slide 35 text

オペコードキャッシュ
 35
 オペコードキャッシュの機構を使う事で更にPHPを高速化させる事が可能です。 
 OPcacheを利用することで、コンパイルしたコードをメモリ上にキャッシュし、 
 高速化することが出来ます。
 
 初期設定では定期的にPHPコードの変更をチェックして、 
 変更があればファイルから再読み込みが行われます。 


Slide 36

Slide 36 text

JIT
 JIT(ジャストインタイム・コンパイラー) 
 PHP8から導入された機能
 JITはOPCcacheの中間コードの重要な部分をネイティブコードまで変換します。 
 コンパイルをバイパスするおかげで、 
 パフォーマンスとメモリ使用状況を大幅に改善させる事が可能です。 
 36
 php.netより引用


Slide 37

Slide 37 text

ユーザーキャッシュ
 37
 ◎DBサーバ負荷が高い
 ユーザーキャッシュの機構を使いKey Valueのキャッシュを利用することで、 
 ディスクアクセスを減らす事が可能です。 
 但しディスクのデータが更新された際のキャッシュクリアが必要になります。 
 - APCu
 - PHP単体で利用
 - Redis
 - キャッシュサーバーとして複数台で利用 


Slide 38

Slide 38 text

PHP
 38
 ◎Webサーバーからのリクエストを受けきれない 
 フォークするプロセス数を増やす事で受け取るリクエスト数を増加させます。 
 - pm.max_children:同時に処理するプロセスの最大数 
 - pm.start_servers:起動時に生成されるプロセス数 
 - pm.min_spare_servers:待ち状態の子プロセス最小数 
 - pm.max_spare_servers:待ち状態の子プロセス最大数 
 サーバーのリソースとmemory_limitの上限に相談して、 
 プロセスを増やすことで処理性能を上げる事が出来ます。 
 Apache + mod_phpの場合はMaxClientの設定に相当します。 
 パラメータチューニングの詳細は Appendixをご覧ください。


Slide 39

Slide 39 text

Webサーバー


Slide 40

Slide 40 text

Webサーバーのパフォーマンス
 40
 ◎クライアントからのリクエストを受けきれない 
 何らかの要因でスパイクアクセスが発生した際に、 
 WebサーバはそのHTTPリクエストを受け切る必要があります。 
 ApacheのMaxClientや、Nginxのworker_processesを調整します。 
 OSパラメータであるulimit等の値を調節して、 
 Too Many Open Fileが発生しない用にするなどの調整が必要になります。 
 設定例はAppindexへ


Slide 41

Slide 41 text

リソース圧縮
 41
 ◎ネットワークトラフィック
 HTMLやCSS等のリソースファイルを返還する際に、 
 データを圧縮して転送してブラウザに解凍してもらう事で 
 ネットワークトラフィックを少なくすることも出来ます。 
 
 ブラウザで解凍する必要がありますので、 
 クライアントに若干の負荷をかける事になりますが、速度の向上に繋がります。 


Slide 42

Slide 42 text

HTTP2
 42
 ◎HTTP/2を利用する
 利用するプロトコルをHTTP/2に変更します。 
 1つのコネクション内で同時に並行して複数のリクエスト/レスポンスを処理出来る為 
 複数のリソースファイルを1つのコネクションで返す事が出来ます。 


Slide 43

Slide 43 text

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


Slide 44

Slide 44 text

まとめ
 Point 3
 ボトルネックを解消する方法は様々な方法があります。知識を蓄え適切に対処しましょう。 
 44
 推測するな、計測せよ。再現性のある対策を行いましょう。
 Point 1
 Point 4
 話せないことがたくさんあった、Appendix見ていってください 
 
 リソースを正しく把握して、ボトルネックを特定しましょう。ディスクアクセスは遅い。
 Point 2


Slide 45

Slide 45 text

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


Slide 46

Slide 46 text

Appendix
 05

Slide 47

Slide 47 text

Nginxパラメータチューニング
 47
 Webサーバーはいかにリクエストを受け付けるのかが鍵 
 ※メモリの消費に直結するのでサーバーリソースと相談しながら 
 - worker_processes
 - 受け付けるNginxのプロセス数を決める。auto = CPU数がオススメ 
 - worker_connections 
 - ひとつのワーカーが開けるConnection数 
 - worker_rlimit_nofile 
 - 開けるファイルディスクリプタの上限値 


Slide 48

Slide 48 text

PHPパラメータチューニング
 48
 Nginxからプロキシされたリクエストを受け切る必要がある 
 僕はパフォーマンスを追い求める時(ISUCON時など) 
 サーバがリソース許せばプロセスフォークする負荷を発生させない為に、 
 待機プロセスを最大化しておく(1プロセスがmemory_limit分メモリを使うのに注意) 
 max_children * memory_limit ≒ サーバーメモリ + OS稼働分 + 余白 
 FastCGI Process Manager (FPM):設定 
 pm.max_children、pm.start_servers:起動、最大のプロセスを制御 
 pm.min_spare_server、pm.max_spare_server:フォーク後に待ち受けるプロセスを制御 
 ※mod_phpの場合はMaxClientsなどが対応します。 


Slide 49

Slide 49 text

RDBパラメータチューニング
 49
 詳しい内容は下記から得た知識になります。 
 ◎僕が心の拠り所にしているQiita MySQLパフォーマンスチューニング -my.cnfの見直し- 
 ◎@mamy1326 さん作成の元ネタ
 初めてのMySQLサーバーチューニング -データベースは怖くない!- 
 こちらを見て是非理解を深めてください。 
 
 次のページからはPostgreSQLの設定をいくつかご紹介します 


Slide 50

Slide 50 text

RDSパフォーマンスチューニング
 50
 増やしたPHPのリクエストを受け切る為に接続数を上げます(max_connections) 
 ただしその分だけメモリとディスクI/Oが増加する恐れがあるため、 
 あまりにも多くし過ぎると接続を許しながら 
 LoadAverageをひたすら増加させる恐れがあります。 
 潔くtoo many clientsを出してLoadAverageの過剰な増加を防ぐのも一手です。 
 ※PHP側で正しく判定する必要が出てきます。 


Slide 51

Slide 51 text

RDSパフォーマンスチューニング
 51
 利用するメモリを増やすことでメモリキャッシュを行ったり、
 ソートやジョインで利用する際のメモリを増やす事で高速化を行います。
 
 effective_cache_size … 単一の問い合わせで利用できるディスクキャッシュの実効容量
 shared_buffers … データベースサーバが使用する共有メモリバッファ
 work_mem … 一時ディスクファイルに書き込む前に、
         内部並べ替えとハッシュテーブル操作が使用するメモリ容量


Slide 52

Slide 52 text

RDSパフォーマンスチューニング
 52
 ログ先行書き込み(WAL)はデータの一貫性を確実にするための標準的な手法です。
 ディスクに書き込む前にメモリ上に保管しておく事が出来、
 メモリに保持する量や頻度を調節することにより、ディスクへの負荷を低減出来る
 checkpoint_timeout、max_wal_size、wal_buffers(-1で大抵の場合妥当な結果となる)
 等が調整の対象となる。
 29.4. WALの設定


Slide 53

Slide 53 text

距離
 53
 データ転送も距離の問題があります。 
 データやコンテンツとの通信の距離を短く設定する為にDNSやCDNで 
 高速化の仕組みを提供します。 
 - DNS:位置情報ルーティングポリシー(Route53など) 
 - CDN:エッジロケーション(Akamai、CloudFrontoなど) 


Slide 54

Slide 54 text

CDN
 54
 CDNはコンテンツキャッシュの機能も有するので、 
 コンテンツをオリジン(配信元)から受け取った後にキャッシュして、 
 後続のサーバーへの負荷を減らすことが出来ます。(例のごとくTTLに注意) 


Slide 55

Slide 55 text

JAMstack
 55
 JavaScript/APIs/Markupの頭文字をとったフロントエンドスタック (PHPでは無いですが)
 FusicのTechBlogはJAMstackを利用したSSGで運用しています。 
 パフォーマンス、スケーラビリティ、セキュアなブログ・サイトを運用出来てます。 
 
 弊社の浦田の登壇資料が分かりやすいのでご参照ください。 
 


Slide 56

Slide 56 text

フロントエンド
 56
 本筋とはズレますが、フロントエンドのチューニングもあります。 
 - Rendering
 - CSS(CSSOMツリー)
 - HTML(DOMツリー)
 - Scripting
 - JavaScript
 - Painting
 - 2Dグラフィックエンジン、ピクセル描画など 
 私が解説出来るほど収めておらず次の本をオススメします。 
 Webフロントエンド ハイパフォーマンス チューニング 
 (著:久保田 光則)にて詳しく解説されています。 
 ここでも開発者ツールのパフォーマンスタブを利用した 
 「推測するな、計測せよ」という思想について触れられています。