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

Google のインフラストラクチャは、大規模で柔軟に動作するように設計されています。大部分のレイヤは、増加するトラフィックの需要に大規模に適応できます。これを可能にするコア設計パターンは、適応レイヤです。これは、トラフィック パターンに基づいて負荷を動的に再割り当てするインフラストラクチャ コンポーネントです。しかし、この適応には時間がかかります。Cloud Tasks は非常に大量のトラフィックをディスパッチできるため、インフラストラクチャの適応能力を超えた速さでトラフィックが上昇する可能性のある状況では、運用にリスクが発生します。

概要

このドキュメントでは、トラフィックの多いキューで Cloud Tasks の高パフォーマンスを維持するためのベスト プラクティスに関するガイドラインを紹介します。高 TPS キューとは、1 秒あたり 500 個以上の作成またはディスパッチされたタスク(TPS)を持つキューです。高 TPS キューグループは、合計 2,000 個以上のタスクが作成またはディスパッチされた [queue0001, queue0002, …, queue0099] などの連続したキューセットです。キューまたはキューのグループの履歴 TPS を表示するには Stackdriver 指標を使用します。「CreateTask」オペレーションについては api/request_count を、タスクの試行については queue/task_attempt_count を使用します。トラフィックの多いキューとキューグループでは、大きく分けて 2 種類の障害が発生しがちです。

キューの過負荷は、タスクの作成と個々のキューまたはキューグループへのディスパッチが、インフラストラクチャの適応能力を超える速度で増加した場合に発生します。同様に、タスクがディスパッチされているレートが原因でダウンストリームのターゲット インフラストラクチャでトラフィックが急上昇した場合、ターゲット過負荷が発生します。どちらの場合も、500/50/5 パターンに従うことをおすすめします。規模が 500 TPS を超える場合は、5 分ごとにトラフィックを 50% 以下の単位で増やします。このドキュメントでは、規模拡大のリスクをもたらすおそれがある、さまざまなシナリオをレビューし、このパターンを適用する方法の例を示します。

キューの過負荷

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

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

これを防ぐには、キューまたはキューグループの作成またはディスパッチ レートが突然急上昇する可能性のある状況でコントロールを確立することをおすすめします。 コールドキューまたはキューグループに対するオペレーションは毎秒 500 回を上限とし、その後でトラフィックを 5 分ごとに 50% 増やしていくことをおすすめします。 理論上は、この増加スケジュールを使用すると 90 分後に毎秒 740,000 回までオペレーションを増やすことができます。 この方法は、多くの状況に適用できます。

例:

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

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

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

App Engine アプリによってタスクが作成される場合は、App Engine のトラフィック分割(スタンダード環境 / フレキシブル環境)を活用して、トラフィックの急増を緩和できます。バージョン間でトラフィックを分割すると(スタンダード環境 / フレキシブル環境)、レート管理が必要なリクエストを徐々にスピンアップするという方法でキューの状態を保護できます。例として、新たに拡張されたキューグループへのトラフィックをスピンアップする場合を考えてみましょう。[queue0000, queue0199] を、ピーク時に作成された TPS を合計 100,000 受け取る高 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

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

キューまたはキューグループへのトラフィックを大幅に増やすリリースを立ち上げる場合、徐々にロールアウトすることは、スムーズに増やすための重要なメカニズムです。最初の立ち上げで新しいキューへの合計オペレーション数が 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% のキューをインターリーブできます。

大規模 / バッチタスクのエンキュー

数百万または数十億のような多数のタスクを追加する必要がある場合、二重注入パターンが便利です。1 つのジョブからタスクを作成する代わりに、インジェクタ キューを使用します。インジェクタ キューに追加された各タスクは拡散して、100 個のタスクを目的のキューまたはキューグループに追加します。インジェクタ キューは、たとえば 5 TPS で始まり、その後 5 分ごとに 50% 増加するなど、時間の経過とともにスピードアップできます。

名前付きタスク

新しいタスクを作成すると、Cloud Tasks はデフォルトでタスクに一意の名前を割り当てます。独自の名前をタスクに割り当てるには、name パラメータを使用します。ただし、これによりパフォーマンス オーバーヘッドが大幅に増え、名前付きタスクに伴うレイテンシが増えて、エラー率が上昇する可能性があります。タスクの名前がタイムスタンプなどで順番に指定されている場合、これらのコストが大幅に拡大する可能性があります。そのため、独自の名前を割り当てる場合は、コンテンツのハッシュなどの適度に分散された接頭辞をタスク名に使用することをおすすめします。タスクの命名の詳細については、ドキュメントを参照してください。

ターゲット過負荷

Cloud Tasks は、キューからのディスパッチが短期間に大幅に増加すると、使用している他のサービス(App Engine、Datastore、ネットワーク使用量など)を過負荷にする場合があります。タスクのバックログが蓄積されている場合、それらのキューの一時停止を解除すると、これらのサービスに負荷がかかる可能性があります。推奨される防御策は、キューの過負荷に対して提案された 500/50/5 パターンと同じです。キューのディスパッチが 500 TPS を超える場合は、キューによってトリガされるトラフィックを 5 分ごとに 50% 以下の単位で増やします。Stackdriver 指標を使用して、トラフィック増加を積極的にモニタリングします。Stackdriver アラートは潜在的に危険な状況を検出するために使用できます。

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

キューまたは一連のキューの一時停止が解除されるか再度有効にされると、キューはディスパッチを再開します。キューに多数のタスクがある場合、新しく有効にされたキューのディスパッチ率は、0 TPS からキューの全容量まで大幅に増加する可能性があります。増加を調整するには、キューの再開をずらすか、Cloud Tasks の maxDispatchesPerSecond を使用してキューのディスパッチ率を制御します。

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

同時にディスパッチする予定の多数のタスクも、ターゲット過負荷のリスクを招く可能性があります。一度に多数のタスクを開始する必要がある場合は、キューレート コントロールを使用してディスパッチ率を徐々に増やしたり、事前にターゲット容量を明示的に増やしたりすることを検討してください。

ファンアウトの増加

Cloud Tasks を通じて実行されるサービスを更新する場合、リモート呼び出しの数を増やすと、稼働上のリスクが発生する可能性があります。たとえば、高 TPS キューのタスクがハンドラ /task-foo を呼び出すとします。新しいリリースで複数のコストの高い Datastore 呼び出しがハンドラに追加された場合など、新しいリリースでは /task-foo を呼び出すコストが大幅に増加する可能性があります。最終的にそのようなリリースの結果、ユーザー トラフィックの変化に直結する Datastore トラフィックが大幅に増加します。こうした増加を管理するには、段階的なロールアウトまたはトラフィック分割を使用します。

再試行数

Cloud Tasks API 呼び出しを行って失敗した場合は、コードで呼び出しを再試行できます。ただし、リクエストのかなりの割合がサーバー側のエラーによって失敗している場合、再試行率が高いと、キューの過負荷状況が悪化し、キューの復元にさらに時間がかかる可能性があります。したがって、リクエストのかなりの割合がサーバー側のエラーによって失敗していることをクライアントが検出した場合、アダプティブ スロットリング アルゴリズムなどを使用して、発信トラフィックの量に上限を設けることをおすすめします。アダプティブ スロットリング アルゴリズムについては、書籍『Site Reliablity Engineering』の「Handling Overload」の章をご覧ください。Google の gRPC クライアント ライブラリでは、このアルゴリズムのバリエーションを実装します。