Reactの「フェッチウォーターフォール」について

早速ですが、以下のような問題に遭遇したことはありますか? または、これはどうですか? これはおそらく見たことがあるでしょう。 ちなみに、上記で挙げたものはすべて同じです。 1つ目の画像はSentryのイベント詳細ページ、2つ目はChromeのネットワークタブ、そして3つ目は、それを引き起こす原因となるコードスニペットです。 もし上の質問に「はい」と答えた方は、今回の記事が大いに役立つと思います。 そうでない場合でも、将来の自分のためにぜひ読んでみてください。 これは「フェッチウォーターフォール」と呼ばれ、Reactにおける一般的なデータフェッチ問題です。 これはデータをフェッチし、「ローディング」状態を表示してから子コンポーネントをレンダリングする(そして同様のことをするなど)コンポーネントの階層を作成するときに発生します。 ざっくりいうと、Webページを表示するために必要なデータをサーバから取得する際に起こる問題であるということです。 各コンポーネントのデータが親のものに依存する場合には問題ありませんが、常にそうとは限りません。 各フェッチが少なくとも1秒かかる場合、ページのロードに3秒以上かかることになります。現代のデジタルネイティブにとって、3秒のロード時間はとても長く感じますよね。 しかし、並行してフェッチすれば1秒で得られるデータを、3秒以上も待たせる理由は一体何なのでしょうか? この問題のCodeSandboxがこちらですので、ご自身の目でご確認ください。 良いパフォーマンスを維持するため、3つの方法をご紹介します。 そして、各ソリューションを使用したい場面を探っていきましょう。 対処法① Suspenseを使用する Suspenseは、フェッチウォーターフォールを避けるための有効な手段の1つです。これは、コンポーネントツリー全体のフェッチを並行してトリガーするため、データがずっと速くフェッチされます。しかしこの方法は本番環境に適していません。 Reactのドキュメントでは、Suspense対応データソースのみがSuspenseコンポーネントを起動し、それらはRelayやNext.jsといったフレームワーク、またはlazyでの遅延ロードコンポーネント、あるいはuseでの約束値の読み取りであると説明しています。 遅延ロードとuseフックも必ずしも良い対応策だとはいえません。 遅延ロードコンポーネントは、フェッチウォーターフォールを確実に排除することはありません。それは同じように振る舞い、Suspenseからの「ローディング」フォールバックが唯一の利点です。 useフックはReactのcanaryバージョンでのみ利用可能なため、まだ安定版としてリリースされていません。 ですので、Next.jsやRelayを使用していない限り、Suspenseを対処法として使うことはお勧めしません。 しかし、本当に使いたい場合は、このCodeSandboxの例を確認してください。本番環境に適したものが出れば、良いパフォーマンスを維持し、フェッチウォーターフォールを避ける素晴らしい方法になり得ます。 対処法② サーバー上でデータをフェッチする Next.jsを使用する場合は、サーバーコンポーネントを使用します。 そうすれば、クライアントはデータとともにHTMLを受信し、データ要求をする必要がなくなります。 リクエストがない……つまりフェッチウォーターフォールがない!ということになります。 では、このようにサーバーサイドでデータをフェッチするのでしょうか? サーバーは、クライアントに処理結果を返す前に、一連のデータをすべてフェッチするのを待つことになります。 これは並列実行の問題であって、レンダリングの問題ではありません。 サーバーでデータをフェッチするだけでは解決することができません。 上の画像を見ると、確かに滝のように見えますね。 古典的なSSR(または “loading “コンポーネントのないServer Components)では、TTFBはデータをフェッチする時間よりも遅くなっています。 そして、TTFBの値が大きくなったので、他のすべてのウェブバイタルも大きくなっています。 Next.jsでもローディングコンポーネントを提供することでTTFBを修正できますが、それでもウォーターフォールは落ち続けます。 もうひとつ考慮すべきことは、SSR/Server Componentsを使用する場合でも、ブラウザ上でレンダリングするということです。 クライアントは、サーバーから受け取ったDOMを表示し、ユーザーのインタラクションに反応できるようにしなければなりません。 つまり、サーバーで生成した静的なWebページに、クライアント側のスクリプトが操作可能にする必要があります。 2台のコンピュータ(ブラウザとサーバー)がレンダリングタスクを実行します。 サーバーは各リクエストに対してより多くのタスクを実行します。 ブラウザは(SSRのとき)ユーザーにはまだ空白のページを表示しており、レスポンスが返ってきたときにもまだレンダリングを行っています。 サーバーでのフェッチは悪いアイデアではありません。 しかしその方法を使う場合は「トレードオフ」であることを踏まえて検討する必要があります。もし問題がないのであれば、このように並列にデータをフェッチするようにしてください。 3つのfetchメソッドは、他のAPIリクエストやデータベース呼び出しなど、どんなものでも構いませんが、Promiseを返さなければなりません。Promise.allはすべての入力プロミスを並行して起動し、入力プロミスの最後が解決されたときに解決される新しいプロミスを返します。 フェッチ・ウォーターフォールを避けるためにデータ・フェッチを持ち上げる フェッチ・ウォーターフォールの問題を解決するもう1つの方法は、データ・フェッチをコンポーネント階層の上位レベルに引き上げることです。 コンポーネント・レベルでデータをフェッチする代わりに、コンポーネント・ツリーの最上位レベル(データをフェッチし始める最初のコンポーネント)でデータをフェッチし、それをコンポーネント・ツリーに渡すことができます。 この場合でも、並行してデータをフェッチすることは重要なので、必ずそのようにしてください。 […]

