Amplitudeはエンジニアに優しい分析ツール
TECH
2022.12.27
2022.08.19
2022.12.23
自社プロダクトのフロントエンド開発に携わっている山口です。
社歴が長いこともあって関わったプロダクトはたくさんありますが、1番長く関わっているのはSEARCH WRITEです。
今回はSEARCH WRITEで行ったパフォーマンス改善の事例をご紹介します。
どのようなパフォーマンス計測と改善を行ったのか、改善後にどのくらいパフォーマンスが改善されたのか、計測結果とともに記録としてまとめました。
SEARCH WRITEはVue.jsで作られたSPAで、バックエンドとのデータのやり取りは全てVue.jsを介して行います。
今回のパフォーマンス改善の対象ページでは、Web APIから受け取ったデータをもとにバブルチャートとリストを表示していました。データの表現方法が異なるだけで、バブルチャートとリストで使用しているデータは同じです。
データの表示件数は最大1,000件で絞っていましたが、画面の表示が遅く動作も重たい状態でした。
バブルチャートのバブルは1,000個、リストは1,000行、見えていない範囲の要素も全てレンダリングしていて、無駄にレンダリングコストがかかっていたからです。
改善の必要性を感じながら新規開発を優先する日々を送っていましたが、ある日こんな要望が上がってきました。
「10,000件のデータが見たい!」という要望です。
1,000件でも厳しいのに10,000件も表示したらどうなるのか。現状のコードのままで表示件数を増やしてみたところ、ブラウザのタブが固まって閲覧不能になってしまいました。
LighthouseはGoogle Chromeの拡張機能で、Webページのパフォーマンスやアクセシビリティなどを分析することができます。
ブラウザのタブが固まる原因はDOMサイズだろうという予想はありましたが、数値として把握するためにLighthouseでパフォーマンス分析を行いました。
分析の結果、DOMサイズが28,030だということがわかりました。
LighthouseではDOMサイズが約800を超えると警告、約1,400を超えるとエラーになるため、28,030は論外の数値です。データの表示件数が1,000件でこの状態なので、10,000件にしたら桁がさらにひとつ増えてもおかしくありません。
要望を叶えるためには、兎にも角にもDOMサイズを減らす必要があります。
Site24x7のリアルユーザーモニタリング(以下、RUM)では、仮想環境ではなく実際のユーザー環境でのパフォーマンスをページ単位で確認できます。
データの表示件数を10,000件にできたとしても、レンダリング時間が長すぎるとユーザーが離脱してしまいます。改修前と改修後でレンダリング時間を比較できるように、RUMで対象ページの現状把握を行いました。
日によってバラつきはありますが、総じてドキュメントレンダリング時間(グラフの薄緑のエリア)が長いことがグラフから読み取れます。
99パーセンタイル値だけが大きい場合は、ごく一部のユーザー環境においてのみパフォーマンスが悪いと解釈できます。しかし、95パーセンタイル値で5秒以上、平均でも3秒以上かかっているため、大体のユーザー環境においてパフォーマンスが悪いことがわかりました。
ドキュメントレンダリング時間はDOMサイズが減ればおのずと短くなると考え、DOMサイズを減らすことに注力します。
DOMサイズを減らすための施策を2つ考え、期間は1ヶ月、1人月でパフォーマンス改善に着手しました。
画面に見えている範囲+αの要素のみをレンダリングする手法を、バーチャルレンダリングと呼びます。バーチャルリストはそれをリストに対して適用したものです。
見えていない範囲の要素はレンダリングしないため、データの表示件数が1,000件でも10,000件でもDOMサイズが変わらないというメリットがあります。
Vue.js用のバーチャルリストのプラグインはいくつか存在し、SEARCH WRITEではvue-virtual-scrollerを導入することにしました。
しかし、このプラグインとスクロールバーのデザインカスタマイズ用のプラグインが共存できない問題が発生しました。どちらのプラグインもリストアイテムをラップする親要素を生成するため、片方を動かすと片方が動かなくなるのも道理です。
このリストのスクロールバーだけブラウザのデフォルトにすることも考えましたが、他のリストのスクロールバーと見た目が異なるとデザインの一貫性が損なわれてしまいます。
最終的に、既存のスクロールバーのプラグインと見た目と挙動が同じ、かつバーチャルリストと連動するスクロールバーを自前で実装することで解決しました。
バーチャルリストを導入した結果、DOMサイズが28,030から3,642まで減りました。桁がいきなり1つ減って驚きましたが、まだエラーは出ている状態です。
SVG要素とその子要素はDOM要素として扱われるため、描画する要素が増えれば増えるほどDOMサイズが肥大します。バブルを10,000個描画すれば、それだけでDOMサイズが10,000になります。
一方、Canvas要素はバブルを何個描画してもDOM要素の数は1個なので、大量の要素を描画する場合はSVGよりもCanvasの方が適しています。
Chart.jsなどでバブルチャートを作ると最初からCanvasで描画してくれるのですが、このバブルチャートはD3.jsを使ってSVGで描画していました。
独自機能がたくさんあるため、Chart.jsなどのプラグインでは実装が困難だったからです。
今回はバブル以外の要素はSVGのままにして、バブル群のみをCanvasで描画することにしました。
バブルはデータの数に比例して増えますが、グラフの軸や目盛りは増えません。バブル以外の要素をCanvasに置き換えても、工数に対してパフォーマンス改善の効果が低いと判断しました。
D3.jsはデータを可視化するためのライブラリで、多種多様なAPIが用意されています。自由度がとても高く、必要なAPIだけを個別にimportして使うことができます。
今回はバブルの座標算出のみをD3.jsのAPIを使って行い、描画処理は標準のCanvas APIを使って実装しました。
出来上がったバブル群のCanvasをCSSでSVGの上に重ねたら、バブルチャートの完成です。
バブル群をCanvasにした結果、DOMサイズが3,642から838まで減りました。当初の28,030と比べると2桁違いです。
まだ警告が出ている状態ではありますが、エラー状態からは脱却できました。
データの表示件数を10,000件にして動作確認を行います。
改修前はブラウザのタブが固まっていましたが、改修後は固まることなく無事にページを閲覧できました。また、データの表示件数が1,000件でも10,000件でも、DOMサイズが変わらないことを確認できました。
改修分のリリース日時は2021年2月8日の17時26分です。改修によってパフォーマンスがどれくらい改善したのか、3ヶ月分のSite24x7のRUMを見て比較していきます。
【2021年1月】
改善前の1月は、データの表示件数が1,000件でもドキュメントレンダリング時間が長い状態でした。
【2021年2月】
改善リリース翌日の2月9日から、データの表示件数が10,000件に増えたにも関わらずドキュメントレンダリング時間が減少しています。
【2021年3月】
3月に入ると全体的に落ち着き、ドキュメントレンダリング時間の95パーセンタイル値が3秒台、平均では2秒を切るという結果になりました。
ドキュメントレンダリング時間(ミリ秒) | |||
---|---|---|---|
平均 | 95パーセンタイル値 | 99パーセンタイル値 | |
2021年1月 | 3,162.83 | 5,641 | 6,269 |
2021年2月 | 2,441.87 | 5,174 | 6,547 |
2021年3月 | 1,963.11 | 3,698 | 5,118 |
データの表示件数を1,000件から10,000件に増やすという要望を叶えながら、ドキュメントレンダリング時間の95パーセンタイル値を約2秒早くすることができました。
もっと早くしたいのは山々ですが、今回のパフォーマンス改善は以上でおしまいです。
当時の私は大量のデータを表示するという経験に乏しく、DOMサイズによるパフォーマンス低下を甘く見ていました。
LighthouseがDOMサイズの警告を800で出すのなら、データの表示件数が1,000件の段階でバーチャルリストとCanvasを導入すべきだったと今なら思います。
経験不足によってプロダクトの価値を損ねていたとも取れる今回の事例は、フロントエンドエンジニアとしては恥ずかしいものです。それでもSEARCH WRITEチームの皆さんは改善の結果が出たことを喜んでくれて、良いチームだなと改めて感じました。
最初からこうできていればという後悔はありますが、今後もこつこつと改善を続けていきます。