Slide 1

Slide 1 text

レガシーWebアプリケーションの 性能とコードの健全性を インクリメンタルに改善する 山本浩平 / GMO Pepabo, Inc. 2021.12.09 1

Slide 2

Slide 2 text

自己紹介 2 ● @kymmt90 ● EC事業部 ECグループ DXチーム ● 主にカラーミーショップを構成するさまざまなWebアプリケーションを横断した 改善活動や開発体験の向上などに従事 山本浩平

Slide 3

Slide 3 text

カラーミーショップの ショップページとその課題 3

Slide 4

Slide 4 text

カラーミーショップのショップページとその課題 4 ● オンラインショップにおける店頭 ○ 回遊して商品を見たり、購入するためにカートに入れたりできる ● ショップオーナー様の商いの起点となる重要なアプリケーション ショップページ

Slide 5

Slide 5 text

5 カラーミーショップのショップページとその課題 「カートに入れる」ボタン 商品詳細ページ ● 商品の説明、画像、値段、バリエーション の確認 ● 注文の起点となる非常に重要なボタン ● 押すとカートアプリケーションへ遷移

Slide 6

Slide 6 text

● ページごとに対応するテンプレート(カスタマイズ可)が存在 ● テンプレートとその中で利用するデータなどをデータストアから取得し、必要に 応じてデータを加工 ● データとテンプレートからHTMLをレンダリングしてレスポンス カラーミーショップのショップページとその課題 6 ショップページというWebアプリケーション RDB/KVS/ ファイル ショップページ アプリケーション ブラウザ 商品ページの リクエスト/レスポンス テンプレート、商品データ などを取得 取得したデータの加工

Slide 7

Slide 7 text

カラーミーショップのショップページとその課題 7 ● 2021年3Q実績は約99.36% (※) ○ ダウンタイム(ショップが閲覧しづらい時間)が約280分/月 ● 理由 ○ アプリの性能問題によるDB高負荷、パブリッククラウド専用線の障害(約400分) ○ あるショップへのアクセス集中が全ショップの閲覧に影響することが何度か発生 ● 障害中はショップ回遊や注文が減りショップオーナー様の不利益に ○ コロナ禍でのニーズを鑑みると、このままでは安心して使っていただけない (※) Slackの障害対応チャンネルで記録した障害検知〜復旧の時間をもとにおおよその値を計算 cf. インシデントレスポンスを自動化で支援する Slack Bot で人機一体なセキュリティ対策を実現する https://speakerdeck.com/hiboma/insidentoresuponsuwozi-dong-hua-dezhi-yuan-suru-slack-bot-deren-ji-ti-nasekiyuriteidui-ce-w oshi-xian-suru 課題: ショップページ稼働率の低下

Slide 8

Slide 8 text

カラーミーショップのショップページとその課題 8 ● サービスのコアともいえるアプリケーションだが、開発におけるフィードバックと 改善のサイクルが不足気味 ○ テンプレートに応じたページのレンダリングが主目的で、機能要件はすでに満たして おり、継続的に開発する機会が発生してこなかった ○ 一方で継続的に開発しないと継続的な改善も行われづらい ○ 結果、古いコードが残って性能や健全性に難があり、テストカバレッジも不十分 ● 稼働率の高さが求められる一方で、積極的な改善がためらわれがち なぜこれまで改善できなかったか

Slide 9

Slide 9 text

カラーミーショップのショップページとその課題 9 ● 目標: 2021年4Qのショップページ稼働率を99.95%以上にする ○ 約21分/月のダウンタイム ● 方針 ○ “パフォーマンス改善に専属で取り組むチーム”を組織 (※) ■ ショップページは@hrysd、@takapi、@kymmtからなるチームが担当 ○ 「カートに入れる」ボタンが存在する商品詳細ページの95%tileレスポンス時間を改善の 指標として選択 ■ 性能に関する変更のフィードバックを得るための指標としてわかりやすく、性能改善により 稼働率向上にもつながるため ○ 性能改善作業の失敗による稼働率低下をコードベースの健全性向上や仕組みづくりで防止 (※) 安定したサービス提供のための改善レポート【2021年7〜9月】 - よむよむカラーミー https://shop-pro.jp/yomyom-colorme/80571 2021年3Qの課題を踏まえて改善する

Slide 10

Slide 10 text

ショップページを例とした レガシーWebアプリケーションの 改善 10

Slide 11

Slide 11 text

ショップページを例としたレガシー Webアプリケーションの改善 11 ● アプリケーション: vendor supportedなPHP 5.4 + php-fpm ○ プライベートクラウド上のVMで稼働 ○ Webアプリケーションフレームワークなし ○ Capistranoでデプロイ ● RDB: Amazon RDS for MySQL (5.7) ● KVS: Amazon ElastiCache for Memcached 現状のアーキテクチャ

