Notice: Function _load_textdomain_just_in_time was called incorrectly. Translation loading for the acf domain was triggered too early. This is usually an indicator for some code in the plugin or theme running too early. Translations should be loaded at the init action or later. Please see Debugging in WordPress for more information. (This message was added in version 6.7.0.) in /var/www/staging_ichizoku.demowpsites2.com/wp-includes/functions.php on line 6121
Node.jsのプロファイリングでコードレベルのボトルネックを解決する – Ichizoku

Ichizoku is an official partner of Arize in Japan

Node.jsのプロファイリングでコードレベルのボトルネックを解決する

プロファイリングは、最も重要なツールです。

プロファイリングを利用することで、本番環境で実行中のプログラム情報を詳しく見ることができます。

パフォーマンスのボトルネックは、ローカルで再現するのが非常に困難であったり、不可能であったりすることがよくあります。そのため、この機能はとても重要であるといえます。

再現が困難である理由は、外部制約や本番環境特有の負荷が理由にあります。

Sentryでは、iOSとAndroidのプロファイリングのサポートをリリースした後、他のプラットフォームもサポートするように取り組んできました。そのうちの一つがNode.jsです。Node.jsのプロファイリングは、現在ベータ版です。

Node.jsのプロファイリングのセットアップ

Node.jsのプロファイリングを設定するには、@sentry/profiling-node パッケージをインストールする必要があります。次に、パッケージをインポートします。その後、プロファイルに必要なサンプリングレートを設定します。

プロファイリングは、当社のSentryパフォーマンス上で動作します。 そのため、Sentryパフォーマンスもセットアップする必要があります。

これらのセットアップが完了すると、Sentry.startTransactiontransaction.finishの間のすべてのコードが自動的にプロファイリングされるようになります。

プロファイリング

プロファイリングは実際に何をしているのでしょうか?

本パッケージは、内蔵のV8プロファイラを使用しており、サンプリング周波数が最大100ヘルツでスタックサンプルを取得します。プロファイル結果のスタックサンプルはSentryダッシュボードにアップロードされます。

すると、そのサンプルをフレームチャートの形で可視化することができます。フレイムチャートでは、プロファイラが収集したスタックサンプルを時系列で確認することができます。

これにより、プログラムがどの関数呼び出しに最も多く時間がかかっているのか確認することができます。

自分たちのコードをプロファイリングしてわかったこと

まず、Chartcuterie(Slackで共有されるチャートリンクのサムネイル画像を生成するサービス)を実行するExpressサーバーにプロファイリングを導入し、フロントエンドのテストスイート全体と、社内のSlackボットにも導入しました。

コードのプロファイリングを成功させるために、まず、すべてのサービスでSentryの パフォーマンスサービスがインストールされていることを確認します。

Sentryはサービス全体のレイテンシー問題を追跡するので、プロファイルとトランザクションを簡単に関連付けることができます。これにより、リクエストが遅くなっている原因を特定することができます。

パフォーマンスのダッシュボードは、アプリケーションがどのように動作しているかを確認するのに役立ちました。プロファイリングを導入する前と後では、p75の時間を正確に追跡することができました。

パフォーマンスの原因を探る-ネタバレ:テストコードとは限らない

Sentryのフロントエンドのコードは、約400のテストファイルにまたがる4000のテストから構成されています。

CI環境からいくつかのプロファイルを確認した後、いくつかのテストでloadFixtures関数呼び出しの内部にかなりの時間がかけられているこることがわかりました。loadFixturesの呼び出しは setup.tsスクリプトの一部で行われます。

これは、テストの実行前に実行され、テストが実行できる環境を準備します。

フレームチャートを見ると、テストのセットアップコードがテスト時間の大部分を占めていることがわかります。そのため、テストセットアップが何を行っているのかを確認することにしました。

テストセットアップ

擬似的なコードでは、loadFixturesは次のように動作しています。

擬似的なコード

このコードには何の問題もありません。むしろ、このコードによって、require/import文を書かなくても、テストの内部で一部のコードをスタブ化することができるようになりました。しかし、これはメリットだけではありません。

stubsDir内にあるすべてのコードを(たとえテストで実際に必要とされないものであっても)ロードしてしまいます。

この問題は、Jestがrequire.cacheを採用しないことが原因となり、さらに深刻なものになります。

さらに、setupFilesAfterEnvで定義されたスクリプトが1ファイルにつき一回実行されるということも、問題の原因です。

この問題を解決するために、JavaScriptのプロキシを活用し、require文を遅延ロードさせるようにしました。これにより、パフォーマンスに影響を与えることなく、問題を解決できました。

また、具体的な修正内容はこちらでご覧いただけます。

スローテストを探す

Sentryのフロントエンドテストの中には、実行にかなり時間がかかるものがあります(時には数分を超えることもあります)。

