スケーリングのリスク管理

Cloud Tasks を使用すると、トラフィックがキューやダウンストリーム サービスに到達するレートを制御しながら、大量の非同期タスクをディスパッチできます。段階的なランプアップ パターンとレート制御を適用することで、インフラストラクチャが適応するよりも速くトラフィックが増加した場合でも、キューのレイテンシの急増やターゲットの過負荷を発生させることなく、ワークロードをスムーズにスケーリングできます。

概要

このドキュメントでは、トラフィックの多いキューで Cloud Tasks の高パフォーマンスを維持するためのベスト プラクティスについて説明します。高 TPS キューは、1 秒あたり 500 個以上のタスクが作成またはディスパッチされるキューです(TPS)。高 TPS キューグループは、連続したキューのセットです。たとえば、[queue0001、queue0002、…、queue0099] のように、1 秒あたり合計 2,000 個以上のタスクが作成またはディスパッチされるキューのセットです。キューまたはキューのグループの過去の TPS を表示するには、Cloud Monitoring 指標、CreateTask オペレーションの api/request_count、タスク試行の queue/task_attempt_count を使用します。トラフィックの多いキューとキューグループは、次の 2 つの異なる広範なクラスの障害が発生しやすくなります。

キューの過負荷は、タスクの作成と個々のキューまたはキューグループへのディスパッチが、キュー インフラストラクチャが適応できるよりも速く増加した場合に発生します。Google のインフラストラクチャでは、トラフィック パターンに基づいて負荷を動的に再割り当てする適応レイヤが使用されます。この適応には時間がかかります。同様に、タスクのディスパッチ率が原因でダウンストリーム ターゲット インフラストラクチャでトラフィックが急増すると、ターゲットの過負荷が発生します。どちらの場合も、500/50/5 パターンに従います。500 TPS を超えてスケーリングする場合は、5 分ごとにトラフィックを 50% 以下増やします。

高 TPS ワークロードの 500/50/5 パターンとは別に、初期キューのランプアップは、コールド状態またはアイドル状態から開始されるすべてのキューに影響します。これには、中程度のディスパッチ レートに構成されたキューも含まれます。このドキュメントでは、スケーリング リスクを引き起こす可能性のあるさまざまなシナリオを確認し、キューのランプアップを管理して 500/50/5 パターンを適用する方法の例を示します。

初期キューの増加

500/50/5 パターンは、すでに安定した状態で動作している高 TPS キューに適用されます。初期キュー ランプアップは、すべてのキューに適用される個別のシステム メカニズムです。これには、20 ~ 100 TPS などの適度なディスパッチ レートに構成されたキューも含まれます。これらのキューは、コールド状態またはアイドル状態から定常状態のオペレーションに移行します。

キューは、次のいずれかの状況でコールド状態またはアイドル状態になります。

  • キューが新しく作成されたか、一時停止後に再開された。

  • キューが数分間非アクティブで、アクティブなディスパッチがない。

  • キューは、ダウンストリームの HTTP 429 または 5xx エラー レスポンスからバックオフした後に回復しています。エラー バックオフがディスパッチ レートに与える影響については、バックオフ エラーと強制レートをご覧ください。

タスクがコールド キューまたはアイドル状態のキューに到着すると、Cloud Tasks は構成された max_dispatches_per_second レートでタスクをすぐにディスパッチしません。ダウンストリーム ターゲットをトラフィックの急増から保護するため、Cloud Tasks は 1 TPS などの低いベースライン レートでディスパッチを開始します。その後、定期的な評価間隔で、強制ディスパッチ レートを段階的に引き上げます。キューの需要が飽和状態のままで、ターゲット ハンドラが HTTP 2xx レスポンスを返す限り、適用されるレートは増加し続けます。ターゲット レートによっては、高い max_dispatches_per_second 値に達するまでに数分かかることがあります。

