一休.com Developers Blog

一休のエンジニア、デザイナー、ディレクターが情報を発信していきます

一休.comホテルリストの表示速度を従来比2倍にしました

宿泊事業本部フロントエンドエンジニアの宇都宮です。

2018年度下期は、一休.comホテルリストページ スマホ版の速度改善に取り組んできました。その結果、ページのデザインはそのまま、機能面はリッチにしつつ、プロジェクト開始前の約2倍のスピードでページが表示されるようになりました。

本記事では、高速化のためにどのような施策を行ったのか紹介します。

なお、Webサイトの高速化手法については、ホテル詳細ページ高速化プロジェクトを実施した際にも記事を書いています。これらの記事で紹介している手法(たとえば、Imgixによる画像最適化等)については、記述を省略しています。あわせてご覧ください。

また、今回高速化の対象としたホテルリストページには以下のURLでアクセスできます(スマホで開くか、PCの場合はUAをスマホに偽装する必要があります)

https://www.ikyu.com/sd/tokyo/140000/

f:id:ryo-utsunomiya:20190227130107p:plain:w320
ホテルリストページ

プロジェクト開始前の状況

下記画像は、プロジェクト開始前の、PageSpeed Insightsの計測結果です。

f:id:ryo-utsunomiya:20190227094518p:plain:w480
PageSpeed Insights: プロジェクト開始前

パフォーマンス監視SaaSのCalibreでは、計測結果は以下のようになっていました。

f:id:ryo-utsunomiya:20190227094944p:plain:w480
Calibre: プロジェクト開始前

遅い4G回線相当の設定(下り1.4Mbps)とはいえ、Time to Interactiveに10秒以上かかっているのは遅いです。主要な指標(First Meaningful Paint、Speed Index、Time to Interactive)をそれぞれ半分にして、FMP 2秒、Speed Index 2.5秒、Time To Interactive 5.5秒くらいになれば、低速回線でもある程度快適に使えるサイトといえるでしょう。

改善結果

PageSpeed Insightsのスコアは従来の2倍以上になりました。

f:id:ryo-utsunomiya:20190227154455p:plain:w480
PageSpeed Insights: 改善後

Calibreでも指標が軒並み改善しています。TTIがFMPやSpeed Indexよりも早くなっているのは、APIレスポンス待ちでCPUがIdleになっているタイミングがあるからだと思われます。FMPまでの時間は半減しています。Speed Indexも改善していますが、もう一声ほしいところ。

f:id:ryo-utsunomiya:20190227155059p:plain:w480
Calibre: 改善後

