React/Redux/Node.jsのSSR/SPAを速くする6つのチューニングポイント:大規模ブログサイト表示速度改善 大解剖(終)(1/3 ページ)
2004年から続くブログサービス「アメブロ」が2016年9月にシステムをリニューアル。本連載では、そこで取り入れた主要な技術や、その効果を紹介していく。今回は、React/Redux/Node.jsを使ったIsomorphic JavaScript特有のパフォーマンスチューニング手法や実際にあった問題および、その解決方法について。
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が表示されます。
このURLをChromeで開くと、Chrome Dev Toolが開きます。
「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問題に対して、下記の解決方法を採用しました。
- 非同期レンダリング
- モジュールの遅延ロード
- キャッシュ
これから詳しく説明します。
Copyright © ITmedia, Inc. All Rights Reserved.
関連記事
- マイクロサービスベースのAbemaTV開発プロジェクトが切り開くスクラムとプロジェクト管理の“新境地”
急成長を遂げるインターネットテレビ局「AbemaTV」の開発現場では、マイクロサービスをベースとするスクラム開発とプロジェクト管理の新たな取り組みが進んでいる。 - サイバーエージェントに聞いた、DevOps実践の要件
近年、開発と運用が連携してリリースサイクルを速める「DevOps」という概念が関心を集めている。だが人によって解釈が異なるなど、いまだ言葉先行の感も強い。DevOpsの本当の意義と実践のポイントを探った。