AbemaTVのWeb版のSSR化とFastly移行

加藤賢一氏(以下、加藤):開発本部のWebチームに所属しています、加藤賢一と申します。よろしくお願いします。今日はAbemaTVのWeb版のSSR化とFastly移行というテーマについて、お話しさせていただきます。

まずAbemaTVのWebブラウザ版なんですがこちらです。

数あるアプリと並行して、デスクトップ版でフルの視聴を提供しています。モバイルもまた違った画面で、視聴はできないんですけれども、こういった画面で右手のようなものがご覧いただけるかたちで提供しています。

さっそく、細かいところですがWebのアプリケーション構成について説明しないと、あとの説明がわかりにくいので先にいきます。AbemaTVのWebは、ReactとRxJSを使って、Fluxデザインパターンで構築しています。

昨今のWebのReactを使った場合、Reduxとかへのフレームワークというか、そういったライブラリを使うんですが、AbemaTVはRxJSを使ってこういったものを構築しております。

あとはSingle Page Application。ブラウザ上でページ切り替えとか、ルーティングを行うものでやっていまして、先ほどの画面のように、デスクトップ版とモバイル版で違う見た目を提供しているので、HTMLやJS、CSS、そういったものも別のものを提供しています。

Webのサーバーの構成は、こんな右手のようなものです。

AbemaTVのシステム全体はGoogle Cloud Platform上に乗っかっていまして、Webのサーバーも漏れなくその上に乗っかっています。Backends For Frontendsという形態を取っていまして、Nginxを通してNode.jsで先ほどのようなHTMLなどを生成して返している構成になっています。

この図にはないんですが、この時点では間にCDNとしてCloud CDNが挟まっていまして。ちょうどセッションAで柿島がしゃべっていますが、負荷対策に当たってCloud CDNを間に挟む構成になりました。開局からそれ以前までが右手のような感じに、LB を通ってNginxという構成です。

Webでアプリケーションをいろいろ開発していますが、いろんな課題がある中で今回取り上げるところとしては、まず巨大なSPA、Single Page Applicationになっていて、そうなると起動までに時間がかかるところがあります。ファイルサイズとか初期処理のところでだいぶ時間がかかってしまいます。開局当初は、たしか数秒くらい真っ白な画面になってから、改めて表示されていました。

途中からローディング画面を表示するようになったので体感はわりと軽減されましたが、それでも時間はかかるところ。あとは、クローラー向けの話。Web版なのでSEOとかそういったところを気にしないといけないんですが、クローラーがJSを解釈すると言っても、Search Consoleを見ても正確に認識していなかったり。

それでちょっと見ると問題があって、そこらへんのSEO面で悪影響を及ぼしているんじゃないかといったところ、課題に挙がっていました。

課題を解決するためにやったこと

そういった課題踏まえて実施したことが、この2点です。

表題にもあるとおり、主要なページに限りますがSSR化と、これの要件を満たすためにはCDNの移行をしないといけなかったので、この2点を行いました。

これらを行ったのが実際には今年の4月、もう半年前になりまして、それから半年経過しています。SSR化は最後にも触れますけれども、順次増やしている感じになります。

まずは1つ目の、SSR化に説明を移らせていただきます。まず、SSRとはなんぞやというところですが、WebアプリケーションにおけるSSRは、こういった感じで解釈がされております。

まず、Server Side Renderingの略称です。技術的には、ブラウザ向けのJS実装、ReactやAngular、最近はVue.jsが採用されることが多くなりましたが、そういったものをNode.js上で動作させてHTMLを生成して返却すると。

流れ的にはそのままブラウザで、同じビューエンジンでSPAとして動かすところが主流になっています。

実際に対応したページは、ざっとこんな構成になっています。

ちょっとわかりにくいと思いますが、もちろんトップページがありまして、Abemaビデオを提供しているのが上の右の欄になっています。ビデオのトップがあって、それぞれがジャンルが、アニメとかニュースとかドラマとか分かれており、そういった区分でさらにシリーズがあって。

シリーズはいろんな話数を束ねている、例えば、アニメだと1クール分とか、ドラマでも1クール分といった単位と、それぞれのエピソード、実際に視聴できる面に分かれています。並行してテレビ側のチャンネルや番組詳細があるんですが、今回この時点で対応したのが、赤に塗っている一番上のトップページと、ビデオのところの束ねているシリーズと、具体的なエピソード、実際に視聴ができるところです。

そういった3種類の画面をSSR(Server Side Rendering)対応しました。

DOM/HTML生成までの違い

実際に生成するんですけれども、SPAとSSRで、どういった感じで違うのかを図式化しました。

ちょっとわかりにくいと思いますが、左がSPAです。SPAは、ブラウザ上ではStoreやViewで構成していて、実際に描画するものはAPIのServerを経由して取得しています。API Serviceは、裏で他のMicro Serviceから情報を取っていて、構築して返却していると。