計測が難しいため直接の指標にはしていませんでしたが、トップ(https://www.ikyu.com/sd/) => ホテルリストの遷移スピードは、体感的にはかなり速くなったように感じられます。これはFirst Contentful Paintの大幅改善(3.69s => 0.65s)が効いていそうです。


(2019-03-04 追記)本記事の「改善後」よりさらに高速化しました。2019-03-04現在のパフォーマンスは下記記事を参照してください:

user-first.ikyu.co.jp

やったこと

パフォーマンス目標値の設定

高速化を実施するには、まず目標となる値を設定する必要があります。目標設定に際しては、(1) 自分たちのサイトの要件から実現可能な数値であること (2) 競合と比較して遅くないこと の2点が重要だと考えています。また、可能であれば、競合よりも速くして、速度で差別化できると、なお良いでしょう。

今回のプロジェクトでは、Expediaのスマホ向けリストページをベンチマークにして、高速化に取り組みました。

このページは高度な最適化を施されており、PageSpeed Insightsのスコアは63点と、競合の中でも群を抜いて速いページです。

f:id:ryo-utsunomiya:20190227095620p:plain:w480
PageSpeed Insights: Expedia

そこで、今回のプロジェクトでは、以下の2段階のゴールを設けて高速化に取り組みました。

  1. PageSpeed Insights 50点以上、主要指標(FMP/Speed Index/TTI)で20%以上の改善
  2. PageSpeed Insights 65点以上、TTI 5秒以内

1は最低限達成したいゴール、2はややチャレンジングなゴールです。

施策1: ikyu-analytics-clientの最適化

一休では、アクセス解析ツールを内製しています。これはikyu-analyticsと呼ばれ、一休.com等では、ikyu-analyticsのクライアントライブラリを読み込んで使用しています。

パフォーマンスの観点で、ikyu-analytics-clientには大きな問題がありました。アクセスログの記録を 同期XHRで 行っていたのです。

私自身、Firefox等で、メインスレッド同期XHRのDeprecation Warningが出ていることは以前から認識していました。が、これがどの程度悪影響を及ぼしているのかは、分かっていませんでした。

しかし、昨年12月、WebPageTestのBlock機能を使ってikyu-analytics-clientの読み込みを行わないようにしたところ、ページの読み込み完了までの時間が約1秒短くなることに気づきました。

ikyu-analytics-clientのJSはサイズも小さく、やってることもアクセスログの送信程度です。したがって、ikyu-analytics-clientがもたらしている遅延のほとんどは、同期XHRのレスポンス待ち時間だと推測しました。

そこで、データサイエンス部と連携して、ikyu-analytics-clientのアクセスログ送信の非同期化に取り組みました。

具体的には、 navigator.sendBeacon()が使える場合はこれを使い、使えない場合は非同期XHRを行うようにしました。

navigator.sendBeacon() は比較的新しいAPIで、iOSでは11.1以上でないと使えません。非同期なデータ送信を確実に行えるという、非同期XHRにはない特長を持っています。一方、非同期XHRは送信中に画面を遷移したりページを閉じたりすると送信がキャンセルされます。これについては、データサイエンス部と協議し、非同期XHRのキャンセルによるログの送信失敗は許容する、という合意を取りました。

ikyu-analytics-clientの非同期化後、PageSpeed Insightsのスコアは10点改善しました。

f:id:ryo-utsunomiya:20190227103703p:plain:w480
PageSpeed Insights: ikyu-analytics-client非同期化後

ikyu-analytics-clientは、一休.comの全ページのみならず、一休レストランなどでも使用されているため、一休が運営しているサービス全体で、読み込み完了が1秒速くなりました。

逆にいうと、ikyu-analytics-clientが同期XHRを使っていたことで、一休のサービス全体が1秒遅くなっていたということです。 ブラウザのWarningにはちゃんと耳を傾けるべし という教訓を得られました。

施策2: コードの大幅な書き直し

ホテルリストページは、従来、ASP.NET WebForms + jQueryというスタックで実装されていました。これらを、ホテルページと同様、ASP.NET MVC(+Web API) + Vue.jsというスタックに置き換えました。

また、機能面では、検索実行時に毎回画面遷移していたのを改め、ページ内で再検索が行われるようにしました。

速いJavaScript/Vue.jsアプリケーションを書くための方法については、下記記事に書いた内容を踏襲しているので、省略します。

一休.comスマホサイトのパフォーマンス改善(JavaScript編) - 一休.com Developers Blog

これに加えて、リストページの実装において特徴的なこととして、今回、Vuexは使いませんでした。

Vuexを使わなかった理由は、リストページはデータの流れがシンプル(検索APIからレスポンスを受け取り、描画するだけ)かつ、コンポーネントが素直なピラミッド構造になっていて、props down/events upで必要なデータの受け渡しを全て表現できたためです。また、Vuex Storeのコードは肥大化しがちなため、パフォーマンスの観点からの懸念もありました。

ただし、Vuexを完全に捨てたわけではなく、今後の改修で必要になれば、Vuexを導入する可能性はあります。

この書き直しによって、パフォーマンスは大きく改善しました。

f:id:ryo-utsunomiya:20190227105859p:plain:w480
PageSpeed Insights: リライト後

この時点で、ストレッチゴールの「PageSpeed Insights 65点以上」を達成できました 💪

施策3: 初回検索のAjax化

当初の目標を超えることができましたが、もう一押し改善できそうなポイントが残っていました。

施策2までの段階では、ページ初回表示時の検索処理は、サーバサイドで行っていました。SEOのためのtitleタグ、metaタグや、SNS等で共有する際に必要な情報(twitter card、facebook OGP等)を書くには検索結果を知っている必要があります。また、Botの中にはJavaScriptを実行しないものもあります。したがって、SEO関係のタグは、サーバサイドで書いて、初回レスポンスのHTMLに含める必要がありました。

一方、パフォーマンス改善の実験として、画面の初期表示時にサーバサイドで検索を行わずAjaxで行うようにしたところ、FCP/FMP/Time to Interactiveのそれぞれについて0.5秒程度の改善が見込めることがわかりました。

SEO関係のタグもJavaScriptで書くようにできれば、初回表示時に検索をサーバサイドで行う必要がなくなり、画面の初期表示はさらに速くできます。この問題の解決のためには、2つのアプローチが考えられました。1つはSSR、もう1つはDynamic Renderingです。

SSR(Nuxt.js)を採用しなかった理由

SSRを行って、JSの初回レンダリングの終わったHTMLを返すようにすれば、SEOの問題は解決します。

しかし、結論からいうと、SSR(Nuxt.js)は採用しませんでした。一休.comのアプリケーションの特性を考えると、SSRの導入によって遅くなる可能性が高いと考えたためです。

下記画像は先日Googleが公開したRendering on the Webというドキュメントから抜粋したものです。

f:id:ryo-utsunomiya:20190227112031p:plain
Rendering on the Web

Nuxt.jsを使ったSSRは、この表の「SSR with (Re)hydration」に該当します。一方、現行の実装は「Full CSR」です。この2つを見比べると、「SSR with (Re)hydration」の方がConsが増えているのがわかります。

SSR with (Re)hydrationは、サーバサイドでレンダリングを行い、フロントエンドでも、Full CSRと同等のリソースを読み込んで、状態の引き継ぎ(Rehydration)を行います。単純に考えると、Rehydrationの分、Full CSRと比べて計算量が増えます。

実際には、SSRを入れると遅くなるという単純な話ではなく、SSRでしかできない最適化を入れることで、Full CSRより速くできます。

しかし、「SSR後のHTMLをCDNでキャッシュする」という強力な最適化手法は、一休.comのアプリケーション要件では、あまり効果的ではありません。宿泊日程等の検索条件に応じて細かく画面を出し分ける必要があるため、同じHTMLを返却できるリクエストの数が多くないためです。

アプリケーション要件の見直しを行い、キャッシュフレンドリーな設計にすればSSRを導入する余地があります。しかし、今回のプロジェクトのスコープにはUIの刷新は含まれていません。現状の画面仕様では、Full CSRの方がパフォーマンスが出ると判断しました。

Dynamic Renderingの導入

Dynamic Renderingとは、bot向けに静的HTMLを配信する方法です。これによって、JS描画済みの静的HTMLをGooglebot等が取得するようになるので、metaタグ等をJSで書いても問題なくなります。

詳細は下記ドキュメントを参照してください。

ダイナミック レンダリングの使用方法  |  検索  |  Google Developers

一休では、Rendertronを使ってDynamic Renderingを行っています。 Rendertronの導入にあたっては色々苦労もあったようですが、これについては akasakas さんが書いてくれると思うので、ここでは詳しく触れません。

Dynamic Renderingによって、SEO関係のタグをJSで書く準備が整ったため、初回検索のAjax化に至りました。

この結果、冒頭の「改善結果」で紹介しているパフォーマンスが実現できました。

今後の展望

フロントエンドに関しては、パフォーマンス上のボトルネックのほとんどを解消した状態にもっていきました。一方、サーバサイドの検索APIについては、レスポンス速度がまちまちで、遅いときは1.5秒ほどかかることがあります。さらなる高速化のためには、検索APIの速度改善が必要そうです。

また、スムーズに宿泊施設を探すには、検索導線(トップ・リスト・ホテル)の全体的な回遊性が重要です。検索導線のSPA化等によって、ページ間のスムーズな移動を実現するような施策も検討しています。

We are hiring

hrmos.co