re:Invent 2023: AWSが語る大規模システムのレジリエンス - 5つの洞察
はじめに
海外の様々な講演を日本語記事に書き起こすことで、隠れた良質な情報をもっと身近なものに。そんなコンセプトで進める本企画で今回取り上げるプレゼンテーションはこちら!
📖 AWS re:Invent 2023 - 5 things you should know about resilience at scale (ARC327)
この動画では、AWSの長年の運用経験から得られた大規模システムのレジリエンスに関する5つの重要な洞察を紹介します。依存関係とモード、ブラストラディウス、キュー、エラー処理、リトライについて、具体的な事例を交えながら解説します。Route 53やEC2など実際のAWSサービスでの経験に基づいた話は、大規模システムの設計や運用に携わるエンジニアにとって貴重な学びとなるでしょう。9の数を超えた可用性の考え方や、障害からの迅速な回復方法など、ここでしか聞けない話が満載です。
※ 動画から自動生成した記事になります。誤字脱字や誤った内容が記載される可能性がありますので、正確な情報は動画本編をご覧ください。本編
AWSの運用経験から学ぶ大規模レジリエンスの5つのポイント
本日は「大規模なレジリエンスについて知っておくべき5つのこと」にご参加いただき、ありがとうございます。AWSのCEOだったAndy Jassyはよく「運用経験には圧縮アルゴリズムがない」と言っていました。 私たちもそれを固く信じています。私たち3人で合わせて30年以上のAWS運用経験がありますが、本日はその中から、システムが初期段階とは異なる挙動を示す大規模な状況下で学んだ5つのことをお話しします。これらの学びをご自身のアプリケーションに活かしていただき、私たちが経験した教訓を、苦労せずに学んでいただければと思います。
私はAlec Petersonです。AWSのNetwork PlatformのVice Presidentを務めており、Route 53とDirect Connectを担当しています。また、AWSのサービスチームやお客様とレジリエンスについてよく話をしています。Mike?
Mike Furrです。Senior Principal Engineerとして、これまで14年ほどAWSで様々なサービスに携わってきました。現在はEC2の可用性に注力しています。
Becky Weissです。AWSのDistinguished Engineerとして10年ほど勤めています。以前はAWSのお客様でもありました。現在はIAMとアイデンティティ、そしてそれらがデータとどう関連するかという分野で働いています。過去にはMikeとEC2で、AlecとDNSで一緒に仕事をしました。今日お話しするのは、これらのサービスを運用する中で学んだ、一見わかりにくい教訓です。
依存関係とモード:システムの相互作用を理解する
では、私から始めさせていただきます。私が非常に情熱を注いでいる「依存関係とモード」について話します。まず、同じ認識で話を進められるよう、依存関係とモードについて簡単に説明します。依存関係とは、アプリケーションが機能を提供するために使用するものです。 より具体的に言えば、S3やSQSなどのAWSサービスかもしれません。また、認証サービスのような、アプリケーションが使用する内部サービスかもしれません。さらに、ネットワークやDNSといった、インフラストラクチャの基本的なコンポーネントである場合もあります。
依存関係は問題ではありません。むしろ、サービス指向アーキテクチャの基盤となるものです。依存関係がなければ、私たちは皆モノリスを構築するだけでしょう。そのため、依存関係があることを喜んでいます。しかし、依存関係が互いに出会う場所は、障害が発生する可能性のある非常に興味深い機会です。そこで、私たちはそれらが出会う場所に注目し、すべてが順調に進んでいるときだけでなく、計画通りに進んでいないときにも、それらがどのように相互作用するかを考えるようにしています。
モードについて話すとき、私たちはアプリケーションの動作に根本的なステップ関数的変化を指しています。抽象的な例を挙げてから、数年前に私たちのサービスの1つに影響を与えた具体的な例について話しましょう。この抽象的な例では、フロントエンドフリートを持つeコマースアプリケーションを考えてみましょう。これがフリートで、すべてのデータのためのバッキングストアがあります。eコマースアプリケーションの一般的なコンポーネントとして、例えば商品カタログがあります。商品カタログは、アプリケーションの他の部分と比べて比較的静的ですが、非常に読み取りの多いワークロードを持っています。
そのため、eコマースサイトの初期段階では、「データベースの前にキャッシュを置いて、データベースの負荷を軽減しよう」と考えるかもしれません。これは素晴らしいパターンで、データベースの負荷を軽減し、コストを削減し、顧客のパフォーマンスを向上させます。しかし、キャッシュはモードの可能性を導入します。一括無効化を考えてみてください。オペレーターが誤って大規模な無効化を行ったり、キャッシュが再起動したりする可能性があります。そうなると、キャッシュがフロントに立っていたすべてのトラフィックがデータベースに戻ることになります。
最近キャッシュをインストールした世界では、データベースはそのワークロードを処理できるようにスケールされている可能性が高いです。なぜなら、キャッシュが導入される前にそのワークロードを処理していたからです。おそらく大きな問題にはならないでしょう。しかし、eコマースサイトが成長し続けるにつれてフリートが成長することを考えると、フリートは水平方向にスケールし、キャッシュもスケールしますが、データベースは同じスケーリングシグナルを持ちません。なぜなら、キャッシュに対するトラフィックがそのデータベースを保護しているからです。そして今、おそらく何年も後に、キャッシュに対する負荷が大幅に増加しています。同じ無効化イベントが発生すると、データベースに対する負荷が大幅に増加し、データベースに問題が発生する可能性があります。これは、モードがアプリケーションのライフサイクルの初期段階とは異なる方法で、スケールでアプリケーションに影響を与える方法の1つです。
Route 53の成長と変化:データベースからS3へのシフト
抽象的な例はこれくらいにして、実際の例を挙げましょう。先ほど述べたように、私が所有しているサービスの1つはRoute 53です。Route 53を何年も前に立ち上げたとき、Route 53 APIの動作は非常に簡略化されたバージョンでこのようになっていました。APIコントロールプレーンがあり、これは先ほどの例のフリートに相当します。これは、DNSリクエストの追加などの顧客からのリクエストを処理し、それらをデータベースに格納します。
そして、当時はまだ小規模なサービスだった頃、インターネット上に展開された少数のDNSサーバーのフリートがありました。これらのサーバーは定期的にデータベースに「何か新しいものはありますか?」と問い合わせていました。時刻Tの情報を持っていて、それ以降どれだけの変更があったかを尋ねていたのです。簡単な仕組みですよね?
しかし、Route 53が成長し始めると、データベースがこれらの問い合わせに応答するのに負荷がかかっていることに気づきました。e-commerceの商品カタログの例と同様に、すべてのDNSサーバーが同じ質問をしているのです。データベースが毎回何が変更されたかを再計算する必要はありません。その代わりに、データベースは単に定期的に最近の変更を公開し、DNSサーバーはAmazon S3に問い合わせるだけで済むようになりました。これにより、最近何が変更されたかを判断する複雑なロジックをデータベースが適切に処理できるようになりました。また、S3の使用パターンとしても非常に適しています。S3は誰かがオブジェクトを要求して送信するのが得意なのです。皆が満足する結果となりました。
さて、先ほどの例と同様に、Route 53は年々成長を続けています。DNSサーバーの数も増え続け、S3も成長し続けています。しかし、ここで成長していないものは何でしょうか?そう、データベースです。確かにスケーリングはしていますが、DNSサーバーが質問する頻度に注意を払ってスケーリングしているわけではありません。なぜなら、もはやその質問を受けていないからです。そして、この状態のまま、運命の日を迎えることになりました。データベースがS3への変更の公開に遅れを取ってしまったのです。
なぜこれが起こったかは重要ではありません。ただ起こってしまったのです。私たちが予見すべきだったことかもしれません。そして、次に何が起こったかというと、DNSサーバーが気づいたのです。「おや、 S3にはもう最新のデータがないようだ。でも、誰が最新の情報を持っているだろう?そうだ、昔は誰に聞いていたんだっけ?」そう、Mikeさん。そして案の定、大量の負荷の矢印が データベースに向かって一斉に押し寄せる事態となりました。
さて、この状況を解決するために呼び出された不運なオペレーターの立場になってみましょう。当然、データベースの遅れに対処するためにオペレーターは既に関与していましたが、今や二つの問題に直面することになります。元の問題に加えて、今やデータベースはDNS世界中のすべてのDNSサーバーから「何が変わったの?」という問い合わせで叩かれているのです。「わからないよ、今調べているところだ」と言いたくなりますね。そのため、最初の問題を解決するだけでなく、DNSサーバーに落ち着くよう伝える方法も考えなければなりません。すべてを修正して先に進むことができるようにするのです。
モーダルな動作の危険性とレジリエンスの向上策
これは明らかにモーダルな動作であり、正しい動作ではありませんでした。では、解決策は何でしょうか?同じ状況を考えてみましょう。パブリッシングが遅くなり、DNSサーバーが「最近の変更が見えない」と言います。手を挙げて、「オペレーターに来てもらって、何が起きているのか調べる必要がある」と言います。変更はしません。S3への問い合わせを続け、頻度を上げたり、S3に負荷をかけたりせず、ただS3に問い合わせ続け、誰かに来てもらって状況を確認してもらいます。
この間、古いDNSの応答が返されていますが、これは実は機能の一つなのです。なぜなら、変更伝播システムは、データを提供するシステムから意図的に切り離されているからです。確かに、常に最新のデータが欲しいところですが、数分前のデータの方が、まったく応答がないよりずっと良いのです。これが、この例から得られた成功事例の一つです。すべてのDNSクエリに応答がありました。確かに、変更の反映に少し時間がかかり、その問題は修正する必要がありますが、Route 53の核となる機能は正常に動作し続けていたのです。
そして、オペレーターが問題を修正します。DNSサーバーは、S3に問い合わせ続けることで、最終的に最新のデータを取得します。「よし、大丈夫だ。問題ない、続行しよう」と言います。これで全員が先に進めます。これは、AWSで長期間にわたって成長してきたシステムで、何度も見てきたパターンです。
この状況の皮肉な点は、フォールバックパスを用意した理由が、レジリエンスを向上させるためだったことです。短期的には、おそらくそれは効果がありました。この新しいシステムへの移行中、問題点を解決する過程で、システムの回復を可能にしました。しかし、長期的には実際にレジリエンスを損なってしまいました。そのため、システムにモーダルな動作のフォールバックがある場合、それを取り除くか、さらに良いのは頻繁に使用することです。AWSで最もレジリエンスの高いシステムの中には、実際に動作の切り替えを行うものもありますが、その切り替えを非常に頻繁に行うので、通常の操作の一部となり、非常に上手く対応できるようになっています。
そしてもう一つの教訓は、フォールバックしないのであれば、プライマリパスをできる限りレジリエントにする動機付けになるということです。フォールバックしないと分かっていれば、プライマリパスがすべての状況に効果的に対応できるようにする必要があるからです。
フォールバックしないことが分かっていて、状況が解決されるまでそれに対処しなければならないと分かっていれば、主要システムをできる限り堅牢にするさらなる動機になります。このような切り替え以外にも、モーダルな振る舞いの例がいくつかあります。言及する価値があるのは、ハードリミットです。32ビット整数のオーバーフロー、スロットル制限などを考えてみてください。そのような状況でシステムがどのように振る舞うかを理解することが極めて重要です。理想的には、システムはより少ない作業を行い、ただ要求を繰り返したりシステムに負荷をかけ続けたりしません。システムの様々な部分がそのような振る舞いに遭遇した場合に何が起こるかを考えることで、未知の領域に入ったときにより堅牢になるでしょう。
Blast Radiusの概念と影響範囲の制限
次に、blast radiusについてお話ししたいと思います。Blast radiusは、私たちが社内でよく使う専門用語です。impact scopeなど、別の言葉で聞いたことがあるかもしれません。私にとって、blast radiusとは、1つの変更や1つのコンポーネントからどれだけの混乱を引き起こせるかを意味します。しかし、blast radiusはまた一つの考え方でもあります。通常、システムを設計する際、私たちは可用性について考えます。システムをどれだけの9の可用性で設計できるか、ということです。AWSで私たちが学んだことの1つは、堅牢なシステムを構築するためには、少し異なる考え方をする必要があるということです。物事は失敗するものだと想定し、それが起こったときに備えることが必要なのです。この考え方は、私たちのソフトウェア開発ライフサイクル全体にある程度浸透しています。システムの設計時、実装時、デプロイ時、そして運用時に、この考え方を適用しています。
AWSでこの教訓をどのように適用しているかを示すために、シンプルなアプリケーションを例に挙げます。ここにいくつかのEC2インスタンスがあり、それらはすべてAmazon S3バケットと通信しています。Amazon S3 APIを使用しているため、そのための許可を与えるIAMポリシーが必要です。そのため、この図には各EC2インスタンスのロールにアタッチされたIAMポリシーがあります。おそらくこのアーキテクチャはテスト済みで、動作することが分かっています。もしかしたら本番環境にデプロイされ、うまく動作していて、かなりの数の9の可用性を持っているかもしれません。非常にシンプルで、可用性も高いでしょう。しかし、堅牢性の観点から考えると、ここで何が問題になる可能性があるでしょうか。シンプルであっても、実際には多くの問題が起こり得ます。ただし、私が焦点を当てるのはそのIAMポリシーです。
明日、Alecがそのポリシーを更新して私たちの権限を削除したらどうなるか考えてみましょう。可用性はどうなるでしょうか?おそらくこのようになるでしょう。これはあまり良い日とは言えません。この可用性は非常に悪いです。そしてこのスライドをよく見ても、9という数字は見つからないでしょう。私たちが多くの9を持つシステムを構築しようと努力したにもかかわらずです。
IAMポリシー更新の影響と障害対応プロセス
このようなことが起きた時、それを引き起こした一連の出来事を振り返ってみるのは有益です。この場合、Alecが影響を及ぼす変更をプッシュしました。何らかのアラームが発生し、できればこれらは自動化されているはずです。私たちが本当に望むのは、アラームがデプロイメントシステムと連携していることです。そうすれば、デプロイメントシステムがAlecの変更をロールバックできます。そうすると、1、2分かかりますが、効果が現れて、復旧することができます。
これはかなり深刻な障害で、どうしてこうなったのかについて多くの質問をしたくなります。Alecが言及したように、AWSでは実際にかなり強力なポストモーテム文化があります。外部イベントや内部サービスに問題が発生した場合、あるいはニアミスの場合でも、プロセスのどこに問題があったのかを深く理解し、再発を防ぐように努めています。
このケースでは、次のような質問をするかもしれません。まず、なぜ顧客に影響が出たのか?私たちは常に顧客を最優先に考えます。なぜ不適切なポリシーがデプロイされたのか?なぜプリプロダクション環境でこれを検出できなかったのか?これは非常に良い質問です。どうすればこのようなことが二度と起こらないようにできるか?これらはすべて素晴らしい質問であり、私たちは満足のいく答えが得られるまで、これらの質問に時間をかけて取り組み続けます。
しかし、これらに加えてもう一つ、レジリエンシーにとって非常に重要だと考える質問があります。それは、「これが再び起こった時、どうすればその影響範囲を縮小できるか?」というものです。アーキテクチャ図に戻ってみましょう。レジリエンシーの観点から見ると、今回の問題はAlecが不適切な変更をプッシュしたことではありません。それが本当の問題ではないのです。問題は、同じアップデートをすべてのインスタンスに同時にプッシュしてしまったことです。そのため、すべてのインスタンスが同時にオフラインになってしまいました。では、これを緩和するために何ができるか考えてみましょう。
部分的デプロイメントによるリスク軽減戦略
多くのデプロイメントで一般的なアプローチの一つは、部分的デプロイメントです。皆さんもコードのデプロイメントでこの方法を使っているでしょう。まずフリートの一部にデプロイし、その後残りのフリートにデプロイします。ここでも同じアプローチを取ります。新しいポリシーを導入し、それを「canary policy」と呼びます。そしてこれを1つのEC2インスタンスにのみ適用します。次に、2つ目のポリシーを「full fleet policy」と呼び、これを残りのすべてのインスタンスに適用します。
さて、これは最初にお見せした図よりも複雑になっていることにお気づきでしょう。今や2つのポリシーがあります。デプロイメントのストーリーも少し複雑になりました。まず、canaryにデプロイし、それからフリートの残りの部分にコピーする必要があります。これらは同期が取れなくなる可能性があり、より複雑です。しかし、ここにはメリットがあります。では、これが再び起こった場合を考えてみましょう。来週、Beckyが私たちのポリシーに手を加え、変更してデプロイしたところ、canaryインスタンスが壊れてしまいました。可用性のグラフは次のようになるかもしれません。
全体的な図は似ています。トリガーがあり、検出があり、ロールバックがあり、そして回復があります。しかし、はるかに良くなっています。可用性は今や9の数で測定されています。このグラフには実際には9が1つもないにもかかわらずです。ただし、このグラフについて、私が完全に見過ごしていた非常に重要なことが1つあります。それは、なぜロールバックしたのかということです。おそらく、ここにアラームのしきい値があったからでしょう。
ここで、「ちょっと待ってください。これは少しずるいのではないですか?このアウテージを検出したであろうポイントにちょうどアラームのしきい値を設定していますよね。もしその線がどこか他の場所で低かったら、話は全然違ってきますよ」と言うかもしれません。これがy軸に何も書かなかった利点です。だから答えを変えることができるのです。もし私たちのアラームのしきい値がここにあって、Beckyが変更をプッシュしたら、どうなると思いますか?すべて順調です。そして、9の数字が消えてしまいます。そして、私たちはまた悪い日を過ごすことになります。
では、これについて何ができるでしょうか?ここで実際に何が起こったのでしょうか?図に戻ってみましょう。図を見て、このシステムの各EC2インスタンスが経験した可用性について考えてみると、canaryインスタンスの可用性は0%で、残りはすべて100%でした。全体の可用性が9の数字だったにもかかわらずです。これはここにあるインスタンスの数の関数です。特に危険なのは、今日はそのロールバックのしきい値に良い値を選んでいても、フリートをスケールアップすると、その値が変わる可能性があることです。今日うまく機能するロールバックのしきい値が、明日はうまく機能しない可能性があります。
このスライドからもう1つの教訓は、canaryポリシーを持つEC2インスタンスが、他のインスタンスとは異なる動作モードを経験しているということです。それは自分だけの小さな世界で生きています。私はそれをフォールトバウンダリーと呼んでいます。そのEC2インスタンスは独自のフォールトバウンダリーを持っています。ロールバックについて考える際は、これを検出するために各フォールトバウンダリーを個別にモニタリングしていることを確認したいと思います。canaryインスタンスだけにアラームを設定し、それがこのように見えるなら、様々なアラームしきい値の選択にはるかに強くなります。適切なアラームしきい値について時間をかけて検討する必要はありますが、特定のフォールトバウンダリーに焦点を当てたしきい値を選んでいれば、それほど影響を受けにくくなります。
フォールトバウンダリーとアラームしきい値の重要性
ここで、少し注意点をお伝えしたいと思います。障害境界でアラームを設定すべきですが、隣接する部分でもアラームを設定すべきです。例えば、IAMポリシーに別の種類の変更を適用して、カナリアがフリート内の他のものが読めないものを書き込めるようにしたとします。これはすぐにロールバックしたい重要な変更です。そのため、特定の障害境界にのみデプロイする場合でも、フリートの残りの部分を監視することが重要です。もちろん、アプリケーションが正しく動作しているかを確認するための他のヘルスモニターも併用する必要があります。
では、本当に高いブラストラディウスの変更を行わなければならない場合はどうすればよいでしょうか?避けられない状況もあると思います。選択の余地がない場合もあります。時間と労力をかければ、多くの変更はリスクを低くできると思いますが、何らかの理由で実施しなければならない変更のカテゴリーも確かに存在します。そのような場合、どうすればよいでしょうか?考えてみましょう。変更をデプロイしようとしている場合、まず最初に「ロールバックしてから質問する」という戦略を適用することができます。
つまり、この変更をデプロイしたばかりです。リスクが高いことは分かっていて、テストもしましたが、本当に確信が持てません。そこで、変更が有効になってから数秒後に、すぐにロールバックします。それが機能するかどうかさえ分かりません。今すぐロールバックします。結果的に、うまくいきませんでした。しかし、良いニュースは、リスクが高いことを認識していたため、ロールバックのプロセスを非常に迅速に行えるようにしていたことです。
これにより、非常に素早くリカバリーができます。ちなみに、アラームがまだ発報していない可能性もあります。アラームはここで発報するかもしれません。つまり、私たちは積極的にすぐにロールバックを行っているのです。
この技術は、様々な分野で適用していますが、本当に意味があると思われる場所で使用しています。一例として、Amazonネットワークのコアネットワーキングデバイスへの変更があります。また、データ移行を行ったり、あるシステムから別のシステムへ権限を変更したりする場合にも適用します。移行してすぐに元に戻すこともあります。常に確実なロールバックパスを用意し、非常に積極的にテストするよう心がけています。
この変更は機能しなかったので、元に戻しました。これが影響範囲とどう関係するのでしょうか?可用性は依然として0%でした。しかし、ここでは時間の次元で影響範囲が減少しました。イベントの期間がはるかに短くなったのです。これも影響範囲の非常に重要な側面です。問題をより迅速に緩和するにはどうすればよいでしょうか?
この変更を行い、うまくいきませんでした。ポストモーテムを行い、何が間違っていたかを突き止め、再度試みます。そして、このようなグラフが得られるまで繰り返し行います。 変更を適用し、すぐに元に戻し、それから機能したかどうかを確認します。うまくいったと確信できれば、もう一度変更を適用して、そのままにしておきます。
アーキテクチャを考える際には、「もし」失敗するのではなく、「いつ」失敗するかを常に考えるべきだということを覚えておいてください。変更を加えたときに、その変更がどれほどの影響を与える可能性があるかを理解することが非常に重要です。そして、システムの特定の側面をターゲットにしている場合は、その変更の正確な結果をモニタリングしていることを確認してください。システムの他のコンポーネントと平均化して、全体的な効果への洞察を失わないようにしましょう。
キューの役割と影響:システム分離とバックログ管理
最後に、最大のリスクを伴う変更については、何か問題が発生した場合に何ができるかを考えてください。答えは、影響時間を短くすることかもしれません。数秒の影響で済むなら、誰も気づかないかもしれません。では、影響範囲における影響の持続時間に関する最後のポイントについて、もう少し詳しく説明したいと思います。
顧客と可用性イベントについて話をする際、彼らのものであれ私たちのものであれ、よく聞くのは「短ければ短いほど良い」ということです。もちろん、誰も長い停止時間を望みませんよね。しかし、私たちはこのフィードバックを真剣に受け止めています。AWSでの経験を振り返ってみると、復旧に時間がかかるイベントの多くには、アーキテクチャのどこかにキューが隠れていることがよくあります。
キューは実際にとても素晴らしいものです。私はキューが大好きです。 私たちはアーキテクチャでキューを頻繁に使用しています。これはキューが悪いと言っているわけではありません。実際、キューは素晴らしいものです。キューが素晴らしい理由は、特にプロデューサーとコンシューマーを分離できる場合に、システムの可用性を分離できるからです。
キューは非常に人気があるため、Simple Queue Service というサービスがあります。これは実際に最も古い Amazon サービスの1つです。最初に構築したサービスの1つで、多くの場所で使用しています。では、なぜこれが分離に役立つのか復習しましょう。プロデューサーがいて、キューに何らかの作業を提供し、 それが完了するとすぐにプロデューサーは呼び出し元に応答できます。プロデューサーの仕事は終わりです。
そして、コンシューマーがキューから作業を取り出し、その作業に必要な処理を行います。すべてが順調です。ここでService Twoであるコンシューマーに小さな問題が発生しても、Service Oneはすでに成功を確認しているので、Service Twoは最終的にそれを処理します。しかし、Service Twoが最終的に処理しない場合はどうでしょうか?処理が遅くなった場合はどうでしょうか?長期的な障害について考えてみましょう。
Service Twoに不適切なデプロイメントがあったかもしれません。ネットワークに問題があるかもしれません。DNSに問題があるかもしれません。理由は何であれ、Service Twoに問題があります。では、これはService Oneにとってどのように見えるでしょうか?Service Oneは単に SQS や使用している他のキューシステムを呼び出し、メッセージを追加し、追加し、追加します。しかし、もちろんService Twoはそこで少し遅くなっています。
キューの回復力測定:アイテムの経過時間に注目
これは可用性にとって何を意味するでしょうか?Service Oneにとっては、可用性は素晴らしいです。成功を得ました。キューにアイテムを追加し、クライアントに成功を返すことができます。呼び出し元、つまり顧客にとってはすべてが順調です。しかし、実際にはすべてが順調ではありません。すべてが順調ではない理由は、このキューに多くのアイテムが溜まっているからです。おそらく、私がどこに向かっているか分かると思います。
キューの回復力を測定したいと思います。測定方法の1つは、単純にキュー内のアイテム数を数えることです。それでも構いませんが、私が好む測定方法は、実際にキュー内のアイテムの経過時間です。これは顧客体験により密接に関連しているからです。つまり、キューに滞留しているものがどれくらい古いかを、平均値や特定のパーセンタイルで考えるのです。このメトリクスを使えば、グラフ化してアラームを設定し、観察することができます。
可用性イベントが発生したときに何が起こるでしょうか。例えば、消費者であるService Twoが遅くなるというイベントが発生したとします。その場合、障害のグラフはこのようになります。
最初は、Service Twoが故障した時点がわかります。それはキューがバックアップし始め、アイテムで満たされ始める時点です。そして、回復する時点も見ることができます。これは回復の傾斜が見られる箇所です。このグラフには、強調したい興味深い点がいくつかあります。1つは2つの線の傾斜です。消費者の回復傾斜は、影響の傾斜よりもはるかに急です。もしそうでなかった場合、つまり消費者がキューからすべてのアイテムを取り出すのに時間がかかる場合を想像してみてください。イベント全体の回復時間は、Service Twoが回復した後も、単にキューを空にするだけで支配されてしまうでしょう。
これは作り話ではありません。これは、私たちの内部監視システムから少し加工したグラフです。かなり大規模なシステムで消費者が故障した実際の例です。これはキュー内のアイテムの経過時間を時系列で示しています。形状は全く同じで、右側に少しドラマがあります。これは実世界の例だからです。ドラマの詳細は重要ではありませんが、これは私たちが何度も見てきた正確な形状です。
回復力とは、物事が失敗したときのことを考えることです。キューが成長しないようにしたいという点を指摘しましたが、キューが成長している場合はどうすればよいでしょうか?Amazon Builders' Libraryをご存じない方は、そこにある記事のコレクションをぜひご覧ください。特に、この話題に関連して、私の同僚のDavid Yanacekが書いた無制限キューに関する記事があります。彼は私よりもはるかに詳しく説明しているので、この話題に興味がある方は、ぜひ彼の記事を読んでみてください。彼は、さまざまな緩和策や、キューについての考え方、大規模にキューを成功裏に使用する方法について詳しく説明しています。
キューのバックログ管理とサイドラインテクニック
このトークでは、2つの側面に焦点を当てたいと思います。1つ目は「sidelining」と呼ばれるテクニックです。消費者が遅くなっている例に戻ってみましょう。生産者がアイテムを追加し続け、それがどんどん積み重なっていく状況を覚えていますか。最終的にこのような状態になります。消費者が回復したとき、何が起こるでしょうか?多くの場合、キューはFIFO(先入れ先出し)なので、キューの先頭にあるものが最も古いものになります。
経験上、最も古いアイテムが最も重要性が低いことがよくあります。顧客にとって回復を示すのは、今まさに到着しているアイテムです。そのため、多くのドメインでは、これらを分離するのが有効です。使える手法の1つは、しきい値を設定し、それより古いものはすべて素早くサイドキューにダンプすることです。これにより、消費者の仕事は主キューを処理し、今到着している全ての作業を行うことになります。二次的な優先度として、バックログを処理できます。そのためにはスケールアップが必要かもしれませんし、少し時間がかかるかもしれません。しかし、新しいリクエストはすべて正常に処理されています。
このテクニックの変形として、サイドラインキューを使う代わりに、すべての更新を削除する方法もあります。もちろん、これが安全かどうかはドメインによって大きく異なりますが、古すぎるものをすべて削除できれば、回復時間を劇的に短縮できます。
ここで少し私の経験を共有したいと思います。以前、AmazonのVPCチームで働いていました。VPCはsecurity groupsやroute tablesなどを制御する責任があります。システムのアーキテクチャは、少し簡略化していますが、このようになっています。これは実際、多くのAWSサービスでも珍しくありません。上部にcontrol planeがあり、これが顧客のAPIコールを受け取ります。そして下部にdata planeがあり、これらの意図を実際に実現します。このサービスでは、control planeが顧客のリクエストを受け取り、更新をキューに入れ、その更新がdata planeに伝播されて適用されるという仕組みになっていました。
では、その伝播サービスに障害が発生したらどうなるでしょうか。伝播サービスに障害が発生すると、想像がつくと思いますが、キューはどんどん大きくなっていきます。これはVPCの顧客からの更新なので、誰かがsecurity groupに2つの異なる更新を送信した場合、その順序を変更したり、破棄したりする自由はありません。そのため、先ほど説明したsideliningテクニックはここでは適用できません。
では、どうすればいいのでしょうか?私たちは、バックプレッシャーと呼ばれる別の手法を使用します。これは、キューに物事が蓄積されることを許容しますが、ある一定のポイントまでしか許容しません。簡単に言えば、これは有界キューのことです。ここで異なるのは、実際にコントロールプレーンにキューの深さを伝えることです。そして、コントロールプレーンがキューが満杯であることを認識したとき、どうするでしょうか?そうです、顧客のリクエストを失敗させるのです。
顧客のリクエストを失敗させるんですか?はい。
顧客のリクエストを失敗させるんですか?そうです。9の数が減ったようですね。1つどころではないでしょう。このキューが満杯である限り、リクエストの失敗は続きます。実はこの方法は、まさにこの理由で当時物議を醸しました。しかし、私たちがこれを行った理由は、回復時間を予測可能で迅速にできたからです。伝播サービスが回復した瞬間、私たちは完全な回復が非常に早く達成されることを知っていました。数分や数時間も続く可能性のある巨大なバックログを抱えることはありませんでした。この場合、私たちは明確に可用性を犠牲にして回復力を優先しています。
皆さんも、自分のアプリケーションに戻って、どこにキューがあるか考えてみてください。必ずしも「キュー」という名前がついているわけではありません。ディスク上のログファイルやデータベースの行など、滞留する可能性のあるものを考えてみてください。バッファーのようなものですね、そう、多くのものが該当します。そして、どれだけ遅れているかを示す統計情報を必ず用意してください。また、そのキューをどれだけ速く排出できるかを知ることも非常に重要です。その数値がわからない場合、良い方法は、プリプロダクション環境でコンシューマーをオフにし、キューを満杯にしてから再びオンにして、排出にかかる時間を測定することです。これを常に行ってください。デプロイメントによって速くなったか遅くなったか?常にその回復時間の指標を把握しておくことが大切です。
そして最後に、他の方法がすべて失敗した場合は、キューに制限を設ける戦略を立てて、回復に長時間かかるほど多くの作業が蓄積されないようにしましょう。では、Beckyに引き継ぎます。
エラーの種類と影響:400エラーと500エラーの違い
ありがとう、Mike。次のトピックはエラーについてです。実は、エラーは私の好きなトピックの1つなんです。可用性やレジリエンス、そして迅速な回復能力との関連性は一見分かりにくいのですが、実際にはとても密接なんですよ。これは私たちが何度も目にしてきたことです。エラーは、完璧な人でない限り(そんな人はいませんよね?)、毎日遭遇するものです。実際、AWSサービスや自社の依存関係を呼び出す際に、様々な種類のエラーに遭遇する可能性があります。
RFCによると、エラーには2つの重要なカテゴリーがあります。400番台のクライアントエラーと500番台のサーバーエラーです。おそらく両方を見たことがあると思いますが、クライアントエラーの方をより多く目にしているでしょう。クライアントエラーは、リクエストに何か問題があったということです。アクセス権がなかったり、要求したものが存在しなかったり、パラメータの組み合わせが無効だったりします。実際、私の場合は間違いなくそうですね。新しいサービス、AWSなどに初めて接続しようとすると、最初のリクエストは必ず何らかの400エラーを返します。これらのエラーは頻繁に目にするものです。
一方、500番台のエラーはあまり見かけません。500エラーは、サーバー側で何か問題が発生したことを意味し、ユーザーの責任ではありません。サーバー側で未処理の例外などが発生して、このエラーが返されたのかもしれません。ユーザーが対処できることはありません。後で再試行すれば動作するかもしれません。もちろん、これらの両方のタイプのエラーは私たちの世界に確実に存在します。実際、私たちの非常に大規模なサービスの1つのグラフをお見せしましょう。1つは400エラー、もう1つは500エラーです。どちらがどちらか、少し時間を取って考えてみてください。
そして、多くの方が優秀なテストの受験者であるように、実は私がグラフを入れ替えてしまいました。ここで左側に見えているのが、500エラーです。そして右側が400エラーです。どうしてわかったのでしょうか?まあ、私がグラフを作ったからですが、グラフの形からもわかります。左側の500エラーのグラフを見ると、ほぼゼロ付近を推移しているのがわかります。実際、ここで疑問に思う人もいるかもしれません。「サービスが正常に動作しているなら、なぜ完全にゼロにならないのか」と。
さて、先ほど申し上げたように、これは非常に大規模なサービスです。膨大な数のホストがあります。このような規模で運用していると、実際にはこのグラフのように0、0、0にはなりません。このグラフは非常にズームインされています。下部に少しノイズがあるのがわかるでしょう。ホストに問題が発生すると、それをサービスから外す自動化が行われます。ただし、その自動化が開始されるまでに少し時間がかかります。400エラーは通常のパターンに従っています。これは1週間分の400エラーのグラフだと推測できますね。週単位の日周パターンが見られます。400エラーは常に発生しています。顧客が存在しないものを要求したり、名前は伏せますが、あるお客様がIAMポリシーを間違って記述してアクセス拒否になったりしています。
マイクがこのスライドを作成する際に私に尋ねました。あなたのサービスではこれらはどのように見えますか?と。マイクがこのスライドを作成する際に私に尋ねましたが、これはS3のソースコードではありません。
エラーコードの実装とその重要性
ここで見ているのは、大まかに言えば、サービスを実装した場合の400エラーを返すコードと500エラーを返すコードです。400エラーは、サービスの観点からは実際にすべてが正常に動作している場合に発生します。通常、何らかのif-else文で異なるケースを処理します。見つからなかった場合は404を返す、IAM評価でアクセス拒否となった場合はアクセス拒否を返す、といった具合です。これらは予想される既知のケースなので、通常は特定のコードで処理します。
500エラーは必ずしもこのように実装されているわけではありませんが、多くの場合そうなっています。何かを実行しようとして、何らかの例外が発生した場合です。依存関係に問題があったり、ネットワークに問題があったり、DNSに問題があったりするかもしれません。根本的に何かが起こり、このリクエストを成功させることができません。積極的に探していたケースではないので、500エラーを投げます。もちろん、過負荷やブラウンアウトなど、意図的に500エラーを投げる場合もあります。
では、なぜこの違いが重要なのでしょうか?可用性、特に障害からの回復能力にとって、このコードを正しく実装すること、400のケースを400として分類して400を返すこと、500のケースも正しく分類することが非常に重要だと言えます。もちろん、何が起こったのか、次に何をすべきかを伝える、有用で実用的なエラーコードを返すことは、お客様にとって非常に良いことです。しかし、それは可用性にも影響するのです。
この500エラーのグラフを少し加工して、障害のような状況を作り出してみましょう。500エラーによる障害です。ここに明らかに影響を受けている期間があることがはっきりと分かります。何かが失敗しています。この図を見れば、何かが明らかに問題を起こしていることが分かります。
アラームのしきい値を設定し、それをうまく行いました。アラームが発報しています。自動修復が行われるかもしれませんし、何かがロールバックされるかもしれません。あるいは、オペレーターが対応するかもしれません。何かがうまくいかなくなったときに起こるべきことが、はっきりと分かるので、それに対応できます。
400エラーについてはどうでしょうか?何かがうまくいっていないとしたら、400エラーのグラフはどのように見えるでしょうか?実は、あまり多くの情報を与えてくれません。 これは実際にはあまり多くの情報を提供していません。これが、これらを分類することが重要な理由です。なぜなら、一方はシグナルの源であり、もう一方はノイズの源だからです。シグナルの一部をノイズに、あるいはノイズの一部をシグナルに混ぜ込みたくはありません。
これが、グラフが本来あるべき姿です。実際、このグラフで何ができるでしょうか? 400エラーのグラフは完全に無用というわけではありません。実際、いくつかの理由で非常に有用です。一つには、成功グラフと同様に、予想される形状を持っており、CloudWatch異常検出のような機能が、この帯から外れたときを実際に検出するのに役立ちます。
セルラーアーキテクチャと個別測定の必要性
これを加工してみましょう。 サービスが400エラー、つまりクライアントエラーを返している期間があるとしましょう。ただし、それが通常の範囲を超えています。これは何を示しているのでしょうか?実のところ、私にもまだ何を示しているのか分かりません。ここには実際に2つの可能性があり、どちらなのかを判断するためにこのグラフを持つ価値があります。
可能性の一つは、サービスに変更をデプロイしたことで、おそらく意図せずに、昨日は成功していたものが今日は400エラーを返すようになったというものです。例えば、新しい設定をプッシュして、何らかのパラメーターの最大値や制限を誤って低く設定してしまったとします。そのため、昨日は範囲内だった人が今日は範囲外になってしまったのです。これは実際にサービス所有者であるあなたの問題です。あなたが何かをしなければなりません。
もう一つの可能性は、顧客が誤った呼び出しを熱心に行っているケースです。実際には何も問題が起きていないのに、トップトーカーがこの状況を引き起こしているのです。この図だけではどちらなのか分かりませんが、これも重要なグラフとして注目すべきです。500エラーほど簡単にアラームを設定できませんが、何かを示していることは確かです。
これがレジリエンスにとって重要な理由を説明しましょう。 重要なのは、検知までの時間です。シグノルとノイズがきれいに分類されていれば、より早く検知できます。AWSでは、運用イベントやシステム障害、問題のレトロスペクティブを行う際、最も重要なのは問題をいかに早く終息させるかです。問題を終息させるまでの時間は、検知までの時間と緩和までの時間の合計です。つまり、問題があることを知り、それを解決または緩和するまでの時間です。私たちは検知までの時間に特に注力しています。そのため、サービスが返すエラーの種類から可能な限り多くのシグナルを得ることが、障害時間を短縮する上で非常に重要なのです。
この関連性が好きなのは、一見して分かりにくいからです。500エラーは注意が必要で、400エラーも注意が必要かもしれません。500エラーのグラフに戻って、非常に短い期間の影響を受けたサービスを見てみましょう。このグラフから、何かが変化し、何かが間違っている期間があることが分かります。これが500エラーのグラフだとしましょう。つまり、これは可用性のグラフです。ほとんどの時間は100%の可用性がありますが、1つの点だけ良くありません。もちろん、これはごく小さな問題で、9の位のアラームしきい値があり、すべてが良好に見えます。アラームは発報していません。
実際、絶対数で見れば、特に500エラーは再試行可能で非常に低い頻度で発生しているため、かなり良好な状態です。ほとんどの顧客は再試行でこの問題を乗り越え、気づきもしないでしょう。しかし、問題は、これが誰の9の位なのかということです。サービスを1つの単一体として扱い、これがその可用性のパーセンテージだとすれば、ストック写真の人物の意見に同意します。確認すべき点はありますが、実際のところ問題ありません。
しかし、多くの場合、サービスはそのように機能していません。特に大規模なサービスでは、セルラーアーキテクチャのようなもの、つまり異なる顧客を異なる種類のインスタンスセルに振り分けるようなシャーディングアーキテクチャを採用していることがあります。このアーキテクチャには多くの利点があり、AWSでも至る所で使用しています。これにより、 予測可能な規模でサービスを運用できます。しかし、単一の可用性メトリクスだけを見ていると、これら4つのセルの平均値になってしまいます。Mikeの話にあったように、平均を取ると、実際に何が起こっているかの精度が少し失われてしまうのです。
CloudWatch Contributor Insightsの活用
これらのセルの可用性を個別にグラフ化すると、青いセルに割り当てられたお客様は、以前考えていたほど良好な状態ではないことに気づくでしょう。他のセルはほぼ100%の可用性に近づいていますが、青いセルのお客様は少し異なる状況にあります。これが非常に重要な理由です。 このようなアーキテクチャをお持ちの場合、実際には各セルを個別に測定する必要があります。全体を平均化すると、個々のお客様の体験が異なる可能性があるからです。
この話題についてもう一つ触れたいのは、 CloudWatch Contributor Insightsです。AWSでサービスを運用している私たちにとって、おそらくCloudWatchの中で最も好きな機能です。私たちは非常に多くの洞察を得ることができます。なぜなら、私たちが運用している規模、そして様々なお客様の数を考えると、それぞれのお客様がクラウドで独自の体験と旅をしているからです。Mikeさんが、CloudWatch Contributor Insightsを使って、400エラーが1人のお客様からのものかどうかを確認できるかと尋ねましたが、その通りです。ここで見ているのは、実際に私たちのあるサービスのスクリーンショットで、サービスで発生している400エラーを示しています。これは典型的な例の期間です。
CloudWatch Contributor Insightsの素晴らしい点は、多くの異なるディメンションがあっても、通常のグラフではそのレベルのディメンショナリティをサポートできないのに対し、Contributor Insightsはまさにそのために設計されていることです。ここでは、400エラーを最も多く受けているトップトーカー、つまりアカウント別のエラーを示しています。ここで気づくことがいくつかあります。まず、青いお客様とオレンジのお客様は、他のお客様とは根本的に異なる体験をしています。なぜそうなのでしょうか?私たちはこういったケースをよく目にします。青いお客様が意図的にエラーを生成するテスト負荷を実行している可能性もありますし、何かの設定ミスがある可能性もあります。
状況によっては、彼らに連絡を取って「あなたがやろうとしていることがうまくいっていません。代わりにこのようにする必要があります」と伝えたいかもしれません。緑のお客様にも興味深いパターンがあります。ここでも、おそらくこのサービスに問題はないのですが、お客様が実際に製品をどのように使用しているかについて学ぶべきことがあります。
エラーとそれをサービスにどう実装するかというこのトピックが大好きです。 エラーを適切に扱うことは、実際の問題(500エラー)を検出し、それに対応する能力に大きく影響します。これは特に短期の障害に対して非常に重要です。400エラーについては、有用な情報源となります。Contributor Insightsをまだ見たことがない方は、ぜひ一度見てみることをお勧めします。お客様が実際にどのようにサービスを使用しているかについての情報源となります。私たちは常にこれを活用しています。
リトライの重要性と課題:マイクロサービスアーキテクチャでの考慮点
最後のトピックは、9の先にある5つのトピックの中のリトライです。リトライは前回のエラーのトピックと密接に関連しています。なぜなら、先ほど話したように、500エラーが発生した場合はリトライすべきだからです。クライアントが一時的に利用できないか到達不可能なサービスにリクエストを送り、500エラーを受け取ったとします。それは嬉しくないですが、500なので、あなたの問題ではなく相手の問題です。そこで、もう一度試してみましょう。
再度試みても、まだうまくいきません。通常、3回というのは多くの人にとって魅力的な数字です。ある種の対称性があるからです。そこで3回目を試みると、成功します。実際のところ、AWSサービスを呼び出す場合、おそらくAWS SDKを使用しているでしょう。これはAWSがサポートしているすべてのプログラミング言語で利用可能です。AWS SDKを使用する利点は、リトライの動作に多くの工夫が施されていることです。これを無料で利用できます。リトライの間隔や設定について何も考える必要はありません。適切なバックオフとジッター処理を含めて、すべてSDKが行ってくれます。あまり深く考える必要はありません。
しかし、自分のサービス間で呼び出しを行う場合は、適切なリトライ動作の実装について考えることが重要です。さて、リトライとスケールでのサービス運用についてもう一つ重要なことがあります。リトライが発生するのは、何か問題が起きているときです。リトライが発生する理由は、サービスから500エラーが返されているからです。ここに2つの異なるAWSサービスのグラフを示しています。それぞれが何らかの短期的な障害を経験しています。これら2つのサービスの下部の緑のグラフは障害を示しており、つまり可用性グラフの逆で、サービスから返される500エラーを表しています。
注目してほしいのは、これらの全く異なるサービスが非常に異なるパターンを示し、2つの運用イベントが明らかに異なる形状を持っているにもかかわらず、この2つの事例には共通点があることです。これは常に真実です:サービスが障害を起こしているとき、より多くの負荷を受けることになります。これはある種のモードのようです。リトライモードに入り、それはほぼ避けられません。エラーが発生すると、より高い負荷を受けることになり、これはまた別の問題です。繰り返しますが、この状況から抜け出すことは実際には難しいです。これは500エラーを返すことの物理法則のようなものです。しかし、この状況を少し緩和するためにできることはいくつかあります。
さて、ここでマイクロサービスアーキテクチャを構築してみましょう。Service OneがService Twoを呼び出し、Service TwoがService Threeを呼び出し、Service ThreeがService Fourを呼び出します。これは明確な依存関係です。この依存関係に問題はありません。素晴らしいアーキテクチャに見えます。では、Service Fourが何らかの可用性イベントを経験し、500エラーを返している場合に何が起こるか、「もし」ではなく「いつ」という観点で話してみましょう。
さて、Service Threeはコールチェーンの中でService Fourまで到達しましたが、Service Fourがうまく機能しませんでした。Service Threeは適切な指数バックオフとジッターを行い、想定通りにService Fourへの再試行を行います。それでもうまくいきません。もう一度再試行してみましょう。それでもダメです。そこでService Threeはどうするでしょうか?Service Threeは「いいえ、500エラーです」と返します。つまり、サーバーエラーということですね。
Service Twoに対して、「あなたのリクエストを満たすことができません」と伝えます。 そして、「私のせいで、あなたのせいではありません」というわけです。では、Service Twoはどうするでしょうか?Service Twoは「わかりました、Service Three、もう一度試してみましょう」と言います。これが再び起こります。Service Fourはここで長時間の障害を起こしているのです。もちろん、この障害が長引けば長引くほど、まさにこの理由で負荷が高まります。
Service Twoはもう一度試みます、Service One。ここでの計算はどうなるでしょうか?高校の代数学で習ったように、 これは等比数列ですよね?このチェーンを1つ下のサービスに降りるたびに、実際には指数関数的に負荷が増えていくのです。これは少し誇張した描写ですが、実際にこのように動作することがあります。途中で再試行が増幅されていくのです。では、これはService Fourにとってどういう意味を持つでしょうか?Service Fourは最悪の日に、おそらく前例のない量の負荷を受けることになります。そして、これがService Fourの回復能力にどのような影響を与えると思いますか?電源が復旧しても、すぐには回復できないかもしれません。
さて、ここで何が起こってほしいと思いますか?もちろん、Service Fourが障害を起こさないことを望みますが、今回は完璧さではなく、レジリエンスについて話しているのです。実際には、こんな風に動作してほしいと思うでしょう。Service TwoとService Threeが自分たちの中間的な立場を認識し、さらなる再試行を行わず、Service Oneに再試行を任せることです。なぜなら、このリクエストを完了させたいからです。 左の図と右の図を比べてみると、どちらがより早く回復すると思いますか?もちろん、膨大な負荷を受けていない方が早く回復するでしょう。
さて、先ほどService TwoとService Three は再試行をしないほうがいいかもしれないと言いましたが、ここには人間的な要素があります。もしService Fourへの再試行をしないように言われたら、あなたの9sはどうなるでしょうか?9sを失うことになります。そしてそれを非常に残念に思うかもしれません。あなたはその9sを誇りに思っているかもしれませんし、再試行しないことで9を1つ失うかもしれません。なぜなら、Service Fourがちょっとしたトラブルを起こす場合があり、あなたの再試行戦略が実際にはチャンピオンのように機能しているかもしれないからです。Service Twoに戻って「あなたのリクエストを処理できませんでした」と言う代わりに、実際にはローカルな視点からすると、これは悪い結果になります。 しかし、このサービス全体のグローバルな視点から見ると、実際にはより速い回復が得られるのです。
リトライ戦略の最適化:EC2 DNSの事例
これらは考慮すべき点です。ローカルでは可用性が低下し、9の数が少なくなる可能性があります。ここでは9の数を超えて、実際にはリカバリー時間の方が重要だと考えています。 これらのリカバリーシナリオで、負荷がかかっている状況下では、スロットリングやレート制限が非常に重要になります。右の図のような状況に陥った場合、実はスロットリングを行うことが最善の対処法となります。もちろん、顧客を押し返すことになるので、ここでも9の数は減ってしまいます。しかし、これによってリカバリーがかなり速くなる可能性があります。実際、リトライの成功も早くなるかもしれません。
ここまでの話はいいでしょう。リトライを少し悪役のように描いてしまいましたが、それは必ずしも適切ではありません。適切なリトライ動作は、実際に問題を乗り越えるのに役立ちます。しかし、リトライの問題点は、先ほど話したように、リトライがモードの一形態だということです。では、リトライは欲しいけれどモーダルな動作は避けたい場合はどうすればいいでしょうか?実は、AlecもPrivate Cloudも、様々な時点で顧客のEC2インスタンスにサービスを提供するDNSインフラストラクチャに取り組んできました。このサービスは現在、Route 53 Resolverと呼ばれています。Virtual Private Cloudの概念をご存知の方なら、VPCのドット2 IPアドレスにあるものです。デフォルトでは、EC2でのDNSクエリはここに行きます。EC2でこれを機能させるための膨大なインフラストラクチャがあります。
当時は、Route 53 Resolverという名前がまだなかったので、単にEC2 DNSと呼んでいました。EC2 DNSとは、EC2ネットワークで運用していたDNSリゾルバーのことで、DNSクエリはここに送られていました。考えてみれば、EC2インスタンスにとってDNSは非常に重要です。EC2の顧客にとって、DNSが機能しないということは、その日はネットワークが使えないということになります。DNSは機能しなければなりません。もちろん、これは非常に大規模なフリートなので、スケールが大きくなると、一部のホストが故障してサービスから外されるまでの間、一定の時間がかかります。その間、そのホストへのDNSクエリは失敗します。顧客はDNSを解決できず、EC2インスタンスで行いたいことができなくなります。
そこで、リトライを考えるかもしれません。しかし、リトライには問題があります。アベイラビリティーゾーンには多くのEC2インスタンスがあり、常にDNSクエリを送信しています。リゾルバーホストに何らかの影響があった場合、例えば不適切なデプロイメントや、複数のホストに同時に影響を与える何かがあった場合、そういったシナリオは十分に想像できます。突然、アベイラビリティーゾーンのDNSトラフィックが2倍になったらどうでしょうか?そうなると、そこにあるすべてのインフラストラクチャにとって大きな驚きとなるでしょう。
では、どうすれば両立できるのでしょうか?何かが失敗した時に最善の対処法は何もしないことですが、私たちはリトライを望んでいます。そこで、私たちが行うのは...現在、EC2インスタンスからDNSクエリを行う際、皆さんには見えませんが、実際には2つのDNSリゾルバに問い合わせが行きます。ほとんどの場合、両方が正しい答えを返し、そのうちの1つ、つまり最初に返ってきた方の答えを受け取ります。しかし、一方が返答せず、非常に遅い場合でも、もう一方から応答を得られるため、問題に気付きません。
もちろん、これにはトレードオフがあります。常に2倍のDNSリクエストを処理しているため、実際には発生していないリクエストも処理することになります。つまり、2倍のスケールが必要になります。このように重要な場合には、そうする価値があります。リトライが本当に重要で、少しのスケールアップのコストを払う余裕がある場合、常にプロアクティブにリトライを行うことを検討できます。これは珍しいケースですが、この方法は私たちにとって非常に効果的でした。
大規模レジリエンスの5つのポイントの総括
さて、これでリトライについての話題は終わりです。リトライは悪いものに聞こえますし、何かが既に間違っている時に追加の負荷を引き起こすことがよくあります。そのため、これを軽減する技術は、完全に排除することはできませんが、迅速な回復を実現する上で非常に有用です。そして、リトライが本当に必要で、非常に重要であるにもかかわらず、モードの切り替えや高いスケールを望まず、追加の容量に対応できる場合、常にリトライを行うという選択肢もあります。
以上が5つのポイントでした。皆さんの多くは経験豊富なオペレーターだと思います。私たちのクラウドカスタマーの多くは、クラウドで素晴らしい運用プラクティスを持っています。経験豊富な方でも、この話から何か新しいことを学んだり、これまでとは違う視点で物事を見ることができたのではないでしょうか。依存関係とモードについて話しました。依存関係が失敗した時に完全にモードを切り替えるのではなく、どう対処するかについて話しました。ブラストラディウスについて話しました。変更が引き起こす影響の範囲を制限する方法について説明しました。
キューについて話しました。これは時間軸に沿ったブラストラディウスのようなものです。そして、そのバックログをどう制限するかについて説明しました。エラーについて、そして検出時間とコードの実装の明確さとの間の予想外のつながりについて話しました。最後にリトライについて、リトライが引き起こす避けられない追加負荷にどう対処するかについて説明しました。このセッションに参加していただき、ありがとうございました。私たちは皆さんと共有したい内容をまとめるのに多くの楽しみを感じました。皆さんと共有できて本当に嬉しく思います。
※ こちらの記事は Amazon Bedrock を様々なタスクで利用することで全て自動で作成しています。
※ どこかの機会で記事作成の試行錯誤についても記事化する予定ですが、直近技術的な部分でご興味がある場合はTwitterの方にDMください。
Discussion