グローバルのランキングで2位となった表示速度

宍戸俊哉氏(以下、宍戸):よろしくお願いします。「日経電子版を速くする」というタイトルでお話しさせていただきます。

はじめに自己紹介をさせてください。宍戸俊哉と申します。今は日本経済新聞社で「r.nikkei.com」のフロントエンド、バックエンド、CDNの配信周りを担当しています。前職ドワンゴで、2016年から日本経済新聞社にジョインしています。好きなものはWebアプリで、嫌いなものはネイティブアプリです。

(会場笑)

はじめにちょっと質問したいんですけど、この中で電子版有料会員の方ってどれぐらいいらっしゃいますか?

(会場挙手)

あっ、思ったよりいますね。でも、なんか社内の人もいるので実際はもうちょっと少なそう(笑)。電子版をご覧になったことがある方ってどれぐらいいますか?

(会場挙手)

それはけっこういらっしゃる。ありがとうございます。

ざっくり電子版の紹介させていただきます。2010年3月創業で、1日900本の記事を配信していて、有料会員54万人以上、無料会員300万人以上、月間3億アクセス以上、1つの世界最大規模の経済メディアになっています。ちょうどいま平昌五輪やっているので見てみてください。VRコンテンツも出しています。

そんな電子版ですが、2017年の11月に大きくリニューアルしています。メニューやセクションの見直し、検索ページやMyニュースのリニューアル、あとモバイルサイトも全面刷新ですね。今日お話しするr.nikkei.comはモバイルサイトと一部のPCサイトで配信しています。

表示速度は、今回のリニューアルで約2倍まで上げることができました。このグラフはモバイルの3G回線でのチェックなのでリダイレクト1回分がけっこう顕著に出てるんですけど、紫の線を見るとほぼ半減してることがわかるかなと思います。

グローバルで比較しても速いところまで来ています。

こちらはHearst社というアメリカのメディアの会社が作っている記事ページのパフォーマンスを比較しているサイトですが、そこで2位ですね。今見たら4位ぐらいに落ちちゃってるかもしれないんですけど、スライドの作成時点では2位でした。

完全に条件が同じではありませんが、NYT、Guardian、BBC、ハフポストといったところと比較しても高スコアを出せています。

エンゲージメントスコアにこだわる

宍戸:サイトの速度は重要なKPIというところで、グループ会社のFinancial Timesが数年先行してWeb版を大きくリニューアルしてました。

そのなかでページの表示に関するテストをしたんですが、結果が右のようなグラフになっています。

これは一部のユーザーにあえて遅いページを表示することで、それがユーザーのエンゲージメントにどのような影響を与えるかを調査した時のグラフです。

エンゲージメントというのは独自の指標になっていて、ユーザーの訪問率、セッションあたりのページ閲覧数などを数値化したものになっています。このグラフから「エンゲージメントが1秒落ちるとエンゲージメントのスコアというのは5パーセント下がってしまう」という結果が出ています。

サイトの速度は検索結果にも影響してくるというところで、「読み込みの遅いサイトは検索ランクを下げますよ」とGoogleが言っています。それが2018年の7月からモバイルにも適用されるというところで、フロントエンドの最適化ではなくて、サーバからの応答速度というのも大事になるということになります。

実際にチーム発足した時なんですけど、こういった事象からチーム内で「最重要のKPIは速度である」と定義してチームが生まれました。

あとはUI/UXの改善ですね。レスポンシブ、PWA対応と、開発体制も内製化してアジャイルなチームでやっていきましょうというところで進めていきました。あとはmicroservicesを使ったり、アーキテクチャの変更も行ってます。この右にあるのが、ちょうどそのチームが発足した時の資料になりますね。

モニタリングしていきましょうというところで、まずは現状確認ですね。これは旧モバイルサイトのSpeedCurveを使ったときのグラフなんですけど、初回の起動にめちゃくちゃ時間がかかってるんです。

9秒ぐらい経ってもまだレンダリングが完了しないというかなり遅いサイトで、Server Side RenderingをしていないBackbone.jsでできたSPAだったので、初回の起動はどうしても遅くなってしまうという問題がありました。一度読み込んでしまえばあとはわりと快適に使うことができたので、一長一短かなという感じがあります。

