【Android tombstone】ネイティブクラッシュ解析の大幅改善

Article by: Mischan Toosarani-Hausberger , Roman Zavarnitsyn (了読時間:13分) Androidにおけるネイティブクラッシュは、これまで本来あるべきよりもデバッグが難しいものでした。 Androidには独自のクラッシュレポーター(debuggerd)があり、クラッシュしたスレッド、実行中の他のすべてのスレッド、レジスタ状態、メモリマップを tombstone と呼ばれるファイルに記録します。tombstone は長年Androidの一部であり、実際にはAndroid最初期のコミットの頃から、形を変えつつ存在してきました。 問題は、Androidの歴史の大半において、アプリ内部から tombstone をプログラム的に読み取ることができなかったことです。そのため、SDKベースのネイティブクラッシュレポート機能(私たちのものを含む)は、プラットフォーム側ですでに存在している仕組みを独自に再実装せざるを得ませんでした。その代償として、バイナリサイズの増加、不完全なJavaフレームのシンボリケーション、さらに変化し続けるAOSPに追従するために維持しなければならないC++フォークが発生していました。 Android 11(SDK level 30)では ApplicationExitInfo が導入されました。さらに Android 12(SDK level 31)では、ApplicationExitInfo.REASON_CRASH_NATIVE に対する trace input stream へのアクセスが追加されました。 SentryのAndroid SDKは、バージョン8.30.0以降、Android 12以上を実行しているすべてのデバイスでこのストリームを読み取り、ネイティブクラッシュイベントとして送信します。これにより、ネイティブコードを使用するAndroidアプリのクラッシュレポートは大幅に改善されました。基本的なクラッシュ通知だけが必要なチームにも、詳細なデバッグ情報が必要なチームにも有効です。 ここからは、以前はどのように動作していたのか、既存のNDK統合を壊さずにこれをSDKへ組み込むために何が必要だったのか、そしてこれによってどのような改善がもたらされたのかを見ていきます。 tombstoneサポート以前:変化し続けるターゲットを追い続けるフォーク tombstoneサポート以前、Android SDKでは Native SDK(sentry-native)がネイティブエラーレポートの主要な仕組みとして使用されていました。AndroidはLinuxベースであるため、SDKのかなりの部分を再利用できました。しかし再利用できない部分については、2019年からAndroid固有コードの統合作業が始まりました。 特に、libunwindstack(現在も debuggerd、そして tombstone のスタックトレース生成に使用されているAOSPのプラットフォームアンワインダ)を統合したことは、Sentry Android SDKでネイティブクラッシュをサポートする上で重要な転機となりました。なぜかというと、Native Development Kit(NDK)には汎用的なスタックウォーカーが存在しなかったからです(今でも存在しません)。 libunwindstack はNDKの一部ではなく、Android Open Source Project(AOSP)のプラットフォームコードの一部であるため、通常の方法ではアプリ開発者から直接利用できません。Sentryは、NDKでビルドできるようプラットフォームコードへパッチを当てたリポジトリをフォークし、その後も上流のパッチ版に変更がないまま、そのフォークを維持してきました。 これにより、非常に複雑なAndroid […]
Reactでフェッチウォーターフォールを特定する方法