ワークロードが数分以上の短いアイドル期間を挟んで急増するトラフィックを送信する場合、キューはバースト間でコールド状態にリセットされ、バーストごとにこのランプアップ プロセスを繰り返します。バーストに敏感なワークロードのランプアップ遅延を最小限に抑えるには、次のベスト プラクティスに従ってください。

  • ベースライン トラフィックの安定したフローを維持して、バースト間でレイテンシに敏感なキューをウォーム状態に保ちます。

  • キューを頻繁に削除して再作成するのではなく、既存のキューを再利用します。

  • ハンドラが HTTP 2xx レスポンスを返し、適用されたディスパッチ レートをリセットする HTTP 429 または 5xx エラー レスポンスを返さないように、十分なダウンストリーム ターゲット容量をプロビジョニングします。

キューのスロットリングを診断する

キューが、構成された max_dispatches_per_second レートよりも遅い速度でタスクをディスパッチする場合は、ボトルネックがコールドまたはアイドル状態のキューのランプアップ、ターゲットの輻輳バックオフ、同時実行制限のいずれに起因しているかを確認します。

コールド キューまたはアイドル状態のキューのランプアップ

次の条件がすべて該当する場合は、初期キューのランプアップが原因である可能性があります。

  • キューが最近作成されたか、一時停止後に再開されたか、トラフィックが到着する前に数分間アイドル状態になっていた。
  • タスクの試行はターゲット エラーなしで成功し、Monitoring 指標 cloudtasks.googleapis.com/queue/task_attempt_count に response_code="OK" が表示されます。
  • 処理中のディスパッチは max_concurrent_dispatches を下回ったままですが、cloudtasks.googleapis.com/queue/depth または cloudtasks.googleapis.com/queue/task_attempt_delays は増加しています。同時に、cloudtasks.googleapis.com/queue/task_attempt_count のディスパッチ レートは、定期的な評価間隔にわたって段階的に増加します。

Monitoring 指標の詳細については、Monitoring に指標を送信するをご覧ください。

ターゲットの過負荷と輻輳バックオフ

ダウンストリーム ターゲットが HTTP 429 Too Many Requests または HTTP 5xx エラー レスポンス(HTTP 503 Service Unavailable など)を返すと、Cloud Tasks はディスパッチ レートを動的に削減します。この輻輳バックオフにより、ターゲットの過負荷が防止されます。

ターゲット エラーが原因で Cloud Tasks がディスパッチをスロットリングしているかどうかを確認するには、response_code ラベルでグループ化された Monitoring 指標 cloudtasks.googleapis.com/queue/task_attempt_count を表示します。キューのロギングが有効になっている場合は、ログ エクスプローラで試行レスポンス ログを検査することもできます。

resource.type="cloud_tasks_queue"
jsonPayload.attemptResponseLog.status!="OK"

response_code と jsonPayload.attemptResponseLog.status の両方で、Cloud Tasks は数値の HTTP ステータス コードではなく、正規の gRPC ステータス コードを記録します。たとえば、HTTP 429 は RESOURCE_EXHAUSTED に、HTTP 503 は UNAVAILABLE に、HTTP 500 は INTERNAL に、HTTP 504 またはタイムアウトは DEADLINE_EXCEEDED にマッピングされます。

ターゲットから RESOURCE_EXHAUSTED、UNAVAILABLE、またはその他のサーバーエラー ステータスが返される場合は、キューのディスパッチ上限を増やす前に、ターゲット サービス インスタンスをスケールアップするか、ターゲットの同時実行数を増やします。

キューログを有効にする方法については、キューで Cloud Logging を使用するをご覧ください。

同時実行のボトルネック

リトルの法則によると、ダウンストリームのターゲット レイテンシは、最大ディスパッチ スループットを制限します。

Max Throughput (tasks/sec) = min(
  max_dispatches_per_second,
  max_concurrent_dispatches / Downstream Latency in seconds
)