Flutter開発で知っておくべきヒントとツールについて

現代のアプリケーションは、複雑なサービスの集合体です。 故障する可能性や、期待どおりに動作しない可能性を持つサービスが、相互に連携し合って構成されています。 Flutterとその開発言語であるDartは、イベント駆動型、並行処理、そしてパフォーマンスの高いアプリケーション開発のために設計されています。 それらを使用する開発者にとって、適切なデバッグツールを使いことなすことは必要不可欠です。 デフォルトのFlutterツールには、他言語よりも優れたデバッグとプロファイリングのツールスイートが含まれており、多くの開発者をサポートしています。この堅牢な基盤によって、人気のあるIDE向けのプラグインや外部モニタリングプロバイダが、Flutterアプリケーションに対する深い洞察を得ることができます。 この記事では、いくつかのオプションを紹介し、それらがFlutterのデバッグとプロファイリングにどのように役立つかを説明します。 Flutterデバッグの例 Flutterデバッグに必要なトップツールとヒントを網羅するために、例を交えながらお伝えしていきます。 この情報収集の目的は、複数のタスク、課題、およびToDoのソースを統合し、それらをソートおよびフィルタリング可能なリストで一緒に表示するアプリケーションを作成することです。 では、TrelloとGitHubの課題からアイテムを取得し、リストで表示するアプリケーションを例として挙げて進めていきます。 このアプリケーションは2つのAPIからデータを取得し、JSONレスポンスを解析し、データ構造を統合し、それらをスクロール可能なリストで表示する必要があります。 このアプリケーションの進捗状況はGitHubで確認できます。 ログ記録のオプション 一般的な開発者であれば「console.log()」といったログを出力するコードを書く所から解析を始めるでしょう。 これが最適な方法ではないことはわかっていますが、簡易なデバッグであればこれで充分です。 FlutterとDartには、これを行うための3つの方法があります。 print()、stdoutそしてstderrを使用してメッセージをコンソールに記録できます。 詳細な情報を受け取るためにdart:developerのlog()関数を使用できます。 log()関数を使用すると、タイムスタンプ、エラーレベル、スタックトレースなどの複数のオプションパラメータが追加された状態で出力されます。 Flutterでフラグをデバッグする方法は FlutterはUIに焦点を当てたアプローチを取り、アプリケーションインターフェースはしばしば数十のカスケードウィジェットから構成されるため、アプリケーションのUIに対する視覚的なフィードバックはとても重要です。 Flutterにはクラスに追加できるいくつかのデバッグフラグがあります。ただし、FlutterのDevToolsにはこれらのフラグを複製(および改善)する機能があるため、それらを使用する目的がない限り、出来るだけ使用しない方が良いでしょう。 Flutterのドキュメントには、これらのフラグの概要が記載されており、レンダリングパッケージのプロパティリストには完全なリファレンスがあります。 DevToolsパッケージ Flutterの最も強力な機能の1つは、DevToolsパッケージです。これはデバッグやパフォーマンス分析機能を提供するツール集です。Android Studio、IntelliJ、またはVS Codeのプラグインと組み合わせて使用するのが最適ですが、ブラウザ内またはコマンドラインからも使用できます。 組み込みツールとして、多彩な機能を提供しており、以下が含まれます。 UIのレイアウトと状態を検査 UIパフォーマンスの問題を診断 CPUプロファイリング(プロファイルモードのみ) ネットワークプロファイリング(プロファイルモードのみ) ソースレベルのデバッグ トレースをサポートするタイムラインビュー メモリの問題をデバッグ 実行中のアプリに関する一般的なログと診断情報を表示 コードとアプリのサイズを分析 アプリケーションをデバッグモードまたはプロファイルモードで実行すると、DevToolsは自動的に開始し、エディター内で表示されるか、コマンドラインから実行した場合はブラウザで表示されます。DevToolsはデフォルトで多くの情報を表示しますが、エディタープラグインを使用してブレークポイントを設定することで、その機能を最大限に活用することができます。 DevToolsを使用して、ブラウザからUSBケーブルで接続した物理デバイスまで、どこからでも実行中のFlutterアプリケーションをデバッグすることができます。 Flutterアプリケーションをデバッグするためのエディタープラグインには Android Studio、IntelliJ、およびVS Codeのプラグインは、DevToolsをIDEにバンドルしており、アプリケーションが実行中でも上記でリストに挙げた機能をリアルタイムで活用できます。 ブレークポイントを設定すると、DevToolsはアプリケーションをブレークポイントで一時停止し、アプリケーションが宣言する変数を含む現在のアプリケーションの状態を表示します。その後、DevToolsのUIを使用してアプリケーションの状態を検査し、実行を続行できます。 Sentry Flutterプラグイン Flutterはアプリケーションを構築する際に包括的なデバッグツールを提供しています。 本番環境でのエラー追跡とモニタリングが重要であり、Sentryはそのサポートを提供しています。 これには手動で追加された例外のキャッチングや、本番環境のパフォーマンス監視が含まれ、Sentryはこれらに加え、メタデータやスクリーンショットなどの情報を提供します。 実際の問題をデバッグする これらの様々なツールは、アプリケーションのデバッグをどのようにサポートするのでしょうか? 以下は、私が遭遇したエラーの具体例と、それらを解決するのに役立ったツールの一部をご紹介します。 もちろん、他にも多くのエラーに悩まされましたがそれらのほとんどは人為的なものでした 😅 開発にはVisual […]

