📖

re:Invent 2024: Netflixが語るクラウドでの突発的負荷対策

に公開

はじめに

海外の様々な講演を日本語記事に書き起こすことで、隠れた良質な情報をもっと身近なものに。そんなコンセプトで進める本企画で今回取り上げるプレゼンテーションはこちら!

📖 AWS re:Invent 2024 - How Netflix handles sudden load spikes in the cloud (NFX301)

この動画では、Netflixが全世界6億人以上の視聴者に対してクラウドでの突発的な負荷スパイクにどう対処しているかを解説しています。Prioritized load sheddingやPredictive automated scaling、Fast reactive auto scalingなど、AWSの機能を活用した具体的な対策を紹介しています。特に、Success bufferとFailure bufferという2つの概念を用いて、トラフィック増加時にどのリクエストを優先的に処理し、どのリクエストを破棄するかを判断する仕組みが詳しく説明されています。また、Region Scale TestやRegional Load Testingなど、本番環境での大規模なテスト手法についても言及されており、Netflixのクラウドインフラストラクチャの全容が明らかにされています。
https://www.youtube.com/watch?v=TkFyZyxFRBM
※ 動画から自動生成した記事になります。誤字脱字や誤った内容が記載される可能性がありますので、正確な情報は動画本編をご覧ください。
※ 画像をクリックすると、動画中の該当シーンに遷移します。

re:Invent 2024関連の書き起こし記事については、こちらのSpreadsheet に情報をまとめています。合わせてご確認ください!

本編

Netflixの負荷スパイク対策:本セッションの概要

Thumbnail 0

これは NFX301: Netflixが全世界6億人以上の視聴者と、拡大し続けるコンテンツカタログ、そしてライブイベントに対して、クラウドでの突発的な負荷スパイクにどのように対処しているかについてです。例えば、11月中旬のMike Tyson対Jake Paulの試合は、最大同時視聴者数が6,500万人を記録し、史上最も多くストリーミングされたスポーツイベントとなりました。また、BeyoncéがハーフタイムショーをつとめるクリスマスのNFLの試合も控えています。

Thumbnail 40

これだけの活動が行われている中で、トラフィックスパイクの管理はNetflixにとって極めて重要な活動です。その成否は、あなたや私の視聴体験を左右することになります。私個人としても、韓国ドラマを見ている時に中断されたくはありません。本日は、Netflixの優秀なエンジニア3名から、これらの負荷スパイクを管理するためのAWS上のアーキテクチャソリューションについてお話しいただきます。Prioritized load shedding(優先順位付き負荷制御)、Predictive automated scaling(予測的自動スケーリング)、Fast reactive auto scaling(高速反応型自動スケーリング)といった用語が出てきますが、ご心配なく - セッションの最後には、これらの意味とNetflixがAWS上でこれらの戦略をどのように大規模に実装しているかを理解していただけるはずです。

これはNetflixとAWSのパートナーシップを示すショーケースです。NetflixはAuto Scaling、CloudWatch、高解像度メトリクス、オンデマンドコンピュートなど、AWSが提供する機能を活用して、これらの戦略とメカニズムを実現しています。私はManju Prasadで、AWSのSolutions Architectです。本日のプレゼンテーションには、NetflixのJoseph Lynch、Ryan Schroeder、Rob Gulewichが登壇します。彼らはエンジニアであり、ビルダーであり、イノベーターであり、AWSで可能なことの限界に挑戦し続けています。

Netflixのクラウドアーキテクチャと負荷パターン

Thumbnail 140

Thumbnail 150

皆様、おはようございます。今朝は、Netflixがクラウドでの突発的な負荷スパイクにどのように対処しているかについてお話しします。 今朝のセッションの概要を簡単にご説明させていただきます。まず、私たちが解決しようとしている問題の概要とコンテキストから始めます。次に、この問題に対するさまざまなソリューションについて詳しく掘り下げていきます。最後に、これらすべてが実際に機能することをどのように証明し、意図したとおりに動作することをどのように確認しているのか、そして主なポイントについてまとめます。

Thumbnail 170

ご存知かもしれませんが、Netflixは長年クラウドを利用してきました。私たちは比較的早い段階でクラウドを採用し、すべての動画アセットを保存するグローバルなContent Delivery Networkを持っています。しかし、私たちの主要なクラウドフットプリントは、これら4つのリージョンにあります。私たちのアーキテクチャの最も興味深くユニークな側面の1つは、Active-Activeアーキテクチャを採用していることです。これは実際には、これらのリージョンのどれでも書き込みを受け付け、その書き込みが他のリージョンに伝播されることを意味します。このActive-Activeアーキテクチャを採用している主な理由は、リージョンフェイルオーバーをサポートするためです。これは基本的に、これらのリージョンのいずれかからトラフィックを排出し、そのトラフィックを他のリージョンにリダイレクトできるツールです。ユーザーにとっては、ほぼシームレスな体験となります。

Thumbnail 230

歴史的に見ると、Netflixのトラフィックパターンは非常に緩やかな変化を示してきました。このグラフは、Starts Per Second(SPS)と呼ばれる重要なメトリクスを示しています。これは文字通り、ユーザーがNetflixアプリで再生ボタンを押す回数を表しています。このメトリクスは、各リージョンの全体的な負荷を示す優れた指標となっています。グラフを見ると、各リージョンでは早朝にNetflixの視聴者が少ない時間帯から始まり、1日を通じて徐々にトラフィックが増加し、夜間にピークを迎えることがわかります。このパターンは24時間ごとに繰り返され、最低時と最高時では10倍もの差があります。各リージョンのピークは、その地域の人々が夕食後にNetflixにログインしてコンテンツを視聴する時間帯に応じて、それぞれ異なる時間に訪れます。

