可用性と信頼性 — クラウドのためどのように変えるか — Part 2, キーコンセプト

Iain Sinclair
google-cloud-jp
Published in
11 min readMar 17, 2020

English version is here

Part 1(日本語版)はこちら

TL; DR

クラウドに移行する際に、クラウドプラットフォームを最大限に活用しながら、Google検索、Gmail、またその他の多くのインターネットサービスが実際に達成しているような継続的な可用性を実現するためには、可用性と信頼性に関する考え方に適応する必要があります。

はじめに

この投稿は、次のトピックを取り上げるシリーズの最初の投稿です。

  1. 背景と目的(この投稿)
  2. 主要な概念(設計パターン、SREなど)
  3. 高可用性のためのGCPサービス

今回のポイントは:

  • Architecture
  • デサインパターン
  • Platforms & frameworks

運用、変更、ネットワークについても触れます。

ハードウェア、セキュリティ、自然災害、人的要因、バグ、競合状態等については可用性に関連がありますが、今回は触れません。

従来のの考え方

従来の’9s’の法則に基づくSLAでは単にシステム全体が正常に動作しているのか、していないかしかカウントされず、部分的なダウンや遅延は含まれませんでした。

さらに、障害によるダウンタイムはカウントされていましたが、計画的なメンテナンスはカウントされてませんでした。

一般的な’9’の法則に基づくSLAと許容されるダウンタイム

それに対して現在の考え方では、20%のユーザーが10分間作業ができなかった場合はSLA上では2分のダウンとしてカウントされます。

計画的なダウンタイム?もはや、それは過去の遺産になりつつあります!

Site Reliability Engineering (SRE)

SREというのはソフトウェアエンジニアにシステムの運用を任せた場合に考えるものです。システムの運用を自動化すると言うことは重要なポイントです。

SREでは、可用性を評価するには、各種の値を計測し、集計することが必要です。

  • 集計/総可用性:成功したリクエスト数/合計リクエスト数
  • SLI(サービスレベルインジケーター):エラー率、遅延(latency)、スループット等
  • SLO(サービスレベル目標):SLIよりも厳密なSLAに基づいて、エラーバジェット(“Error Budget”)を決定します。

参照: https://landing.google.com/sre/books/

エラーバジェットは重要な概念です-それはベロシティ(開発と展開の速度)と可用性の間のトレードオフを表します。

SLIを時系列にトラッキングする

上記の例では:

  • SLI: %レスポンス < 20ms & リクエスト成功(200)
  • SLO: 99.95%
  • Error budget: 期間中のリクエストの0.05% (100% — SLO%)
  • SLA: 99.9%

SLIがSLOに対してどのようにトラッキングしているかを観察していて、SLOを下回ろうとしていることに気づいたら、すぐに新たな開発を止めて、原因を特定して修正するべきです。

エラーバジェットはベロシティと可用性の間のトレードオフであり、エラーバジェットによって取るべき対応が変わります。エラーバジェットを使い果たしていない限り、新しい機能をリリースすることができます。そうでない場合は、エラーの数を減らすために新しい機能のリリースを停止し、原因を修正すべきです。

カナリアデプロイ

カナリアデプロイは、可用性を向上させるための重要なプラクティスです。 これは、計画的なダウンタイムの必要性を軽減または無くし、リリースのリスクを軽減するための重要な要素です。これこそが、Googleが自社の製品とサービスのリリースを管理する方法の中核を担っています。

カナリアデプロイでは、ダウンタイムを必要とせずに新しいバージョンを徐々に導入することができます:

「N」は各クラスターが受信するトラフィックの割合

上の図では、myappがv1からv2にアップグレードされています。 v1ベースラインとv2カナリア用に、同じリソースを割り当てて2つの新しいクラスターを作成します。カナリアとベースラインはそれぞれ同じN%のトラフィックを受信し、カナリアはベースラインと直接比較して評価されます。”N”は、トラフィックの100%を新しいバージョンに送信することが決定されるまで、段階的に増やします。

重要な考慮事項は次のとおりです:

  • リリースの成功基準を定義する
  • リソース使用量、エラー率などの予期しない変化に注意する
  • 問題なければNを1%、2%、5%、等と段階的に増やす
  • v1ベースラインとv2カナリア用に、同じリソースを割り当てる
  • トラフィックの100%が新しいバージョンになっても、v2からv1にロールバックができるようにする

カナリアデプロイは総可用性に適切なプラクティスです。もしv2が完全に失敗だったとしても、影響を受けるのがゾーン1つの1%のユーザーであればSLOを守り続けられる可能性が高まります。逆に、もし100%のユーザーが影響を受けたらSLOは完全に破られてしまうかもしれません。

カスケード障害 (Cascading Failures)

カスケード障害は、一つの小さい障害から始まり、ポジティブフィードバックの結果、時間とともに雪だるま式に問題が大きくなり、悪循環に陥っていく障害です。

2つノードの単純なクラスタ

上記の単純なクラスタでは、名ノードの使用率は60%です。一台が落ちたら残りのノードは対応可能以上のトラフィックを受信すようになって落ちるため、クラスタは完全にダウンとなります。

参照: https://landing.google.com/sre/sre-book/chapters/addressing-cascading-failures/

このクラスタが他のコンポーネントによって利用されている場合、さらに複雑になります。このクラスタが利用できなくなった場合、他のコンポーネントは、どう反応するでしょうか。

先の単純な例より複雑なケース