2024年、AIエンジニアになるために知っておくべきこと、できること。

2024年、AIエンジニアになるために知っておくべきこと、できること。 AI開発の分野は、音声認識、画像認識、ビジネスプロセス管理、医療診断などAIの能力によって、タスクに革命をもたらし急成長しています。 高度なコード生成ツールを導入することで、ソフトウェア開発に変革をもたらしています。 この業界全体の変化は、日本のみならず世界中のソフトウェアエンジニアにとって重要な岐路となります。ソフトウェアエンジニアは今、厳しい現実に直面しています。恐らく今後、あなたが現在報酬を得ているスキルの一部は、AIによって100%自動化されるでしょう。 そんなAIに劣らない技術を維持するためには、エンジニアはAI技術と多くのアプリケーションを受け入れられるようにスキルセットを変えなければなりません。 しかし、重要なのはこの転換を「どのように行うか」ということです。 何を理解しなければならないのか? AIを使った開発を熟達するためにエンジニアはどのようなステップを踏むべきなのか? 多くのエンジニアは、業界と必要なスキルを理解する時間を取ろうとしません。 この記事では、必要なスキル、求人の種類、AIエンジニアとして成功するための重要なステップについて解説していきます。 AIエンジニアは実際に何をするのか? AIエンジニアへの転職を成功させるためには、エンジニアはまずAIエンジニアが実際に何をするのかを深く理解する必要があります。 AIエンジニアは、機械学習やディープラーニングのニューラルネットワークを用いたAIモデルの開発を担当しています。ニューラルネットワークがどのようなものなのかについては割愛します。 AIエンジニアの責務 AIエンジニアは、AIをより広範な組織の枠組みに統合する上で極めて重要な役割を果たします。その責務は以下の通りです。 機械学習モデルをAPI(Application Programming Interface)に変換し、様々なアプリケーションへの統合と活用を可能にする。 AIモデルを一から構築し、プロダクトマネージャーや利害関係者を含むさまざまな組織部門にその利点を説明する。 データの取り込みと変換のためのインフラを構築し、効率的なデータの取り扱いと処理を保証する。 データサイエンスチームが使用するインフラを自動化し、効率性と生産性を高める。 統計分析を実施し、組織の意思決定のために結果を洗練させる。 AI開発と製品展開のインフラを確立し、維持する。 AI統合を成功させるためには、様々な組織チームとの連携が不可欠であるため、チームワークに優れていること。 AIエンジニアになるために必要なスキル AIエンジニアを目指すプロフェッショナルたちは、この分野で求められるスキルについて知っておく必要があります。その中には以下のようなものがあります。 テクニカルスキル プログラミング・スキル AIエンジニアになるために必要なスキルの第一はプログラミングです。   AIに精通するためには、Python、R、Java、C++などのプログラミング言語を習得し、モデルを構築・実装することが極めて重要です。   線形代数、確率、統計 隠れマルコフモデル、ナイーブ・ベイズ、ガウス混合モデル、線形判別分析など、さまざまなAIモデルを理解し実装するには、線形代数、確率、統計の詳細な知識が必要となります。 Sparkとビッグデータ技術 AIエンジニアは、テラバイトやペタバイト単位のストリーミングや、リアルタイムの本番レベルの大量データを扱うことが多いです。このようなデータを扱う場合、エンジニアはSparkやその他のビッグデータ技術について知っておく必要がある。Apache Sparkの他にも、Hadoop、Cassandra、MongoDBなどのビッグデータ技術を使用することが求められます。 アルゴリズムとフレームワーク 線形回帰、KNN、Naive Bayes、Support Vector Machineなどの機械学習アルゴリズムの仕組みを理解すれば、機械学習モデルを簡単に実装できます。   さらに、非構造化データでAIモデルを構築するには、深層学習アルゴリズム(畳み込みニューラルネットワーク、リカレント・ニューラル・ネットワーク、生成敵対ネットワークなど)を理解し、フレームワークを使って実装する必要があります。   人工知能で使われるフレームワークには、PyTorch、Theano、TensorFlow、Caffeなどがあります。 人間力 成功するエンジニアと苦戦するエンジニアの違いは、ソフトスキルに根ざしていることが多いです。   AIエンジニアは主に技術的な仕事ですが、他者と効果的にコミュニケーションをとり、問題に対処し、時間を効果的に整理し、他者と協力して仕事をする能力は、プロジェクトが問題なく完了し、納品されるかどうかを決定する重要な要素でもあります。 AIを効果的に導入するには、社内の複数の部門が効果的に協力する必要があります。MLパイプラインやAIを活用したアプリケーションを効果的に構築、デプロイ、維持するためには、データエンジニア、データサイエンティスト、ドメインエキスパート、ソフトウェアエンジニアなどが協力し合う必要があります。   異なるチーム間で効果的な協力者となるためには、以下のようなスキルを身につけることが重要です。 コミュニケーション・スキル 人工知能エンジニアが話をしなければならない相手は、幅広い能力レベルの異なるメンバーたちです。 […]

すべての開発者のためのパフォーマンス監視 ウェブ・バイタルと機能回帰の問

