ヤフー株式会社より出向しております、卯田と申します。
主務で、一休.comおよびYahoo!トラベルのフロントエンド開発を担当しています。
兼務で、ヤフー株式会社の全社横断組織でWebパフォーマンス改善の推進を行っております。
本稿では、直近半年弱(2023年2月〜8月)で、断続的に行っていた一休.comのパフォーマンス改善について振り返ります。
開始が2023年2月となった理由は、Nuxt3バージョンアップ以降にパフォーマンス改善活動に着手したためです。
一休.com/Yahoo!トラベルのNuxt3バージョンアップ詳細については、以下のブログをご覧ください。
サイトパフォーマンス改善の意義
サイトパフォーマンスは、「お客様に上質な体験を提供するための重要非機能要件」と考えています。 一休.comは、「心に贅沢を」をコンセプトに宿泊予約サイトを提供しております。 こちらのコンセプトのもと、便利な機能やUIをお客様に提供したいという気持ちで日々開発しており、パフォーマンスに関しても同じです。 お客様に気持ちよくサイトをご利用いただくためにも、パフォーマンスを維持することは非常に重要であると考えています。
改善の方針
方針1: Core Web Vitalsを改善する
パフォーマンス改善の指標は、サイト全体のCore Web Vitals(フィールドデータのLCP・FID・CLS)としました。
PageSpeed Insightで示すと、赤枠の箇所です。
GoogleではCore Web Vitalsを以下のように定義しています。
Core Web Vitals は、Web 上で実際にユーザーが体験するユーザー エクスペリエンスに関する重要な観点の測定を目的とした一連のフィールド指標(データ)です。 Core Web Vitals には指標と各指標のターゲットとなるしきい値が含まれており、これらを参考にすることで、運営するサイトでのユーザー体験が "良い"、"改善が必要"、"悪い" のいずれの状態にあるかを開発者が定性的に理解できるようになります。
引用: https://web.dev/i18n/ja/defining-core-web-vitals-thresholds/
LCP : Largest Contentful Paint (最大視覚コンテンツの表示時間): 読み込みのパフォーマンスを測定するための指標です。優れたユーザー エクスペリエンスを提供するためには、ページの読み込みが開始されてからの LCP を 2.5 秒以内にする必要があります。
FID : First Input Delay (初回入力までの遅延時間): インタラクティブ性を測定するための指標です。優れたユーザー エクスペリエンスを提供するためには、ページの FID を 100 ミリ秒以下にする必要があります。
CLS : Cumulative Layout Shift (累積レイアウト シフト数): 視覚的な安定性を測定するための指標です。優れたユーザー エクスペリエンスを提供するためには、ページの CLS を 0.1 以下に維持する必要があります。
引用: https://web.dev/i18n/ja/vitals/
また、Core Web Vitalsを改善するためのパフォーマンス指標として、ラボデータ(synthetic monitoringと言う場合もあります)とフィールドデータ(RUMと言う場合もあります)の2種類を提供しています。
Lab data: Lab data is determined by loading a web page in a controlled environment with a predefined set of network and device conditions. These conditions are known as a lab environment, sometimes also referred to as a synthetic environment.
Field data: Field data is determined by monitoring all users who visit a page and measuring a given set of performance metrics for each one of those users' individual experiences. Because field data is based on real-user visits, it reflects the actual devices, network conditions, and geographic locations of your users.
引用: https://web.dev/lab-and-field-data-differences
ラボデータとフィールドデータの関係において重要なことは、フィールドデータの改善が主たる目的であり、ラボデータは、あくまでフィールドデータを改善するための補足情報であるということです。 ユーザー体験を改善することが目的であることを鑑みると、フィールドデータ、さらにはその中でも最も重要と位置付けているCore Web Vitalsが、改善すべきパフォーマンス指標として適切です。 もちろん、Core Web Vitalsを改善するためにブレイクダウンした値として、ラボデータのスコアを改善指標とすることもできますが、あくまで参考程度に留めています。
Core Web Vitalsの3つの指標のバランスも重要と考えています。 LCP、FID、CLSの特定の指標が非常に良い状態を目指すのではなく、3つの指標が満遍なく良好な状態を目指しています。
Core Web Vitalsの良好に関する詳細は、以下のページをご覧ください。 web.dev
方針2: 重要課題から優先的に対応する
パフォーマンス改善は、重要課題(大きく改善が見込める領域)から取り組むことで効率的に改善できます。 改善に時間をかけたにも関わらず、対して改善しなかったでは意味がありません。 特に、解決したい課題を理解せずにTipsベースで取り組むのは注意です。 「xxxでCore Web Vitalsが改善しました!」という記事をみて、同じ手法を取り入れてみたが、イマイチだったという経験がある方もいらっしゃるかもしれません(私もあります)。 そのようなことにならないよう、重要な課題から優先して対処していくことを念頭におきました。 もちろん、全て正しいアプローチで進められたわけではありませんが、常にチーム内で心掛けていました。
改善の進め方
可視化
まずは、重要課題を把握するために、現状を可視化しました。
ブラウザサイド
ブラウザサイドのパフォーマンス可視化には、GoogleのCodelabsに掲載されている資料を参考に、Looker Studioのダッシュボードを作成しました。
こちらがとても役立っています。データ量次第では、無料で構築可能です。
サーバーサイド
サーバーサイドのパフォーマンス可視化には、もともと一休で導入しているDatadogのダッシュボードとトレース機能を利用しました。
全体把握には、ダッシュボードを使って、サーバーサイドのレイテンシを可視化しています。
以下はダッシュボードに載せている図の一例で、レスポンスの75パーセンタイル値の推移です
より詳細を見るためにはトレース機能を活用します。 デフォルトのプリセットで、ある程度のタスクをトレースできます。 デフォルトで表示されないタスクのトレースを試みたい場合、Node.jsであればdd-trace-jsのwrap関数で、traceしたい処理をwrapします。
以下は、東京のホテル・旅館のリクエストをトレースしている図です。赤枠は詳細トレース用にwrapしたApollo Clientのキャッシュ計算処理です。 そのほかにもネットワークリクエストの流れを把握できます。
優先順位決め
可視化したところ、3つの指標で特にスマートフォンのCLSが不良でした。 そこでまずはスマートフォンのCLS改善に取り組みました。 次に、LCPも良好ではなかったためLCPの改善に取り組みました。 FIDは良好であった、かつ、2024年3月にINPに置き換わるということが2023年3月に周知されたため、後対応としました。
INPは、ユーザー操作に適切に画面が反応できているかを示す指標です。 不良な状態であるということはユーザーの操作を阻害していることを意味しています。 したがって、Core Web Vitalsが2024年3月に置き換わるタイミングに関わらず、可能であれば早急に改善したい課題です。 ただし、今後計測のエコシステムが整ってくるだろうという楽観的な希望もあり、節足に取り組むことはしないとチームで判断しました。
- INP - web.dev
- 2023年8月執筆時点で、Chromiumのみが対応 - w3
- 別タブでリンクを開いた際に、極端にスコアが悪くなってしまう問題- chromium
- INP変更履歴 - chromium
具体的な改善内容
具体的な改善内容を、改善に取り組んだ時系列、CLS、LCP、FIDの順に紹介します。
※パフォーマンス改善で実施した改善施策のうち、分かりやすい施策を中心に紹介します。
CLSの改善
上記で作成したダッシュボードを利用することで、
- どのDOM要素が
- どれくらいの頻度で
- どれくらいの大きさ
レイアウトシフトしているかを一覧できるようになりました。
下の画像左のプロット図をご覧ください。
右上にプロットされている点が、頻度が高く、大きくレイアウトシフトしているDOM要素を示しています。
この図に従い、右上に存在するDOM要素から順にレイアウトシフトを特定し、改善を施していきました。
以下に、分かりやすい事例として2つ、頻繁に起きていた事例と大きくレイアウトシフトしていた事例を紹介します。
こだわり条件更新時に発生するレイアウトシフト
こだわり条件を変更した際に、「夕朝食付」が消え、「エリア・駅名・キーワード」が表示されます。 この一瞬で、検索フォームの高さが変わることでレイアウトシフトが発生していました。 レイアウトシフトの大きさとしてはそこまでですが、頻繁に発生している事例です。 色々な条件で検索するお客様にとっては、度々ガタついており、目障りな印象を抱いていたかもしれません。
クチコミ画像表示時に発生するレイアウトシフト
お客様の投稿したクチコミ画像を表示するモーダルです。
画像領域の高さ指定をしていなかったため、画像読み込みの間、領域の高さが0となっていました。
頻度は低いですが、レイアウトシフトの大きさとしては非常に大きい事例です。
※図はYahoo!トラベルですが、一休.comでも同様です。
LCPの改善
リソースの読み込み順序の改善
ChromeのDev Toolsのネットワークタブで、リクエストウォーターフォールを確認したところ、大量のJavaScriptとCSSをpreloadしており、LCPとなる画像を取得するタイミングが遅れていました。
LCP画像にResource Hints を定義することで一定の改善も見込めますが、ページごとに個別最適した実装が必要で少し手間がかかります。 より、サイト全体に効果があるアプローチとして、すでにNuxt3のGitHubで議論されており、解決方法まで示されていたので、こちらを先に採用することにしました。 結果的には、この改善はNuxt3で動いているページ全体への効果が非常に大きく、計測ページ全体で、400msほどLCP改善しました。 本修正は、JavaScriptのloadを遅らせるため、FIDに悪い影響が出る懸念もありましたが、結果的には問題ありませんでした。
※上記課題は、2023年8月25日リリースのNuxt 3.7experimental機能でも改善が図られています。以下のように、headNext機能を有効化することで検証できます。
export default defineNuxtConfig({ experimental: { headNext: true } })
documentのgzip圧縮
Nuxt3ではdocumentのgzip圧縮をできていませんでした。 そこで、Nuxt3が採用しているhttpフレームワークのunjs/h3のpatchを独自で用意しました。 レスポンス直前にdocumentをgzip圧縮する処理を追加しています。
サーバーサイドKeep Aliveの実装
DNS LookupやTCP connectionをバックエンドへのリクエストの度に行っていたため、HTTP(S)/1.1 KeepAliveの実装をしました。
※Node.jsのバージョン19からデフォルトで有効になる機能です。
SQLの最適化
非常に遅いSQLです。 SQLの実行のみで1.4秒近く時間を要しています。
対象のデータベースはSQL Serverを使っています。 SQL Server Managementで実行プランを確認し、不足しているインデックス情報に従い、インデックスを設定し直すことで改善しました。
検索システムのバージョンアップ
検索システムにはApache Solrを使っています。 古いバージョンのApache Solrを使用していたため、まずは改善土壌を整えるべくバックエンドチームが4月から3ヶ月ほどかけてバージョンアップを行いました。 バージョンアップを行う過程でDeprecatedとなったFieldTypeを改修したところ、検索システムのレイテンシが劇的に改善しました。
Solrのドキュメントにレイテンシが改善すると記載されてはいたものの、正直想定していた以上の結果だったとのことです。
(嬉しい誤算ですね。こういうこともあります。 )
結果として、検索システムを呼び出している画面のLCPが200ms改善しました。
FIDの改善
上述の通り、FIDは当初より良好であったことと、INPへと置き換わることが周知されており、後対応としました。 今後は、INPの改善に取り組んでいきたいと考えています。
LCP、FID、CLS、3つの指標が良好になった後
機能開発で、パフォーマンスが悪化することもあります。 週1で、パフォーマンスチェックをする機会を設け、惰性で悪化することを防ぎました。 悪化した場合には、Looker Studioのダッシュボード、Datadog、一週間のコミットを照らし合わせ、改善しています。 幸い、一休.comのフロントエンド開発ではビッグバンになるようなリリースが極めて稀なため、変更コード量も限られており、悪化したとしても原因を特定することには苦労していません。
結果
下図は、直近6ヶ月弱のCore Web Vitalsの推移です。 線が下に行くほど、良い状態を示しています。 2月,3月の改善着手初期でCLSとLCPが大きく改善し、以降、3つの指標が要改善となるのを防ぎつつ、SolrのバージョンアップでLCPがさらに改善しました。 CLSは、良好の範囲内で一時的に悪化しておりましたが、作業時間を確保できたところで改善を施し、元の水準までスコアを戻しています。
Googleが毎月更新しているChrome User Experience Reportでもフィールドデータの大まかな傾向を確認できます。
下図は、一休.comのCrUXダッシュボードです。 緑色の領域が良好を示しています。 2022年11月に比べ、2023年8月では良好の割合が増えていることが確認できます。
一休.comのCrUXダッシュボードの詳細は、以下のページでもご覧いただけます。 lookerstudio.google.com
今後
CLSの改善
CLSは、この6ヶ月間でも、機能改修で、幾度か悪化することがありました。 引き続き監視しつつ、良好を維持できるようにします。
LCPの改善
スタイルの計算とレンダリングの最適化
スタイルの計算とレンダリングに時間がかかっています。
CSSセレクタのパフォーマンスをMicrosoft Edgeのパフォーマンスツールで確認したところ、*, ::after, ::before のCSS変数の計算が大半を占めていました。
CSS変数を埋め込んでいるのは、Tailwindのベーススタイルです。 GitHubで検索してみたところ、Tailwindのリポジトリで同様の議論をしていました。
DOMサイズが大きい場合に顕著に悪化する問題で、一休.comは全体的に初期表示時のDOMサイズが大きいサイトなため影響を受けています。
最もDOMサイズが大きいトップページで、開発環境検証してみたところ、スタイル計算のパフォーマンスが改善されることを確認できました。 他のページへの修正影響を確認した上でリリースしたいと考えています。
また、ブラウザサイドでの画像のリクエストタイミングが、最善ではありません。 LCPの値が好ましくない、かつ、お客様訪問の多いページ・デバイスからResource HintsもしくはFetch Priority属性を実装し、改善を図りたいです。
Apollo Clientのキャッシュ計算処理
Apollo Clientのキャッシュ計算処理に時間を要しています。 実装改修コストも非常に高いですが、重要なページから異なるGraphQL Clientへの移行を始めています。
算出方法の改善期待
Googleでは"soft navigations"に関するLCP算出方法の変更を検討しています。 一休.comでは"soft navigations"を多用しており、ユーザーが体験したパフォーマンスにより近くなると期待しています。
FID(INP)の改善
CLSで行った手法同様、まずはINPの計測環境を整備します。 そして、不良かつ頻繁に発生するイベント(動作)を特定し、改善に最も効く重要なイベントから最優先で改善を行っていきたいです。
パフォーマンス改善によるビジネス貢献
現状、パフォーマンス改善によって、どれだけビジネスに貢献できたは把握できておりません。 ヤフー株式会社の全社横断組織にて、ビジネス指標(直帰率、離脱率、コンバージョン率など)とパフォーマンスの相関を計測する環境が整備されつつあるので、 次は、Yahoo!トラベルで、ビジネス貢献にもつながるパフォーマンス改善に取り組んでいきたいです。
最後に
株式会社一休では、上質なウェブ体験を一緒に実現してくださる方を絶賛募集していますo(^▽^)o~♪
一緒に、宿泊・飲食予約の未来を作りましょう!