Slide 12

Slide 12 text

ショップページを例としたレガシー Webアプリケーションの改善 12 ● ショップページの機能に対応したクラス(この資料では画面クラスと呼ぶ)が存在 ○ 全ページ共通、トップページ、商品一覧ページ、商品詳細ページ、カテゴリページなどに 対応するクラスがそれぞれ存在 ○ 画面クラスから一部Eloquent(LaravelのORM)モデルやプレーンなクラスも利用 現状のアプリケーション設計

Slide 13

Slide 13 text

ショップページを例としたレガシー Webアプリケーションの改善 13 ● 画面クラスはほぼトランザクションスクリプト ○ データ取得、計算、テンプレート変数の設定までを担う関数を持つ ○ ルーティング層で必要な画面クラスのインスタンスを生成し、その関数を呼び出すイメージ 現状のアプリケーション設計 fetchData(...); break; case 'category': // … } $view->render();

Slide 14

Slide 14 text

ショップページを例としたレガシー Webアプリケーションの改善 14 ● アプリケーションの95%tileレスポンス時間が約960ms ○ “サイト表示が0.1秒遅くなると、売り上げが1%減少” という話から速いとはいえない ● クエリ発行回数が多いという特徴 ○ スロークエリの発生 ○ 一連のトランザクションスクリプトで同じ結果を取得するクエリを重複して発行 ○ テンプレート編集機能で利用できる独自タグのためのさまざまなデータ取得にも起因 ■ 商品詳細ページだけで使える独自タグでも30種類存在 技術的な課題: 性能

Slide 15

Slide 15 text

ショップページを例としたレガシー Webアプリケーションの改善 15 ● ロジックやクエリの重複が複数の画面クラス間で発生 ○ トランザクションスクリプトのデメリットそのもの ● クラスの構造やコーディング作法に起因して可読性に難あり ○ 2,000行超えの巨大なクラス、文字列連結でベタ書きのSQL、処理内容と合っていない 関数名など ● 低いテストカバレッジ ○ 24.0% ○ 画面クラスに対してPHPUnitでざっくり書かれたテストは存在 技術的な課題: 設計と健全性

Slide 16

Slide 16 text

ショップページを例としたレガシー Webアプリケーションの改善 16 ● 細かい粒度での改善と検証を繰り返して変更失敗のリスクを軽減 ● 改善の1サイクルは1週間 ○ 現状のアプリケーションの振る舞いを観察して、優先して改善すべき点を3つ選択 ○ 1週間手分けして改善に取り組み、性能の状況を毎日確認 ○ 作業してみて健全でないことに気づいたコードの改善も徐々にタスク化 ● 健全でないコードの改善を並行して進めることで安全かつ高速な性能改善を実現 ○ テスト追加、静的解析、稼働率維持の仕組み 進めかた: インクリメンタルな改善のサイクルを回す

Slide 17

Slide 17 text

性能の改善 17

Slide 18

Slide 18 text

性能の改善 18 ● APM (application performance monitoring)ツールを利用するのが楽 ○ 今回の取り組みではNew Relicを利用 ● 遅いエンドポイント実行のトレースを採取してくれるので、それらを確認 ○ アプリケーションレベルのトレースである関数呼び出しにかかる時間を確認 ○ 遅いエンドポイントで発生しているスロークエリを確認 性能改善できる箇所の調査

Slide 19

Slide 19 text

性能の改善 19 ● スロークエリの実行計画をとって適切なインデックスの作成 ● ある画面クラスで同じクエリに対する結果をメモ化 ● まとめられるクエリをまとめ、結果を使っていないクエリを削除することで 発行回数を減らしDB負荷を低減 ● 入れ子集合モデルで表現された木構造から複数のパスを取得するクエリの チューニング ● etc. 基本的には愚直に10ms〜100ms程度の削減を積み重ねて改善 SQLの改善

Slide 20

Slide 20 text

性能の改善 20 ● Amazon ElastiCache for Memcachedでサーバサイドキャッシュ ○ 当初から「イチオシ商品」など即時性が必須でないデータはサーバサイドでキャッシュし ていたが、ローカルのファイルにキャッシュを保存するCache_Liteという PEARパッケージを使った方式のまま運用 ○ 今となってはショップページが稼働するVMは数十台あり、ローカルのファイルを 使うと高いキャッシュヒット率は望み薄なので、Memcachedにすべて置き換え RDBのIOの削減

Slide 21

Slide 21 text

性能の改善 21 ● Redashでダッシュボードを作成して、チームで毎日性能を確認 改善効果の確認

