アプリにおけるレイテンシの増加をトラブルシューティングする

多くの場合、アプリケーションのレイテンシが増加すると、最終的に 5xx サーバーエラーが発生します。したがって、それぞれの原因が同じである可能性を考慮して、エラーの原因とレイテンシの急上昇の根本原因を絞り込むために、同様の一連のトラブルシューティングを行うことには意味があります。

問題の範囲を確認する

まず、関連情報を収集して、問題の範囲をできるだけ絞り込んで定義します。以下に、関連する可能性のある情報の候補を示します。

  • どのアプリケーション ID、サービス、バージョンが影響を受けるか。
  • アプリのどのエンドポイントが影響を受けるか。
  • 世界中のすべてのクライアントに影響するか、それとも特定のクライアントのサブセットに影響するか。
  • インシデントの開始時刻と終了時刻はいつか。タイムゾーンを指定する必要があります。
  • 具体的にどのようなエラーが表示されているか。
  • レイテンシにはどれほどの差分が観測されるか、特定のパーセンタイルでの増加として通常示されるものはどれか。たとえば、90 パーセンタイルでレイテンシが 2 秒増加したなどです。
  • レイテンシをどのように測定したか。具体的には、レイテンシはクライアントで測定されたものか、または Cloud Logging や App Engine の提供インフラストラクチャによって提供される Cloud Monitoring のレイテンシ データで確認できるか。
  • アプリケーションにどのような依存関係があるか、依存関係のいずれかにインシデントが発生しているか。
  • この問題を引き起こす可能性のあるコード、構成、またはワークロードの変更を最近行ったか。

アプリケーション独自のカスタム モニタリングとロギングを使用すると、上述の推奨事項を超えて、問題の範囲をさらに絞り込むことができます。問題の範囲を定義することで、考えられる根本原因を特定し、次のトラブルシューティング手順を決定できます。

失敗した項目を特定する

次に、リクエストパス内のどのコンポーネントがレイテンシまたはエラーの原因となっているかを判断します。リクエストパスの主なコンポーネントは次のとおりです。

クライアント -> インターネット -> Google Front End(GFE)-> App Engine サービス インフラストラクチャ -> アプリケーション インスタンス

ステップ 1 で収集した情報で障害の原因が特定できない場合は、まずアプリケーション インスタンスの正常性とパフォーマンスを確認することから始める必要があります。

問題がアプリケーション インスタンスにあるかどうかを判断する方法の一つは、App Engine リクエストログを調べることです。HTTP ステータス コードエラーやレイテンシの増加が見られた場合、通常、アプリケーションを実行しているインスタンスで問題が発生していることを意味します。

リクエストログでのエラーやレイテンシの増加がアプリケーション インスタンス自体によって発生したのではないというシナリオもあります。アプリケーションのインスタンス数がトラフィック レベルに合わせてスケールアップされていない場合、インスタンスが過負荷状態になり、エラーやレイテンシの増加が発生する場合があります。

Cloud Monitoring でエラーまたはレイテンシの増加が発生している場合は、一般的に問題はロードバランサの上流にあると結論付けられ、App Engine の指標が記録されます。ほとんどの場合、これはアプリケーション インスタンスに問題があることを示しています。

ただし、モニタリング指標でレイテンシの増加やエラーが見つかってもリクエストログには見つからない場合は、さらなる調査が必要になることがあります。これは、ロード バランシング レイヤで障害が発生しているか、インスタンスで重大な障害が発生しているため、ロードバランサがリクエストをインスタンスにルーティングできないことを示しています。これらのケースを区別するには、インシデントが起こる直前のリクエストログを確認します。リクエストログで障害の直前にレイテンシが増加している場合は、ロードバランサがリクエストのルーティングを停止する前に、アプリケーション インスタンス自体で障害が発生したことを示しています。

インシデントを引き起こす可能性があるシナリオ

次のようなシナリオが考えられます。

クライアント

クライアント IP を地理的リージョンにマッピングする

