H12 エラーの防止 (リクエストタイムアウト)
この記事の英語版に更新があります。ご覧の翻訳には含まれていない変更点があるかもしれません。
最終更新日 2024年03月13日(水)
Table of Contents
H12 リクエストタイムアウトエラーは、HTTP リクエストの完了に 30 秒以上かかる場合に発生します。これらのエラーは、多くの場合、次の原因で発生します。
- コストの高いクエリや低速の外部 API 呼び出しなど、実行に時間のかかるリクエスト。
- 結果としてトラフィックの急増時にリクエストキュー時間が長くなる、不十分な並列性。
この記事には、アプリケーションの H12 エラーの数を最小限に抑えるための手順が含まれています。
アプリで大量の H12 エラーが発生する場合は、即時の修正手順についてH12 エラーへの対処 (リクエストタイムアウト)を参照してください。
リクエストを 30 秒より長く実行する必要がある場合、Heroku は長いポーリングおよびストリーミングの応答や WebSocket などの機能をサポートします。
Web サーバーにタイムアウトを追加する
Heroku ルーターは実行に時間のかかるリクエストを 30 秒後に削除しますが、その背後にある dyno は完了するまでリクエストの処理を続行します。タイムアウトを追加すると、dyno 自体が実行に時間のかかるリクエストを削除し、他のリクエストのための容量を作成します。
アプリのタイムアウトを 10 ~ 20 秒の間で、ユーザーに悪影響を与えずにできるだけ低く設定します。特定の言語に対する提案は次のとおりです。
- Node.js: Node.js のタイムアウトモジュールをインストールします。これにより、応答タイムアウト例外が発生します。
- PHP:
php.ini
のmax_execution_time
オプションを設定して、一定期間の経過後に PHP の実行を強制的に停止します。デフォルト値は 30 秒です。 - Python: Gunicorn を使用する場合は、タイムアウトをデフォルトの 30 秒から短くします。
- Ruby: ラックタイムアウト gem を使用します。タイムアウトしきい値を超えた後、gem は
Rack::TimeoutError
例外を発生させます。その他のヒントについては、Ruby でのリクエストタイムアウト (MRI)を参照してください。
リクエストタイムアウトトランザクションをデバッグする
Heroku ルーターのタイムアウト値は設定できません。H12 エラーを最小限に抑えるには、時間のかかるコードを確認して修正し、バックグラウンドジョブを使用します。
アプリコードと外部呼び出しの速度低下を特定する
Heroku Dashboard にはいくつかのメトリクスが表示されますが、リクエストのどの部分が完了するのに最も時間がかかるかを診断するために必要な詳細は提供されません。New Relic などのアドオンや別のパフォーマンス監視アドオンを使用して、時間のかかるトランザクションをトレースします。
次の方法については、監視ツールのドキュメントを参照してください。
- 99 パーセンタイルで、最大応答時間のトランザクションをトレースする。500 ミリ秒未満の応答時間を目指します。
- 時間がかかり、スループットが高いトランザクションを特定する。これらのトランザクションは、アプリの全体的なパフォーマンスに最も大きな影響を与える可能性があります。
- 平均応答時間が最も遅いトランザクションを特定する。スループットが低い場合でも、時間のかかるトランザクションは全体的なパフォーマンスに影響を与える可能性があります。時間のかかるトランザクションは、他のクエリの実行を妨げるなど、データベースのロックを保持する可能性があります。
- これらのトランザクションのトレースとタイミングに従う。一部の監視ツールは、サンプルのトランザクションのトレースを自動的に取得するか、監視する重要なトランザクションを指定する機能を提供します。トランザクションのトレースは、コードによるタスクの実行、データベースの操作、外部 API の呼び出しなどにかかる時間を特定します。
時間のかかるトランザクションのどの部分がボトルネックを引き起こしているのかを特定したら、その速度低下を軽減するための手順を実行します。次に例を示します。
- ファイルのアップロードや高度な計算アクションなど、時間のかかるタスクをバックグラウンドジョブに移動します。
- 時間のかかる外部呼び出しをバックグラウンドジョブに移動します。外部呼び出しをバックグラウンドに移動できない場合は、エラーの発生に備えて計画してください。ほとんどの言語で HTTP リクエストのタイムアウトを指定できます。
- 低速なクエリに対処します。詳細は、データベースの速度低下を特定するを参照してください。
Node JS の応答を調査する
Node.js は、応答を送信することによって暗黙的にリクエストを処理しません。代わりに、開発者は処理ロジックを個別に作成する必要があります。その結果、Node.js アプリケーションでよく見られるのは、応答ロジックが欠落しているコードパスです。
- 各
if
ステートメントや関数呼び出しなど、失敗したルートのすべてのブランチに沿ってログを追加します。ログのフィルタリング機能を追加するために、x-request-id ヘッダーを含めます。 - そのルートへのリクエストを生成し、ログでそのリクエストを検索します。最後の成功した手順は何ですか?
- アプリケーション内のどのパスが適切な応答を生成しないかを見つけます。
Express では、このような論理エラーを最小限に抑えるために、多数のブランチのある単一の関数を使用する代わりに、責任の連鎖パターンを使用します。
たとえば、 次のものの代わりに、
app.*get*('/app/:id', doEverythingInOneBigFunctionWithAsyncBranches);
責任の連鎖パターンを使用します。
app.get('/app/:id', checkUserAuth, findApp, renderView, sendJSON);
function checkUserAuth(req, res, next) {
if (req.session.user) return next();
return next(new NotAuthorizedError());
}
上記のパターンでは、エラーのミドルウェアスタックによる統合エラー処理の利点が加わります。
データベースの速度低下を特定する
data.heroku.com でデータベース内の高コストなクエリを確認します。一覧からデータベースを選択して、その [Diagnose] (診断) タブに移動します。ログを検索して、調査中にこれらのクエリに渡された正確なパラメータを見つけます。
Heroku pg-extras
プラグインの pg:outliers
コマンドは、低速なクエリを見つけるのに役立ちます。そのコマンドを実行して、実行時間の多くを占めているクエリを突き止めます。多くの場合、不足しているインデックスを追加することで、多くの低速なクエリをすばやく修正できます。Heroku Postgres で低速なクエリを特定するための詳細な手順と、それらを最適化するためのその他のヒントについては、高コストなクエリのセクションを確認してください。
データベースのロックや肥大化などのデータベースのパフォーマンスの潜在的な問題を特定して対処するには、「Heroku Postgres の監視」セクションを確認してください。
Web の並列性を最適化する
並列性とは、多数のリクエストを同時に処理することです。複数のサブプロセスにフォークしたり、複数のスレッドを使用したりすると、並列性が向上します。一般に、サブプロセスが多いほどメモリ使用量が多くなり、スレッドが多いほど dyno の負荷が高くなります。
基準メトリクスを収集する
適切な Web の並列性と dyno の数とタイプを決定することは、絶え間なく繰り返す必要がある重要なタスクです。dyno の負荷とメモリについて観察して、dyno リソースの使用率レベルと、それらが推奨値を上回っているか下回っているかを把握してください。Heroku Dashboard 内の [Metrics] (メトリクス) タブ、または接続されている場合はアプリケーションの監視ツールで、過去 7 日間の dyno の負荷とメモリ使用量を記録してください。それらのメトリクスを推奨ガイドラインと比較してください。
- 「What is an acceptable amount of dyno load?」 (許容可能な dyno の負荷の量) を参照します。アプリケーションの dyno タイプに関する推奨事項を考慮してください。
- 最大メモリ使用量をメモリ割り当ての 85% 未満に保ちます。アプリケーションの dyno タイプのメモリ制限を確認してください。
リソース使用量を最適化する
アプリケーションの基準メトリクスで高負荷の問題が明らかになった場合は、CPU 負荷の高いタスクをバックグラウンドジョブに移動します。
メモリの基準メトリクスを確認してください。合計メモリ使用量には、RAM とスワップメモリが含まれます。スワップが使用されている場合、または R14 または R15 のメモリ関連のエラーがログに記録されている場合は、メモリリークまたは肥大化がないか確認してください。Common Runtime で最大 50 MB のスワップが見られるのはよくあることです。Private Spaces の dyno はメモリをスワップしませんが、代わりにメモリが使い果たされると再起動します。これらの言語でのメモリ使用量を調査するための具体的なガイダンスについては、「R14 - Ruby (MRI) でのメモリ割り当ての超過」およびNode.js のメモリ使用のトラブルシューティングを参照してください。
並列性設定を調整してテストする
本番環境に適用する前に、ステージング環境で並列性設定を必ず調整してテストしてください。
時間のかかるトランザクションとリソースの使用を最適化した後、並列性設定を調整します。理想的な同時実行レベルは、dyno の負荷に関する推奨ガイドラインの範囲内で、85% のメモリ割り当てを維持しながら、必要な応答時間を融合させます。スレッド、プロセス、および dyno の数を変更して、並行性を調整します。
- スレッドの数を増やすことにより、並列性を高めます。ただし、スレッドが多いほど、dyno の負荷が高くなります。
- プロセスの数を増やすことにより、並列性を高めます。ただし、プロセスが増えると、より多くのメモリが消費されます。
- dyno の負荷とメモリ制限のためにスレッドとプロセスを追加しても、並列性を高めることができない場合:
- 実行中の dyno の数を増やします。
- dyno タイプをアップグレードします。大容量の dyno は、追加のリソースを提供します。そこからスレッドとプロセスの数を増やします。
- 前の箇条書きの逆のことを実行して、並列性を減らします。
すべてのアプリケーションは固有ですが、一部の言語の詳細と推奨されるデフォルトの並列性設定は次のとおりです。
- Ruby: 複数のプロセスとスレッドで Puma Web サーバーを使用します。各プロセスまたはスレッドには、データベースへの異なる接続が必要です。Rails では、ActiveRecord には、同時に複数の接続を保持できる接続プールが用意されています。詳細は、ActiveRecord による Ruby での並列性とデータベース接続参照してください。
- Node: Node はシングルスレッドですが、複数のプロセスをフォークして、使用可能なリソースを最大化できます。具体的なヒントについては、Node.js アプリケーションの並列性の最適化を参照してください。
- Python: Gunicorn は、各 dyno 内で複数のシステムワーカープロセスをフォークします。具体的なヒントについては、「Python アプリケーションの並列性の最適化」を参照してください。
- PHP: PHP-FPM は、複数の子プロセスを生成します。
本番環境に適用する前に、ステージング環境で並列性設定への変更を必ずテストしてください。並列性の調整に役立つように、テスト中に応答時間、dyno の負荷、およびメモリ使用量を確認してください。さまざまなトラフィックパターンを使用してテストし、高、低、および平均のトラフィックの需要を満たす dyno の数を決定します。詳細なガイダンスについては、負荷テストの記事を参照してください。
オートスケールを有効化または調整する
オートスケールにより、Web dyno の数を自動的に調整できます。応答時間がしきい値に達したときのオートスケールを有効にすると、dyno が追加されて Web の並列性が向上するため、H12 エラーの防止に役立ちます。さまざまなトラフィックパターンに対する以前の負荷テストに基づいて、実行する dyno の最小数と最大数を選択します。本番環境に適用する前に、ステージング環境でオートスケール設定への変更を必ずテストしてください。詳細は、オートスケールを参照してください。
定期的なレビュー
この記事の手順を実行するには、四半期または年次のレビューをスケジュールしてください。コードとトラフィックパターンの変化に基づいて、これらのレビュー中にリソースを適切に調整してください。