Thumbnail 300

通常はこのように緩やかなトラフィックの増加が見られますが、グラフの点線で囲まれた部分に例外があることにお気づきでしょう。これは先ほど言及したFailover機能を示しています。

このグラフでは、us-east-1からすべてのトラフィックを排出し、代わりに他のリージョンがそのトラフィックを吸収するFailoverを実施しました。私たちがリージョンFailoverを使用する主な理由は、問題のあるソフトウェアのプッシュを行った際にトラフィックを迂回させる場合や、リージョンに障害が発生した場合、そして最も一般的なのは、練習のためです。これは私たちのレジリエンス戦略の核となる部分であり、毎週練習を重ねなければ、必要な時に確実に機能するという保証はできません。このグラフの線は、実際には通常業務の一環として、意図した通りに機能しているかを確認するために実施した計画的なFailoverを示しています。

Thumbnail 360

興味深いのは、これが自己誘発的な負荷スパイクを引き起こし、潜在的に自分たちに悪影響を及ぼす可能性があるという点です。あるリージョンからトラフィックを退避させる必要がある場合、1〜2分以内にSaviorリージョンへトラフィックを再ルーティングするという迅速な対応が求められます。リージョンへのトラフィックを復元する際は、より段階的に行いますが、それでもかなり速いペースで実施します。これは、むしろより大きな負荷スパイクと言えます。なぜなら、これらのリージョンのサービスは、トラフィックがまったく流れていない状態(1秒あたりのリクエスト数がゼロ)から、通常のレベルまで急速に引き上げる必要があるからです。

Thumbnail 400

予期せぬトラフィックスパイクには他の要因もあります。新作タイトルのリリース時には長時間のサージが発生します。例えば、「Stranger Things」の新シーズンが深夜0時に公開されると、人々は夜更かしをして、数時間にわたって一斉に再生ボタンを押そうとします。また、ワールドカップの試合が終了して皆がNetflixにログインしたり、他のストリーミングサイトがダウンしてユーザーがNetflixに切り替えたりするような外部イベントによっても、大きなトラフィックサージが発生します。さらに、より短時間のトラフィックスパイクも経験します。内部サービスの1つが負荷に耐えきれなくなり、Autoscalingが行われている間にも呼び出し元が再試行を繰り返すことで、Retry Stormに発展することがあります。また、様々なクライアントやデバイスのバグにより、Cloudに対する小さな負荷スパイクという形で問題が発生することもあります。

複雑なマイクロサービス環境での負荷管理

Thumbnail 470

さらに複雑なことに、Netflixでは非常に複雑な呼び出しグラフを持つ何千ものMicroservicesを実行しています。これは、Netflixのフロントエンドで2倍のLoad spikeが発生した場合、サービスがCall graph上のどの位置にあるかによって、まったく異なる動作をする可能性があるということです。あるサービスは2倍のLoad spikeを受けるかもしれませんし、別のサービスは全てのリクエストで呼び出されるわけではないため、より小さなSpikeで済むかもしれません。さらに別のサービスでは増幅効果により4倍のLoad spikeを受ける可能性もあります。

Thumbnail 500

では、このような複雑なグラフの中で、個々のコンポーネントが実際にレジリエントであることをどのように判断すればよいのでしょうか?社内では「Buffer」という概念を使用しています。これは個々のサービスがLoad spikeを処理する能力のことで、Success bufferとFailure bufferの2種類に分かれます。Success bufferは、クライアントが劣化に気付かないようにしながら、Load spikeを処理する能力です。一方、Failure bufferはシステムの健全性を維持するための異なる目的を持っています。Failure buffer状態では、サービスはThrottlingを行い、エラーを返し、稼働を維持するためにできることを何でも行います。このFailure bufferを超えると、サービスは「Congestive failure」と呼ばれる状態に陥る可能性があり、Throttlingやリクエストの拒否に忙しすぎて前に進めなくなってしまいます。画面に表示されているこの図では、このサービスは2倍のSuccess bufferを持っており、クライアントに何も気付かれることなく、通常の2倍のトラフィックを正常に処理できることを示しています。さらにその上に4倍のFailure bufferがあり、クライアントにエラーを返しながらも追加で4倍のSpikeを処理できます。これはシステムの健全性を維持するという目的において許容される動作です。

Thumbnail 600

そして、なぜ今なのかという疑問があると思います。最大の理由は、私たちのビジネスが変化しているからです。

私たちは異なるタイプのストリーミングや新しいビジネス領域に進出しています。特に、より大規模なタイトルのローンチが増えており、それらはますますグローバル化しています。次のシーズンの「Squid Game」のようなタイトルを考えてみてください。これは世界中で広く支持されているタイトルで、もはや特定の国に集中しているわけではありません。

この取り組みにおける私たちの目標は明確です。Load spikeからの復旧時間を短縮し、できるだけ早くそのトラフィックを処理できるようにしたいと考えています。問題から抜け出すための主要な緊急対策として、Region failoverの使用を減らしたいと考えています。また、このようなLoad spikeは常に発生するものと想定し、私たちのサービスがいつでもそれに対してレジリエントであるように、レジリエンスの姿勢を変えていきたいと考えています。

Ryanによる負荷スパイク対策の改善策

Thumbnail 660

Thumbnail 670