Google は、DNS ルックアップで使用されているクライアント IP アドレスに基づいて、App Engine アプリケーションのホスト名をクライアントに最も近い GFE に解決します。クライアントの DNS リゾルバが EDNS0 プロトコルを使用していない場合、最も近い GFE にクライアント リクエストがルーティングされないことがあります。

インターネット

インターネット接続が不安定

クライアントで次のコマンドを実行して、インターネット接続が原因になっていないか確認します。

$ curl -s -o /dev/null -w '%{time_connect}\n' <hostname>

time_connect の値は通常、最も近い Google Front End へのクライアント接続のレイテンシを表します。この接続が遅い場合は、traceroute を使用してトラブルシューティングをさらに行い、ネットワーク上のどのホップが遅延の原因となっているかを特定できます。

テストは、さまざまな地域のクライアントから行えます。リクエストは最も近い Google データセンターに自動的にルーティングされます。ルーティング先はクライアントのロケーションによって異なります。

クライアントの帯域幅不足

アプリケーションが迅速に応答できても、ネットワークのボトルネックが原因でレスポンスが遅くなる可能性があります。そのようになると、App Engine を提供するインフラストラクチャで、本来送信できる速さでネットワーク全体にパケットが送信されなくなります。

Google Front End(GFE)

HTTP/2 ヘッド オブ ライン ブロッキング

複数のリクエストを並列で送信する HTTP/2 クライアントでは、GFE でのヘッド オブ ライン ブロッキングが原因でレイテンシが増加する場合があります。クライアントは、QUIC プロトコルを使用するようにアップグレードすることをおすすめします。

カスタム ドメインの SSL 終端

SSL 接続は GFE で終端されます。appspot.com ドメインではなくカスタム ドメインを使用する場合は、SSL 終端用に追加のホップが必要です。これにより、一部のリージョンで実行されているアプリケーションのレイテンシが増加する可能性があります。

App Engine サービス提供インフラストラクチャ

サービス全体のインシデント

Google は https://status.cloud.google.com/ にサービス全体の重大な問題の詳細を投稿します。Google は段階的なロールアウトを行うので、サービス全体のインシデントがすべてのインスタンスに同時に影響を与えることはほとんどありません。

自動スケーリング

トラフィックのスケールアップが速すぎる

App Engine の自動スケーリングでは、トラフィックが増加してもインスタンスは同じ速さでスケールされないため、一時的に過負荷を引き起こす可能性があります。通常これが発生するのは、エンドユーザーが有機的にラフィックを生成するものでなく、コンピュータ プログラムによって生成される場合に発生します。解決するための最善の方法は、トラフィックを生成するシステムのスロットリングです。

トラフィックの急増

トラフィックが急増すると、レイテンシに影響を与えずに自動スケーリング アプリケーションが可能な速度よりも高速なスケールアップが必要とされる場合に、レイテンシが増加する可能性があります。通常、エンドユーザーのトラフィックによるトラフィックの急増は頻繁には発生しません。これが見られる場合は、トラフィックが急増している原因を調査する必要があります。バッチシステムが一定の間隔で実行されている場合は、トラフィックを平滑化するか、別のスケーリング設定を使用できることがあります。

オートスケーラーの設定

オートスケーラーは、アプリケーションのスケーリング特性に基づいて構成できます。これらのスケーリング パラメータは、特定のシナリオでは最適でなくなる可能性があります。

App Engine フレキシブル環境のアプリケーションは、CPU 使用率に基づいてスケールされます。しかし、インシデント発生時にアプリケーションが I/O 制約を受けている場合には、リクエストの増加によって CPU ベースのスケーリングが行われないため、インスタンスが過負荷を引き起こす可能性があります。

App Engine スタンダード環境のスケーリング設定が厳しすぎると、レイテンシが発生する可能性があります。ログにステータス コード 500 とメッセージ Request was aborted after waiting too long to attempt to service your request を含むサーバーのレスポンスがある場合は、アイドル状態のインスタンスを待機している保留中のキューでリクエストがタイムアウトしたことを意味します。