課題は多くあって、Start Render6秒以上かかったり、Speed Indexの完了までに9秒かかっていました。あとはブロッキングリソースの問題。JavaScriptが22ファイルあって、CSSが3ファイル。そもそも使ってないファイルをたくさん呼び出していました。

「継続的な計測ができる体制をつくりましょう」というところでSpeedCurveを利用しています。

旧プロダクト、競合のサービスですね。〇〇社さんや〇〇新聞社さん、そういったやつです。競合のサービスと比較したり、あとは毎日計測することでリリース時の影響をわかりやすくするなどです。

こういったところを主に使っていて、古いサイトのリプレースなので旧サイトのボトルネックを細かく見たりはしていないです。あくまでベンチマークって感じですね。

Fastlyの優れている点について

宍戸:その他の計測ツールは用途に応じて使っているんですけど、Kibanaのダッシュボードを作成したり、New Relicでパフォーマンスを見たり、Prometheusでサーバの負荷状況を見たり、そういった感じで使っています。「計測の準備はできた」というところで、ここからどう速くしていくというところなんですけど、そこで出てくるのがCDNですね。

速さは大事なんですけど、もちろん大量のアクセスにも対応する必要がありました。デスクトップとモバイルを合わせた負荷にも対応させる必要性があるのと、もう1点対応する必要があるのは突然のアクセス集中ですね。ニュースサイトなので大きな事件や災害が起きたときにアクセスが集中してしてしまう。

CDNに関する要件というところで出たんですけど、高速で低レイテンシであること、大量のトラフィックに対応できること、あとはキャッシュクリア。速報をいち早く出すことが大事なのでキャッシュクリアが重要です。あとは海外からも閲覧できたらいいよねということで、HTTP/2、IPv6、新しいプロトコルにも対応できるとよいですね、という話ですね。

はじめはCDNを使わずに自前のVarnishで管理してもいいかなと思っていたんですけど、こういったところをいろいろ考えて見ていくと「CDNを使ってサービスに乗っていったほうが楽だね」という話でCDNを使うにいたりました。

そこで出てくるのが「Fastly」なんですけど、最近、Fastlyの話いろいろしすぎていて「お前はFastlyのセールスなのか?」とか言われます(笑)。

(会場笑)

Fastlyの話をします。

POPについてです、世界中に点在しているFastlyのサーバ群のことをPOPって言っていて、POPが30以上、日本には3つのPOPがあって、充分な数ですね。オープンソースのVarnishをベースにできているので、VCLを使って設定変更し、柔軟にキャッシュを制御をできるので、そういったところが利点かなと思います。

逆にVCL使うと使えない機能もあったりして、GUIだけでもだいたいのことはできます。

APIが充実していて、実際に自分たちのローカルでソースコードを変更してそのままプルリクエスト。マージするとCIでアップロードとアクティベートまで全部やってくれるというところまでできています。

キャッシュの削除が150msぐらいで終わります。あとはSlackを使ってサポートの人と気軽に連絡が取れる。日経社内だとVarnishの運用実績があって、FT(Financial Times)の中ではFastlyがすでに使われていたというところが大きな選定理由になっています。

高速化の3つの基本方針

宍戸:システム構成はざっくりしているんですけど、アーキテクチャはこんな感じになっています。前段にFastlyがどんと1つあって、その後ろにr.nikkei.comの各ページやAPIを配信するサービス群がだいたい30ぐらいある状態になっています。

Fastlyは今r.nikkei.comでも使ってるんですけど、ネイティブアプリとバックエンドのAPIも今はFastly使うかたちになっています。

高速化の基本方針として3つあります。CDNでできることは基本的にはCDNに任せようというところ。動的なコンテンツもCDNで配信していくこと。あとはVCLで柔軟にキャッシュを制御していくことですね。

実際に新しいプロトコルもCDN、Fastlyを使って実現しています。常時SSLとIPv6対応ですね。この間、ログをとってみたんですけど、だいたい20パーセントぐらいもうIPv6でアクセスが来たりしている状態ですね。

こういうのを全部自前でやっていこうとしていくと大変だと思うので、CDNに任せられるところは任せていくといいのかなと思います。