フェッチウォーターフォールは、複数のフェッチリクエストが並列ではなく、逐次的に呼び出されるシナリオです。これは深刻なパフォーマンス低下につながります。 以下にその様子を示します。 この場合、2番目と3番目のリクエストは並行してフェッチされ、ページロードとデータ表示が4.053秒改善されます。フェッチウォーターフォールによるパフォーマンスへの悪影響は、スタッキングでも発生します。つまり、リクエストが多ければ多いほど、パフォーマンスへの影響は悪化します。 この記事では、トレースを使用してReactアプリケーションのフェッチウォーターフォールを特定する方法を見ていきます。 トレース入門 トレースとは、あるプロセスやフローを定義する操作やコマンドの論理的なグループを記述するスパンの階層からなるデバッグ・データ・セットをキャプチャするために、コードを「インスツルメンテーション」するプロセスのことです。ページのロードを例にとってみましょう。 ページロードを操作の流れとして記述しようとすると、(おおよそ)次のようになるはずです。 ブラウザがサーバーにページをリクエストする サーバーはHTMLで応答し、ブラウザはそれを解析する パース中に、ブラウザはリンクされたJSファイルに出くわす。JSファイルにはReactとページコードが含まれているので、ブラウザはそれを実行する。 ブラウザは、ページコードの指示に従ってコンポーネントのフェッチとレンダリングを行う。 さらに、ブラウザーは画像、ファビコン、CSSファイルなどのリソースをリクエストする。 これらの処理はすべて特定の順序で行われますが、その時間はデバイスの処理能力やインターネット接続の速度などの要因によって異なります。 この記事のトップにあるスクリーンショットは、APIにHTTPリクエストを送信する3つのhttp.clientスパンを示しています。それぞれ、特定の開始時刻、特定の終了時刻、そして雑多なデータが添付されています。上のスクリーンショットのトレース・ビューを見ると、3つのHTTPリクエストが次々と実行されていることがよくわかります。 プロジェクトの設定 まず、ReactプロジェクトにSentryをセットアップする必要があります。始めるには、Sentry React SDKをインストールする必要があります。 この時点で、すでにサインアップしているはずです。新しいReactプロジェクトを作成しましょう。 Create Project」ボタンを押すと、React SDKのインストール方法と初期化方法が表示されます。初期化設定は以下のようにします。 Sentry SDKの最も優れた点は、コードベースの大部分を自動的に計測してくれることです。フェッチのような既知の操作を自動的にスパンでラップし、Sentryインスタンスに送信するので、すぐにデータの検査を開始できます。 これで、アプリをデプロイして、ユーザーがアプリを使用している間に測定された実際のパフォーマンスデータを得ることができます。データが得られれば、フェッチウォーターフォールのようなパフォーマンスの問題を特定するための調査を始めることができます。 Reactでフェッチウォーターフォールを識別するには? フェッチウォーターフォールの症状には、著しく遅いページロードが含まれるため、不審に遅いページロードを警戒する必要があります。 Performanceページを使用すると、疑わしい遅いページロードを簡単にピックアップして検査することができます。以下は、私たちのアプリのインデックスページのPerformanceページのスクリーンショットで、ユーザーが私たちのページを訪問している間にキャプチャされたトランザクションを示しています。 どのスパンが不審に遅いか、はっきりとわかります。そのうちの1つをクリックすると、トレース・ビュー画面が表示され、すべてのスパンを見ることができます。http.clientのスパンを拡大してよく見ると、ウォーターフォールが見えます。 この場合、3番目のリクエストグループは、どの結果にも依存しないので、2番目のリクエストの終了を待つ必要はありません。つまり、フェッチウォーターフォールを分解すると、2秒の改善を見ていることになります。 フェッチウォーターフォールを修正するには、その原因を調べる必要があります。フェッチウォーターフォールはサーバーに原因があることもあります。フェッチ・ウォーターフォールのよくあるケースの修正方法についてもっと知りたい方は、「Reactにおけるフェッチウォーターフォール」の記事をご覧ください。 これを見ると、トレースを使って他のタイプの問題も解決できると思うかもしれません。そして、それは正しいでしょう!トレースは本当に一般的なデバッグ手法で、Web Vitalの不具合、ネットワークの遅延、サーバーレスアプリケーションのコールドスタート、キャッシュの欠落やキャッシュ機構の問題、その他様々な問題やバグを特定し、デバッグし、修正するのに役立ちます。トレースは、”トレース “をたどって、いつ何が起こったか、どれくらいの時間がかかったかを調べるようなデバッグや修正に使うことができます。 結論 つまり、トレースはフェッチウォーターフォールの特定に役立つということです。 簡単に復習しましょう。 トレースとはデバッグテクニックの一つで、ページロードのような操作の流れを視覚化しやすくするために、デバッグデータをキャプチャすることです。 トレースとは、互いに関連し、開始時刻と終了時刻を持ち、任意のデータが付加されたスパンのコレクションです。 アプリケーションでトレースのキャプチャを始めるために、私たちはSentryのReact SDKをインストールし、アプリのトップで初期化し、変更を単純にデプロイしました。SDKは自動的にアプリをインスツルメンテーションするので、トレースデータをすぐに見ることができました。 キャプチャされたトレースをすべてリストアップし、その継続時間に基づいて、どのトレースが不審に遅いかを確認できました。 遅いページロードを検査すると、最適化するとページロードを秒単位で改善できるフェッチウォーターフォールが見つかりました。 この記事が、トレースとは何か、どのように始めるべきかを理解する助けになれば幸いです。それでは、よいトレースを! IchizokuはSentryと提携し、日本でSentry製品の導入支援、テクニカルサポート、ベストプラクティスの共有を行なっています。Ichizokuが提供するSentryの日本語サイトについてはこちらをご覧ください。またご導入についての相談はこちらのフォームからお気軽にお問い合わせください。
JavaScript v8 SDKにおけるOpenTelemetryとNodeサポートの改善