たとえば、ダウンストリームの実行レイテンシが 2 秒で、max_concurrent_dispatches が 100 に設定されている場合、max_dispatches_per_second が 500 に構成されていても、スループットは 1 秒あたり 50 タスクに制限されます。

同時実行がボトルネックかどうかを診断するには、max_concurrent_dispatches を、Logging の jsonPayload.attemptResponseLog.attemptDuration フィールドに記録されたターゲット レスポンス レイテンシで割ります。この計算されたしきい値を、Monitoring 指標 cloudtasks.googleapis.com/queue/task_attempt_count の実際のディスパッチ率と比較します。cloudtasks.googleapis.com/queue/depth または cloudtasks.googleapis.com/queue/task_attempt_delays が増加しているときに、実際のディスパッチがこのしきい値付近で横ばいになっている場合は、次のいずれかを行います。

  • ダウンストリーム ターゲットに予備の容量がある場合(タスクが外部 I/O の待機にほとんどの時間を費やしている場合など)、max_concurrent_dispatches を増やします。
  • ダウンストリーム ターゲットのリソース使用率がすでに高い場合、max_concurrent_dispatches を増やすと、ターゲットの過負荷が悪化する可能性があります。代わりに、ターゲット インスタンスをスケールアウトするか、ハンドラの実行レイテンシを短縮します。

キューの過負荷

トラフィックが急増すると、キューまたはキューグループが過負荷になることがあります。その結果、これらのキューでは次のような問題が発生する可能性があります。

  • タスク作成レイテンシの増加
  • タスク作成エラー率の上昇
  • ディスパッチ レートの低減

これに対処するには、キューまたはキューグループの作成率またはディスパッチ率が急激に上昇する可能性がある状況で、制御を確立します。コールドキューまたはキューグループに対するオペレーションは、毎秒 500 回を上限とし、その後、5 分ごとにトラフィックを 50% 増やしていきます。理論的には、このランプアップ スケジュールを使用すると、90 分後に 1 秒あたり 74 万オペレーションまで増やすことができます。これが発生する状況はいくつかあります。

次に例を示します。

  • Cloud Tasks を大量に使用する新機能の立ち上げ
  • キュー間のトラフィックの移動
  • 多いまたは少ないキュー間のトラフィックのバランス調整
  • 多数のタスクを投入するバッチジョブの実行

このような場合は、500/50/5 パターンに従ってください。

App Engine のトラフィック分割を使用する

App Engine アプリがタスクを作成する場合は、App Engine のトラフィック分割(App Engine スタンダード環境または App Engine フレキシブル環境)を利用して、トラフィックの増加を平滑化できます。バージョン(スタンダード環境またはフレキシブル環境)間でトラフィックを分割することで、レート管理されたリクエストを時間の経過とともに増やし、キューの健全性を保護できます。たとえば、新しく拡張されたキューグループへのトラフィックを増やす場合を考えてみましょう。[queue0000、queue0199] を、ピーク時に合計 100,000 TPS の作成を受け取る高 TPS キューのシーケンスとします。

[queue0200, queue0399] を新しいキューのシーケンスとします。すべてのトラフィックを移行すると、シーケンス内のキューの数が 2 倍になり、新しいキュー範囲がシーケンスの合計トラフィックの 50% を受信します。

キューの数を増やすバージョンをデプロイする場合は、トラフィック分割を使用して、新しいバージョン(つまり新しいキュー)へのトラフィックを段階的に増やします。

  • トラフィックの 1% を新しいリリースに移行し始めます。たとえば、100,000 TPS の 1% の 50% は、新しいキューのセットに対して 500 TPS になります。
  • 次の表に示すように、5 分ごとに新しいリリースに送信されるトラフィックを 50% 増やします。
