リクエストタイムアウト
最終更新日 2024年05月07日(火)
Table of Contents
アプリで H12 エラーが大量に表示される場合の当面の修正手順については、「Addressing H12 Errors」(H12 エラーへの対処) を参照してください。
アプリケーションの dyno を調整すると、タイムアウトを防ぎ、全般的なパフォーマンスの改善に役立つことがあります。詳細情報: dyno の使用の最適化および H12 エラーの防止。
Heroku によって処理される Web リクエストは、いくつかの Heroku ルーターを経由して dyno に転送されます。これらのリクエストは、アプリケーションですぐに処理されることになっています。ベストプラクティスは、Web アプリケーションの応答時間を 500 ミリ秒未満にすることで、これによりアプリケーションが解放されてさらに多くのリクエストを受けられるようになり、訪問者に対して高品質のユーザーエクスペリエンスを提供できます。たまに Web リクエストが異常停止したり、アプリケーションによる処理に非常に時間がかかったりすることがあります。このような場合、完了までにかかる時間が 30 秒を超えると、ルーターはリクエストを強制終了します。
この 30 秒のカウントダウンは、リクエスト全体 (すべてのリクエストヘッダーと、該当する場合はリクエスト本文) がルーターから dyno に送信されてから開始されます。その後、リクエストはアプリケーションによって dyno 内で処理される必要があり、応答はタイムアウトを避けるために 30 秒以内にルーターに返信される必要があります。
タイムアウトが検出されると、ルーターはカスタマイズ可能なエラーページをクライアントに戻し、H12 エラーがアプリケーションログに送出されます。ルーターはクライアントに応答を返す一方、アプリケーションは処理中のリクエストがタイムアウトになったことを認識しないため、アプリケーションはリクエストの処理を続行します。このような状況を避けるために、Heroku ではアプリケーション内部でタイムアウトを 30 秒よりもずっと低い値 (たとえば 10 秒や 15 秒) に設定することをお勧めします。ルーティングのタイムアウトとは異なり、これらのタイマーは、アプリケーションによるリクエストの処理が開始されたときに始まります。これについて詳しくは、以下の「タイムアウトの動作」を参照してください。
タイムアウト値は設定できません。サーバーが所定のリクエストを完了するのにかかる時間が 30 秒を超える場合、その作業をバックグラウンドタスクに移動するか、処理中のリクエストが完了したかどうかを確認するために Worker からサーバーに定期的に ping を送信するようにすることをお勧めします。このパターンにより、Web プロセスが解放されてさらに多くの作業が実行できるようになり、アプリケーションの全体的な応答時間が減少します。
言語固有の H12 の記事
長いポーリングおよびストリーミングの応答
Heroku では、長いポーリングおよびストリーミングの応答などの HTTP 機能をサポートします。アプリケーションには、クライアントに単一バイトで応答する 30 秒の初期ウィンドウがあります。ただし、その後に転送される各バイト (クライアントから受け取るか、アプリケーションによって送信されるもの) については、55 秒間のローリングウィンドウに再設定されます。55 秒間のウィンドウ期間中にデータが送信されない場合、接続は強制終了されます。
サーバー送信イベントなどのストリーミング応答を送信している場合、クライアントがハングアップしたときに検出する必要があり、アプリサーバーが接続をすぐにクローズするようにしてください。サーバーがデータを送信しないまま接続を 55 秒間オープンしたままにした場合、リクエストタイムアウトが発生します。
タイムアウトの動作
接続が強制終了されると、クライアントに対してエラーページが発行されます。リクエストを処理していた Web dyno はそのまま放置され、応答を送信できなくてもリクエストの処理を続行します。その後、後続のリクエストは同じプロセスに送られることがありますが、そのプロセスは (アプリケーションの言語またはフレームワークの並列性動作によっては) 応答できないため、さらなる悪化を引き起こします。
言語によっては、アプリサーバーレベルでタイムアウトを設定できる場合もあります。1 つの例が Ruby の Unicorn です。Unicorn では、config/unicorn.rb
に次のようにタイムアウトを設定できます。
timeout 15
このタイマーは、Unicorn がリクエストの処理を開始したときに開始され、15 秒が過ぎるとマスタープロセスは Worker に SIGKILL を送信しますが、例外は生成されません。
サーバーレベルのタイムアウトの他に、別のリクエストタイムアウトライブラリを使用できます。1 つの例が、Ruby の rack-timeout gem を使用して、タイムアウト値をルーターの 30 秒のタイムアウトよりも低い 15 秒などに設定することです。この方法では、アプリケーションレベルのタイムアウトと同じように、暴走したリクエストがアプリケーション dyno にいつまでも残ることを防ぎますが、時間のかかるリクエストのソースの追跡がかなり容易になるエラーを生成します。
類似のツールは他の言語でも存在し、あるいはランタイムに組み込まれています。他の言語に対する提案については、「Preventing H12 Errors」(H12 エラーの防止) を参照してください。
リクエストタイムアウトのデバッグ
リクエストタイムアウトの一因は、コード内の無限ループです。ローカルでテストを行い (多くの場合は本番データベースのローカルコピーを使用します。抽出に使用するのは pgbackups) 問題を再現してバグを修正できるか確認します。
もう一つの可能性として、次のような何らかの長時間実行するタスクを Web プロセス内部で実行しようとしていることがあります。
- メールの送信
- リモート API へのアクセス (Twitter への投稿、Flickr の照会など)
- Web スクレイピングまたは Web クローリング
- 画像または PDF のレンダリング
- 大量の計算 (フィボナッチ数列の計算など)
- データベースの大量使用 (時間のかかるクエリまたは多数のクエリ、N+1 クエリ)
このような場合、この重い作業を Web リクエストとは非同期的に実行できるバックグラウンドジョブに移動する必要があります。詳細については、「Worker dyno、バックグラウンドジョブおよびキューイング」を参照してください。
アプリケーションによって使用されている外部サービスが利用できないか過負荷状態になったときに、別の種類のタイムアウトが発生します。この場合、作業をバックグラウンドに移動しない限り、Web アプリは高い確率でタイムアウトします。これらのリクエストを Web リクエスト中に処理しなければならない場合は、エラーの発生に備えて常に計画しておく必要があります。ほとんどの言語では、たとえば HTTP リクエストに関するタイムアウトを指定できます。
Unicorn または Gunicorn を使用している場合、メインのワーカーがタイムアウトし、Web ワーカーを強制終了する可能性があります。詳細は、ナレッジベースの記事を参照してください。
リクエストキューイングの効率
リクエストタイムアウトは dyno 内部の TCP 接続のキューイングが原因で発生することもあります。一部の言語およびフレームワークでは、一度に 1 つの接続しか処理できませんが、ルーターは複数のリクエストを dyno に同時に送信することができます。この場合、リクエストはアプリによって処理中のリクエストの後ろにキューイングされ、これらの後続のリクエストは独自に処理されるよりも長い時間がかかることになります。リクエストのキューイング時間は New Relic addon を使用していくらか可視化できます。この問題は、次の技術によって改善できます (一般的に効果の高い順に記載されています)。
- dyno あたりの実行プロセスを増やし、相対的に dyno の数を減らします。一度に 1 つのリクエストのみの処理が可能なフレームワークを使用している場合 (Ruby on Rails など)、Worker プロセスの多い Unicorn などのツールを試してみてください。これにより、アプリの全体的な並列性は同じに維持されますが、dyno の個々のキューを多くのプロセスで共有することによって、リクエストキューイングの効率が大幅に改善されます。
- アプリのコードを最適化することで時間のかかるリクエストの速度を上げます。これを効果的に行うには、99 パーセンタイルとアプリの最大サービス時間に注目します。これにより、リクエストが時間のかかる別のリクエストの後ろで待機する時間が減少します。
- 実行する dyno を増やすことで、全体の並列性を増やします。これにより、特定のリクエストが別のリクエストの背後で詰まって処理されなくなる可能性がわずかに低下します。
サイズの大きいファイルのアップロード
多くの Web アプリケーションではユーザーがファイルをアップロードできます。これらのファイルのサイズが大きかったり、ユーザーのインターネット接続が遅い場合、アップロードにかかる時間が 30 秒を超えることがあります。リクエストをブロックする一部の種類の Web アプリケーションでは、これにより H12 リクエストタイムアウトが発生することがあります。これについては、S3 に直接アップロードすることをお勧めします。