パフォーマンス・モニタリング・ツールから適切な洞察結果を出すのは、フラストレーションが溜まるものです。一般的なモニタリングツールを使用すると、必要以上のデータが返されてしまい、そのデータを実際のソースコードで確認するまでに大変な手間がかかります。 Sentryが提供するパフォーマンス・モニタリングは、集中すべき課題をピンポイントで検出することで不要なデータを除外し、原因のコード行番号を的確かつ素早く表示します。その結果、より少ないノイズ、より実用的な結果として得ることができます。 本日、Web/モバイル/バックエンドの開発者がアプリのパフォーマンス問題を発見し、解決するための2つの新機能「Web Vitals」と「機能回帰問題」を発表します。 Web Vitals は、パフォーマンススコアから遅いコードまで確認可能 Web Vitalsは、ページの品質を測定するために、読み込み速度、インタラクティブ性、視覚的安定性のような指標として有効なもののみに統一しています。これらの指標を使用して、Web Vitalsメトリクスの加重平均を使用して計算された100点満点の正規化スコアであるSentry Performance Scoreを開発しました。 Sentryのパフォーマンス・スコアは、Google の Lighthouse のパフォーマンス・スコアと似ていますが、1つだけ重要な違いがあります。 それは、Lighthouseは管理されたラボ環境からデータを収集するのに対し、Sentryは実際のユーザー体験からデータを収集します。 私たちは、ラボ環境でしか関連性のないコンポーネントを除外しながら、Lighthouseにできるだけ近いスコアになるようにモデルを作成しました。 Web Vitals が最大の改善機会を特定する 全体的なパフォーマンス・スコアを向上させるには、パフォーマンスの改善が必要な個々の主要ページを特定することから始める必要があります。 これを簡素化し、すぐに本題に入るために、1つのページが全体のパフォーマンス・スコアに与える影響を示す「機会(Opportunity)」ごとにページをランク付けします。 それでは実際にSentry独自の課題の詳細ページをお見せします。この例では、Webアプリで最もアクセス機会の多いページを表示しています。これは当社の製品で最もよくアクセスされるページであるため、このページのパフォーマンスを向上させることは、Sentryの使用体験全体を大幅に改善することになります。 問題のあるページを特定したら、ユーザー体験が悪かったイベントを探します。 以下に、実際のユーザーが問題の詳細ページを読み込んでいることを示すイベントを表示します。 上のスクリーンショットでは、パフォーマンス・スコアが100点満点中9点(悪い)となったユーザーが表示されていますが、これは主に10秒以上のLargest Contentful Paint(LCP)が原因です。 このような最悪のケースは、ローカル開発中や理想的な条件下(ユーザーが高速ネットワーク接続やハイスペックなデバイスを使用している場合など)では明らかにならないパフォーマンスの問題を浮き彫りにしています。 これらのイベントの中には、『▶️(リプレイ)』ボタンがあることにお気づきでしょう。利用可能な場合は、そのページでのユーザーの実際の体験をビデオのように再現して見ることができます。アプリのパフォーマンスを最適化する場合、これらのリプレイは、ユーザーが劣悪な体験をしている場所を解析するのに役立ちます。 スパンウォーターフォールは、最も価値のあるオペレーションを強調します。 LCPが遅くなった原因を調べるには、イベントの「トランザクション」ボタンをクリックすると、ページ・ロード中に発生した操作の詳細な内訳が表示されます。これらの操作を「スパン」と呼びます。 最も関連性の高いスパンは、赤いLCPマーカーの前に発生するスパンで、これらのスパンはLCPブロックの可能性があるためです。LCPマーカーの後に発生するスパンは、ページ全体のパフォーマンスには影響を与えますが、最初のページロードには影響を与えません。 明らかにパフォーマンスのボトルネックになっているように見える最初のスパンは、app.page.bundle-loadスパンで、JavaScriptバンドルのロードにかかる時間を測定します。この場合、バンドルのロードだけでほぼ6秒、つまりLCPの総所要時間の約60%を要しています。 JavaScriptバンドルのロード時間は、主にそのサイズに依存します。バンドルのサイズを小さくすれば、ページ読み込み時間は大幅に改善されます。 しかし、バンドルの読み込み時間を50%短縮しても、LCPは12秒から7秒にしか短縮されません。 次の明確な改善箇所は、このui.long-task.app-initスパンに1秒近くかかっていることです。長いタスクのスパンは、ブラウザがJavaScriptコードを実行し、UIスレッドをブロックしている50ミリ秒以上の操作を表します。 これは純粋なJavaScriptの操作なので、さらに深く掘り下げて何が起こっているのか調べてみましょう。 ブラウザのプロファイリングは、ソースコードの原因となっている行を表示 従来までは、処理が長いタスクの原因となっているコードを特定することは困難でした。その理由は、プロファイラにアクセスできる開発環境で問題を再現する必要があったためです。 Sentryでは、これを解決するために本番環境(Chromiumベースのブラウザ)でブラウザJavaScriptプロファイルを収集するための新しいサポートを開始しました。これにより、実際のユーザーの問題をデバッグし、ユーザーベース全体で幅広いサンプルプロファイルを収集することができます。 以下の例では、ページ読み込みイベントに関連するプロファイルを開き、1秒ほどのタスクスパンの間に実行されたコードを見ることができます。 EChartsReactCore.prototype.componentDidMount関数の実行時間は558ミリ秒であり、これは長いタスクスパンの半分以上です。この関数は、オープンソースのEChartsビジュアライゼーション・ライブラリが提供するチャートをレンダリングするReactコンポーネントにリンクされています。これはまさに、Issues Detailsのページロード時間を短縮したい場合に注目すべき箇所のようです。 ここまでの流れを要約していきます。 まずパフォーマンス・スコアが低いページを特定します。 次に JavaScript バンドル・サイズを縮小して、特定の React コンポーネントを最適化することで、問題の詳細ページのパフォーマンスを大幅に改善できると判断しました。機会(Opportunity)スコアが高いページを見つけ、ページ読み込みイベントを分解し、プロファイルを使用して JavaScript パフォーマンスを深く掘り下げることで、製品の全体的なユーザー体験を向上させることができます。 Web […]