Sentry Launch Weekで初めて発表したように、私たちはJavaScript SDKのメジャーリリースに向けて取り組んできました。 このアップデートにより、Sentry JavaScript SDKをさらに簡単に使い始めることができます。このリリースは、自動instrumentationを提供するフレームワークとライブラリの数を広げます。しかし、Sentryのセットアップをカスタマイズしたい場合、OpenTelemetry (OTel)の拡張サポートにより、必要なcustom configurationを85行のコードから10行以下に減らしました。 OpenTelemetryによるトレース OpenTelemetry はスタンダードな機能とメンテナンス機能を提供してくれますが、それらのツールと収集されたデータを使って、あなたに洞察を提供するのは私たちの役目です。 例えば、Sentry の新しいトレース・ビューは、v8 の改良された Node.js サポートの恩恵も受ける最新の機能改良の一つに過ぎません。 私たちのNode SDKにOTelを組み込むことで、以前のバージョンよりも詳細なスパンデータを収集します。 Next.jsとPrisma 4で構築されたSentry独自のChangelogに記録されたスパンの詳細をご覧ください。 より多くのNodeフレームワークとライブラリの自動インストルメンテーション 特にOTelのNode SDKは、Sentryの以前のNode SDKよりも多くのNodeフレームワークを自動パフォーマンス計測のためにサポートしています。Node SDKの一部をOTelに置き換えることで、SentryがデフォルトでサポートするNode.jsフレームワークとライブラリのサポートを拡大しました。 これは、あなたのNodeプロジェクトに以下のフレームワークやライブラリがある場合、Sentry Node SDKが自動的にそれらを検出し、設定することを意味します。 Express(改善) Connect(改善) Nest.js(新規) Koa(改善) Fastify(新規) Hapi(新規) ライブラリ pg (Postgres) pg-native (Postgres) mongodb (Mongo) mongoose (Mongo) mysql2 (MySQL) mysql (MySQL) graphql (GraphQL) apollo-server-core (Apollo) @nestjs/graphql (Apollo) Prisma […]
Laravelとの提携に興奮する理由

Laravelの友人がSentryとの新しいパートナーシップを発表しました。 簡単に説明すると、新規または既存の Forge/Vapor サイトに数クリックでエラー監視とトレース機能を追加できるということです。 この新しい統合は、PHP 開発者が可能な限り簡単にプロジェクトの実際の遠隔測定を収集できるように設計されています。 PHPアプリケーションのビルド、デプロイ、管理、デバッグ ForgeとVaporのUIを通して、Sentryを初めて使う開発者は、組織やSentryプロジェクトを作成することができます。 ForgeはLaravelのサーバー管理とデプロイサービスであり、Vaporは60万以上のPHPアプリケーションを提供するサーバーレスデプロイプラットフォームです。 Laravelのビデオで、実際の動作をご覧ください。 この統合により、PHP 開発者はアプリケーションのビルド、デプロイ、管理、デバッグをより効率的かつ確実に行えるようになります。 この統合を通じてPHPのエコシステムをサポートし続けるため、今後も多くの改善が行われることを期待しています。 拡大するLaravelユーザーをサポート Laravelは、10年以上にわたってアプリケーション開発の中核を担ってきました。 PHPフレームワークの中で最も人気があり、急成長しているフレームワークの1つで、そのクリーンで表現力豊かな構文とモダンなコーディング原則により、書きやすく読みやすくなっています。 大企業や新興企業の開発者は、Laravelを使用してアプリケーションを迅速に構築し、デプロイしています。 少なくとも毎年1つのメジャーリリースと、数週間ごとの反復リリースがあり、Laravelのイノベーションは衰えていません。例えば、Laravel 11では以下のようなリリースが行われています。 Reverb (ファーストパーティのWebSocketサーバー) グレースフル暗号化キー・ローテーション 秒単位のレート制限 合理化されたアプリケーション構造 Sentryには、100以上のSDK、フレームワーク、ライブラリがあり、開発者が使用するツールに精通しています。 Sentryの中で、Laravelは3番目に人気のあるバックエンドSDKで、毎年増え続けています。 まだ始まったばかり PHP開発者の間でのLaravelの持続的な人気は、驚異的としか言いようがありません。 Laravelが長年愛されているのは、その技術のパワーとPHPコミュニティ内で築かれた信頼があってこそだと物語っています。 Sentryは、LaravelとPHPコミュニティにコミットし、皆様を念頭に置いて構築し続けています。新しいForgeとVaporの統合により、私たちはLaravelアプリの管理、デプロイ、デバッグのための新しいスタンダードを築き上げます。 詳しい使い方はドキュメントをご覧ください。 IchizokuはSentryと提携し、日本でSentry製品の導入支援、テクニカルサポート、ベストプラクティスの共有を行なっています。Ichizokuが提供するSentryの日本語サイトについてはこちらをご覧ください。またご導入についての相談はこちらのフォームからお気軽にお問い合わせください。
Node.jsのLoaderパフォーマンスを向上させる