それをもとにDOMを構成して、実際に目に見えるブラウザ上に見えるかたちで表現しています。変わって、SSRをすることになると、右の図になるんですが。先ほどブラウザ上で、StoreからAPIで取得すると言ったところを、ここは全部GCP上に乗っかっているので、Node.js上から直接Micro Service、APIサービスを介さずに取得して構成します。

API Service介していない理由としては、GCP内でやっているので、外部のAPIに飛ばさずにGCP内部で、grpcで直接取ろうといったところが、Abemaのポリシーとしてあるからです。それらをもとにして、最終的にHTMLとして返却しております。実際にこんなことをやっていたんですが、このあたりが課題というか、いろいろ苦戦したところです。

SSR化にあたっての課題

backendで先ほどのSSRをするに当たって、いろいろなエラーが多発しました。

もともとはSSRを想定していない作りになっているので、ブラウザでしか触れないwindowsオブジェクトなどが頻繁にあり、そういったところで引っかかったのを逐次直したり。あとは、モジュールのインポートをいろいろして構成するんですけれども、そこらへんの依存関係が曖昧というか。

「最終的にはパッケージされるだろう」という前提になっていたので、それらで余計なものがインポートされて不要なのにメモリを圧迫したり、インスタンスもいらないものを作っちゃったりがあったので、そのへんのリファクタリングもやりながら、1つずつ改修していきました。

あとは実際にViewを生成するときに、clientとbackendでそれぞれ作るんですけれども、流れとしてはbackendでSSR、HTMLを生成して、clientでSPAを動かして、そこでもまたビューエンジンが動きます。そこでの結果の差異が発生してしまいました。そういったものを1つずつ、Consoleのエラーを見ながら解消していきました。

わかりやすいWarningはわかりやすく潰したりするんですが、ちょっとわかりにくいものとかが。一番下の「もう何が違うのかがわからない」といったものがあったりして、こういったものを地道に片付けていく作業をしていました。

それらでSSR化をやっていたんですが、最終的に提供するに当たって課題になっていたのがデスクトップ版とモバイル版とで異なるHTMLやJS/CSSを配信したいというところでした。冒頭にお話ししましたが、そういったところで引っかかりまして。

当時採用していたCDNがCloud CDNなんですが、User-Agentごとにキャッシュの切り分けなどができないといったところで、そのままこのCDNを使うわけにはいかず、User-Agentごとに切り替えができるCDNへの移行が必要になりました。

Fastlyを選定した理由

次がCDNのパートになります。改めて、それまでのWebサーバーの構成で先ほど説明したところにちゃんとCloud CDNを挟んでこんな感じになっていたところを、今回のもので間にFastlyを挟むかたちで。Fastly経由してからGCP上に入るかたちの構成に、変更をすることになりました。

Fastlyを選定した理由としては、ざっとこんな感じです。

次世代CDNとして、いろんな豊富な機能があるところです。一番の特色と知られているところで、インスタントパージで即時にキャッシュの削除ができる、あと、Varnishベースで動いているのでVCLでいろんなカスタマイズができるといった柔軟性があります。

あとは社内含めて、採用事例が増えてきたところです。社内だと、最近だとAmebaニュースが採用していて、そこはちゃんとした活用をしていたり。その前は、日経さんの事例がけっこう広まったんですけれども、そこがちょうど注目されていたところでした。

そのころは実装中でした。やっているところで発表が起きて、「おお」と言っていたところでした。

一番最後、けっこう大事な要素としては、エンジニア各位で「使ってみたい」といったモチベーションがあったのでFastlyを最終的には選びました。

Varnishの設定方法

Varnishの設定方法ですが、こんな感じで設定できます。

基本的な設定は、管理画面上から行うことができ、APIが公開されていたり、Terraformで反映できるので、そんな感じで。AbemaではTerraformを介してやっています。VCLを直接記述できるといったところで、こんな2つの方法があるんですけれども、今回はカスタムVCLを使っています。

実際に、もともとのモチベーションで「User-Agent別にキャッシュを振り分けたい」といったところで、それをどうやって実現しているかは、こんな感じです。

まず、Varnishで受けていて、ヘッダーを付与します。User-AgentをもとにDeviceとかVendorを振り分けて、それを付与し、最終的なキャッシュ用にVaryにそれぞれを指定します。

それをNode.jsで受け取り、X-UA-DeviceやX-UA-Vendorをもとにして処理を振り分け、それぞれ用のHTMLを生成します。それを配信して、それぞれでキャッシュされるといったところです。次回以降のアクセスに関しては、キャッシュされたものに従ってデスクトップ版やモバイル版が配信されるかたちになっています。

オリジンサーバーの要件

オリジンサーバーです。