あとはHTTP/2ですね。

これもFastlyのHTTP/2の機能を使っています。このスライドは先ほどの発表でAndrew Bettsが話していたので追加しました。Varnishの前段にH2Oが存在していて、このH2Oを経由して配信することでHTTP/2にできます。

一般的な話ですけど、ストリームによる通信の高速化、あとはリクエストの並列数の増加、ヘッダサイズの削減、あとServer Pushですね。このあたりがHTTP/2を使えるメリットかなと思います。

リソースの圧縮では基本的に、CDNで圧縮を全部行っているかたちになっています。

あとはH2Oを使っている場合はオンザフライで圧縮することもできて、そのときはBrotliで圧縮できて効率がいいかなと思っています。

開発環境が実質不要になるほどの便利さ

宍戸:Fastlyを使ってあとどんなことをやっているかというと、Feature Flagですね。microservicesの文脈で言われるFeature Flagを使ってA/Bテストをやったりしていますね。

これは内部的に使っているトグラーツールというものなのですが、ちょっと読みづらいかもしれないんですけど、paidContentIndicatorというのがあると思います。これを切り替える時にローカルでCookieに値を上書くことでA/Bテストができるようになっています。

これで上書いた結果がこんな感じです。左のAパターンが有料記事に鍵アイコンを出しているパターンで、Bパターンのほうが無料記事に無料表記と出しているパターンですね。

どうやっているかというと、Preflightリクエストって呼んでるんですけど、Fastlyトップページにリクエストを受け付けたときに、まずFastlyの中でFlag APIというものを呼んであげます。

ここから返ってくるレスポンスヘッダに入っている値ですね。こいつをパースしてあげて、Cookieをすでに持っていればそのCookieの値をマージしてあげます。最終的に本来呼びたかったトップページを読み出すことでA/Bテストを実現しています。

Feature Flagを作るのに必要ものは、Flag情報のレジストリですね。JSONファイルでやってます。あとはトグラーツール。そのリクエストを受けたときにユーザーのセグメントを分割するなどのパターンを割り当てるAPIですね。

ここでFeature Flagの話したのは、A/Bテストだけじゃなくて開発やQAでも利用できて、パフォーマンスの比較もこの機能を使うとやりやすくなるのでお話しさせていただきました。

もう本番環境でそのまま開発のソースコードをアップロードしてFlagを割り当てて開発するということができるので、開発環境は実質不要になりつつあります。といっても、既存の機能、ほかのシステムが開発環境にしか機能を実装していないのでそっちで検証してくださいといったことがあって、実際の現場だとけっこう開発環境はなんだかんだ必要だなという状況ですね。

動的コンテンツの配信とキャッシュについて

宍戸:動的コンテンツもCDN、Fastlyを使って配信しています。トップページや記事ページ、そういう動的コンテンツですね。

例えば、トップページは更新頻度が欲しいから30秒ぐらいキャッシュしようとか。記事のページはさほど更新されることがないので5分ぐらいキャッシュしようとか。そういうかたちで個々にキャッシュコントロールを柔軟に制御しています。

今日Andrew Bettsがもう話したと思うんですけど、動的コンテンツを配信する上で必須の機能にVaryヘッダの利用があります。Varyヘッダの値にリクエストヘッダの名前を提供することでクライアントの種別や設定ごとにキャッシュを出し分けることができます。

Accept-Languageを使った例なんですけど、実際の電子版は英語版をまだ配信してないので、これはあくまでサンプル程度に見ておいてください。

ログインの有無、会員属性、そういったものがあるコンテンツであってもキャッシュが可能になります。先ほどのVaryヘッダの機能と、CDN上で署名付きのJWTトークンですね。JWTトークンをFastly上でデコードすることで、有料会員限定の記事、ログインユーザーの記事などもキャッシュできるようになりました。

これを少し説明すると、CookieにJWTトークンの文字列を持っておいて、Fastlyの中でそれをデコード。この「UserRank: paid」って書いてあると思うんですけど、デコードした結果「この人有料会員だよ」という情報をCDNの中で確認して、「UserRank: paid」というリクエストヘッダをつけてあげます。