まず最初のソリューションとして、ソリューション領域に移行する方法が最も簡単で、シンプルかつ効果的です。 負荷スパイクが予想される場合、特にその負荷がどの程度になるか予測できる場合は、事前にスケールアップしておくべきです。例えば、Stranger Thingsが午後7時に公開される場合、予想される負荷に対応するため、午後6時頃からフリートのスケールアップを開始することが考えられます。これは、トラフィックを事前に予測して準備を整えるという考え方です。Autoscalingは素晴らしい機能ですが - この後Ryanが詳しく説明しますが - 数千のサービスを一度に事前にスケールアップするようには設計されていません。どちらかというと、反応的な対応策なのです。

Thumbnail 710

そのため、私たちはFailoverツールを使用してフリート全体をスケールアップします。これは、先ほど説明したStarts Per Second(SPS)メトリクスを通じて機能します。基本的に、Netflix内部の各サービスについて、1 SPSあたりのRPSのマッピングを持っています。1 SPSが、あるサービスでは4 RPS、別のサービスでは10 RPSになるといった具合です。SPSがどれだけ増加するかわかれば、それに応じて全てのサービスを適切にスケールアップできます。SPSが2倍になれば、各クラスターを適切にスケールアップできるわけです。グラフを見ると、まさにこれが行われているのがわかります - イベントの前に、Auto Groupの最小値を予想される負荷に合わせて増やしています。負荷スパイクが発生し、このトラフィックの急増を乗り切った後、ツールが最小値を元のレベルに戻し、Autoscalingが徐々にサービスをスケールダウンしていきます。

Thumbnail 780

プロアクティブなツールキットの最後の手段は、トラフィックシェーピングです。グローバルな規模でタイトルを公開する一方で、特定の地域に限定されるタイトルもあります - 例えば韓国や北米で特に人気が出そうなものなどです。このような状況では、ユーザーが最寄りのリージョンにのみアクセスするのではなく、4つのリージョン全体で均一にトラフィックを分散させたいと考えています。1、2のリージョンを大幅にスケールアップするのではなく、全てのリージョンを少しずつスケールアップすることで、リスクを軽減し、各リージョンでAutoscalingが意図通りに機能するようになり、効果的に「卵を複数のバスケットに分散させる」ことができます。

Thumbnail 860

Thumbnail 870

ありがとう、Rob。 今聞いたように、突然の負荷スパイクから保護する最善の方法は、それを予測してあらかじめスケールアップしておくことです。しかし、未来を予測することはまだまだ難しいので、突然の負荷増加に対してより適切に反応できるよう、スケーリングポリシーをどのように調整したかについてお話しします。 これはRobが先ほど示したグラフを拡大したもので、時間経過に伴うトラフィックとキャパシティの推移を示しています。ご覧の通り、通常はトラフィックがなだらかに増加し、それに合わせてキャパシティが上がり、需要が減少すると同様にキャパシティも下がっています。これは何年も続いており、このトラフィックパターンに非常に適した、高度に調整されたAutoscalingポリシーを持っています。では、突然の負荷スパイクに対してどのように機能するか見てみましょう。特に注目していただきたいのは、1秒あたりのリクエスト数と、必要なインスタンス数を示す赤い線です。

Thumbnail 910

Thumbnail 920

Thumbnail 930

Thumbnail 940

負荷の開始時には、 1秒あたり60,000リクエストの増加がありました。しかし、Autoscalingは非常に緩やかなプロセスのままでした。データを調査したところ、 この負荷増加を検出するまでに約4分かかっていたことがわかりました。インスタンスがプロビジョニングされた後、起動に時間がかかり、これに約6分を要しました。 最初のステップで十分なスケールアップができなかったため、まだスケール不足であることを検出し、このプロセスを再度繰り返す必要がありました。 これらを全て合わせると、この大規模な負荷スパイクからの回復に約20分かかったことになります。

Thumbnail 950

Thumbnail 980

私たちはもっと改善できると考え、Time to Recoveryの各コンポーネントを分解しました。まず検知(Detection)から始まります - いつ負荷が到達し、そこからいつスケーリングを開始するのか。次にAWSが提供するプロビジョニングつまりコントロールプレーンがあります。最後にシステムの起動、アプリケーションの起動、そしてトラフィックの処理があります。これらのインスタンスがトラフィックを処理できるようになって初めて、リカバリーが達成されます。私たちは負荷スパイクをシミュレートするための負荷を人工的に生成できる実験環境を開発しました。既存のポリシーと新しいポリシーの比較のため、ベースラインと実験の比較を行い、複数のポリシー設定を同時に試行して、どれが最も効果的かを判断しました。

Thumbnail 1020

データを分析すると、興味深い発見がありました。遅いスケーリング応答の原因は、単一の要因ではありませんでした。検知時間を筆頭に、アプリケーションの起動時間、システムの起動時間、そしてプロビジョニングという複数の要因が関係していました。プロビジョニングは非常に早く完了していました。そこで、まず検知フェーズに焦点を当てることにしました。あの負荷スパイクの際、スケーリングを開始するまでに4分かかっていたことを思い出してください。

Thumbnail 1030

最初に行ったのは、RPSに基づくスケーリングの開始でした。これまでCPUのTarget Trackingを使用してきましたが、これは長年にわたる緩やかな変化には効果的でしたが、大規模なスパイクに対しては十分な情報を提供できませんでした。通常、CPUのTarget Trackingポリシーでは50%程度の使用率で運用していますので、トラフィックが2倍になるとCPUは100%になります。しかし、トラフィックが10倍になった場合もCPUは100%になってしまい、どれだけのスケーリングアクションが必要かという情報が失われてしまいます。システムを保護しようとしてLoad Sheddingを行う際、リクエストの拒否はCPU負荷が低いため、CPUは必要な処理量を正確に反映しなくなっていました。