ソースマップのアップロードエラーを修正する方法

すべての変数と関数名を含むソースコードが含まれていないスタック・トレースは、開発者が問題の根本原因を分析することを困難にさせます。 Sentry には、根本原因分析において開発者を支援する重要な機能があります。 Sentry では、ソースマップをアップロードすることができます。 これにより、元のソースコードにマップバックすることができ、コード内の問題の原因をより簡単に理解することができます。しかし、Sentyにソースマップをアップロードすることは困難です。 このガイドでは、ソースマップのアップロードを試みたもののエラーが表示されてしまう場合の対処方法をご説明します。 最も一般的なアップロードの問題を解決するために、再確認すべき事項やベストプラクティスについて詳しくご紹介します。 まだソースマップをアップロードしていない場合は、以下の1行のコードからプロセスを開始しましょう。 npx @sentry/wizard@latest -i sourcemaps Sentryのソースマップの使い方 スタックトレースにオリジナルのソースコードを表示するには、Sentry はイベントペイロード内のスタックトレースを、そのリリースまたはビルド用にアップロードされたソースマップと照合する必要があります。 そのために、Sentry は「Debug IDs」と「release + abs_path」に基づく2つの照合方法を提供します。上記のコマンドでウィザードを実行すると、どの方法があなたのアプリに有効かを判断するのに役立ちます。 ウィザードを実行してアップロードした後、どの方法を使用しているかを確認することができます。 設定 > プロジェクト > [プロジェクト名] > ソースマップページから、アップロードしたファイルにアクセスすることで確認できます。バンドルを開き、ファイル名の下に Debug ID があるか確認します。 デバッグIDが表示されていれば、現在デバッグIDが設定されているということになります。 デバッグIDが表示されない場合は、リリース+abs_pathマッチングに設定されているということになります。 Sentryでは、より簡単にセットアップするために、Debug IDの使用を推奨しています。 release + abs_pathマッチングを使用している場合でも心配ありません。このアップロードプロセスのデバッグ方法についてもご説明します。 デバッグIDアップロードエラーの修正 デバッグIDは、ソースマップをアップロードするための推奨される方法です。これは release + abs_path アプローチの欠点を無くしたものです。Debug IDのサポートを追加することで、リリースを作成する必要がなくなります。Sentry はパス(信頼性に欠ける可能性がある)に依存するのではなく、Debug ID で圧縮されたソースとソースマップのペアを一意に識別しバインドします。これにより、Sentry はパスを確認することなく、最小化されたソースと対応するソース マップを識別することができます。 以下は、デバッグIDを使用したソースマップ・アップロード・エラーをデバッグするためのトラブルシューティング・チェックリストです。 1. SDKバージョンをアップグレードする: SDKがデバッグIDを使用できることを確認してください。issueの詳細ページの下部に、イベントが送信されたSDKが表示されます。 […]

Sentry パフォーマンス – 関数回帰の問題: Pythonのプロファイリングの例

先日、アプリケーション全体で最も遅く、リグレッション(リリース後、パフォーマンスが低下)している関数を表示する機能を開始しました。 今回、新しいタイプのパフォーマンス・イシューで、関数レベルのリグレッションをデバッグできるようになりました。関数のリグレッション問題は、アプリケーションの関数がリグレッションしたときに通知されますが、単にリグレッションを検出するだけではありません。 関数リグレッションの問題は、Sentry Profilingをサポートしているプラットフォームであれば検出することができます。以下では、Pythonプロジェクトのバックエンドを例に説明していきます。 上のスクリーンショットは、Sentryのライブランニングサーバーコードのスローダウンを特定した、実際のFunction Regression Issueです。Redisに保存された顧客のレート制限をチェックする関数の持続時間が50%近くも後退したために発生しました。 上のグラフは関数の持続時間の経時変化を示し、下のグラフは呼び出し回数(スループット)を示しています。スループットもスローダウン期間中に増加していることがわかります。これは、負荷の増加がこの回帰の原因の1つである可能性を示唆しています。 上のスクリーンショットでは、同じ問題によって、どのAPIエンドポイントがリグレッションの影響を受け、どれだけパフォーマンスが後退したかといった他の重要な情報を見ることができます。このデータから、レート制限関数が多くのエンドポイントで広く使用され、呼び出された結果、バックエンド全体のパフォーマンスが大幅に低下したことがわかります。 回帰問題からプロファイルを確認して根本原因を見つける リグレッションした関数の問題では、リグレッションの前後にキャプチャされたプロファイルを簡単に確認できます。これらのプロファイルを比較することで、リグレッションの原因となった実行時の動作の変化を(コードレベルで)明らかにし、最も重要なコンテキストを提供します。 ここで実際に、リグレッションが発生する前にキャプチャされたプロファイル・イベントの例を見てみましょう。リグレッションが発生した関数は、サードパーティのredisモジュール内の2つの関数を呼び出していることに気づきました。 ConnectionPool.get_connectionとConnectionPool.releaseです。 これをリグレッション後に収集したプロファイルと比較したところ、これら2つの関数のうちの1つであるConnectionPool.get_connectionに以前よりも大幅に時間がかかっていることがわかります。 プロファイルの各関数フレームは、関数が定義された場所と実行された行番号のソース・コンテキストを提供します。この場合、redisモジュールでこのソースの場所を開くと、次の行(以下の画像参照)であることがわかりました。 この行はロックの取得を試みており、この行を実行したときの壁時間の大幅な増加は、複数のプロセスまたはスレッドが同時にロックを取得しようとしていることを示唆しています。このロックの競合がコード・レベルでのリグレッションの原因であることがわかります。 このロック競合の問題は、先にスループット・グラフで見たこと、つまりスループットが増加すると競合が起こりやすくなることとも一致しています。追加の調査を通じて、この関数のスループットの増加は、リグレッションの頃に始まったRedis接続数の増加に対応していることがわかりました。 今回の例を通じて、リグレッションした関数の問題が、プロファイリングデータを使ってリグレッションを引き起こしているコードに直接リンクするのに役立つことを説明しました。 今回の例はバックエンドのユースケースに焦点を当てていますが、この機能はSentry Profilingをサポートするどのプラットフォームでも機能します。 ファンクション・リグレッションの問題は、アーリー・アダプターの皆様には本日よりご利用いただけます。 結論 人々が使いたいと思うような差別化された製品を構築するためには、高性能なバーが不可欠です。ウェブ・バイタルとファンクション・レグレッション・イシューにより、すべての開発者がコードに接続することでパフォーマンスの問題を解決できる方法を提供していきます。 IchizokuはSentryと提携し、日本でSentry製品の導入支援、テクニカルサポート、ベストプラクティスの共有を行なっています。Ichizokuが提供するSentryの日本語サイトについてはこちらをご覧ください。またご導入についての相談はこちらのフォームからお気軽にお問い合わせください。

