re:Invent 2024: AWSとCapital Oneが語るレジリエントなアプリ構築
はじめに
海外の様々な講演を日本語記事に書き起こすことで、隠れた良質な情報をもっと身近なものに。そんなコンセプトで進める本企画で今回取り上げるプレゼンテーションはこちら!
📖 AWS re:Invent 2024 - Building resilient applications on AWS with Capital One (ARC334)
この動画では、AWSとCapital Oneによるレジリエントなアプリケーション構築について解説しています。AWSのAndrew BairdがFault Isolation、Observability、Recoveryの3つの要素を中心に、レジリエンスの基本原則を説明し、Capital OneのMoe BanihaniがCell-based Architectureを活用した具体的な実装例を紹介します。特に、250ミリ秒以内の処理が求められる認証プラットフォームと、複数テナントの要件に対応するCore Banking Platformという2つの重要なシステムにおいて、Cell Router、Resilience Engine、Deep Health Checkなどを組み合わせた実践的なレジリエンス戦略が詳しく解説されています。障害の分離、継続的なモニタリング、迅速な復旧を実現するための具体的な設計手法と、その実装における重要なポイントが示されています。
※ 画像をクリックすると、動画中の該当シーンに遷移します。
re:Invent 2024関連の書き起こし記事については、こちらのSpreadsheet に情報をまとめています。合わせてご確認ください!
本編
AWSでのレジリエントなアプリケーション構築:セッション概要
それでは、始めさせていただきます。Architecture 334「Building Resilient Applications on AWS with Capital One」へようこそ。私はAWSのAndrew Bairdです。そしてこちらがCapital OneのMoe Banihaniです。本日は会場にお越しいただき、ありがとうございます。他の2会場でシムルキャストをご覧の皆様もありがとうございます。そして、将来YouTubeでご覧になる皆様、そして自分の動画に「いいね」を押したい誘惑と戦っているであろう未来の自分にも、こんにちは。その誘惑には負けないようにしましょうね。
本日のアジェンダはこのような流れで進めていきます。私が前半を担当し、これから30分ほどで約60枚のスライドを手早く進めていきます。具体的な質問がございましたら、セッション後に個別の話題について詳しくお話しできればと思います。今回は、皆様のアーキテクチャに適用できる原則やパターン、ガイダンスについてお話しします。特定のアプリケーションにしか当てはまらないような個別のパターンについては深く掘り下げません。というのも、ミッションクリティカルな環境でレジリエンスを実現するには、アプリケーションごとに要件やパターンが大きく異なるからです。
マルチリージョンのパターンやアーキテクチャについては、それに特化した別のセッションがありますので、そちらで詳細をご確認ください。私のパートでは、会場の皆様全員に当てはまり、お役に立つと思われるトピックを中心にお話しします。その後、MoeがCapital Oneでこれらのトピックをどのように解決しているか、そして彼らのポートフォリオ内でミッションクリティカルなアプリケーション運用を実現するために使用している具体的なアーキテクチャパターンについてお話しします。
レジリエンスの基本原則とGray Failureの課題
それでは、いくつかの指針となる原則から見ていきましょう。以前にre:Inventに参加され、レジリエンスに関する私たちのプレゼンテーションをご覧になった方は、CTOのDr. Werner Vogelsの「Everything fails all the time(すべてのものは常に故障する)」というフレーズをご存知かもしれません。 これに加えて、EC2の創設メンバーの一人による第二の指針となる言葉をご紹介します:「You can't legislate against failure, focus on fast detection and response(障害を法律で防ぐことはできない。迅速な検知と対応に注力せよ)」。
これら2つの原則を組み合わせて考える必要があります。つまり、すべてのものは故障するという事実と、アーキテクチャ内のどの要素も故障してアプリケーションに影響を与える可能性があるという考え方です。これらの障害を防ぐための作業や、ベストプラクティスを徹底することで完全に防げるわけではありません。むしろ、障害が発生することを前提に準備を整え、お客様への影響を最小限に抑えるために、できるだけ早く復旧できるよう努力を注ぐべきなのです。
自動車に例えて説明させていただきます。車には、レジリエンスや障害の検知、保護を実現するための様々な仕組みが備わっています。特に最近の車では、ドライバーが事故や故障を未然に防ぐための機能が数多く搭載されています。しかし、実際に障害が発生した際の対処に関連するコンポーネントも依然として重要です。設計アーキテクチャだけに注力し、冗長性や堅牢性を確保することや、本日発表したAurora D SQLなど、利用可能な最善のプラクティスや機能を徹底的に活用することだけに時間とエネルギーを費やすわけにはいきません。障害発生時のチームの対応方法やアプリケーションの動作、そして取るべきアクションにも焦点を当てる必要があります。
AWSでレジリエンスを語る際に多くの方が思い浮かべる最も基本的な例として、Multi-AZアーキテクチャの設計を見てみましょう。ここでは、プライマリとセカンダリのデータベースを持つ3層アプリケーションがあります。 AZ1が停止したとします - これは明確な条件とアプリケーションの動作状態を持つ、シンプルな障害シナリオです。 そのAZ内で完全な停止が発生します。Amazon RDSの管理機能が作動し、データベースインスタンスをセカンダリインスタンスにフェイルオーバーし、システムが復旧した際にはプライマリインスタンスへのトラフィックが回復しますが、すべては自動で管理されます。これは完全な停止イベントで、 検知も容易で、管理機能も簡単に作動します。
もう一つの可能性のあるシナリオを見てみましょう。AZ1のアプリケーション層とプライマリデータベース間の接続に特定の問題が発生し、サービスの可用性が異なる状態になったとします。Amazonのインフラ側から見ると、すべてが正常に見える可能性があります。アプリケーションの問題かもしれませんし、メモリ不具合によってお客様に影響を与えている単一のホストの問題かもしれません。インフラ内の単一のネットワーク接続が不安定になっているかもしれませんし、メモリの状態やユーザーのアクセスパターンにより、特定のインスタンスで単一のデータベースクエリがタイムアウトしているのかもしれません。このような状況では、私たちが提供する標準機能を使って構築した設計上の対策を活用するだけでは対処が難しくなります。特にミッションクリティカルなアプリケーションの場合、非常に厳しい状況となる可能性があります。
では、このような状況にどう対応すればよいのでしょうか?私たちは、これらの様々な障害シナリオを、システムの状態(正常/異常)とお客様の視点という2つの軸からなる2×2のグリッドで考えています。 右上の象限が、本日の話の中心となる「Gray Failure(グレー障害)」です。これは、レジリエンスと復旧のために構築した機能や観測システムでは正常と判断されているにもかかわらず、お客様が実際には問題を経験している状態です。これがGray Failureであり、検知、予防、緩和が困難な障害です。
これらに対処し、より深く計画を立てるためには、より優れたObservabilityを構築する必要があります。なぜなら、このグリッドは健全性の観点に関するものだからです。Observabilityを向上させ、お客様が経験している問題をより細かく検知できるようにすることで、これらの障害シナリオを、事前に十分な対策を講じることで対応が容易な他の象限に移動させることができます。 ミッションクリティカルなレジリエンスを構築するために、私たちは次の3つの要素に焦点を当てていきます:Fault Isolation(障害の分離)、Observability(観測可能性)とRecovery(復旧)です。そして最後に、これらすべてを結びつけていきます。
Fault Isolationとセルベースアーキテクチャの重要性
Fault Isolationについて、これは通常、AWSアーキテクチャを設計する際に多くの人がまず最初に考えることです。 物理的な設計、つまり私たちが提供する様々なサービスが、どのように異なる物理的な可用性のカテゴリーに分類されるかを考えます。Zone単位のサービスなのか?Zone levelで冗長性が組み込まれていてRegion levelで動作するのか?それとも、Amazon Route 53やAmazon CloudFrontのように、アプリケーションがどこに構築されているかに関係なく、グローバルに提供されるサービスなのか?これは設計の重要な側面であり、正しく実装する必要があります。しかし、お客様は通常、レジリエンスに関する時間のほとんどをここに費やしています - つまり、RTOとRPOの目標を達成するために、適切な冗長性を確保できるよう、アーキテクチャに正しい物理的な障害境界を選択することに注力しているのです。
また、論理的な境界も構築することになります。これは、論理的な境界もレジリエンスの一部であることを示すもう一つの例です。私たちはモノリスからMicroservicesへと進化し、何らかのService-Oriented Architectureを採用することで、異なるコンポーネント間に独立した障害分離境界を設けることができるようになりました。これにより、各コンポーネントが独立して障害を起こし、独立してスケールし、独立してデプロイできるようになります。これらすべての要素が、レジリエンスというトピックにどのようにアプローチし、異なるサービス境界間でその所有権をどのように定義するかという点で、状況を大きく変えることになります。
AWS内で論理的な境界を定義する方法は他にもあります。複数のAWSアカウントの使用も、優れた論理的な障害境界の一例です。チーム内で発生する特定の操作の中には、AWSアカウントレベルで行われるものがあります。Service QuotaやLimitなどは、AWSアカウントレベルで存在します。そのため、アプリケーションのコンポーネント間の論理的な境界としてこれを考えれば考えるほど、障害の影響範囲は小さくなり、アーキテクチャ内で発生する可能性のある他の障害シナリオも排除できるようになります。
そして、Moeがより具体的に掘り下げる予定のパターンについて私が強調したいのは - もし以前に私たちのレジリエンスに関するトピックを聞いたことがあれば、おそらく耳にしたことがあると思いますが - これはAWS内の新しいサービスでほぼ普遍的に採用されているアーキテクチャパターンです。AWS内のエンジニアリングチームは、Cellular Architectureを強く支持しています。Availability Zone全体に物理的な障害ドメインを配置し、Service-Oriented Architectureで境界を提供することだけを考えるのではなく、そのスタック全体をCellに分割し、顧客ベースや、特定のアプリケーションのワークロードをどのように考えるか、またはパーティションが何を意味するかに基づいて分割します。
それぞれのCellには、そのCell内のサービスエコシステム全体の独立したスタックが専用に割り当てられ、独立してデプロイされ、独立して運用され、専用のインフラストラクチャと専用のサービスを持っています。これらの各Cellは、そのビジネス機能の垂直スタック全体が提供する機能をすべて提供します。アーキテクチャに論理的または物理的な問題が発生した場合でも、顧客を別のパーティションに分割することで、すべての顧客への影響を抑制できる境界がここにあります。そして、Moeがさらに詳しく話す予定の新しい層は、Cell Routerという考え方です。
これらのパーティションにアーキテクチャをデプロイしたら、顧客リクエストが来た時にどのパーティションに割り当てるかを決定する必要があります。Cell Routerと呼ばれるこの新しいコンポーネントは、入ってくるリクエストを観察し、適切なスタックにルーティングします。ただし、これについてはMoeが詳しく説明するので、ここでは簡単に触れるだけにしておきます。 AWSアーキテクチャ設計で考慮されているであろう設計要素、つまりAvailability ZoneやRegionに関連するインフラストラクチャの物理的な設計を見直し、Cell-basedアーキテクチャを通じてより多くのパーティショニングと小さな影響範囲を導入することで、アーキテクチャ設計を別の方法で活用できることを理解することが重要です。
Observabilityの重要性とその実装方法
利用可能なメカニズムとパターンを意識する必要があります。 定義する境界によって、障害が発生した際の分離の程度が決まります。障害が発生した時の影響範囲は小さければ小さいほど良いのです - なぜなら、障害は必ず発生するからです。実際に経験する障害は、すべてが停止して検出や復旧が容易な、きれいで整然とした、粗い粒度の白黒はっきりした障害シナリオではありません。 これから、Observabilityについて話します。これは、私が考える中で最も重要で、かつ顧客の間で最も軽視されがちなレジリエンスの側面です。
このディスカッションから一つだけ持ち帰って、チームと共により良いレジリエンスを達成するために本当に優先し、強調したいものがあるとすれば、私が話をした顧客のほとんどが何らかの形で改善できる領域であるObservabilityだと思います。 すでに強調したように、障害はバイナリではありません。しかし、より細かい粒度でバイナリに近い形で障害を捉えるためには、優れたObservabilityが必要です。 例えば、顧客が通常見るような典型的なメトリクスで、全顧客のサービスレイテンシーを示すものがあります。少しノイズはありますが、正常範囲内に見えるにもかかわらず、多くの顧客が問題を報告しています。経営陣は重大な問題があると報告を受けていますが、メトリクスにはそれが表れていません。
より詳しく見て、メトリクスをAvailability Zone別に分割し、AZレベルの粒度で見てみると、特定のAZが他のAZほどトラフィックを受けていないことがわかるかもしれません。集計されたメトリクスの中では簡単に隠れてしまいますが、問題が発生している明確な変曲点があります。ダッシュボードやアラートの最前線で、適切な粒度のObservabilityを持つことが、顧客の痛点がいつ発生しているかを理解する鍵となります。
こちらは別の例です。 真夜中に、クエリのレイテンシーが大幅に上昇するイベントが発生しました。アラームが作動し、エンジニアが調査を行いました。顧客からの問題報告はなく、すべてが順調に見えましたが、何が起きているのか確信が持てなかったため、ロールバックを実行しようか迷いました。状況は自然に解決しましたが、チームの不安やストレスを引き起こしました。 もしこのシナリオが、すべてのデータベースクエリのレイテンシーを集計した単一のメトリクスだけで定義されていたらどうでしょうか?そのデータベースには異なるタイプのクエリが送信されているかもしれません - 例えば、eコマースウェブサイトでの商品の取得と新規注文の送信といった具合に。
クエリレベルでより詳細なレイテンシー追跡のメトリクスがあり、各クエリタイプごとに専用のダッシュボードがあれば、そのイベント中は両方のメトリクスが正常に見えるかもしれません。上部の大規模クエリのメトリクスは、大量の注文や、特定のヘビーユーザーが通常より多くの長時間クエリを実行したことを示している可能性があります。個別に見ると全て正常に見えても、メトリクスが集計されると問題のあるイベントとして表示されることがあります。ここでのポイントは、メトリクスの次元と適切な粒度を理解し、アプリケーション内の動作を可能な限り細かく把握できるようにすることです。
このようなアプリケーション動作の粒度は、設計、デプロイ、実行方法によって異なります。これは、Amazon CloudWatchのput-metric-data APIの例です。このAPIには、特定のメトリクスを一意に識別する異なる属性を追加できるdimensionsという属性があります。私たちは、CloudWatchで公開しているサービスのメトリクスに対して、これらのディメンションを標準で提供しています。Amazon EC2インスタンスにアクセスすると、実行されているAvailability Zoneなど、多くのディメンションが関連付けられています。
このディメンションの概念を使用して、設計とアプリケーション内に存在する障害分離境界と動作境界に準拠する必要があります。それが完了すると、運用上のアラートや調査においてより細かい粒度を実現するための、豊富なメトリクスとディメンションが利用可能になります。これは多くのお客様が苦労している領域の1つです。多くの場合、お客様は本当にフロントエンドでのアプリケーションの可用性だけを測定しています。サードパーティのモニタリングツールからのリクエストがAPIのフロントドアにヒットし、そのAPIを通じてサービスが利用可能かどうかを判断し、すべてが良好に見えます。
実際には、これらのリンクのそれぞれで問題が発生する可能性があり、これらのリンクのいずれかで問題が発生すると、アプリケーションが経験する可能性のある異なるタイプの障害シナリオが定義されます。フロントエンドだけをモニタリングしている場合、問題の存在を検知できるかもしれませんが、運用上のイベントの初期調査が、障害が発生している可能性のあるサービスエコシステムを手動で追跡することに依存してしまいます。スタック内で障害が発生している場所を人間が調査することに依存している場合、観測可能性やモニタリング、アラートが行われていない個々のレベルでの復旧の自動化やレジリエンスは構築できていません。
ミッションクリティカルなことを行っている場合(これがこのセッションのテーマです)、スタック内の依存関係とそれらの依存関係間のリンクの1つ1つが独立してモニタリングされることが期待されます。独立した可用性、動作、健全性を理解することになります。その集約された図では、AWSとAmazonで広く使用しているパターンであるComposite Alarmを作成できます。エンジニアが数十から数百の個別の重要度の高いアラートに常にページングされる可能性があることを期待するのではなく、アプリケーションと依存関係の総合的な健全性を表す個々のメトリクスをすべて取り、アプリケーション全体の健全性を表す単一のアラームを作成します。
レジリエンスのためのリカバリー戦略
実装できるサイクルがほぼ完成しています。構築した計装、Observabilityに含まれるのディメンション、メトリクスの深さ、アプリケーション内部から報告されるビジネス指向のメトリクスとそれに関連するディメンション、アラームやダッシュボード、自動復旧につながるログ、メトリクス、トレースの収集量。これらは調査時の質問につながり、ミッションクリティカルなアプリケーションの場合、影響、復旧時間、収益への影響について経営陣から電話で質問を受けることになるでしょう。
緊急時の対応に追われることなく運用を改善したい場合は、より積極的かつ定期的なペースでそれらの質問に対応できるようにする必要があります。これはAmazonの文化の重要な部分です。ミッションクリティカルなシステムにAWSを使用している場合、AWSの文化の中核に運用の卓越性を据えていることは、もう一つの大きな差別化要因となっています。私たちには週次AWS Ops Metrics Reviewというメカニズムがあります。これは組織として運営している中で群を抜いて高コストな会議の一つですが、個々のチーム内で行われる積極的かつ体系的な対話を確立するため、私たちの文化にとって最も重要なものの一つとなっています。前週のサービススタックの健全性について質問される可能性のある、この週次のOpsとメトリクスレビューの準備の一環として、チームは多くの事前作業を行う必要があります。
チームは過去1週間のサービスの健全性を調査し、質問を引き起こす可能性のある異常や運用上のイベントを探す必要があります。毎週このプロセスを実施し、各チームに準備させることで、誰かがページングを受けることなく改善の機会を発見できる可能性があります。さらに、イベントを経験した他のチームから得られた教訓がある場合、復旧がどのように行われたか、またはアプリケーションやお客様が遭遇したインシデントについて共有することができます。この大規模な会議は、教訓や経験の共有を促進します。
この会議に効果的に参加し、質問に答えるために必要な堅牢なObservabilityを作り出すことで、そのフライホイールが完成し、失敗が適切に処理されなかった痛みを伴うイベントからの回復だけでなく、運用が継続的に改善される機会を得ることができます。Observabilityの部分をまとめると、実現できる調査と復旧の堅牢性は、文字通りObservabilityの質によって制限されます。顧客が経験している痛みやアプリケーションの不健全な状態を具体的に示すレベルまでObservabilityが備わっていない場合、復旧作業はより粗い粒度となり、リスクが高く感じられ、実装コストも潜在的に高くなる可能性があります。
Observabilityの粒度を細かくすればするほど、復旧のために取る行動の種類についてより積極的になれ、障害の発生を防ぐことができます。これらはアプリケーションのクランプルゾーン(衝突安全域)です - Observabilityに粒度を追加すればするほど、それらのクランプルゾーンをより小さくすることができ、より積極的に実装することができます。この粒度はすべて、物理的および論理的に実装したデフォルトの境界に合わせて達成されるべきであり、すべての人がこれらのことを気にかけなければならないと積極的に規定する優れた組織的メカニズムを持つことが、フライホイールを回転させる方法なのです。
組織内でOperational Excellenceを重視しているチームや部門がある場合は、そのチームにCTOやCIOからのトップダウンの支援を得て、このような仕組みを実装するよう働きかけましょう。私たちのMetricsとOperationsミーティングの進め方については、オンラインで多くの資料を公開しています。 では、これまで何を実現してきたのでしょうか?私たちは、分離された設計の冗長性を活用し、障害の発生箇所を特定することに成功してきました。しかし実際には、それでも障害は発生し、私たちは調査方法を熟知していても、回復のための方法を実装していなければ、顧客は依然として不便を感じることになります。
Capital Oneの視点:銀行業界におけるレジリエンスの重要性
これは、ミッションクリティカルなレジリエンスの専門家であるMike Tysonの、この分野に関する私の好きな引用です。これは私たちが顧客と接する中でよく目にする状況です。顧客がレジリエンスというトピックに多大な時間と労力を費やし、多くの機能を実装してきたにもかかわらず、いざ問題が発生すると何も行動が取れないという状況に陥ってしまうのを見るのは残念なことです。どんなに優れた計画を立てていても、戦略として設計した行動を実行する自信がなければ、行動が取れなくなってしまいます。 問題が発生した日によく聞かれる質問は、「大きな赤いボタンを押さなければならないのですか?」とか「復旧までどのくらいかかりますか?押さなくて済むまで待ちたいのですが」といったものです。このような躊躇は、アプリケーションが予期せぬ打撃を受け、顧客が不便を感じている状況に対して、適切な準備ができていないことを示すサインです。
復旧について話しましょう。まず第一に、私たちが作成した異なる分離境界には冗長性という概念があります。Multi-AZデータベースがどのように冗長性を提供するかについては説明しませんが、特にミッションクリティカルなレジリエンスを議論する際に同様に重要なStatic Stabilityという概念について説明します。Static Stabilityとは、障害が発生しても、アプリケーションの運用状態を維持するために新しいアクションを必要としない - アーキテクチャのどこかで障害が発生しても、アプリケーションの継続的な運用が維持される、ということを意味します。 依存関係がアプリケーションの健全性と運命を共にしないということです。その例として、
Auto Scaling groupsがあります。 これは顧客が避けられたはずの問題に遭遇することがよくある例です。Auto Scalingは障害が検出された際に新しいインスタンスを提供する優れた機能ですが、実際には障害が検出されたときにAmazon EC2の新しいインスタンスをデプロイし、Create InstancesのAPIを呼び出しています。これは、アプリケーションの通常の実行時には含まれない新しいアプリケーションの動作の例であり、通常の運用時には使用していない依存関係です。
EC2サービスで問題が発生している場合、Launch Instances APIも同様に問題を抱えている可能性があり、新しいインスタンスを作成するニーズを満たせない可能性があります。Auto Scalingへの依存をLaunch Instances APIを呼び出す回復メカニズムとするのではなく、AWS Lambdaサービスを参考にすることをお勧めします。Lambdaは、 必要と考えられる容量をEC2インスタンスのウォームプールとしてあらかじめプロビジョニングしておくことを重視しています。新しい顧客環境をプロビジョニングするメカニズムは、顧客が新しいLambda関数を作成したり、新しい環境をプロビジョニングする必要が生じたりするたびに、リアルタイムで新しいインスタンスを起動することに依存していません。
Lambda サービスでは、専用の領域で Instance のウォームプールを既に稼働させており、Lambda サービスには独自の Control Plane があって、プロビジョニングされた通りにお客様にキャパシティを割り当てています。EC2 サービスに依存することなくキャパシティを確保できるよう、適切な余裕を見込んで事前にプロビジョニングを行っているのです。これが Static Stability の考え方です。
次は、リトライについてお話しします。リトライは基本中の基本ですが、多くのお客様が実装しているリトライの方法が、障害時にかえって状況を悪化させてしまうことがあります。一時的なネットワークの不具合や接続断のような障害が発生した場合は、リトライを行うべきです。しかし、あまり賢くないリトライを実装してしまうと、サービスの依存関係に障害が発生した際に、リトライストームが発生してしまい、リクエストが2倍になってしまいます。
下流のサービスが一時的なダウンを経験している場合、これらのリトライは指数関数的に増加していきます。すると、その前段のコンポーネントすべてが、リトライを繰り返すことでさらに負荷をかけることになります。既にメモリ負荷やキャパシティの問題で苦しんでいるコンポーネントに対して、指数関数的なリトライストームが押し寄せ、障害がさらに悪化していくという悪循環に陥ります。AWS SDK を見ていただければ分かりますが、私たちがどのようにこれを実装しているかの例があります。
Exponential Backoff は重要な戦略で、リトライの度にバックオフ時間を設けることで、可能な限り早くリトライするのを防ぎます。また、ここには示されていませんが、Jitter の導入も重要です。これにより、すべてのコンポーネントが同じタイミングでリトライするのを避け、少しずつずらしたタイミングでリクエストを送信することができます。Client-side Rate Limiting も良い例です。サービスを構築してお客様にクライアントを提供する際は、お客様のソフトウェアで独自のリトライロジックを実装することを期待するのではなく、クライアント SDK やライブラリの中でその責任を持ち、Client-side Rate Limiting のような機能を実装して自身のサービスを保護すべきです。
では、Capital One の話に移れるよう、ここは手短に進めていきます。これらの情報はすべて YouTube で確認できますので、特定のスライドを見直したい場合はそちらをご覧ください。さて、Recovery の観点で最も重要なのは「回避」という考え方です。なぜなら、これが正常な状態に戻る最も早い方法だからです。障害が発生しているコンポーネントを復旧させるのではなく、問題を抱えているコンポーネントへのトラフィックを単純に回避する方が、はるかにシンプルな対応となります。そしてこれは、ほぼ常に Health Check に依存しています。
今回は2種類のヘルスチェックについて説明します。1つ目は、シャローヘルスチェックです。これは、サービスが起動して稼働しているかどうかを確認するヘルスエンドポイントを持つものです。サービスとアプリケーションが利用可能かどうかを単純にチェックするだけで、アプリケーションの実行時の動作については詳しくチェックしません。もう1つは、ディープヘルスチェックで、依存関係をチェックし、アプリケーション内でより詳細な一連のアクションを実行します。これにより、アプリケーションの全体的な健全性についてより完全な把握が可能になります。
それぞれのシナリオでの動作例を見てみましょう。Elastic Load Balancing (ELB) があり、シャローレベルでヘルスチェックを行っている場合、アプリケーション内で障害が発生しても、その問題に気付くことができません。ヘルスチェックが浅いレベルなので、トラフィックは送り続けられ、依存関係スタック内で発生している障害は見えません。Load Balancerは、メモリモジュールの不具合やインスタンスのエッジケースでリークが発生するなどの問題を抱えているアプリケーションコンポーネントを迂回することができません。
ディープヘルスチェックでは、インスタンス内で障害が発生した場合、それを検出して障害のあるインスタンスを迂回することができます。これはインスタンスレベルで障害が発生した場合はとてもシンプルです。しかし、依存関係に障害が発生した場合はどうでしょうか?依存関係に問題が発生し、すべてのインスタンスが不健全な状態を示している場合、Elastic Load Balancingはすべてのインスタンスに問題があると判断し、Auto Scalingは実際には依存関係に問題があるにもかかわらず、すべてのインスタンスを順次置き換えようとします。これは、Auto Scalingの本来の動作に反して、複合的な障害状況に陥ってしまいます。これを「フリートサイド」と呼びます。Auto Scalingがスタックに対して永続的にフリートサイドを引き起こしている状態です。
混合アプローチを取り、Load Balancerでアプリケーションレベルの詳細な健全性を確認するディープヘルスチェックを行い、Auto Scalingはアプリケーションの動作のみを見るようにすると、グレー障害が発生した場合にそれを検出し、トラフィックを適切に迂回することができます。ただし、特定のインスタンスでインスタンス障害が発生した場合、Auto Scalingグループはただ単にAmazon EC2インスタンスの健全性だけを見ているため、それを検出できなくなります。正しく迂回してフリートサイドを防ぐことはできますが、シャローヘルスチェックでは検出されないアプリケーションレベルの問題が発生した場合に、Auto Scalingによるインスタンスの置き換えに頼ることができなくなります。
それでは、トレードオフについて手短に説明させていただきます。 Auto Scaling groupにディープヘルスチェックを統合すると、フリートサイド(fleet-icide:全インスタンスの一斉終了)が発生するリスクが非常に高くなります。そのため、ディープヘルスチェックを実装する場合は、Auto Scaling groupのルールとは関連付けないようにしましょう。これにより、グレー障害を回避することができますが、Auto Scalingがスタックをスケールする際に依存する、実際のローカルインスタンスの健全性をより包括的に検出するメカニズムを実装する必要があります。シャローヘルスチェックの場合は、Auto Scaling groupとの統合は容易ですが、グレー障害に対処するための追加のメカニズムを構築する必要があります。 どちらが正しいというわけではありませんが、それぞれに長所と短所があり、スタックでの適用方法によって、適切に使用されているかどうかが決まってきます。
ヘルスチェックに慣れ、障害が発生した際のルーティング回避や検出、アーキテクチャの影響を受けている部分を回避する方法について理解を深めたら、 Amazon Route 53 Application Recovery Controllerがその解決策となります。Route 53 ARCを使用すると、リージョンレベルで大きな赤いボタン(いわゆるBig Red Button)の概念を活用できます。このサービスの場合、コントロールプレーンではなく、データプレーンの一部として機能します。私たちにとって、これは静的安定性の一例と見なされます。なぜなら、大きな赤いボタンを押す機能と、すべてのスタックで行われているヘルスチェックは、コントロールプレーンAPIに依存することなく、そのサービスの通常の運用の一部として機能するからです。 これは、ロードバランサーレベルでのゾーンレベルでも同様に適用されます。
運用における既知の正常状態への復帰について、プロダクションデプロイメントを例に挙げてみましょう。これは、AWS Builders Libraryのホワイトペーパーに記載されている、私たちのプロダクションデプロイメントシーケンスの例です。ご覧のシーケンスは、私たちが定義したすべての障害境界(Availability Zone、セル、リージョンなど)に合わせて調整されています。デプロイメントの実行方法や、ベイク時間中のコンポーネントの健全性の理解方法は、すべて私たちが構築した障害境界の方法に沿っています。 デプロイメントとレジリエンスのアプローチは互いに整合性がとれている必要があります。リカバリーの境界は、デプロイメントパイプラインのアクションのセグメントに合わせ、アプリケーションコンポーネントへの変更が、リクエスト処理中に変更されないよう安全に確保するためのデプロイメント中のアクションと同じタイプのアクションを取る必要があります。これは、アプリケーションのコンポーネントをルーティングで回避することと同じように聞こえますね。そのため、同じコード、同じ実行ステップが互いに対称的であることが望ましいのです。
レジリエンスのリカバリーアクションは、デプロイメントと同じように感じられるべきで、互いに対称的である必要があります。これまで説明したリカバリーの側面をまとめると: 静的安定性は、障害シナリオの最中に新しいランタイム依存関係を取り込まないようにするための重要なコンポーネントです。リトライロジックとそれに関連するベストプラクティスについて考慮してください。障害シナリオ中に素早く回復するための最も推奨される方法は、ルーティングによる回避であり、リカバリーアクションをデプロイメントのように感じさせ、その逆も同様です。
では、これらすべてをどのように組み合わせればよいのでしょうか?それは次のような好循環になります。物理的および論理的な障害境界のすべてが、 観測戦略に関連する次元に情報を提供する必要があります。観測可能性と障害境界の両方によって定義される境界が、デプロイメントについての考え方と一致するようにします。このスライドのストーリーをより堅牢で細かく作り込めば作り込むほど、より良い準備が整います。Wernerが常に発生すると言っている障害に対処するための粒度が細かくなり、検出した障害からより迅速に回復できるようになります。
これらのコンポーネントを構築し、できる限り細かく分割した後でテストを行います。そのテストのために、AWS Fault Injection Simulatorというサービスも用意されています。これを使うと、下部にある様々なコンピューティングサービスやデータベース、ネットワーク、ストレージなどに障害を注入し、Observabilityがどのように問題を検知し、実装した復旧アクションがどのように動作するかを確認できます。これらの対策が適切に実装され、通常のデプロイメントの一部として復旧アクションが実行されていれば、次回アプリケーションが予期せぬ問題に直面しても、普段のデプロイメント時と同じ復旧手順を実行するだけで済むため、より安心して対応できるはずです。
Capital Oneの認証プラットフォーム:Zonal Independence Cellアーキテクチャの実装
ここで、Capital Oneの視点からより詳細な話をしていただくために、Moを呼び上げたいと思います。ありがとうございました。私はMoe Banihaniと申します。Capital OneのDistinguished Engineerを務めており、本日のAWS re:Inventで、クラウドにおけるレジリエントなアプリケーション構築に関するCapital Oneの取り組みについてお話できることを大変嬉しく思います。大学時代を振り返ると、18歳で家族から何千マイルも離れた新しい国にいた自分を思い出します。この新しい環境で築こうとしていた新しい関係に、私はワクワクしていました。その時は気づきませんでしたが、人との関係以外で最初に築いた関係の一つが、銀行との関係でした。それは信頼と透明性に基づいた関係で、新しい機会や扉を開き、エキサイティングな冒険への道を切り開いてくれました。ご安心ください - これが最後の関係というわけではありません。現在は結婚して素晴らしい2人の子供がいます。
確かに、それは信頼に基づいた関係でした。私たち全員が銀行との間でそういった経験をしているはずです。私たちはお金が安全に守られていることを信頼し、必要な時にそのお金を引き出せることを信頼しています。そのため、Capital Oneではクラウドでのアプリケーション構築に情熱を注ぐと同時に、顧客体験とそのアプリケーションをお客様がどのように体験するかということにも非常に情熱を注いでいます。優れた技術を構築するだけでなく、お客様がアプリケーションで体験する経験と、私たちが設計するすべてのシステムに対してお客様が寄せてくださる信頼が重要なのです。本日は、金融サービス分野におけるそれらの例と、アプリケーションのレジリエンスをどのように実装しているかについてご説明します。アプリケーションのレジリエンスは、設計、実装、モニタリング、運用のあらゆる面に組み込まれています。銀行は、高負荷な状況下でも、安全なデータ転送と優れた顧客体験のための堅固な基盤を提供することが求められます。Capital Oneでは、私たちのシステムが銀行業界に不可欠な透明性と信頼を提供する手助けをしています。そのため、アプリケーションのレジリエンスに投資する際は、リスク軽減だけでなく、クライアントとの関係を構築し、強化することにも投資しています。
そのため、アプリケーションを設計する際は、一貫性のある提供、正確な応答、そして避けられない障害が発生した際の迅速な対応ができるように設計しています。私たちのレジリエンスアプローチは3つの領域で構成されています。1つ目のアプローチはクロスリージョンフェイルオーバーです。大規模なリージョンや距離を超えて作業を分散させることで、1つのリージョンで障害が発生した場合でも、他のリージョンでサポートできるようにしています。2つ目はAZ分散で、複数のAvailability Zoneにワークロードを分散させることで可用性を高めています。最後に、Cell-based Architectureをレジリエンスモデルに導入し、より重要度の高いプラットフォームにより高度なレジリエンスを提供できるようにしています。これについては後ほど詳しく説明します。
これらのアプローチすべてを機能させるためには、継続的なモニタリングとObservabilityの堅固な基盤を組み込み、問題が発生した際に適切にアラートを発し、チームが迅速に対応できるように通知する必要があります。本日は、Capital OneでCell Architectureを活用している2つのプラットフォームについてお話しします。1つ目は認証プラットフォーム、2つ目はCore Bankingプラットフォームです。最初のシナリオである認証側では、250ミリ秒という厳密な実行要件があります。このタイムウィンドウ内でエンドツーエンドの実行チェーンを完了する必要があります。また、そのワークフロー内でより小さなトランザクションを完了するか、機能が低下したモードにフォールバックする必要があります。Core Banking側では、顧客やパートナーにまたがる複数のアプリケーションがこのコアプラットフォームに対してトランザクションを実行しており、それぞれに異なる要求とスケーラビリティ要件があり、それらすべてをサポートし対応する必要があります。
認証プラットフォームは銀行にとって極めて重要なプラットフォームです。なぜなら、お客様から日々送られてくる何百万もの取引を検証し、その妥当性を確認するからです。こうした重要なイベントはすべて、お客様に最高の体験を提供するために250ミリ秒以内に処理しなければなりません。お客様の立場になって考えてみてください。クレジットカードをスワイプして、食料品の支払いやホテルのチェックインをしようとしているときに、ずっと待たされる状況を想像してみてください。もし取引が失敗したら、どんな気持ちになるでしょうか?お客様はどう感じるでしょうか?そのため、私たちは認証プラットフォームを設計する際に、スピードを最重要視しました。
認証プラットフォームでは、Zonal Independence Cell アーキテクチャを採用しました。これにより、1つのAvailability Zone、つまりこの場合は1つのCellの中で完全な取引を実行することができます。ここでの要件は、低レイテンシーで厳密な取引の実行であり、そのワークフロー中に独立したサービスによって処理される場合でも、データは取引全体を通じて一貫性を保つ必要があります。Capital Oneが開発したアルゴリズムを使用してレプリカCellに負荷を分散できるように、Traffic Routerを実装しました。取引がCellに入ると、その取引全体がそのCell内で完結します。一般的なCellアーキテクチャとは異なると思われるかもしれませんが、私たちが必要とする低レイテンシーを実現するために非常に効果的なCellアーキテクチャとなっています。さらに、Availability Zone間やリージョン間のデータ転送に関するコストも削減できています。
この設計の主要な特徴として、AZ Affinityが重要な属性であり、低レイテンシーが主要な推進要因となっています。Cell内でのAuto Scalingを実現し、完全に運用化する前にCanaryを実施し、さらに先ほど述べたように、機能が低下したモードへのフォールバックも可能です。
Cell レベルでのリトライは避けています。低レイテンシーの面では、Cell間でトラフィックを均等に分散させるためのデータシャーディング戦略を実装する必要がありました。Cellレベルでは強い整合性を維持しつつ、リージョン間では結果整合性を維持したいと考えていました。
この設計の特徴とそこから得られる利点をいくつか見ていきましょう。 まず1つ目は、Deep Health Checkです。Cellの健全性を取引レベルまで正確に把握するために、Deep Health Checkを実装しました。そのCellに定期的にトラフィックを送信し、そのCellに影響を与える可能性のあるすべての障害や問題についてアラートを出すことで、Cellの健全性を理解し、問題が発生した際にトラフィックを切り替えることができます。これは重要なプラットフォームなので、お客様が障害に気付くのを待つわけにはいきません。Cellで何が起きているのかを予測し理解するモデルを構築し、SLOに違反する前にトラフィックを影響を受けているCellから移動できるようにする必要があります。また、Amazon CloudWatchやその他のObservabilityツールを活用して、CellからのすべてのアラートをResilience Engineに集約し、各Cellでの取引とCellのパフォーマンスを適切に把握できるようにしています。
Resilience Engineは、すべてのセルの健全性をトランザクションレベルまでモニタリングする、私たちのアクティブモニタリングツールです。素晴らしいのは、CloudWatchや他のObservabilityツール、さらには私たちのアプリケーションやそこで発生するトランザクションからも、これらすべてのアラートを収集できることです。このデータをAmazon DynamoDBに集約し、各トランザクションに評価を割り当てています。セルの健全性だけでなく、そのセル内で発生する各トランザクションの健全性も把握できるのです。SLOの変動や違反の兆候が見られ始めると、Resilience Engineは、あるセルから別のセルへトラフィックをシフトする判断を下します。同時に、下流のチームに問題を通知し、迅速な対応と解決を可能にします。また、Resilience Engineは、その環境に十分なセルと容量があるかを判断するキャパシティ管理の計算も実行し、必要に応じて追加のセルをデプロイするためのアクションを起こすことができます。
このモデルで考慮する別の点は、別のAvailability Zoneへのフェイルオーバーです。先ほど述べたように、Resilience Engineは、セルレベルからトランザクションレベルまで、何が起きているかを測定する重要な役割を果たします。問題を検知すると、そのセルからトラフィックのシフトを開始します。これらはすべて自動化されています。セルとトランザクションの健全性を理解できるだけでなく、Resilience Engineは問題が発生する可能性を予測し、顧客が気付く前に予防的にトラフィックをシフトし始めることができます。
最後に、共有したい興味深い側面があります。それは、デグレードモードへのフォールバックです。この例では、250ミリ秒という厳密な実行要件があります。この時間内に複数のワークフローを実行し、それらのワークフロー内で、最終的な判断が完了する前に、複数の小さな判断を出せる仕組みを作りました。最初の判断は暫定的な判断として出され、次のワークフローに渡され、2番目のワークフローが更新された判断を出す、というように、250ミリ秒以内で完全な実行が完了するまで続きます。このアーキテクチャの利点は、SLOを違反した場合でも、常に顧客に返答を送れることです。なぜなら、250ミリ秒以内で処理を完了することでのみ実現できる最適な顧客体験を目指しているからです。これは、ビジネスアーキテクチャとアプリケーションアーキテクチャが協調して、高性能なシステムを設計する素晴らしい例です。
Capital OneのCore Banking Platform:リージョナルセルベースアプローチの採用
Core Banking Platformについて見ていきましょう。Core Banking Platformとは何かと思われるかもしれません。 Core Banking Platformは、基本的に銀行との取引や関係性すべてを処理するシステムです。これには、預金や引き出しを行うリテール銀行の顧客や、異なる設定や支払い方法を持つ複数のクレジットカードを所有するカード顧客が含まれます。銀行との取引には無限の可能性があり、Core Bankingシステムが処理しなければならないトランザクションの種類も無数にあります。銀行には適切な記録管理と優れた結果が求められるため、Core Bankingシステムは堅牢で正確でなければなりません。このプラットフォームは非常に大規模で複雑な性質を持っています。
この例では、 リージョナルセルベースのアプローチという、異なるセルアーキテクチャアプローチを選択しました。このモデルでは、スピードが重要な要素だった認証プラットフォームとは異なり、様々なチャネルから発生する異なるトランザクションを持つ大規模プラットフォームにおいて、テナントのセグメンテーションが重要な要素となります。これらのテナントには、スケーラビリティ、レジリエンス、トランザクションのレスポンス速度など、様々な要件とニーズがあります。私たちはこれらすべての顧客とテナントに対応しなければなりません。このモデルでは、テナントをセル間で分割し、より優れたレジリエンスと分離を提供できます。テナント固有のニーズに対応できるので、高いトランザクション量を期待するテナントがいれば、そのテナントを別のセルに分離し、プラットフォーム全体とは異なる方法で処理とスケーリングを行うことができます。同時に、他のテナントに影響を与える可能性のあるノイジーネイバーからも分離することができます。
主要な特徴について見ていきましょう。 私たちの設計における重要な要素は、Tenant isolationとダイナミックなテナンシーのサポートでした。Tenant isolationの観点では、障害の影響範囲を排除、もしくは少なくとも制御したいと考えていました。Tenant routerを使用してテナントをCellに分散させ、トラフィックを分散できるようにしました。また、AWS IAMを活用してデータを保護し、共有テナントを使用する際のデータ分離を実現しました。さらに、リージョン間でのデータの結果整合性も重要な要素でした。テナンシーのサポートについては、Siloed方式とPooled方式の両方を実装し、複数のテナントモデルをサポートすることを目指しました。特にトランザクション率の高い特定の顧客がいる場合、Noisy neighbor effectを最小限に抑え、これらのモデル間でテナントを移行できる柔軟性も確保したいと考えていました。
この設計の重要なメリットについて説明しましょう。 Core bankingの観点では、Fault isolationが最大の利点でした。これにより、大規模なプラットフォームの運用を2つの方法で柔軟に制御できるようになりました。1つ目は、デプロイメントの失敗を分離できることで、チームに運用の柔軟性が生まれました。ソフトウェアやリリースを実験的に行う際も、特定の顧客やグループに影響が限定され、テナント間での波及効果を心配する必要がないという自信を持って作業できます。2つ目は、複数のリージョンとAvailability Zoneで運用しているため、Cell単位でシステム障害を分離できることです。そのレベルで障害が発生した場合、複数のAZ間でトラフィックを切り替えることができ、AZ内での問題や障害がその境界内の1つの顧客に限定されるようになっています。
もう1つの重要なメリットは、Cellを独立して運用できることです。これは、アプリケーションや大規模プラットフォームのFailoverを考える際に特に興味深い点です。従来は、1つのリージョンで障害が発生した場合、アプリケーション全体を別のリージョンにシフトするという単一のエンティティとして考えていました。これには相当な運用能力と細心の注意が必要でした。このプラットフォームの規模と複雑さを考慮し、私たちはこれらのCellを独立して運用できるようにしました。
例えば、あるリージョンで障害が発生したり、1つのCell内で問題が発生したりした場合、プラットフォーム全体に影響を与えることなく、そのCellだけを独立して運用し、トラフィックを別のリージョンに移動することができます。これには2つの利点があります。1つは障害の影響範囲を1つのリージョンに限定できること、もう1つは問題に迅速に対応しようとしているチーム内での混乱を防げることです。このアプローチにより、複数のCellや大規模なプラットフォーム障害に対処する際に、さらなる複雑さを生み出すことを防いでいます。
Retry stormの防止については、 2つのAWSの機能を活用しています。1つ目は、Cell内でのFailoverを支援するAWS Route 53とApplication Load Balancerです。これらは、アプリケーションを適切にFailoverさせるためのネイティブな機能を提供しています。2つ目は、Exponential backoffとRetryを組み合わせることで、アプリケーション内でRetry stormが発生しないようにしています。このような問題が発生しても、そのCell内に限定され、より大きなプラットフォーム全体への波及を防ぐことができます。Cell architectureとこれらの機能を組み合わせることで、1か所に障害の影響範囲を抑えることができています。
まとめに入りましょう。レジリエンスへの取り組みは、すべてに当てはまる画一的なアプローチではありません。新しいアプリケーションの特性や要件、そしてビジネス上の要因を十分に理解し、最適なレジリエンスモデルを選択するための投資が必要です。Deep Health Checkは強い味方です - これを活用することで、アプリケーションとワークフローの詳細な動作を理解し、それに応じて対応時間と対応方法を調整することができます。継続的な改善は終わりのない旅路です。アプリケーションを詳しく調べ、アーキテクチャを深く理解し、アプリケーションの動作を把握することは、皆さんの責任です。そうすることで、全体的なレジリエンスの姿勢につながるコンポーネントレベルの障害モードを設計することができます。
最後に、多くの人が見落としがちですが、設計を始める前にビジネスワークフローを理解することが極めて重要です。先ほど述べたように、私たちは複数の判断のために複数ホップの呼び出しパスを持つAuthorization Platformを設計しました。これは、ビジネス設計とアプリケーション設計が融合して、より強靭で高スループットなプラットフォームを生み出した例です。本日は時間を割いていただき、ありがとうございました。Andyにも、そして皆様にも感謝申し上げます。特にセッションを楽しんでいただけた方は、イベントについてのアンケートへのご記入をお願いいたします。カンファレンスの最後まで参加していただいた皆様に、重ねて御礼申し上げます。
※ こちらの記事は Amazon Bedrock を利用することで全て自動で作成しています。
※ 生成AI記事によるインターネット汚染の懸念を踏まえ、本記事ではセッション動画を情報量をほぼ変化させずに文字と画像に変換することで、できるだけオリジナルコンテンツそのものの価値を維持しつつ、多言語でのAccessibilityやGooglabilityを高められればと考えています。
Discussion