このケースでは、例えばペイメントサービスなどのビジネスに不可欠(ミッションクリティカル)な共通サービスが、多くの外部サービスや信頼性の低い「レガシー」サービスに依存しています。

シナリオ:

  1. 外部サービス1で問題が発生しています。応答に非常に長い時間がかかっており、知らないうちにリクエストをドロップしている場合もあります
  2. ミッションクリティカルな共通サービスは単純に実装されており、外部サービスへの接続を長時間保持します
  3. クライアントは、共通サービスへの再試行を続けます。共通サービスは、これらのリクエストを外部サービス1に渡そうとします
  4. 共通サービスがリソース(プールされた接続など)を使い果たし、サービスが落ちてしまいます
  5. 共通サービスが落ちたため、正常に動作している外部サービスにもアクセスできなくなりました。

本質的な問題は、クライアントからのリクエストを止める方法がないことです。外部サービスを改善できないと想定する場合、共通サービスを改善する必要があります。 1つの方法として、外部サービスとの通信リソースを再利用しながら、これ以上のリクエストを受け付けられないことをクライアントに通知することも考えられます。

カスケード障害の問題は、サービスと依存関係の数が増加するにつれて指数関数的に悪化します。

次に、壊滅的な障害を引き起こすカスケード障害を防ぐために使用できる、いくつかのデザインパターンをみていきます。

デザインパターン(Design Patterns)

カスケード障害を防ぐ、適切なデザインパターンがいくつかあります。

  1. Circuit Breaker
  2. Exponential backoff
  3. Fail fast
  4. Handshaking

1. サーキットブレーカー (Circuit Breaker)

サーキットブレーカー

サーキットブレーカーの役割は下記の通りです。

  • エラー率のトラッキング
  • パーセンタイルに基づくタイムアウト
  • クリーンアップ
  • 他の手段へのフォールバック、例えばキャッシュや非同期サービス
  • エラーをレスポンスする
  • ロギング

サーキットブレイカーは、エラー率が決まった閾値を超えた場合に継続的なエラーとみなし、サーキット (回路) を一時的に開放 (open) します。こうなると、しばらくリクエストは送られなくなります。クライアントはしばらくした後にリトライします。

タイムアウトは長過ぎないようにし、リソースを保持したままカスケード障害を引き起こしてしまわないように気をつけます。

フォールバックの場合、例えば最新のデータが使えないときにキャッシュされたデータを使ったり、または非同期サービスにメッセージを置いたりすることができます。

このようにしてサーキットブレーカーは、クライアントとサービス間の通信をより堅牢にし、アプリケーションコードからこういった心配事を分離 (separation of concerns) します。

2. 指数関数的バックオフ(Exponential Backoff)

このパターンは、限られたリソースを使い果たすことを避けるために、クライアント側で実装されます。 これにより、問題が発生しているサービスへの圧力を調整します。 イベントのシーケンスは次のようになります

  1. リクエストする
  2. 失敗→1秒+ランダムジッタ待機し、再試行
  3. 失敗→2秒+ランダムジッタ待機し、再試行
  4. 失敗→4秒+ランダムジッタ待機し、再試行
  5. …maximum_backoff時間までこれを繰り返す
  6. 再試行回数がmaximum_retriesに達するまで繰り返す
ネットワークがダウンしたときにGmailがバックオフする例

ランダムジッタは、問題が発生したときに、すべてのクライアントがまったく同じタイミングで再試行を続けないようにする方法です。

3. Fail Fast

リクエストに応答する場合:

  • ベストケース:SLO内で応答する
  • OKの場合:応答時間はSLOを超えるが、正常に応答する
  • 悪い場合:失敗の応答をするのに時間がかかる
  • 最悪の場合:応答しない

推奨されるベストプラクティスは、応答時間に短い制限を設定することです。

4. Handshaking

これは、次のようなクライアントとサービス間の合意に基づいています。

  • サービスごとの応答時間のSLO
  • クライアントがいつどのようにバックオフする必要があるか

例えば、

  • オーバーロードされたサービスが429 Too Many Requestsまたは503 Service Unavailableで応答する
  • クライアントは指数バックオフを実行します

備考

GCPサービスは、5xxや429などのさまざまなHTTP応答コードを使用して、クライアントにバックオフを指示する場合があります。

  • 429 Too Many Requests
  • 503 Service Unavailable

クラウドプラットフォームサービスを使用する場合、単一の503応答はサービスがダウンしていると解釈されるべきではありません。 設定された期間にわたる障害の集約に基づいて、「ダウン」の決定を行います。

Java用Google HTTPクライアントライブラリを使用すると、一時的な障害を簡単に処理できます。

まとめ

この投稿では、可用性に関するSREの考え方を検討しました。エラーバジェットがベロシティと信頼性のトレードオフを決定することがわかりました。また、カスケード障害、それらの原因、および設計パターンでそれらを防ぐ方法についても検討しました。

これらの重要な概念のサポートは、プラットフォームとSDKに組み込まれています。

次の投稿では、高可用性システムの構築という観点から、いくつかのGCP製品とサービスを取り上げます。

推奨読書: Release it! 2nd Edition — Michael T. Nygard https://books.google.com/books/about/Release_It.html?id=md4uNwAACAAJ

以下の出版物には多くの素晴らしい記事があります:
(JP) https://medium.com/google-cloud-jp
(EN)https://medium.com/google-cloud

--

--

Iain Sinclair
google-cloud-jp

Customer Engineer at Google Cloud & certified Google Cloud Professional Architect