JavaScriptプロジェクトのノイズを減らす

あなたがJavaScriptのエラー監視にSentryを使用している場合、よくある課題にお気づきかもしれません。それはSentryが挙げるすべての問題に目を通し、無視してもよい帝優先度のエラーと、修正が必要な高優先度のエラーを分類する作業のことです。 JavaScriptのブラウザ・プロジェクトでエラーを捕捉するのはとても手間がかかります。それは、単一の環境だけではないからです。一般的に数多くの主要ブラウザ、JavaScriptエンジン、オペレーティングシステム、そしてブラウザ拡張のエコシステムが存在します。そのため、関連するエラーを効果的に捕捉するのは難しいのです。 Sentryは、このようなノイズをできるだけ減らすために、多くの機能を迅速に実行します。しかし、いくつかのステップを追加するだけで、簡単にエラーに優先順位をつけることができるので、重要なことに集中することができます。そのため、開発者はすぐに修正する必要があるものだけに集中することができます。 ソースマップとタグリリースのアップロード ソースマップは、コードを最小化しないことで、本番環境のスタックトレースデバッグを容易にします。また、ソースマップはSentryがエラーを個々のissueにグループ化することを容易にします。これは、イベントがより小さく、より管理しやすいissueの集合にバケットされ、issueストリームのノイズが少なくなることを意味します。 ソースマップのアップロードを設定する最も簡単な方法は、Sentry Wizardを使用することです。以下のコマンドを実行するだけで必要なSentryパッケージをインストールし、ソースマップを生成してアップロードするようにビルドツール/CIを設定することができます。 ここでは、ソース・マップ・ドキュメントをサポートするためのドキュメントをいくつか紹介します。 ソース・マップは、ノイズを減らすのに役立つだけでなく、モニタリングとデバッグのワークフローを改善する上で、最もインパクトのある改善となる可能性を秘めています。 ソースマップをアップロードしたら、リリースバージョンをSentry SDKに渡すようにしてください。以下のようになるはずです。 本番ビルドのために、本番稼働中に環境変数経由で渡すことで対応してもよいでしょう。 元のエラー原因を渡す try…catchステートメントを使うと、同じエラーを再スローする代わりに新しいエラーをスローすることがあります。元のエラーのコンテキストを保持し、新しいエラーに原因を追加することができます。これは、Chrome 93、Safari 15、Firefox 91以降のブラウザでサポートされています。 リンクされたエラーは、SDKでデフォルトサポートされており、エラースタックはグループ化され、元のメッセージと共に表示されます。元のエラーを原因として渡すと、スタックトレースにコンテキストが追加され、デバッグ時に役立ちます。 実行不可能なエラーを無視する Sentryではあらゆる種類の興味深いエラーを見てきました。ページに直接注入されたカスタムJavaScript、ブラウザの拡張機能、無駄なエラーを投げるベンダーのコードからエラーを出力します。あなたのアプリケーションでも、これらのエラーのいくつかを見たことがあるかもしれません。 一般的に、エラーはあなたのコードからではないので、これを修正することはできません。 ノイズの原因になっている場合は、ignoreErrorsを使用してSDKで無視することができます。 Sentryの開発者は、実際に私たち自身の環境でいくつかのエラーを黙殺してきました。 TypeError: can’t access dead object というエラーは、ブラウザ拡張機能が削除された DOM 要素にアクセスしたときに Firefox がスローするエラーです。これらのエラーは、ブラウザ拡張機能を作成していない場合、通常は対処できません。 ‘Node’ で ‘removeChild’ の実行に失敗しました: 削除するノードはこのノードの子ではありません。これは、Chromeなどのブラウザでtranslate機能が有効になっていて、Reactがコンポーネントを更新しようとしたときにReactがスローするエラーです。このエラーは、文字列リテラルをスパンでラップすることで修正できますが、大規模なアプリケーションでは、これを強制するのは困難な場合が多いです。Sentryアプリは、ユーザー設定で有効にできる翻訳機能も提供しています。 これらのエラーは、ignoreErrorsを宣言することで、報告される前にクライアント上で直接フィルタリングすることができます。 プロジェクトの受信フィルタを有効にする プロジェクトレベルのフィルタは、Sentry JavaScript SDKのignoreErrorsと同様に、issueストリームにissueが表示されないようにすることができます。設定 → プロジェクト → プロジェクト設定 → 受信フィルタ で有効にできます。アプリケーションのコードに起因しないブラウザのエラーによって引き起こされる可能性が高いエラーを抑制するために、ブラウザの拡張機能によって引き起こされることが分かっているエラーをフィルタリングする、およびローカルホストから来るイベントをフィルタリングするをオンにすることをお勧めします。 インバウンドフィルタページで独自のルールを作成し、カスタムエラーメッセージをフィルタリングすることもできます。例えば – *ResizeObserver loop limit […]