デプロイ開始以降の分 新しいバージョンに移行した総トラフィックの割合 新しいキューへの総トラフィックの割合 古いキューへの総トラフィックの割合
0 1.0 0.5 99.5
5 1.5 0.75 99.25
10 2.3 1.15 98.85
15 3.4 1.7 98.3
20 5.1 2.55 97.45
25 7.6 3.8 96.2
30 11.4 5.7 94.3
35 17.1 8.55 91.45
40 25.6 12.8 87.2
45 38.4 19.2 80.8
50 57.7 28.85 71.15
55 86.5 43.25 56.75
60 100 50 50

リリース駆動型のトラフィック急上昇

キューまたはキューグループへのトラフィックを大幅に増加させるリリースを起動する場合、段階的に展開は、増加を平滑化するための重要なメカニズムです。インスタンスを段階的にロールアウトして、最初の起動時に新しいキューに対するオペレーションの合計が 1 秒あたり 500 回を超えないようにします。その後、5 分ごとに 50% を超えない範囲で増やしていきます。

新しい高 TPS キューまたはキューグループ

新しく作成されたキューは特に脆弱です。キューのグループ([queue0000、queue0001、…、queue0199] など)は、初期ロールアウト段階では単一のキューと同じように敏感です。これらのキューでは、段階的に展開が重要な戦略となります。高 TPS のキューまたはキュー グループを段階的に作成する新しいサービスまたは更新されたサービスをリリースします。初期負荷を 500 TPS 未満に抑え、50% 以下の増加を 5 分以上の間隔で段階的に行います。

新たに拡張されたキューグループ