Thumbnail 1080

次に、RPS Hammerポリシーと呼ばれるStep Scalingポリシーを導入しました。このアイデアは、予想以上のRPSの増加を検知したら、その需要に見合う容量を一度で追加することでした。2倍のスケーリングだけでは、既存のTarget Trackingポリシーと大差ありません。可能な限り最大限にスケールすることもできましたが、それは無駄が多く、過剰なスケーリングになってしまいます。必要な容量を正確に得るために、適切なステップとステップサイズのバランスを見つける必要がありました。

Thumbnail 1130

Thumbnail 1140

Thumbnail 1150

Thumbnail 1170

また、より高解像度のメトリクスも導入しました。EC2上で実行しているため、CPUなどの標準的なメトリクスはCloudWatchに送信されますが、基本的なモニタリングではデフォルトで5分間隔の解像度でした。まず最初にフリート全体でDetailed Monitoringを有効にし、これによってメトリクスが1分間隔の解像度になり、AutoscalingのCPUポリシーも改善されました。RPSなどのカスタムメトリクスについては、内部のAtlasテレメトリーシステムを経由してからCloudWatchに送信されます。これまでは1分間隔の解像度でしたが、CloudWatchの高解像度メトリクスのサポートにより、アーキテクチャを強化する機会がありました。現在では5秒間隔の解像度でメトリクスをCloudWatchに送信しています。より高解像度でデータポイントを増やすことで、トラフィックの増加をより早く検知できるようになりました。これらのスケーリングポリシーの修正により、検知時間を3分の1に短縮することができました。

Thumbnail 1190

Thumbnail 1200

そこから、私たちはアプリケーションの起動に焦点を当てることにしました。 データを分析したところ、アプリケーションの起動時間に非常に長いテールがあることがわかりました。将来を予測する場合は、すべてをスケールアップしたいところですが、 事後対応の場合は最も遅いシステムの速度が全体の足かせとなってしまいます。負荷スパイクに対応する場合も、最も遅いシステムの速度以上には対応できません。

Thumbnail 1220

データを見ると、大半のシステムは1分以内に起動できていましたが、この長いテールが問題でした。このテールを解消することで、全体的なパフォーマンスが向上しました。最後に、システムの起動を見直し、すべてのサービスが使用している基本OSを活用しました。 そこでの小さな改善でも、広範囲に効果が及ぶことができます。

Thumbnail 1230

Thumbnail 1260

年月の経過とともにデータを見直すと、 起動ステップが順次的に積み重なっていることがわかりました。最初にこれを起動し、次にあれを起動し、その後アプリケーションを起動する、という具合です。これらの起動チェーンを分析することで、複数の処理を同時に開始し、後で合流させる並列パスを作成することができました。これらの修正により、さらに2倍のパフォーマンス向上を達成しました。これらすべてを組み合わせた結果、負荷が2倍以上に増加し、新しい機能強化を施したAutoScalingが起動して、 迅速な単一ステップでの起動が可能になりました。

Joeyが語る優先順位付き負荷制御の仕組み

Thumbnail 1270

Thumbnail 1280

検知時間はわずか1分だったため、負荷スパイクに素早く対応できました。 最適化により起動時間は2分に短縮され、最初のステップを適切に行ったことで、それ以上のスケーリングは不要でした。これにより、全体的な復旧時間を 3分まで短縮することができました。ただし、スケールアップが完了するまでの間は、依然として負荷スパイクの状態が続きます。

Thumbnail 1310

ここで、その数分間の可用性維持についてお話しするJoeyにバトンタッチしたいと思います。本日は、将来を予測する方法と、予測できなかった場合の迅速な回復方法について多くのお話を伺いました。これから私は、その短い 3分間の間、できるだけ多くのリクエストを正常に処理し続けるためにどのような取り組みを行っているかについてお話ししたいと思います。

Thumbnail 1320

最初のステップは、サービスのCriticalityについての共通認識を確立することでした。少し変に聞こえるかもしれませんが、実際にとても重要なことでした。というのも、Netflixではかつて、Tier 1とTier 2の違いについて何ヶ月も議論を重ねていましたが、それは時間の無駄でした。その代わりに重要だったのは、Criticalityに関する一連のタグを設定し、サービスにタグ付けを始めることでした。この話に関連する最も重要なタグは、サービスのCriticality(またはAvailability Tier)、Business Domain、そしてLifecycleです。私たちは限られたコンピューティングリソースを、Netflixの最重要サービスに確実に割り当てたいと考えています。例えば、SVODのロードスパイクが発生した場合、Studioのサービスではなく、SVODを提供するサービスにコンピュータリソースを確実に割り当てたいのです。

Thumbnail 1380

Thumbnail 1410

これを理解した上で、次に異なるTierごとの影響について理解を深めました。これをより良く理解するために、これらの異なるTierに対してBufferをどのように設定するかについて説明したいと思います。Success Bufferに入る際、渋滞していない高速道路のようなイメージを持つことができます。高速道路には車が何台か走っていて、みんな目的地に向かって順調に進んでいます。すべてが順調です。Success Bufferに入り始めると、高速道路は徐々に混み始めます。少し遅くなってきているのが分かりますが、まだすべての車を高速道路に通し、すべてのリクエストに正常に応答できています。

Thumbnail 1420

Thumbnail 1440