その理由はテストによって異なりますが、あるテストは、大きな依存関係グラフを持つ複雑なコンポーネントをテストしているためであったり、すべてのコードを初めてトランスパイル(あるプログラミング言語から、他のプログラミング言語に変換すること)すると、動作に時間がかかったりします。

また、レンダリングに時間がかかるコンポーネントをテストしている場合も同じように動作が遅くなります。

さらに言えば、私たちのテストはデフォルトのGitHub Ubuntuランナー上で実行しています。これはローカル開発で使っているM1チップのmacOS環境とはかなり異なる環境です。

あるコードの実行が遅い理由として、さまざまなことが考えられます。

しかし、プロファイリングがなければ、原因を正確に把握することは難しいでしょう。そのため、実際の本番環境から収集したプロファイルを持つことが非常に重要なのです。

そこで、いくつかのプロファイルがウォーターフォールパターンを示していることに着目しました。

ウォーターフォールパターン

上のフレームグラフでは、keyboardImplementationの呼び出しがウォーターフォール型になっており、テスト実行全体のおよそ7回呼び出されていることがわかります。

これは、テスト時間の大部分を占めています。

また、新しいチームメンバーが招待されたときに、ユーザーに表示されるモーダルをテストしていました。
長文のメールを送信する際に、 userEvent.typeを繰り返し使用していました。そのため、コンポーネント全体の再レンダリングが発生していました。

レンダリングを最適化するためにコンポーネント自体を書き直すことはすぐに対応できることではないため、現実的ではありません。しかし、おそらくそれが最良の解決策だったのでしょう。

私たちは、userEvent.pasteを使用して、ユーザーがメールアドレスを入力することをシミュレートすることで、負荷を軽減することを選びました。これにより、文字列の各文字に対してイベントを発生させることなく、テストの速度を向上させることができました。

当社のサービス以外の問題を発見する

Chartcuterieサービスは、Slackのリンクが共有されるたびに、サムネイルを生成します。

これにより、ダッシュボードを開かなくても、Slackですぐにチャートを確認することができます。しばらくの間、このサービスはメモリリークに悩まされており、その都度、再起動させる必要がありました。

最終的に、メモリリークの原因はプロファイリングを活用する前に特定することができ、修正されました。

メモリリークの根本的な原因を調査するのは難しく、簡単な修正作業ではありませんでした。

私の同僚が発見したように、最大35,000個の別々のマーカー(チャート上に描画される小さな線のこと)をサービスに送信していたケースがありました。

最も遅いプロファイルをいくつか調べてみると、HashMap.each,eachGlobalModel.eachSeriesなどの関数を呼び出すチャート作成ライブラリに時間がかかっているのかがすぐにわかりました。

チャート作成ライブラリ

この情報は、最初の調査時に貴重であり、入力に問題があることを推測させ、本来の原因究明につなげることができたはずです。また、実はこの時の原因は外部にありました。(詳細については、修正を含む PRを参照してください。)

さらに、本番環境、本番負荷でサービスをプロファイリングすることの重要性が改めてわかりました。

終わりに

プロファイリングを導入したすべてのNode.jsプロジェクトで、最適化の可能性を見つけることに成功しました。いくつかの例では、p75の時間をほぼ半分にすることができました。

その結果、直接ユーザー体験を向上させたり、開発者の負担を軽減させることができました。

私たちは、開発者に迅速なフィードバック体制を提供することができます。

また、プロファイリングはSentryのパフォーマンス監視プロダクト上に構築されることも見てきました。プロファイリングは、開発者がパフォーマンス関連の問題を解決するのに必要な、論理的な次のような情報を提供します。

パフォーマンス関連のことならどこでもそうですが、まずベンチマークのようなパフォーマンス目標を設定することを忘れないでください。

修正前に、パフォーマンスの向上を追跡する必要があります。

今日からプロファイリングを始めましょう

最初にパフォーマンスのモニタリングが有効になっていることをご確認ください(わずか5行のコードで可能です)。

また、より多くのSDKコミュニティやユーザーにプロファイリングのことを知ってもらうために、皆様のご意見をお聞かせください。


 

Sentryは、アプリケーションコードの健全性を監視するために不可欠です。エラートラッキングからパフォーマンスモニタリングまで、開発者は、フロントエンドからバックエンドまで、アプリケーションをより明確に把握し、より迅速に解決し、継続的に学習することができます。

Sentryは、世界中の350万人以上の開発者と85,000以上の組織に愛され、Disney、Peloton、Cloudflare、Eventbrite、Slack、Supercell、Rockstar Gamesといった世界的有名企業の多くにコードレベルの監視機能を提供しています。

毎月、世界中で人気のサービスやアプリケーションから、数十億件の例外を処理し続けています。

 


 

IchizokuはSentryと提携し、日本でSentry製品の導入支援、テクニカルサポート、ベストプラクティスの共有を行なっています。Ichizokuが提供するSentryの日本語サイトについてはこちらをご覧ください。またご導入についての相談はこちらのフォームからお気軽にお問い合わせください。

シェアする

Recent Posts