アプリがエンドユーザー トラフィックを処理する場合は、App Engine スタンダード環境の手動スケーリングを使用しないでください。手動スケーリングは、タスクキューなどのワークロードに適しています。十分なインスタンスをプロビジョニングしても、手動スケーリングを使用すると保留時間が長くなることがあります。

レイテンシの影響を受けやすいアプリケーションには、App Engine スタンダード環境の基本スケーリングを使用しないでください。このスケーリング タイプは、レイテンシを犠牲にしてコストを最小限に抑えるように設計されています。

App Engine スタンダード環境のデフォルトのスケーリング設定で、ほとんどのアプリケーションで最適なレイテンシが得られます。それでも保留時間の長いリクエストが見られる場合には、最小数のインスタンスを指定できます。アイドル状態のインスタンスを最小限に抑えることでコストを抑えるようにスケーリング設定をチューニングした場合、負荷が急増するとレイテンシが急増するリスクがあります。

デフォルトのスケーリング設定でパフォーマンスのベンチマークを行い、その後これらの設定を変更するたびに新たにベンチマークを実施することをおすすめします。

デプロイ

デプロイの直後にレイテンシが増加する場合は、トラフィックを移行する前に十分にスケールアップが行われていないことを示しています。新しいインスタンスでは、ローカル キャッシュのウォームアップが行われていないために、古いインスタンスよりも処理が遅くなる可能性があります。

レイテンシの急増を回避するため、アプリの既存のバージョンと同じバージョン名を使用して App Engine アプリをデプロイしないでください。既存のバージョン名を再利用すると、トラフィックを新しいバージョンに徐々に移行することはできません。すべてのインスタンスが短時間に再起動するため、リクエストが遅くなる可能性があります。以前のバージョンに戻す場合は、再デプロイも必要です。

アプリケーション インスタンス

アプリケーション コード

アプリケーション コードの問題は、特に問題が断続的に発生する場合や再現が困難な場合に、デバッグが非常に困難になる可能性があります。問題の診断に役立てるために、Cloud LoggingCloud MonitoringCloud Trace を使用してアプリケーションを計測することをおすすめします。Cloud Profiler を使用して問題を診断することもできます。Cloud Trace を使用して各リクエストのタイミングに関する追加情報をアップロードする場合は、読み込みリクエストのレイテンシを診断する例をご覧ください。

また、ローカル開発環境で問題を再現することもできます。そうすれば、App Engine では実行できない言語固有のデバッグツールを実行できます。

App Engine フレキシブル環境で実行している場合は、インスタンスに SSH 接続し、スレッドダンプを取得してアプリケーションの現在の状態を確認できます。負荷テストまたはアプリのローカル実行で、問題が再現するかどうかを試せます。インスタンス サイズを増やして、問題が解決するかどうかを確認できます。たとえば、RAM を増やすと、ガベージ コレクションによる遅延が発生しているアプリケーションの問題が解決することがあります。

アプリのエラーやボトルネックについて理解を深めるには、障害が発生するまでアプリケーションの負荷テストを行います。最大インスタンス数を設定し、アプリケーションに問題が発生するまで負荷を徐々に増やします。

レイテンシの問題が新しいバージョンのアプリケーション コードのデプロイと相関している場合は、ロールバックして、新しいバージョンがインシデントの原因かどうかを判断できます。デプロイを継続的に行うと、デプロイが頻繁に発生するため、デプロイ開始時点でデプロイによってインシデントが発生したかどうかの判別が困難になります。

アプリケーションは、Datastore 内などに構成設定を保存する場合があります。構成変更のタイムラインを作成し、レイテンシの増加の開始と一致するものがあるかどうかを判定できると便利です。

ワークロードの変化

ワークロードの変化により、レイテンシが増加する可能性があります。ワークロードの変化を示すモニタリング指標には、QPS、API の使用状況やレイテンシなどがあります。リクエストとレスポンスのサイズの変化を確認することもできます。

ヘルスチェックの失敗