Node.jsは2種類のモジュールをサポートしています。 EcmaScriptモジュールとCommonJSモジュールです。 ESモジュールはJavaScriptにおけるモジュールの公式な標準であり、すべてのモダンブラウザでサポートされています。 CommonJSモジュールは、Node.jsがデフォルトで使用するモジュールです。これらはブラウザによってサポートされておらず、公式の標準でもありません。しかし、現在でも広く使われています。 Node.jsはどのようにエントリーポイントをロードするのか? どのローダーを使うかを区別するために、Node.jsはいくつかの要因に依存することを理解しておきましょう。 最も重要なのはファイルの拡張子です。 ファイル拡張子が .mjs の場合、Node.js は ES モジュールローダを使用します。 ファイル拡張子が.cjsの場合、Node.jsはCommonJSモジュール・ローダーを使用します。 ファイル拡張子が .js の場合、 package.json ファイルに “type”: “commonjs” があれば(または単に “type “フィールドがない場合)、Node.js は CommonJS モジュールローダを使用します。 package.jsonファイルに “type”: “module”があれば、Node.jsはESモジュールローダを使用します。 この決定はlib/internal/modules/run_main.jsファイルで行われます。 以下にコードの簡略版を記載します。 readPackageScope は、package.json ファイルを見つけるまで、ディレクトリツリーを上方向に走査します。 この投稿で最適化する前は、readPackageScopeはpackage.jsonファイルを見つけるまで内部バージョンのfs.readFileSyncを呼び出します。 この同期呼び出しはファイルシステム操作を行い、Node.js C++レイヤーと通信します。 この操作には、データのシリアライズ/デシリアライズのコストがかかるため、返す値/タイプによってパフォーマンスのボトルネックがあります。 そのため、readPackageScope内でreadPackage(別名fs.readFileSync)を呼び出すことはできるだけ避けたいです。 Node.jsはどのようにpackage.jsonを解析するの? デフォルトでは、readPackageは内部バージョンfs.readFileSyncを呼び出してpackage.jsonファイルを読み込みます。 この同期呼び出しは、Node.js C++レイヤから文字列を返し、後でV8のJSON.parse()メソッドを使用して解析されます。 このJSONの妥当性に応じて、Node.jsは残りのローダーの実行に必要なオブジェクトをチェックした後作成します。 これらのフィールドは、pkg.name、pkg.main、pkg.exports、pkg.imports、pkg.typeです。JSONの構文に誤りがある場合、Node.jsはエラーを投げて処理を終了します。 この関数の出力は、同じパスに対して readPackageScope を再度呼び出さないように、後で内部 Map にキャッシュされます。このキャッシュは、プロセスの寿命が尽きるまで保存されます。 package.jsonフィールドとリーダーの使用法 最適化の前に、Node.jsがこれらのフィールドをどのように使用しているかを見てみましょう。 Node.jsコードベースでpackage.jsonフィールドをパースして再利用する一般的なユースケースは以下の通りです。 pkg.exportsとpkg.importsは、入力に応じて異なるモジュールを解決するために使われます […]
Reactにおけるメモ化の不具合を修正する

