SPA、流行ってますね。勢いがありますね。
ただ、最近のフロントエンド界隈を見ていると、
「これからはSPAが主流になっていくぞ!」 「新規サービスをはとにかくSPAで作るべき!」
というような過激(?)な言論を聞くこともしばしばです。
今回は、そんなイケイケな雰囲気にたいしてちょっとだけ「待った」をかけるべく
- 今のWeb開発において、SPAは本当にファーストチョイスなのか?
という議題について、 枯れた技術が大好きなサーバーサイドおじさん が、考えをまとめたいと思います。
全体の構成として
- 【1】パフォーマンスチューニング
- 【2】SSR
- 【3】ルーティング
の3点について、今のSPAが抱える諸問題や、必要以上に期待されてしまっている点などを示した上で、
- SPAが導入しやすいサービス、逆に導入しないほうがいいサービス
- SPAを導入しない場合のフロント構成の代替案(MPAや限定的なSPA)
を提示する、という構成にしたかった1のですが、【1】パフォーマンスチューニングの時点で分量が膨大になってしまったため、
いったん第一章のみで単独記事として上げる形態を取ります。
本記事のSPAや周辺用語の定義
今回の「SPA(Single Page Application」の要件としては以下です。(想定フレームワークはNuxt.jsです。)
最近のSPAを構成する概念はたくさんありますが、今回の議論に係る特徴をいくつかあげます。
- フロントエンドが原則として、単一のJavaScriptによる1つのソースコードで提供される。
- サーバーサイド側でのHTMLレンダリングは原則として行われず、URLによるルーティングも含めて、ブラウザ上のフロントエンド処理によってで画面が描画・切り替えされる
- 検索エンジンのクローリングや、OGP表示用など、サーバー側でレンダリングされた値を返す必要がある時は、Node.jsによるSSR処理を実施する。
また、呼び分けの便宜上、
- Node.jsを用いて、フロントで扱うレンダリング処理の一部/全部のサーバー側で行うことを「SSR」
- 従来のWebフレームワーク(Ruby on Rails やLaravel)やテンプレートライブラリ等を用いて、HTTPリクエストに対してWebページのHTMLをレスポンスとして返すことを「サーバーサイド処理」
と表記させて下さい。(どっちもサーバーサイドやん、ってツッコミはご勘弁願います )
【1】とりあえずSPAにするとパフォーマンスが上がる、は本当か
SPAのメリットとして、 読み込み速度が早くなる と言う事が喧伝されます。
ただ、「SPAの方が読み込みが早いからSPAを採用する」と考えて安易にSPAの世界に突撃するのは、大抵の場合間違いです。
初歩的な誤解:非同期処理だから早くなる
「非同期処理で、たくさんのAPIを同時に読み込むことができるので早くなる」 という事がたまに言われますが、これは大きな間違いです。この誤った「1画面に、各部品それぞれのデータを、20個回ぐらいのAPIを非同期で読み込む」みたいなコードを書いてしまうと、その画面は悲惨なほど遅くなります。この誤解を正すために考えることは2点です。
第1に、 非同期処理は並列処理ではありません。 JavaScriptは並列処理に対応していないため、非同期でn個の処理を同時に動かしたからと言って、処理時間が単純にn分の1になるわけではありません。 非同期処理を活用することで処理時間が短縮されるのは、 外部処理の待ち時間(APIの応答待ち時間など)に、JavaScript側で別の処理を動かすことができる場合においてのみです。 たとえば、0.3秒掛かかるAPIを非同期で読み込み、処理が帰ってくるのを待たずに別の処理を「先読みする」ことが出来れば、トータルの処理時間は短縮されます。 ただ、このような先読み処理は、多くのページ読み込みにおいては、消して簡単なものではありません。
たとえば、1つの商品情報を表示するページがあったとして、「商品の基本情報API」を読み込んでいる間に他の処理ができる・・・となったところで、先読みして表示できる内容は消して多くないでしょう。 いくら先読みができると言っても、コアになるような情報を読み込む際には、どうしてもその情報が返ってくるのを待たなくてはいけないことがしばしばです(ブロッキング)
このような情報を踏まえると、非同期処理をフル活用して画面スピードを高速化するには、このようなブロッキングの制約を踏まえた上で、まるで時刻表パズルのようにAPIを組み合わせる必要があります。(そしてインターネットの速度は環境によって異なるため、APIの読み込みタイミングはしばしば逆転します!!)結論として、各画面ごとに、全く違う職人的なパフォーマンスチューニングとなることでしょう。
第2に、 一般的にAPIの呼び出し回数が増えると、トータルでのサーバー処理時間は長くなります。 一般的なHTTPリクエストには、本質的な処理内容にかかわらず発生する共通処理(オーバーヘッド)が存在するからです。なので、 「APIをまとめるなどで、リクエスト回数を減らすことでトータルの読み込み時間やサーバー負荷を減らす」 という、先程述べた「複数のAPIを非同期で読み込む」とは真逆のアプローチによる最適化手法も成立します。
これが何を意味するかというと、非同期読み込みの最適化による短縮時間よりも、バックエンド処理の最適化に寄る短縮時間のほうが上回る場合は、フロントエンドによる最適化がベストプラクティスではなくなってしまう可能性があるという事です。
#### まとめ
- SPAによる高速化は「アーキテクチャの恩恵」ではなく、フロントエンドの読み込みロジックを知り尽くした職人芸的なチューニングを行って初めて実現する
- フロントエンドのパフォーマンスチューニングとバックエンドのパフォーマンスチューニングがトレードオフになる可能性がある。「非同期で沢山のAPIを読み込ませる」といった手法は、その意味において逆効果になる可能性がある。
多くのWebサービスでまず求められるのは「5秒を1秒にするチューニング」であり、その領域においてフロントエンドは無力である
前項で挙げたような「フロントエンドによるパフォーマンスチューニング」の効果は、 非常に限定的です。 0.5秒掛かるAPIの間に処理を詰め込む手法で0.6秒以上処理が短縮されることがないのは自明ですし、非同期の隙間に詰め込めるブラウザ処理が10msしかなければ、短縮できる時間も10msになります。 (ブラウザ側のレンダリング処理で数秒もかかるような処理はあまりないし、もしあるとしたら非同期以前にそっちを改善すべきです)
結果として、フロントエンドでのパフォーマンスチューニングは、0.1秒くらいの速度改善の地道な積み重ねになります。
ここで考えてください。 あなたがいま構築しようとしている、まだどの程度の人が使うかどうかもわからないサービスに、0.1秒単位の速度改善は、本当に意味がありますか?
新しいサービスの立ち上げにおいて、まず求められるのは、「ユーザーがストレスに感じない程度に快適にページが表示されること」だと思います。そして、多くのWebサービスの開発においてエンジニアが直面するのは「このページの読み込みがめっちゃ重い。5秒位掛かる 」とか、「全体的に重い。ページ切り替えのたびに2~3秒かかる 」みたいな症状です。
こうした状況においては、大体以下のような優先順位でボトルネックが発生しています。(※経験則)
- DBレベルで重い(SLOW QUERYやN+1クエリが発生している)
- サーバー側のアプリケーション処理で重い(計算オーダーが非線形になるようなロジックや、メモリスワップが発生している)
- インフラレベルで重い(そもそもインフラがリクエストをさばけていない)
そして、よっぽど複雑なアルゴリズムが要求されるサービスでなければ、上記の1〜3のボトルネックを教科書どおりに解決するだけで、 あなたのサービスの表示時間は1.0秒〜0.5秒位には収められるでしょう。 この一連の流れにおいて、最先端のフロントエンドでの速度チューニングの出る幕は未だありません。
#### まとめ
- Webサービスの速度改善は、各レイヤーのパフォーマンスの総合的な足し算でなされるもので、フロントエンドだけで解決するものではない
- 求められるレベルが「ストレスなく使える」くらいであれば、フロントエンドのパフォーマンス・チューニングをしなくても十分に構築できるし、その段階においてフロントエンドでのチューニングに手を出す必要はない(早すぎた最適化)
- もちろん、本当の意味で「最速」を目指し、そこに対する工数を書けるだけのリソースのがあるのであれば、フロントレベルでも細かなチューニングをすべき
## もう1点: 「APIによる通信量の削減」がどれくらいパフォーマンスに影響するか
SPAのパフォーマンス・メリットとしてよく挙げられるもう1点に
- APIで、必要なデータだけを読み込み、ページ上の必要な箇所だけDOMを切り替えるため、データ通信量が少なくなりパフォーマンスが改善する
と言うものがあります。こちらに関しては、実質的な効果があるかどうかは、非常に微妙なラインだと言えるでしょう。
ちゃんと計算してみる
具体的な尺度で言うと、
- ユーザーの通信回線が3G回線(1Mpbs程度)であれば、転送量100~200KB程度削減すると、通信時間が1秒位短くなる
- 通信回線が4G回線(10~20Mbps)であれば、転送量1~2MB程度の削減が、通信時間1秒の短縮に対応する
- 通信回線が固定回線(500M~1Gbps)だれば、転送量50~100MB程度の削減が、通信時間1秒に短縮に対応する
くらいです。
「ページ全体をHTMLで再度受信」と「差分データをJSONで受信」の間のデータ量の差は、最大限に多く見積もって、500KB程度かと思いますので、 2
- APIを効果的に使用することで、モバイル4G環境で0.2~0.5秒程度のスピードアップができる可能性はある
という結論になりそうです。(これは「全体再読込」と「差分読み込み」の差なので、全く新しいページを読み込む際はSPAでも、サーバーサイドで1画面分のHTMLを返す方式でもそこまで差は出ないはずです)
まとめ
- SPAのAjaxによる差分更新は、モバイル環境においての通信速度に影響する可能性がある。
次回予告
この章だけ切り出すと、SPA特有というよりは、JSのパフォーマンス・チューニング全般の存在意義の話みたいになりますね・・・。
第2章では
- 【2】SSR
- SSRの圧倒的な複雑さとそれに伴うリスク
- 「SEO対策にSSR」にどれくらいの投資をすべきか?
- そもそも旧来のサーバーサイドと併用すれば良いのでは?(SSRとサーバーサイド処理の決定的な違い)
という感じで、現在のSPAの構造やパフォーマンス、問題点などについてまとめたいと思います。
-
アドベントカレンダーだから無駄に気合が入ってしまった] ↩
-
ちなみに、Amazonのトップページ のHTMLがちょうど500KB位で、WebサービスのLPやニュースサイトのトップページは大体100KB前後くらいが多いみたいです。 ↩