実際には、流れとしてはFastlyを経由してから、Fastly側からオリジンサーバーにリクエストします。どういった要件かといいますと、まず、もちろん従来公開しているURLとは別のURLを提供しないといけません。次に、別のURLを提供するとなると通常のアクセスをされるわけにはいかないので、認証用のリクエストヘッダーを定義したりなどして、アクセスを制限しています。最後に、Cloud CDNを使ったままになると二重になっちゃうんで、それを無効にしています。

それらをもとにして細かい構成がこんな感じになっていて、これが切り替え前です。

abema.tvから入ったところでCloud CDNを経由して、Load Balancerを通って、Nginx、Node.jsで返ってきています。Fastly経由へ変えるのは、こんな感じの経路になっていまして。

FastlyからはCloud CDNを通らずにLoad Balancerに入って、先ほど説明したように、認証用のリクエストヘッダーの処理があるので、専用のNginxを別に立てて、それ経由でNode.jsにアクセスし、返すといった構成にしています。

そういったところをやっていまして、無事に移行完了しました。

移行後のパフォーマンス

手順としては、まずCDNを先に移行しないといけないので、まずFastlyの移行をしてから、だいたい1週間くらい様子を見てSSRを適用しました。

結果です。もともとのモチベーションで、体感速度向上させたいといったところを測っている指標です。ちょっとよくわからない図になっちゃっているんですが、これがBeforeで、After。

計測にはSpeedCurveという、Webで使われている計測のものを使っています。上の方にちらっとあって、すみません、これ絶対見えないと思うんですけれども、体感速度、指標と言うか、(参考にしている)ものにしていて。

これはテレビトップなんですが、ブラウザ版のテレビトップってチャンネルが一覧が並ぶかたちになっています。Beforeでは、中央にabemaくんのローディング画面が出ます。

この図だとだいたい3秒後くらいに、チャンネル一覧っぽいのが出てきます。実施後はこんな感じでローディング画面は挟まずに、だいたい1秒後くらいにチャンネル一覧が見え始めます。

当然、このチャンネル一覧の状態をSSRで生成してHTMLを介しているので、そんな結果になります。これだけでも、けっこうな体感速度が変わっていまして。今説明したものがFirst Meaningful Paintですが、ここが大幅に短縮しました。さっき言ったようなのは、ここの計測ツールの結果だと3秒から1秒くらいになりました。

Start Renderというところで、Renderingが開始されるところの秒数なんですけど、これが逆に増加しました。これはもちろん、返すHTMLがそれだけ膨大になるんで、増えるのは致し方ないと。最終的には、First InteractiveとかFully Loadedという、実際に操作できるまでの時間。それらは最終的に、どちらもSPAで動かすところは変わりないので、ここらへんの結果は変わらないという結果になりました。

最終的な操作のタイミングまでは変わらないんですが、体感速度は大きく向上しました。

実際に導入した後でも、社内でも本当に「見た目は速くなったよね」などと声をいただいたので、目的は達成されたのかなと思っています。

CDNの気になるキャッシュ状況なんですが、Cloud CDNのときはだいたい95パーセントくらいでした。

それ以外の5パーセントが、オリジンで処理しているところです。キャッシュのパターンが増えたにもかかわらず90パーセント以上は維持しているので、負荷的にも問題ない構成になったのかなと思っています。

今後の展望

最後に入ります。今後の予定としては、まだFastlyを活用しきれていなくて、半年前からある課題でなかなか実行できていないんですけれども、より細かい分類によるキャッシュの最適化です。

例えば、今できていないのが、AbemaTVは課金プランがフリーとプレミアムがあるんですが、そこらへんの処理がSSRで今できていないので。

そこをCookie経由でFastlyで処理して、うまいことそれぞれ無料User用と、Abemaプレミアム向けのHTMLを返すようにやりたいとか。あとが、この後メインセッションであるFeature Flagで、A/B testが可能になるんですが、それ別のキャッシュとかもやりたいといった計画が挙がっています。

あと、他のページのSSR化で、これも随時仕込んでいる最中です。あと、ブラウザごとの配信JSの最適化です。AbemaTVはブラウザもサポートしているので、一番低いものでInternet Explorer11があるんですが、そこでサポートしているAPIのために、追加のバンドルをしているものがあるので。IEに向けては、そういったPolyfillを含んだものを配信して、最新のChromeは最小限にしたパッケージを配信して、最適化をしたいといった狙いがあります。

FastlyではHTTP/2 Server Pushが使えるので、またこれもなかなか導入できていないんですけれども、これも使いたいですね。

今のSSRとかCDNの文脈以外にも、いろいろWeb版では開発を仕込んでいまして。ちょうど10月の後半くらいに、1つ大きな機能をリリースすることになるので、まだ詳細は言えないんですけれども、楽しみにしていてください。けっこう、今年一番のリリースになるかなと思っています。

それ以外にも、そもそもの今回、体感速度の向上といったところで改善を図りましたが、そもそもの起動速度の向上を図るためにも、内部的な改善をいろいろしたり、随時行っています。ご清聴ありがとうございました。

(会場拍手)