Share
Embed
レガシーWebアプリケーションの性能とコードの健全性をインクリメンタルに改善する / pepabotech-20211209
ペパボテックカンファレンス #18の発表資料です
Kōhei Yamamoto (山本浩平)
December 09, 2021
Transcript
33 slides
-
自己紹介 2 • @kymmt90 • EC事業部 ECグループ DXチーム • 主にカラーミーショップを構成するさまざまなWebアプリケーションを横断した
改善活動や開発体験の向上などに従事 山本浩平 -
• ページごとに対応するテンプレート(カスタマイズ可)が存在 • テンプレートとその中で利用するデータなどをデータストアから取得し、必要に 応じてデータを加工 • データとテンプレートからHTMLをレンダリングしてレスポンス カラーミーショップのショップページとその課題 6 ショップページというWebアプリケーション
RDB/KVS/ ファイル ショップページ アプリケーション ブラウザ 商品ページの リクエスト/レスポンス テンプレート、商品データ などを取得 取得したデータの加工 -
カラーミーショップのショップページとその課題 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 課題: ショップページ稼働率の低下 -
カラーミーショップのショップページとその課題 8 • サービスのコアともいえるアプリケーションだが、開発におけるフィードバックと 改善のサイクルが不足気味 ◦ テンプレートに応じたページのレンダリングが主目的で、機能要件はすでに満たして おり、継続的に開発する機会が発生してこなかった ◦ 一方で継続的に開発しないと継続的な改善も行われづらい
◦ 結果、古いコードが残って性能や健全性に難があり、テストカバレッジも不十分 • 稼働率の高さが求められる一方で、積極的な改善がためらわれがち なぜこれまで改善できなかったか -
カラーミーショップのショップページとその課題 9 • 目標: 2021年4Qのショップページ稼働率を99.95%以上にする ◦ 約21分/月のダウンタイム • 方針 ◦
“パフォーマンス改善に専属で取り組むチーム”を組織 (※) ▪ ショップページは@hrysd、@takapi、@kymmtからなるチームが担当 ◦ 「カートに入れる」ボタンが存在する商品詳細ページの95%tileレスポンス時間を改善の 指標として選択 ▪ 性能に関する変更のフィードバックを得るための指標としてわかりやすく、性能改善により 稼働率向上にもつながるため ◦ 性能改善作業の失敗による稼働率低下をコードベースの健全性向上や仕組みづくりで防止 (※) 安定したサービス提供のための改善レポート【2021年7〜9月】 - よむよむカラーミー https://shop-pro.jp/yomyom-colorme/80571 2021年3Qの課題を踏まえて改善する -
ショップページを例としたレガシー Webアプリケーションの改善 11 • アプリケーション: vendor supportedなPHP 5.4 + php-fpm
◦ プライベートクラウド上のVMで稼働 ◦ Webアプリケーションフレームワークなし ◦ Capistranoでデプロイ • RDB: Amazon RDS for MySQL (5.7) • KVS: Amazon ElastiCache for Memcached 現状のアーキテクチャ -
ショップページを例としたレガシー Webアプリケーションの改善 13 • 画面クラスはほぼトランザクションスクリプト ◦ データ取得、計算、テンプレート変数の設定までを担う関数を持つ ◦ ルーティング層で必要な画面クラスのインスタンスを生成し、その関数を呼び出すイメージ 現状のアプリケーション設計
<?php switch($_GET['mode']) { case 'product': $view = new ProductView(...); $view->fetchData(...); break; case 'category': // … } $view->render(); <?php class ProductView extends BaseView { public function fetchData(...) { // データ取得、計算、テンプレート変数設定 // など該当の画面に関係する処理 } } 呼び出し 画面クラス -
ショップページを例としたレガシー Webアプリケーションの改善 14 • アプリケーションの95%tileレスポンス時間が約960ms ◦ “サイト表示が0.1秒遅くなると、売り上げが1%減少” という話から速いとはいえない • クエリ発行回数が多いという特徴
◦ スロークエリの発生 ◦ 一連のトランザクションスクリプトで同じ結果を取得するクエリを重複して発行 ◦ テンプレート編集機能で利用できる独自タグのためのさまざまなデータ取得にも起因 ▪ 商品詳細ページだけで使える独自タグでも30種類存在 技術的な課題: 性能 -
ショップページを例としたレガシー Webアプリケーションの改善 15 • ロジックやクエリの重複が複数の画面クラス間で発生 ◦ トランザクションスクリプトのデメリットそのもの • クラスの構造やコーディング作法に起因して可読性に難あり ◦
2,000行超えの巨大なクラス、文字列連結でベタ書きのSQL、処理内容と合っていない 関数名など • 低いテストカバレッジ ◦ 24.0% ◦ 画面クラスに対してPHPUnitでざっくり書かれたテストは存在 技術的な課題: 設計と健全性 -
ショップページを例としたレガシー Webアプリケーションの改善 16 • 細かい粒度での改善と検証を繰り返して変更失敗のリスクを軽減 • 改善の1サイクルは1週間 ◦ 現状のアプリケーションの振る舞いを観察して、優先して改善すべき点を3つ選択 ◦
1週間手分けして改善に取り組み、性能の状況を毎日確認 ◦ 作業してみて健全でないことに気づいたコードの改善も徐々にタスク化 • 健全でないコードの改善を並行して進めることで安全かつ高速な性能改善を実現 ◦ テスト追加、静的解析、稼働率維持の仕組み 進めかた: インクリメンタルな改善のサイクルを回す -
性能の改善 18 • APM (application performance monitoring)ツールを利用するのが楽 ◦ 今回の取り組みではNew Relicを利用
• 遅いエンドポイント実行のトレースを採取してくれるので、それらを確認 ◦ アプリケーションレベルのトレースである関数呼び出しにかかる時間を確認 ◦ 遅いエンドポイントで発生しているスロークエリを確認 性能改善できる箇所の調査 -
性能の改善 19 • スロークエリの実行計画をとって適切なインデックスの作成 • ある画面クラスで同じクエリに対する結果をメモ化 • まとめられるクエリをまとめ、結果を使っていないクエリを削除することで 発行回数を減らしDB負荷を低減 •
入れ子集合モデルで表現された木構造から複数のパスを取得するクエリの チューニング • etc. 基本的には愚直に10ms〜100ms程度の削減を積み重ねて改善 SQLの改善 -
性能の改善 20 • Amazon ElastiCache for Memcachedでサーバサイドキャッシュ ◦ 当初から「イチオシ商品」など即時性が必須でないデータはサーバサイドでキャッシュし ていたが、ローカルのファイルにキャッシュを保存するCache_Liteという
PEARパッケージを使った方式のまま運用 ◦ 今となってはショップページが稼働するVMは数十台あり、ローカルのファイルを 使うと高いキャッシュヒット率は望み薄なので、Memcachedにすべて置き換え RDBのIOの削減 -
健全性の改善 24 • 健全性を改善するとコードの変更しやすさ、デプロイの安全性が向上 • デプロイ頻度、リードタイム、MTTR、変更失敗率の改善はサービス自体の競争力 強化につながる ◦ 『LeanとDevOpsの科学』のfour key
metrics • 性能改善も容易に 健全性を改善してサービスの競争力強化につながる -
健全性の改善 25 • 画面クラスのトランザクションスクリプトから改善するデータ取得ロジックを クラスとして切り出し • ある種のゲートウェイクラス ◦ 取得データに対応するSQLの隠蔽 ◦
PDOのラッパー: データ取得、加工と結果のarray組み立て ◦ 複数の画面クラスから利用可能 • このクラスに対してPHPUnitでテストを作成 ◦ データ取得ロジックを細かくテスト ◦ 徐々に取り組みを進めてコードベース全体のカバレッジは24.0%から29.4%に増加 テストが書きやすく再利用しやすい構造にしながら改善する -
健全性の改善 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 静的解析で実装時に問題を発見する -
健全性の改善 27 • 性能改善の変更で稼働率を下げないように稼働率を維持するための施策も実施 • stagingデプロイ時に別リポジトリのE2Eテストを実行 ◦ RSpec+Capybara ◦ GitHub
ActionsでデプロイワークフローからE2E実行ワークフローをトリガー 稼働率を維持するための改善 E2Eテスト ワークフロー stagingデプロイ ワークフロー 2. POSTリクエストでE2Eテストのワークフローをトリガー staging環境 1.デプロイ 3. テスト実行 -
健全性の改善 28 • SlackからCapistranoのdeploy:rollbackを実行可能に ◦ 本番にデプロイしてから障害が見つかったときの復旧を早める ◦ cf. GitHub ActionsをSlack
Botから起動する https://ten-snapon.com/archives/2625 稼働率を維持するための改善 -
改善の結果 31 • コード変更回数を増やしつつも、10/1から昨日12/8までの稼働率は99.91%まで 向上 ◦ 目標まであと0.04%…… ◦ 2021年3Qは20回デプロイだが、2021年4Qはすでに52回デプロイ ◦
アクセス過集中での障害が減少し、3Qと比較してダウンタイムを約280分/月から 約39分/月まで削減 月平均稼働時間が約240分増加 -
まとめ • レガシーなWebアプリケーションであるショップページの性能/健全性の インクリメンタルな改善に2021年4Qに2か月強のあいだチームで取り組んだ ◦ 95%tileレスポンス時間を約44%改善 ◦ 稼働率を99.36%から99.91%に改善 ◦ テストカバレッジ向上や静的解析で健全性を向上し、安全なコード変更を担保
◦ E2Eテストやロールバックの簡易化など稼働率を維持するための仕組みの導入 • 今後の課題 ◦ より本番環境に近い負荷試験環境 ◦ 大規模テーブルのクエリチューニングのための仕組みづくり ◦ PHPバージョンアップによる性能改善 ◦ デプロイ後障害検出時の自動ロールバック 33