Failure Bufferに入ると、ここで交通整理を始める必要があります。「申し訳ありませんが、高速道路は満員です。今入れば状況は悪化するだけです」と言わなければなりません。これは先ほどのCongestive Failureの考え方に戻ります。Congestive Failureとは何でしょうか?それは、永遠に続く渋滞に巻き込まれて、なぜこの高速道路に乗ったのかと考え込んでしまうような状態です。私たちはこれを避けたいと考えています。なぜならこのFailureモードからの回復は非常に困難で、Congestive Failureの状態では多くのAuto-scaleメソッドが正しく機能しないからです。

Thumbnail 1470

Microserviceではどのような状態になるのでしょうか?追加の負荷をかけていくと、Ryanが話していたCPU使用率100%に到達します。Congestive Failureに入ったことを示す信号は、成功リクエストの急激な低下です。これは非常に深刻な問題です。なぜなら、支払っている容量さえも使いきれていないことを意味するからです。成功リクエストが2-3%しかない状態から回復するには、桁違いに多くの容量が必要になります。

Thumbnail 1500

これを防ぐために、私たちはSuccess Bufferにおける単純な優先度なしのCPU Load Sheddingをさらに進化させました。Success Bufferに優先度付きCPU Sheddingを追加しました。これが重要な洞察です。

このインサイトは、もし別の信号があったらどうなるか、つまり問題が発生する前に、重要でないトラフィックに対して「高速道路から離れてください」と伝えることができたらどうなるかということについてです。私がよく使う例えで言うと、救急車は高速道路を走らせたいけれど、週末のドライブで自分のスポーツカーを運転している人には帰ってもらいたい、というようなものです。この件については興味深いブログ記事を書いており、ご興味があればぜひご覧ください。異なる種類の再生ワークロードを優先順位付けする方法について、詳しく説明しています。

Thumbnail 1540

最初のステップとして、開発者がバッファを設定します。Netflixでは、ターゲット使用率と最大使用率を宣言することでこれを行います。 例えば、CPUのターゲットを40%、最大を90%に設定した場合、Success Bufferは40%の使用率から始まり、Failure Bufferは90%から始まります。注目すべき点として、Success Bufferは実際にそのスタートアップ時間に比例します。先ほど話した長いテール起動時間のサービスは、より低い負荷で実行する必要があります。なぜなら、負荷スパイクに対して反応的な対策に頼ることができないため、代わりに事前に冷却する必要があるからです。

Thumbnail 1580

開発者たちはさらに、単一アプリケーション上でリクエストをセグメント化する機能を求めていました。先ほど、Tier ZeroサービスやTier Oneサービスについて話しましたが、これは大まかな区分けとしては良いものです。しかし、Tier Zeroサービス内でも、あるAPIコールは他のものより重要である場合があります。このグラフは、Progressive Load Sheddingと呼ばれるものを示しており、X軸にシステムの負荷状態、Y軸にそのPriority Bucketのトラフィックが破棄される割合を表示しています。Success Bufferに入る前でも、Bulkトラフィックは破棄されます。これはNetflixではかなり稀なケースです。基本的に、少しでも容量があれば受け入れるリクエストですが、通常はほぼ完全に破棄されることを想定しています。

Thumbnail 1680

Best Effortトラフィックは、より興味深い部分です。これらはデバイスのプリフェッチや体験を向上させる要素などですが、これらのリクエストを破棄しても深刻な品質低下にはなりません。Prioritized Load Sheddingでは、Success Buffer内でBest Effortの破棄を開始します。40-60%の間で、バッファの終わりに近づくにつれ、Failure Bufferに入ると、Degradedトラフィックの破棄を開始します。これらは可能であれば提供したい機能です。例えば、Netflixのパーソナライゼーション関連の機能などがこれに当たります。パーソナライゼーションは製品の重要な部分だと考えていますが、再生機能を維持するためにパーソナライゼーションを犠牲にする必要がある場合は、そのような選択をします。最後に、Criticalリクエストは通常Failure Buffer内でのみ破棄されます。

Thumbnail 1700

これは、サービスの容量をこれらの異なる優先度バンドに分割し、それらの優先度バンドに負荷が入ってきたときに、適切なクラスのトラフィックを破棄することで対応していると考えることができます。 Netflixでは、サービス所有者に対して提供される全てのリクエストに、いくつかの重要なメタデータを付加しています。重要な情報の1つがRequest Contextで、これにはボディ、ペイロード、URL、ヘッダーなどが含まれます。そのサービスのAPIコントラクトに応じて、何がCriticalで何がそうでないかは異なります。例えば、サービス所有者は、自分のサービスの特定のURLが動画再生に絶対に重要だと判断し、それらのリクエストをCriticalとして分類し、バックグラウンドリクエストと区別することができます。

Thumbnail 1760

アプリがバックグラウンドでユーザーエクスペリエンスのために行っている処理は、重要度の低い「Degraded」に分類しています。また、Netflixのデバイスプロトコルという優れたフォールバック機能も備えています。TV、ノートPC、スマートフォンなどのデバイスは、リクエストを送信する際に、そのリクエストの優先度をヘッダーを通じて伝えています。これにより、サービス提供者が重要度を判断できない場合でも、デバイスの判断を信頼することができます。 この優先順位付けにより、救急車とスポーツカーの比喩のような状況が実現できます。Success Bufferに入る際に、信号機が黄色になり、どのリクエストが重要で通すべきか、どのリクエストを破棄すべきかを判断するのです。

レイテンシーベースの負荷制御とその効果