Slide 22

Slide 22 text

健全性の改善 22

Slide 23

Slide 23 text

健全性の改善 23 ● 読みやすく変更しやすいコード ○ Webアプリケーション開発一般の知見を適用しており設計やコーディングの質が高い ○ テストカバレッジが高く変更しやすい ● mainブランチはつねに安全にデプロイ可能 健全なコードベースとは

Slide 24

Slide 24 text

健全性の改善 24 ● 健全性を改善するとコードの変更しやすさ、デプロイの安全性が向上 ● デプロイ頻度、リードタイム、MTTR、変更失敗率の改善はサービス自体の競争力 強化につながる ○ 『LeanとDevOpsの科学』のfour key metrics ● 性能改善も容易に 健全性を改善してサービスの競争力強化につながる

Slide 25

Slide 25 text

健全性の改善 25 ● 画面クラスのトランザクションスクリプトから改善するデータ取得ロジックを クラスとして切り出し ● ある種のゲートウェイクラス ○ 取得データに対応するSQLの隠蔽 ○ PDOのラッパー: データ取得、加工と結果のarray組み立て ○ 複数の画面クラスから利用可能 ● このクラスに対してPHPUnitでテストを作成 ○ データ取得ロジックを細かくテスト ○ 徐々に取り組みを進めてコードベース全体のカバレッジは24.0%から29.4%に増加 テストが書きやすく再利用しやすい構造にしながら改善する

Slide 26

Slide 26 text

健全性の改善 26 ● 静的解析ツールで実装上のミスや型を実装時に発見してコードの質を向上 ● レガシーコードに対しても使いやすいPhanを利用 (※) ○ いきなりコードベース全体を検知パターンに沿うように直すのは難しい ○ 既存コードではプリセットの検知パターンの違反をいったん無視 (baseline.php) ○ 新規コードに対しては必ずチェック ● 新規コードではPHPDocでプロパティや関数に型を記述してチェック ● コミットpush時にCIで毎回実行 (※) cf. Tutorial for Analyzing a Large Sloppy Code Base · phan/phan Wiki https://github.com/phan/phan/wiki/Tutorial-for-Analyzing-a-Large-Sloppy-Code-Base 静的解析で実装時に問題を発見する

Slide 27

Slide 27 text

健全性の改善 27 ● 性能改善の変更で稼働率を下げないように稼働率を維持するための施策も実施 ● stagingデプロイ時に別リポジトリのE2Eテストを実行 ○ RSpec+Capybara ○ GitHub ActionsでデプロイワークフローからE2E実行ワークフローをトリガー 稼働率を維持するための改善 E2Eテスト ワークフロー stagingデプロイ ワークフロー 2. POSTリクエストでE2Eテストのワークフローをトリガー staging環境 1.デプロイ 3. テスト実行

Slide 28

Slide 28 text

健全性の改善 28 ● SlackからCapistranoのdeploy:rollbackを実行可能に ○ 本番にデプロイしてから障害が見つかったときの復旧を早める ○ cf. GitHub ActionsをSlack Botから起動する https://ten-snapon.com/archives/2625 稼働率を維持するための改善

Slide 29

Slide 29 text

改善の結果 29

Slide 30

Slide 30 text

改善の結果 30 ● 95%tileレスポンス時間を960ms→530msに削減 ○ ショップのCVRにもよい影響がある可能性 95%tileレスポンス時間が約44%高速化

Slide 31

Slide 31 text

改善の結果 31 ● コード変更回数を増やしつつも、10/1から昨日12/8までの稼働率は99.91%まで 向上 ○ 目標まであと0.04%…… ○ 2021年3Qは20回デプロイだが、2021年4Qはすでに52回デプロイ ○ アクセス過集中での障害が減少し、3Qと比較してダウンタイムを約280分/月から 約39分/月まで削減 月平均稼働時間が約240分増加

Slide 32

Slide 32 text

まとめ 32

Slide 33

Slide 33 text

まとめ ● レガシーなWebアプリケーションであるショップページの性能/健全性の インクリメンタルな改善に2021年4Qに2か月強のあいだチームで取り組んだ ○ 95%tileレスポンス時間を約44%改善 ○ 稼働率を99.36%から99.91%に改善 ○ テストカバレッジ向上や静的解析で健全性を向上し、安全なコード変更を担保 ○ E2Eテストやロールバックの簡易化など稼働率を維持するための仕組みの導入 ● 今後の課題 ○ より本番環境に近い負荷試験環境 ○ 大規模テーブルのクエリチューニングのための仕組みづくり ○ PHPバージョンアップによる性能改善 ○ デプロイ後障害検出時の自動ロールバック 33