App Engine フレキシブル環境のロードバランサは、ヘルスチェックに失敗したインスタンスへのリクエストのルーティングを停止します。これにより、他のインスタンスの負荷が増加し、カスケード障害が発生する可能性があります。App Engine フレキシブル環境の Nginx ログには、ヘルスチェックに失敗したインスタンスが表示されます。ログとモニタリングを分析して、インスタンスが異常になった理由を特定するか、一時的な障害の影響を受けないようにヘルスチェックを構成します。正常でないインスタンスへのトラフィックのルーティングをロードバランサが停止するまで少し時間がかかります。ロードバランサがリクエストを再試行できない場合、この遅延によりエラーが急増する可能性があります。

App Engine スタンダード環境ではヘルスチェックを使用しません。

メモリ負荷

モニタリングでメモリ使用量にのこぎり歯のようなパターンが見られることや、デプロイに相関してメモリ使用量の低下が見られることがある場合は、メモリリークが原因でパフォーマンスの問題が発生している可能性があります。メモリリークによりガベージ コレクションが頻繁に発生し、レイテンシが高くなる可能性があります。コードの問題へのトレースが簡単にはできない場合、より多くのメモリを使用して大きなインスタンスをプロビジョニングすると、問題が解決する可能性があります。

リソースリーク

アプリケーションのインスタンスで、インスタンス起動からの経過時間に伴うレイテンシの上昇が見られる場合は、リソースリークの発生によってパフォーマンスの問題が引き起こされている可能性があります。このタイプの問題では、デプロイ直後のレイテンシの低下も見られます。たとえば、CPU 使用率が高いために時間の経過とともに遅くなるデータ構造があると、CPU 制約によってワークロードが遅くなる可能性があります。

コードの最適化

App Engine でコードを最適化してレイテンシを減らす方法がいくつかあります。

  • オフライン作業: Cloud Tasks を使用し、メールの送信などの作業の完了待ちが、ユーザー リクエストによってブロックされないようにします。

  • 非同期 API 呼び出し: API 呼び出しの完了待ちがコードでブロックされないようにします。ndb などのライブラリには、この機能の組み込みサポートが用意されています。

  • バッチ API 呼び出し: 通常、API 呼び出しのバッチ バージョンは、呼び出しを個々に送信するよりも高速です。

  • データモデルの非正規化: データモデルを非正規化することで、データ永続化レイヤに対する呼び出しのレイテンシを短縮します。

依存関係

アプリケーションの依存関係をモニタリングすると、レイテンシの急増が依存関係の問題と相関しているかどうかを検出できます。

依存関係によるレイテンシの増加は、トラフィックの増加だけでなく、ワークロードの変化が原因である可能性もあります。

スケーリングされない依存関係

App Engine インスタンスの数がスケールアップしても依存関係がスケールしない場合、トラフィックが増加すると、依存関係が過負荷状態になる可能性があります。スケールできない依存関係の例として、SQL データベースがあります。アプリケーション インスタンスの数が多くなるとデータベース接続数が増え、データベースの起動が妨げられてカスケード障害が発生する可能性があります。

これを回復する方法の一つを次に示します。

  1. データベースに接続されない新しいデフォルト バージョンをデプロイします。
  2. 以前のデフォルト バージョンをシャットダウンします。
  3. データベースに接続する、デフォルト以外の新しいバージョンをデプロイします。
  4. トラフィックを新しいバージョンにゆっくり移行します。

予防策として、アダプティブ スロットリングを使用して依存関係へのリクエストをドロップするようにアプリケーションを設計することをおすすめします。

キャッシュ レイヤの障害

リクエストを高速化するには、複数のキャッシュ レイヤを利用する方法があります。

  • エッジ キャッシング
  • Memcache
  • インスタンス内のメモリ

これらのキャッシュ レイヤのいずれかの障害が原因で、レイテンシが急増する場合があります。たとえば、Memcache のフラッシュによって、低速の Datastore に送信されるリクエストが増える場合があります。