これは、バッファーの拡張として考えることができます。優先順位付けされたLoad Sheddingを導入する前は、リクエストを破棄するための余地が最後の部分にしかなく、ユーザーへの影響も大きかったのです。Netflixでは以前、Sheddingは深刻な問題とされていました。しかし、優先順位付けされたSheddingを導入したことで、通常運用の一部となり、より多くの余裕を持って対応できるようになりました。では、優先順位付けされたLoad SheddingはRPSグラフでどのように表れるのでしょうか?

Thumbnail 1810

その効果として、大幅なスケーリングが可能になりました。 X軸は時間を示しており、負荷が400倍まで大幅に増加する中で、重要度の低いSheddingが早期に開始され、成功したリクエストのバランスが保たれていることがわかります。輻輳による急激な失敗は見られません。これは、CPUをより効率的に使用して早期かつ頻繁に処理を拒否することで、ベースラインを維持しながら、より高い利用率を実現できているためです。

Thumbnail 1860

これは、Netflixのクラウドアーキテクチャにおいて特に重要です。十分なSPSを確保できれば、ユーザーは動画を視聴できます。システムの完全な失敗は、誰も視聴できない状態になった時に発生します。 ここで、リトライが優先順位付けされたLoad Sheddingにどのように適合するのか疑問に思われるかもしれません。私たちが気付いたのは、フリート内の各マシンの負荷は均一ではなく、ある時点で一部のマシンがより高負荷になっているということです。Load Sheddingの場合、1回のリトライを行う価値があると判断しました。これは、高負荷なマシンに当たってしまった場合、平均的な負荷のマシンで再試行する価値があるという考え方です。Load Sheddingの場合のみリトライを行うため、最初のリクエストは実際には処理されておらず、システムに追加の負荷をかけることはありません。

Thumbnail 1910

実際には、これは修正された指数バックオフアルゴリズムとして実装されています。 数学的に見えますが、重要なポイントは、最初の数回のリトライが通常の指数バックオフよりも早く行われるということです。これは、可能な限り早くレスポンスを得ようとし、それができない場合は速やかに失敗させる(Fail Fast)という考えに基づいています。この場合、100ミリ秒の指数バックオフを使用していますが、最初の数回は20ミリ秒というターゲットを使用して、ユーザーエクスペリエンスの許容範囲内に収めようとしています。

Thumbnail 1940

Thumbnail 1960

実際のところ、私たちのRetryアプローチと一般的に使用されているものとの違いは、最初の数回のRetryでは、より小さな範囲内でランダムな遅延を設けているという点です。これにより、フリートのサービス品質メトリクスをより良好に保つことができます。優先順位付けされたLoad Sheddingについて、そしてユーザーへの影響を軽減する方法について学んできました。しかし、実際の解決策は容量を増やすことだということを強調しておく価値があります。これまで説明してきたことは全て、クラウドを通じて新しいレーンを追加し、システム容量を回復し、人々が高速道路に乗れるような青信号を得るまでの間、被害を軽減するための方法に過ぎません。

Thumbnail 2000

これは、先ほど見たバッファーを拡張したグラフに関連しています - 回復までの時間が依然として重要なのです。私たちが話している全ての対策は連携して機能します。なぜなら、単一のアプローチだけではLoad Spikeを乗り切ることはできないからです。鋭い方々は、この高速道路のアナロジーには限界があることにお気づきでしょう。特に、データストアへのアクセスや他のサービスへの呼び出しなど、CPUを使用しない作業の場合です。この場合、高速道路のアナロジーは少し難しくなります。最も近い例えは、車が高速道路に入って一時的に消えた後、しばらくしてから再び現れるというものでしょう。避けたい問題は、全ての車が同時に再び現れて高速道路が混雑することです。

Thumbnail 2050

これは大きな課題です。観察できないシステムの使用率をどのように理解すればよいのでしょうか?車が高速道路から消えてしまうと、高速道路は空いているように見えますが、実際には待機中の作業が戻ってきて輻輳障害を引き起こす可能性があります。私たちは、レイテンシーを使って使用率の指標をエミュレートできることを発見しました。過負荷システムの重要な特性の一つは、遅くなるということです。システムの通常のパフォーマンスと、遅くなった時のパフォーマンスを測定することができます。

Thumbnail 2070

Thumbnail 2090

これを使用率の観点で言い換えることで、優先順位付けされたLoad Sheddingの機能を再利用できます。サービスのターゲットレイテンシーを50ミリ秒に設定します - これはおそらくサービスのタイムアウト値です。そして、その値より遅いリクエストと速いリクエストの数を数えることで、使用率の指標が得られます。この例では、サービスのターゲットレイテンシーの使用率は21%です。一方、下流のデータベースの遅延やブロックデバイスのスロットリングなどにより、そのサービスが遅くなった場合、この遅延はサービスのレイテンシー分布に反映されます。ここでも、ターゲットより遅いリクエストと速いリクエストの数を数えているだけです。これはサービスのレイテンシー分布に反映されています。

Thumbnail 2120

これで、88%のリクエストが遅すぎるという、非常にアクショナブルな使用率の指標が得られました。これを先ほど説明した優先順位付けされたLoad Sheddingシステムに組み込むことができます。今度はCPU使用率ではなく、バックエンドのKV-SLO使用率について話しています。Success BufferのKey Value SLOでトラフィックの拒否を開始したい場合に使用できるカスタムリミッターをインストールすることができます。

Thumbnail 2140