エスカレーションする問題で、トレンドの問題をより早く見つける

どのissueを優先し、最初に解決すべきか判断することは、開発者が共通して直面する問題です。 この問題に対処するため、Sentry は、エスカレーション、進行中およびアーカイブの課題ステータスのような新機能をリリースしました。 さらに、優先度ソートを更新し、最も重要な課題をより簡単かつ迅速に特定できるようになりました。開発者が最も重要な課題を特定するのに役立つ機能を以下で紹介していきます。 新しい問題状態の導入 Sentryは、新しい問題から始まり、進行中の既知の問題、解決またはアーカイブされた問題まで、問題のライフステージに関するより多くのコンテキストを提供するようになりました。修正された問題がいつクローズするか、進行中の問題やアーカイブされた問題がいつ劣化するかを簡単に発見できるように、RegressedとEscalatingという2つの新しいタブが追加されました。 以前は “Regressressed “というステータスがありましたが、それはユーザーが以前に問題を解決(つまり修正)し、その問題が引き続きイベントを受信している場合にのみ表示されるものでした。しかしこのロジックでは、悪化しつつあり、優先されるべきissueが考慮されていませんでした。  このロジックにより、無視のアクションとステータスをアーカイブに置き換えることにしました。 以前は、「無視」機能は開発者が行える正確なアクションではありませんでした。問題を見えなくする最も簡単な方法でした。 しかし、問題が悪化し始めた場合には役に立ちませんでした。 というのも、いつ無視されなくなるかのしきい値を手動で定義しなければならなかったからです。現在では、アーカイブによってissueはアーカイブ・タブに移動し、issueがエスカレートして悪化し始めると、issueはエスカレーションに移動します。issueが以前にアーカイブされたか、一度も対処されなかったかにかかわらず、issueが通常よりも著しく多くのイベントを受信した場合に、新しいエスカレーションissueステータスが割り当てられます。 また、その他の要因も考慮されます。これにより、issueフィードを整理しやすくなり、優先度の高いissueをより早く特定することができます。課題のエスカレーションはアルゴリズムに基づいて行われるため、どの課題に焦点を当てるべきかを手動で判断する必要はありません。 ベータテストでは、アルゴリズムによってエスカレーションされた課題は、解決される可能性が3倍高いことがわかりました。 また、エスカレーションのしきい値を手動で設定した場合と比較して、これらの課題が再度アーカイブされる可能性も低くなりました。 ソートと検索の改善 早急な対応が必要な問題を特定するために使用できるもう1つのツールが、課題ストリームの優先順位ソートオプションです。最近、優先順位ソート機能がアップグレードされ、年齢、全体的なイベント量、最近のイベント量に基づいて課題が順番に表示されるようになりました。例えば、イベント件数の多い新しい課題は、イベント件数の少ない進行中の課題よりも優先順位が高くなります。どのような課題を上位に表示すべきか、ご意見があればGitHub のディスカッションスレッドに投稿してください。 また、重要な問題をより簡単に見つけるという精神に基づき、問題検索バーに人気フィルターを導入しました。 問題への迅速な対応 注意が必要な問題を素早く見つけることは、Sentryにおける良いユーザー体験の核心であると言えます。上記の改善は、Sentryにログインするたびに、「ホットフィックスに値する」問題を簡単に見つけられるようにするためのほんの始まりにすぎません。 IchizokuはSentryと提携し、日本でSentry製品の導入支援、テクニカルサポート、ベストプラクティスの共有を行なっています。Ichizokuが提供するSentryの日本語サイトについてはこちらをご覧ください。またご導入についての相談はこちらのフォームからお気軽にお問い合わせください。

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

Node.jsのプロファイリング

プロファイリングは、最も重要なツールです。 プロファイリングを利用することで、本番環境で実行中のプログラム情報を詳しく見ることができます。 パフォーマンスのボトルネックは、ローカルで再現するのが非常に困難であったり、不可能であったりすることがよくあります。そのため、この機能はとても重要であるといえます。 再現が困難である理由は、外部制約や本番環境特有の負荷が理由にあります。 Sentryでは、iOSとAndroidのプロファイリングのサポートをリリースした後、他のプラットフォームもサポートするように取り組んできました。そのうちの一つがNode.jsです。Node.jsのプロファイリングは、現在ベータ版です。 Node.jsのプロファイリングのセットアップ Node.jsのプロファイリングを設定するには、@sentry/profiling-node パッケージをインストールする必要があります。次に、パッケージをインポートします。その後、プロファイルに必要なサンプリングレートを設定します。 プロファイリングは、当社のSentryパフォーマンス上で動作します。 そのため、Sentryパフォーマンスもセットアップする必要があります。 これらのセットアップが完了すると、Sentry.startTransactionとtransaction.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ですぐにチャートを確認することができます。しばらくの間、このサービスはメモリリークに悩まされており、その都度、再起動させる必要がありました。 最終的に、メモリリークの原因はプロファイリングを活用する前に特定することができ、修正されました。 メモリリークの根本的な原因を調査するのは難しく、簡単な修正作業ではありませんでした。 […]

