React/Redux/Node.jsのSSR/SPAを速くする6つのチューニングポイント大規模ブログサイト表示速度改善 大解剖(終)(1/3 ページ)

2004年から続くブログサービス「アメブロ」が2016年9月にシステムをリニューアル。本連載では、そこで取り入れた主要な技術や、その効果を紹介していく。今回は、React/Redux/Node.jsを使ったIsomorphic JavaScript特有のパフォーマンスチューニング手法や実際にあった問題および、その解決方法について。

» 2017年06月08日 05時00分 公開
[侯斌株式会社サイバーエージェント]

 2004年から続くブログサービスである「アメブロ」は、2016年9月にシステムをリニューアルしました。本連載「大規模ブログサイト表示速度改善 大解剖」では、そこで取り入れた主要な技術や、その効果を紹介しています。

 アメブロのリニューアルでは、React/Reduxを採用し、サーバサイドとフロントエンド両方でのレンダリング、いわゆる「Isomorphic JavaScript」を導入しました。このようにさまざまな新しい技術を導入する上で、パフォーマンスは重要な課題です。

 今回は全4回にわたる連載の最終回として、Isomorphic JavaScript特有のパフォーマンスチューニング手法や実際にあった問題および、その解決方法をお伝えします。

負荷試験1:レスポンスタイムが一気に高騰した

 「アメブロ」リニューアルの最後に、負荷試験を実施しました。過去のアクセスログを負荷試験のデータとして利用し、「New Relic」と「Datadog」を使うことでレスポンスタイムやスループットを監視しました。

 注意すべきは、この負荷試験のデータは全て従来のSSR(サーバサイドレンダリング)により生成されたページへのアクセスログだという点です。Isomorphic Web Applicationの場合、2ページ目からSPA(シングルページアプリケーション)になるので、SSRページへのアクセスが減少し、データにまつわるアプリケーションへのアクセスが大幅に増加することが想定されます。

 「システム全体の中でSSRとSPAの割合が、どれぐらいあるか」、つまり「どれぐらいのユーザーが2ページ目に遷移するか」は、本番環境でないと推測が難しいので、ひとまずフルSSR時と同じ負荷をかけて検証していきました。

 初めに「JMeter」を使って負荷をかけ始めましたが、負荷をかけた瞬間レスポンスタイムが一気に高騰し、負荷試験が全く進められない状態になりました。

 そこで、ローカル環境で「ab(Apache Bench)」を使って確認すると、下図のようにパフォーマンスが非常に悪かったことが分かりました。また接続数と関係なく、スループットは常に1200rpmほどでした。

パフォーマンスが悪かったときのグラフ

 このままだと完全に使いものにならないので、これをきっかけにパフォーマンスチューニングを始めました。

Node.js 6.3.0の「--inspect」を使ってパフォーマンス問題を発見

 2016年7月にNode.js 6.3.0がリリースされ、「--inspect」フラグが使えるようになりました。このフラグを使うと、Chrome経由でデバッグやプロファイリングが行えます。便利なツールなので、負荷試験1の結果を受けて、使ってみることにしました。

 Node.jsサーバを起動するコマンドに「--inspect」を付与すると、専用のURLが表示されます。

「node --inspect」コマンドの実行結果

 このURLをChromeで開くと、Chrome Dev Toolが開きます。

Chrome Dev Tool(Chromeのバージョンにより表示が異なる場合がある)

 「Profiler」タグを選択し、「Record JavaScript CPU Profiler」の「Start」をクリックして記録を始め、abなどのツールを使ってしばらく負荷をかけ続けます。最後に「Stop」をクリックしてプロファイラの結果を表示した結果、下記のようになっていました。これは、びっくりするほどNode.jsらしくない結果です。

プロファイラの結果

 Node.jsは、基本的な処理はmain threadで行われ、Event Loopを利用してノンブロッキングI/Oを実現します。一番時間がかかるI/O処理をEvent Loopに入れることで、結果が返されるまでの間に、並行して処理を実行できるようになります。

 Event Loopの1周は「Tick」と呼ばれ、基本的に1回のTickの時間を短くすればするほど、パフォーマンスが良くなります。このプロファイラの結果を見ると、「renderToString()」メソッドは同期処理なので、非常に時間がかかることが分かりました。

 平均1つの処理は40msぐらいかかるため、この処理を含める1回のTickの時間も長くなっています。そうすると、単位時間内のEvent Loopの実行数も少なくなり、全体のパフォーマンスが非常に劣化したことが分かりました。

renderToString問題への3つの対応

 このような重い処理へのよくある対応方法は「process.nextTick」メソッドや「setImmediate」メソッドを使い、1つの同期処理を複数回に分けて、非同期処理にすることです。ただReactの設計上、レンダリングは同期処理なので、修正することはほぼ不可能です。そこで、アメブロではrenderToString問題に対して、下記の解決方法を採用しました。

  • 非同期レンダリング
  • モジュールの遅延ロード
  • キャッシュ

 これから詳しく説明します。

       1|2|3 次のページへ

Copyright © ITmedia, Inc. All Rights Reserved.

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。