そのリクエストヘッダ「UserRank」をVaryの中に入れてあげて、有料会員用のコンテンツとしてキャッシュすることができる、という仕組みですね。

複雑な処理が必要になる場合の対処法

宍戸:そんな感じで「VCL最高じゃん」ってなるんですけど、実際の開発はつらくて、なんかもう状態遷移がめちゃくちゃ複雑なんですね。

まずフローが難しくて、変数が限定的にしか使えないことであるとか、リクエストのレスポンスも本体のほうは見ることができなかったりだとか、for文がないのでループできないことであるとか、あとはテストできないなどいろいろ問題があります。

そんな問題にどう対応しているかという回答としては、先ほどのお話ししたFeature Flagsの例ですね。これが参考になるかなと思ってます。

r.nikkei.comだとVCLが細かくサブルーチンに分割しているのでコードはこんな感じなんですけど、変数のスコープが1つのサブルーチン内に閉じているときはローカル変数を使う。サブルーチン間でヘッダーを取り回すときはHTTPヘッダーに変数として入れておくとか、ヘッダーは最後デリバーする前にちゃんとunsetする必要があったり。

あとは、このFlagの情報ってAPIからやってくるって言ったんですけど、Fastlyの中だとレスポンスのボディ自体は見れないので、そのFlagの結果をHTTPレスポンスヘッダーの中に入れてから、そいつをFastly上でパースして、Cookieの値があれば上書いて、といった複雑な処理が必要になります。

「ループなくてつらい」という話もありますが、1つの解決例として、サービスの定義ファイルをJSONで書いて、それをHandlebarsのeachでガーッと生成してあげて、最終的にバックエンドのルーティングをFastly上に出力するという形です。

最後「else if」が列挙されていることでわかると思うんですけど、生成後のVCLというのはもう何千行になって読もうと思うとつらいです。

キャッシュヒットしなかったときどう対処するか

宍戸:「VCLは工夫して使えば超強力」ですが、キャッシュヒットしなかったときどうなるのというと、リリース前は割と遅かったんですよね。キャッシュがないときは1秒以上かかったていました。

なぜかというと、もちろんリリース前でそもそもぜんぜんユーザーアクセスがないのでキャッシュヒットしなかったのと、一部のAPIのパフォーマンスがよくないところですね。

そういったところがあって、社内で公開しても「速さ重視してるって言ったのにぜんぜん速くなくない?」みたいな反応をもらって対応しました。それが「Promise Cache」という仕組みですね。こいつはなにかというとexpressのmiddlewareで、キャッシュがあるとき・ないときを区別せずに扱える関数を呼び出しの仕組みですね。

日経の記事は、1つの記事でも膨大なデータ量があるので、それはzstdで圧縮したりしてました。CDNのキャッシュ、もう今は安定し始めているのでわりと有用性は薄くなりつつありますが、まだコード上は残っている感じですね。

あとはキャッシュの非同期更新ですね。

stale-while-revalidate。これHTTPのCache Controlの拡張なんですけど、キャッシュ期限切れ後も指定された期間はキャッシュからコンテンツを返せるようにしてバックグラウンドで新しいキャッシュを更新していくという処理をCDNでやっています。

あと画像配信ですね。

これSaaSのimgixを使っているんですけど、「画像とかがWebPで読み込まれている」とリリース直後に反応いただいたと思うんですけど、全部このimgixを使ってやっています。

あとはレスポンシブで対応しなきゃいけないので、同じ画像から複数のバリエーションの画像を生成する、そういった処理をimgixを使ってやっています。これ全部HTTPのAPIでクエリパラメータを使ってできるのですごい便利です。

CDNで高速化したまとめなんですけど。キャッシュヒット率、平均するとだいたい90パーセントぐらいで、有料会員に対しても70パーセント以上でCDNのキャッシュからレスポンスを返すことができています。それによってオリジンサーバのリソース、通信量など、そういったものが削減できます。

キャッシュヒットしたときはだいたい100ms以内で応答可能で、高速になったかなと思います。VCLは工夫して使えば超強力なのでやっていきましょうという感じですね。あとは画像の最適化、リソースの圧縮、HTTP/2対応といったところも全部CDNの上でやっています。