キューグループの合計容量を増やす場合([queue0000-queue0199 から queue0000-queue0399 に拡張する場合など)、500/50/5 パターンに従います。ロールアウト手順では、新しいキューグループは個々のキューと同じように動作します。500/50/5 パターンを、グループ内の個々のキューだけでなく、新しいグループ全体に適用します。

これらのキューグループの拡張では、段階的に展開が重要な戦略となります。トラフィックのソースが App Engine の場合は、トラフィック分割を使用できます。サービスを移行して、増加したキューにタスクを追加する場合は、最初の起動時に新しいキューへのオペレーションの合計数が 500 を超えないように、インスタンスを段階的にロールアウトします。また、トラフィックの増加は 5 分ごとに 50% 以下にします。

キューグループの緊急拡張

場合によっては、既存のキューグループを拡張する必要があります。たとえば、キューグループにタスクが追加される速度が、グループがタスクをディスパッチする速度よりも速いと予想される場合です。新しいキューの名前を既存のキューの名前の中に辞書順で均等に分散させると、それらのキューにすぐにトラフィックを送信できます。新しいインターリーブ キューが 50% 以下であり、各キューへのトラフィックが 500 TPS 未満であることを確認します。この方法は、トラフィック分割と段階的に展開を使用する代わりに使用できます。

このタイプのインターリーブされた命名を実現するには、偶数で終わるキューにサフィックスを追加します。たとえば、既存のキューが 200 個([queue0000~queue0199])あり、100 個の新しいキューを作成する場合は、新しいキューの名前として [queue0200~queue0299] ではなく、[queue0000a、queue0002a、queue0004a、…、queue0198a] を選択します。

さらに増やす必要がある場合は、5 分ごとに最大 50% のキューをインターリーブできます。

大規模またはバッチタスクのエンキュー

数百万や数十億など、大量のタスクを追加する必要がある場合は、二重挿入パターンが役立ちます。単一のジョブからタスクを作成する代わりに、インジェクタ キューを使用します。インジェクタ キューに追加された各タスクはファンアウトし、関連するキューまたはキューグループに 100 個のタスクを追加します。インジェクタ キューの速度は、時間の経過とともに上げることができます。たとえば、5 TPS で開始して、ターゲット キューまたはキューグループで 500 TPS を生成し、その後 5 分ごとにレートを 50% ずつ増やします。

名前付きタスク

新しいタスクを作成すると、Cloud Tasks はデフォルトでタスクに一意の名前を割り当てます。name パラメータを使用すると、タスクに独自の名前を割り当てることができます。ただし、これによりパフォーマンス オーバーヘッドが大幅に増え、名前付きタスクに伴うレイテンシが増えて、エラー率が上昇する可能性があります。タイムスタンプなどの連続したタスク名を使用すると、このオーバーヘッドが大幅に増加する可能性があります。

独自の名前を割り当てる場合は、コンテンツのハッシュなどの適度に分散された接頭辞をタスク名に使用します。タスクの命名の詳細については、Task.name フィールド リファレンスをご覧ください。

ターゲット過負荷

キューのディスパッチが急増すると、Cloud Tasks は、App Engine、Datastore モードの Firestore(Datastore)、ネットワークなど、使用するダウンストリーム サービスやリソースに過負荷をかける可能性があります。タスクのバックログが蓄積されている場合、これらのキューの一時停止を解除すると、これらのサービスに過負荷がかかる可能性があります。ターゲットの過負荷を防ぐには、500/50/5 パターンに従います。キューが 500 TPS を超えてディスパッチする場合は、キューによってトリガーされるトラフィックを 5 分ごとに 50% 以下ずつ増やします。トラフィックの増加を事前にモニタリングするには、Monitoring 指標を使用します。Monitoring アラートを使用すると、潜在的に危険な状況を検出できます。

高 TPS キューの一時停止を解除または再開する

一時停止したキューを再開すると、Cloud Tasks は初期キューのランプアップを適用し、低いベースラインからキューに構成された max_dispatches_per_second レートに向かってディスパッチ レートを段階的に引き上げます。ただし、高 TPS キューまたは同時に再開する複数のキューに大量のタスクが蓄積されている場合、ディスパッチ レートはダウンストリーム ターゲット サービスのスケーリングよりも速く上昇する可能性があります。バックログが大きいキューを再開するときにターゲットの過負荷を防ぐには、複数のキューを再開するタイミングをずらします。または、max_dispatches_per_second レートを一時的に下げ、500/50/5 パターンに従って徐々に増やします。キューが再開されたときに Cloud Tasks がディスパッチをスロットリングしてランプアップする方法については、バックオフ エラーと適用レートをご覧ください。

一括スケジュールされたタスク

同時にディスパッチするようにスケジュールされたタスクが多数ある場合も、ターゲットが過負荷になるリスクが生じる可能性があります。多数のタスクを一度に開始する必要がある場合は、キューレート制御を使用してディスパッチ レートを徐々に増やすか、ターゲット容量を事前にプロビジョニングすることを検討してください。

ファンアウトの増加

Cloud Tasks が呼び出すサービスを更新するときに、リモート呼び出しの数を増やすと、本番環境のリスクが生じる可能性があります。たとえば、高 TPS キュー内のタスクがハンドラ /task-foo を呼び出すとします。たとえば、新しいリリースでハンドラに高コストの Datastore 呼び出しがいくつか追加されると、/task-foo の呼び出しコストが大幅に増加する可能性があります。このようなリリースの最終的な結果として、ユーザー トラフィックの変化に直接関連する Datastore トラフィックが大幅に増加します。段階的に展開またはトラフィック分割を使用して、ランプアップを管理します。

再試行数

Cloud Tasks API 呼び出しを行って失敗した場合は、コードで呼び出しを再試行できます。ただし、リクエストの大部分がサーバーサイド エラーで失敗した場合、再試行率が高いとキューがさらに過負荷になり、復旧が遅くなる可能性があります。クライアントが、リクエストの大部分がサーバーサイド エラーで失敗していることを検出した場合は、送信トラフィックの量を制限します。たとえば、サイト信頼性エンジニアリングの書籍の過負荷の処理の章で説明されている適応型スロットリング アルゴリズムを使用できます。Google の gRPC クライアント ライブラリには、このアルゴリズムのバリエーションが実装されています。