これは本当に素晴らしい機能です。同じツールスイートを使用して、CPUバウンドではなくIOバウンドのシステムを保護できるからです。違いは、どの使用率を見るか、どのバッファーでシェディングを行うかだけです。これは、Netflixにとって非常にレイテンシーに敏感な方法でBlobを提供するサービスで実施した実験です。このBlobストアで50から100ギガビット/秒までのスループットを目指していたので、このシステムが50ギガビット/秒を処理する容量が不足していることを知りながら、負荷を徐々に上げていくテストを行いました。

Thumbnail 2200

システムが限界に達したとき、右側にエラーのスパイクが発生しましたが、重要なポイントは、これらのスパイクが優先度の低いファイルで発生したということです。 このサービスには、重要なデータと重要度の低いデータがあります。限界点に達したとき、Successバッファー内のサービスは、優先度の低いファイルのシェディングを開始し、その処理を早期に拒否することで、重要なファイルを提供する能力を維持しました。優先度の高いファイルへの書き込みは決してシェディングしませんでした。使用率が80%に達した時点で優先度の高い読み取りをシェディングしますが、非常に低い目標使用率で、優先度の低い読み取りのシェディングを開始します。

Thumbnail 2250

このテスト中にCPU使用率の上昇は見られなかったにもかかわらず、システムは保護されました。このアプリケーションのCPU使用率は最大でも3%でしたが、それでもバックエンドのデータストアでボトルネックが発生していました。 これをさらに詳しく説明するために、それぞれのResource LimiterとUtilization測定値について、この段階的な曲線上でどの程度まで達しているかを示す使用率を出力しています。このロードシェディングの際、Successバッファーが40%を超えるレイテンシーターゲットに反応して、優先度の低い処理の最大65%をシェディングしました。

クロスリージョン対策とレジリエンス検証の方法

Thumbnail 2270

この素晴らしい点は、サービスオーナーが負荷スパイクから自身を守るために使用できるツールのレパートリーを構築していることです。SuccessバッファーとFailureバッファーという用語は、ロードシェディングの結果について明確に考えるのに役立ちます。Successバッファーでは、優先順位付けされたCPUロードシェディングやレイテンシーシェディングを行うことを推奨しています。Failureバッファーでは、できるだけ低コストでシェディングを行う必要があります - リクエストの中身を見たりボディをパースしたりせず、確率的にリクエストをシェディングするか、レイテンシータイムアウトのような指標を見るだけにします。

Thumbnail 2320

これらのテクニックを着実に蓄積しており、今日のトークから少なくとも1つは役立つものを持ち帰っていただければと思います。次に取り上げたいトピックは、リトライやフォールバックをより高い抽象レベルで扱うという考え方です。先ほど、Netflixのデバイスからクラウドへのプロトコルにはこれらの優先順位が付加されていると言及しました。私たちのエッジゲートウェイはZuulと呼ばれ、テックブログで詳しく説明しています。デバイスからの各リクエストには異なる優先順位が付けられています - 少し紛らわしいのですが、低い優先順位が実際には重要で、高い優先順位が重要でないということです。優先順位17のリクエストは、優先順位2や3のリクエストと比べて相対的に重要度が低いのです。

Thumbnail 2380

Thumbnail 2400

私たちはZuulゲートウェイレイヤーで、バックエンドに問題が発生した際にこれを使用します。 エッジゲートウェイの背後にあるMicroserviceに問題が発生した場合、実際には大きな整数値である優先度の低いトラフィックから順に制限を開始します。私たちは4つのリージョンで完全アクティブ運用を行っているので、これを活用できないかと考えました。 なぜなら、すべてのリージョンや、特定のバックエンドがすべてのリージョンで同時に問題を抱える可能性は実際にはかなり低いからです。

過去のインシデントを振り返ってみると、あるサービスがスロットリングを行っている際に、すべてのAmazonリージョンで同時にスロットリングが発生することは実際にはまれでした。そこで、Cassandraベースのレプリケーションシステムを活用して、別のリージョンにリクエストを再試行する仕組みを導入しました。鋭い方々は、過負荷時に別のリージョンへリクエストを再試行することで、そのリージョンまでダウンさせてしまうリスクについて疑問を持たれるかもしれません。そこで、カスケード障害を防ぐために、他のリージョンに送信する際にリクエストの優先度を下げています。この場合、数値を99に引き上げ、実質的に他のリージョンに対して「キャパシティに余裕がある場合のみ処理し、過負荷の場合はすぐに制限する」と指示しています。これは1回だけ行い、最近の負荷スパイク時には、このクロスリージョンシフティングによって、スロットリングされるはずだった90%以上のリクエストを救済することができました。これはユーザーへの影響を軽減する非常に効果的な手法です。

Thumbnail 2470

Thumbnail 2500

これにより、ユーザーにエラーを返す代わりにグリーンチェックマークを表示でき、ストリーミングを楽しむユーザーも満足していただけるはずです。これらの手法を共有したところで、最後に、これらの仕組みが私たちのスケールで実際に機能することをどのように証明するのかについてお話ししたいと思います。 Netflixで何度も学んできた教訓の1つは、何かが本当に機能するかどうかを知るためには、本番環境で繰り返しテストする必要があるということです。これは特にレジリエンス技術において重要です。なぜなら、これらは例外的な状況で使用されることを想定しているからです。常にFailure Bufferに負荷を流したり、リージョン間の負荷シフトを行ったりしているわけではありません。これらは、シートベルトやエアバッグのような安全機構なので、必要な時に確実に機能することを確認する必要があります。

Thumbnail 2550