Reactのメモ化を使用することで、ウェブアプリケーションを小さなコンポーネントに分割し、再利用しやすくすることができます。 コンポーネントの更新が必要な場合、Reactは再レンダリングを契機に、動的なデータやアニメーションなどを表示する方法です。 しかし、再レンダリングが必要ないコンポーネントを再レンダリングするとなると、アプリケーションのパフォーマンスに悪い影響を与えます。 以下の状況を想像してみてください。 親コンポーネントが、コールバック関数を子コンポーネントにpropsを通じて渡す場面です。 そして、子コンポーネントはメモ化されているにも関わらず、親コンポーネントが再レンダリングされるたびに子コンポーネントを再レンダリングしてしまう。 この問題を調査し、その修正方法を学んでいきましょう。 問題点 親コンポーネントは、コールバック関数を子コンポーネントにpropsを介して渡します。 子コンポーネントはメモ化されていますが、Reactは親コンポーネントが再レンダリングされるたびにそれを再レンダリングします。 何かが原因で、メモ化の特性を失わせています。 以下は親コンポーネントと子コンポーネントのコードスニペットです。 このコードを試したい場合は、こちらのCodeSandboxリンクをご覧ください。 _Numberコンポーネントには、再レンダリングごとに発生する重い操作が含まれています。 これが問題であることを特定するために、私たちはReact SDKに付属するSentryのwithProfilerメソッドを使用して、関心のあるすべてのコンポーネントをラップします。 これにより、その特定のコンポーネントのui.react.mountおよびui.react.updateイベントがキャプチャされます。アプリをリロードし、「増加」ボタンを数回クリックすると、Sentryパフォーマンスダッシュボードで以下のように表示されます。 トランザクションのうち50%がUI操作に費やされたことがわかります。 こちらについて詳細に調べる必要がありそうです。 しかし、なぜでしょうか? 私たちは_NumberコンポーネントをReactのmemo()でラップしました。 なぜそれが再レンダリングされ続けるのでしょうか? Reactと再レンダリングに関して知っていることを考えると、Reactはコンポーネントを再レンダリングするとき、それらの状態またはプロパティのいずれかが変更されたときです。 _Numberコンポーネントを見てみると、状態変数が定義されていないことがわかりますが、propsからsetMessageコールバックを受け入れています。 問題は「増加」ボタンをクリックしたときに発生します。 _Numberコンポーネントには全く関係ありませんが、それによりClosureRerenderコンポーネントが再レンダリングされ、それが_Numberコンポーネントに渡されるonClickメソッドを再作成します。 _Numberコンポーネントはメモ化されていますが、親が再レンダリングされるたびにsetMessageプロパティに異なる値を受け取り、これによりメモ化をバイパスし、再レンダリングします。 onClickメソッドは変更されないにもかかわらず、その参照が変わります。 自分で確認したい場合は、このページでコンソールを開いて、次のように一行ずつ入力してみてください。 最後のx===yコマンドはfalseを出力します。 それにもかかわらず、両方のオブジェクトは同じ値(’Lazar’)を持つ同じ名前のプロパティを持っています。 JavaScriptは、非プリミティブ型を扱う際に変数の値として参照を保持し、手動で両方のオブジェクトを作成したため、xとyは異なる参照を持ち、したがってx===yはfalseになります。 Reactでも同様です。ClosureRerenderが再レンダリングされるとき、onClickメソッドが再作成されるため、実質的に新しい参照が渡されます。 古いsetMessageプロパティは新しいものの値と一致しないため、Reactは_Numberコンポーネントを再レンダリングします。 では、これを修正するにはどうすればよいのでしょうか? 解決策 解決策としては、`useCallback`フックを使用する必要があります。 `useCallback`フックは、コールバックに対する`useMemo`や`memo()`がコンポーネントに対するものであるようなものです。 依存関係の配列に変更がない限り、コールバックの再作成を防ぎます。 新しい`onClick`メソッドは次のように記述します。 このメソッドを`useCallback`フックでラップし、`props.setMessage`を依存関係の配列に配置します。 それが変更されない限り、再レンダリングの間に`onClick`は同じ参照値を保持し続けます。 もはや「増加」ボタンをクリックしても_Numberコンポーネントの再レンダリングがトリガーされません。そして、それをSentryで検証できます。 ずっと良くなりました。 不必要なui.react.updateイベントもなく、長時間実行されるUIブロッキングタスクもありません。 結論 `useMemo`フックや`memo()`メソッドは、常にコンポーネントが不必要に再レンダリングされるのを防ぎません。 今回の記事のようにメモ化を壊してしまう場面があり、それによってパフォーマンスが損なわれることがあります。 これは、`useCallback`フックを使用しないコールバックメソッドを渡す場合だけでなく、`setMessage={(number) => props.setMessage(number)}`のようにコールバックをインラインで定義する場合にも発生する可能性があります。 慣習的にコードを書いていると思うので「それが何を引き起こすか」にまでは、あまり注意を払っていないかもしれません。 今回のように、これらの状況をアプリ全体で修正したことを検証し、Reactアプリのパフォーマンスを監視し始めるために、アプリにSentryを導入してみてください。 開始は無料で、インストールも簡単です。 SentryがReactアプリに対して何ができるかをもっと詳しく知りたい場合は、Sentry […]
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 […]
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 […]
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つ目のボタンをクリックしても、例外を明示的に処理していないにもかかわらず、アプリがクラッシュすることはなくなりました。 これまで、さまざまな例外処理の方法と、デフォルトの例外処理されていない時の例外ハンドラを提供する方法について見てきました。では、さらに一歩進んで、これらの例外を監視する方法を見てみましょう。 […]