Androidの例外を処理し、アプリケーションのクラッシュを回避する方法

Androidの例外を処理

まず、2つのことについて説明します。 例外とは、プログラムの実行中に発生し、本来のプログラムの動作を中断させるものです。そして、例外処理とは、例外に対応するための処理のことです。 Androidでは、例外を処理しないとアプリケーションがクラッシュし、恐ろしい「App keeps stopping」ダイアログが表示されることになります。 クラッシュし続けるアプリを使う人はいないですよね。ですので、例外処理を行うことは非常に重要なことであるといえます。 この記事では、例外を処理するいくつかの方法と、「処理されない例外」を処理する方法を紹介します。 また、これらの例外をキャッチする方法も紹介します。今回の記事における例については、Kotlinで解説します。 例外の階層構造 すべての例外は、Throwableのサブクラスです。ドキュメントによると… ”Throwableクラスは、Java言語におけるすべてのエラーと例外のスーパークラスです。このクラス(またはそのサブクラスの1つ)のインスタンスであるオブジェクトのみが、Java仮想マシンによってスローされるか、Javaのthrow文によってスローすることができます。” 例外の種類 Javaでは、例外は主に大きく2つに分けられます。チェックされた例外と、チェックされていない例外の2種類です。 チェックされた例外: コンパイル時にチェックされる例外です。例外を投げるメソッドを呼び出す場合は、呼び出し側として例外を処理するか、throws句で宣言する必要があります。 チェックされる例外の例: IOException、NoSuchMethodException、ClassNotFoundException チェックされていない例外: コンパイル時にチェックされない例外のことです。 チェックされていない例外の例: NullPointerException, ArithmeticException, ArrayIndexOutOfBoundsException。 興味深いのは、Kotlinにはチェックされた例外がないということです。次のコードをご覧ください。 このメソッドを呼び出したら、どうなると思いますか?「実行時例外が発生する」と思ったあなたは正しいです。 別のコードを考えてみましょう。 doSomethingメソッドを呼び出すと、ClassCastExceptionが発生します。 これらのスニペットを実行したい場合は、ここで実行することができます。 とあるシナリオ(処理のパターン)を定義するために、内蔵の例外クラスでは不十分な場合があります。このような場合、独自の例外を作成することも可能です。 例外処理のさまざまなやりかた 例外について理解したところで、例外をどのように扱えばよいかを見ていきましょう。まずは、例外を投げる小さなコードの一部を考えてみましょう。 このメソッドを呼び出して例外を処理しないと、アプリケーションがクラッシュし、「App keeps stopping」ダイアログが表示されることになります。 この例外を処理する最も簡単な方法は、メソッド呼び出しをtryブロックで囲んで、例外をキャッチすることです。必要であれば、finallyブロックで処理を追加することもできます。 例外をキャッチするだけで、finallyブロックで実行する追加処理がない場合、KotlinはrunCatching関数を提供します。 これらのスニペットを実行したい場合は、ここで実行することができます。 例外を伝播させる あるメソッドがさまざまな例外を投げる場合、そのうちの一部だけを受け取り、他の例外は上位に伝搬させるというパターンが考えられます。 次のようなコードを考えてみましょう。 try/catch文を使うと、次のようなことができます。 runCatchingを使うと、以下のようなことができます。 ここで重要なことは、例外を伝播させるため、より上位で例外を処理する必要があるということです。 これらのスニペットを実行したい場合は、ここで実行することができます。 Androidの例外処理 上記のテクニックを使って例外を処理するサンプルアプリケーションを考えてみましょう。 このアプリには1つの画面があり、2つのボタンがあります。それらがクリックされると何らかの処理が行われるようになっています。 最初のボタンは必要な例外処理をしており、2番目のボタンをクリックするとアプリがクラッシュします。 コードは非常にシンプルです。 実際のところ、どんなに最善を尽くしてもすべての例外を処理することは非常に困難です。もしかしたら、内部ライブラリの例外が発生するかもしれませんし、稀に処理されない「エッジケースシナリオ」があるかもしれません。 では、アプリケーションがクラッシュする頻度を最小限にするためには、どうすればよいのでしょうか。 Thread.UncaughtExceptionHandlerインターフェイスを使用することで、デフォルトの例外ハンドラにフォールバックすることが可能です。 しかし、デバッグアプリでこれを行うことは決してお勧めしません。なぜなら、クラッシュが発生したらすぐにフィードバックが欲しいからです。 これで、2つ目のボタンをクリックしても、例外を明示的に処理していないにもかかわらず、アプリがクラッシュすることはなくなりました。 これまで、さまざまな例外処理の方法と、デフォルトの例外処理されていない時の例外ハンドラを提供する方法について見てきました。では、さらに一歩進んで、これらの例外を監視する方法を見てみましょう。 […]

;