テストピラミッドについてはご存じかもしれません - Googleで検索すると、50の異なるレベルを持つバージョンもあります。 しかし、これが古典的なものです。一般的な考え方として、テストピラミッドの下部にあるものはカバーする範囲が狭くなります。テストの焦点は絞られていますが、実行するのが安価で容易です。テストピラミッドを上に行くほど、テストする範囲は広がりますが、実行コストが高くなり、オーケストレーションも複雑になり、確実に動作させるのが難しくなる可能性があります。これらのレジリエンスメカニズムの検証方法は、テストピラミッドとして考えることができます。

Thumbnail 2590

Thumbnail 2610

ピラミッドの底辺には合成負荷テストがあります。 ここでの考え方は、個々のアプリケーションを単独で負荷テストを実行することです。アプリケーションコードのボトルネックや、不適切に設定された負荷制限の設定など、手近な問題を発見することを目指しています。 ピラミッドの次のレベルは、本番環境でのテストです。実際の本番リクエストを使用してこれらのシステムに負荷をかけます。これが重要なのは、実際の本番システムを使用してテストを行うことで、緊急時に使用する本物のAutoスケーリング設定や負荷制限の設定が正しく機能することを確認できるからです。

私たちには、Chaos Automation Platformという素晴らしい社内プラットフォームがあり、様々な実験を設定することができます。Auto Scaling Squeeze Testと呼ばれる新しいタイプの実験では、基本的にサービスに対して4倍の負荷スパイクを発生させ、その挙動を観察します。Load Sheddingは機能するのか?Prioritized Load Sheddingは適切なレベルで発動するのか?Auto Scalingは想定した復旧時間内にスケールアップするのか?Success Bufferはどうか?Failure Bufferはどうか?これにより、コールグラフの個々の部分が実際にレジリエントであるかどうかを、サービスごとのレベルで把握するために必要な情報がすべて得られます。

Thumbnail 2690

さらに、Region Scale Testも実施しています。これは、クラウド環境にいる私たちが恵まれていることの一つで、素晴らしいパートナーであるAmazonには本当に感謝しています。実質的に、Netflixのグローバルトラフィックをすべて単一のリージョンにリダイレクトし、Netflixのすべての機能を単一のリージョンで実行しています。この取り組みの基本的な考え方は、

一部の問題はこのような規模でしか顕在化しないということです。例えば、リージョン内のインスタンス数に関連する問題や、特定のRPSレベルに達した時にのみ発生する問題があります。このテストにより、そうした問題の有無を特定し、修正することができます。

Thumbnail 2740

さらに、Regional Load Testingも実施しています。これは別の合成テストですが、個々のアプリケーションに対してではなく、Netflixのフロントドアであるzuulに対して実行します。これを使用して、異なるアプリケーションでのユーザーフローをシミュレートします。これにより、大規模なローンチが実際に起こる前に、そのシミュレーションを行うことができます。タイトルのローンチで特定の量のトラフィックが発生すると予測し、その負荷下でシステムがどのように動作するかを確認できます。また、実環境では再現が難しい様々な障害シナリオもシミュレートできます。この合成負荷があれば、システム内の異なる障害モードを確実にトリガーする複雑なテストケースを作成できます。

Thumbnail 2810

これにより、さらに高いレベルのスケールをテストすることができます。Region Scale Testは実際のプロダクショントラフィックを使用する素晴らしいテストですが、グローバルトラフィックに制限されます。この方法があれば、グローバルピーク時よりもさらに多くのキャパシティを注入できます。これらすべてを組み合わせることで、個々のアプリから、サービス間の相互作用、そして大規模な運用時の挙動まで、システムのあらゆるレベルで実際のレジリエンスを検証することができます。

負荷スパイク対策の成果と今後の展望

Thumbnail 2830

先ほど、この取り組みの一環として複数の目標があったことをお話ししました。 まず、Ryanが非常に適切に説明したように、復旧時間の短縮を目指しています。Autoscalingの導入により、大幅な短縮を実現しました。また、Regional Failoverの使用頻度を減らしています。これは、グローバルなイベントが発生した際に、Regionを切り替えることなく負荷に対応できるようにするためです。そして最後に、負荷スパイクが常時発生することを前提とした耐障害性の姿勢への転換です。これはまだ進行中で多くの課題が残されていますが、これらすべての面で着実な進展を遂げています。

Thumbnail 2870

私にとって最も重要な教訓は、ProactiveなメカニズムとReactiveなメカニズムの両方が必要だということです。これらは連携して機能するように設計されるべきで、両方が状況に応じて使用されることを想定しておく必要があります。次に、Joeyが説明したように、既存のコンピューティングリソースを活用して優先度の高いリクエストを処理し、優先度の低いものを破棄できる場合、これは非常に強力な手段となります。これにより、Success Bufferを効果的に増やし、ユーザーへの高品質なサービスを維持することができます。同様に、問題が発生してFailure Bufferに入った場合は、可能な限り早く失敗させる必要があります。これらのリクエストは有害無益なので、システムから取り除く必要があります。最後に、繰り返し申し上げますが、可能な限り本番環境でテストを行うことが重要です。本番環境でのテストを繰り返すことで、私たちは常に新しい発見をしており、その価値は毎回証明されています。

ご清聴ありがとうございました。この後、皆様とお話しできることを楽しみにしています。


※ こちらの記事は Amazon Bedrock を利用することで全て自動で作成しています。
※ 生成AI記事によるインターネット汚染の懸念を踏まえ、本記事ではセッション動画を情報量をほぼ変化させずに文字と画像に変換することで、できるだけオリジナルコンテンツそのものの価値を維持しつつ、多言語でのAccessibilityやGooglabilityを高められればと考えています。

Discussion