re:Invent 2024: AWSエンジニアが語るAmazon S3の内部設計と技術
はじめに
海外の様々な講演を日本語記事に書き起こすことで、隠れた良質な情報をもっと身近なものに。そんなコンセプトで進める本企画で今回取り上げるプレゼンテーションはこちら!
📖 AWS re:Invent 2024 - Dive deep on Amazon S3 (STG302)
この動画では、Amazon S3の設計思想と実装について、Senior Principal EngineerのSeth MarkleとPrincipal EngineerのJames Bornholtが詳しく解説しています。400兆以上のオブジェクトを保持し、1秒あたり1億5000万以上のリクエストを処理するAmazon S3のスケールを活かした設計手法として、Shuffle ShardingやPower of Two Random Choices、Erasure codingなどが紹介されています。特に、数千万台のドライブにデータを分散させる仕組みや、障害耐性を高めながら開発速度も向上させる方法、ShardStoreの段階的な展開事例など、Amazon S3の内部設計における具体的な工夫が語られています。
※ 画像をクリックすると、動画中の該当シーンに遷移します。
re:Invent 2024関連の書き起こし記事については、こちらのSpreadsheet に情報をまとめています。合わせてご確認ください!
本編
Amazon S3のスケールがもたらす利点:自己紹介と概要
みなさん、こんにちは。Seth Markleです。本日はJames Bornholtと一緒にお話しさせていただきます。彼はもうすぐこちらに来る予定です。今回はAmazon S3についての詳細な解説をさせていただきますが、今年は特に興味深い内容になると思います。まず自己紹介をさせていただきますと、私はAmazon S3のSenior Principal Engineerを務めています。来年の2月で在籍15年になりますが、この間、オブジェクトのインデックス作成、ディスクフリート、フロントエンドシステムなど、S3のスタック全般に携わってきました。本日のMatt Garmanのキーノートをご覧になった方もいらっしゃると思いますが、私は今日プレビューしたS3メタデータシステムの開発に携わっていました。後ほど登壇するJames Bornholtは、Principal Engineerとして、ディスクフリートの開発や、Mount Point PyTorchを含むオープンソースコミュニティとの連携に取り組んでいます。最近では、本日発表したS3 Tablesの立ち上げにも関わっています。
本日は、スケールを私たちとお客様の利点として活用する方法についてお話しします。技術的なスケールに関する講演というと、通常はスケールに対応するための構築方法、つまり、トラフィックが多すぎたり、データが多すぎたり、メタデータが多すぎたりした場合に、ソフトウェアが機能停止しないようにする方法について説明することが多いですね。つまり、スケールにどう対応するかという話です。しかし今日は、スケールを異なる視点から見ていきます。スケールに対応するための設計方法ではなく、そのスケールが実際にどのようにS3をお客様にとってより良いものにしているかについてお話しします。
まず始めに、S3のスケールについて見ていきましょう。こちらが統計データです。ご存知の通り、S3はオブジェクトストアで、400兆以上のオブジェクトを保持しており、これはエクサバイト規模のデータ量になります。1日平均で1秒あたり1億5000万以上のリクエストを処理し、2000億以上のイベント通知を送信しています。そして、ピーク時には世界中のフロントエンドフリートを通じて、1秒あたり1ペタバイトを超えるトラフィックを処理しています。1ペタバイトですよ。
本日は、このスケールがもたらす利点について3つの観点からご説明します。まず、データの物理的特性についてお話しします。物理的特性とは、ハードウェアとソフトウェアの実世界における特性のことで、これらがシステムの動作をどのように決定づけているかということです。先ほどのAndy Warfieldのキーノートをご覧になった方は、私たちがディスクフリートを大規模な熱力学システムのように考えていることをお聞きになったと思います。システムが成長するにつれて、これらの特性が実際にどのように改善されていくのかについてお話しします。その後、Jamesが、S3のスケールが実世界での障害シナリオにおいても、一貫したパフォーマンスと安定した運用経験を提供するのにどのように役立っているかについて説明します。最後に、スケールを活かした障害対応の設計が、お客様により迅速なサービス提供を実現する方法についてお話しします。
ハードドライブの物理的特性とS3の分散ストレージ戦略
データの物理的特性について詳しく見ていきましょう。ソフトウェアエンジニアとして、私はよくアルゴリズムの効率性、テスト、ドキュメントなどについて考えています。今朝のQデベロッパーに関する発表を受けて、個人的にはテストやドキュメントについて考える機会は減るかもしれませんが、現時点では依然として重要な課題です。ストレージエンジニアとして、私は基盤となるハードウェアの実世界における物理的特性について頻繁に考えています。これはより良いソフトウェアだけでは解決できない問題です。これらの物理的特性について説明し、ストレージシステムが大規模化するにつれて実際にどのように改善されていくのかをお見せします。
先ほど申し上げたように、私たちはスケールについて、「どうやって大規模化に対応するか」という観点で考えがちです。しかし今日は、システムが大規模化によって実際にどのように改善されるのかについてお話ししたいと思います。Amazon S3は数千万台のドライブを持つまでに成長し、後ほどお話しするように、それらは非常に高負荷で稼働しています。ドライブには処理できる作業量の限界があります。もし数千万台のドライブにトラフィックを分散できなければ、そのトラフィックを処理する能力があるにもかかわらず、お客様のできることを制限してしまうことになります。しかし、大規模になると、負荷を分散するのに役立つ有用なパターンが現れてきます。システムの3つのアクター、つまりお客様、ハードウェア、そしてソフトウェアを見ていくことで、その仕組みを理解していきましょう。
まずはハードウェアの物理的な特性についてお話ししましょう。ここにハードドライブがあります。ハードドライブはデータを読み取るために2つの主要な動きを必要とする機械的なコンポーネントです。この図に示されているように、プラッターと呼ばれる円盤が、Spindleと呼ばれる中心部分の周りを回転します。システムのドライブ数を議論する際、多くの人がSpindleについて言及し、システム内のSpindle数について話します。また、Actuatorと呼ばれる機械的なアームが前後に動き、その先端にある読み書きヘッドでデータが存在するプラッターの特定の部分にアクセスします。
ハードディスクドライブには、効率的なデータアクセスと保存を確保するためのこの2つの主要な動きがあります。回転するプラッターによって異なるセクターからのデータの素早い取り出しが可能になり、Actuatorアームの動きによって読み書きヘッドの正確な位置決めが可能になります。この回転運動と直線運動の組み合わせが、従来のハードディスクドライブの動作の基本となっています。
データを読み取る際、Logical Block Address(LBA)と呼ばれる、ゼロからドライブの容量までのアドレスを指定します。これによって、アクセスしたいデータがどのプラッターのどの位置にあるかをドライブに指示します。Actuatorは物理的なコンポーネントとして前後に揺れながら所定の位置に移動する必要がありますが、これは瞬時には行えません。正しいトラックに到達しても、目的のデータが直下にない場合があり、プラッターが回転してデータが下に来るのを待つ必要があります。平均して、これは半回転分の時間がかかります。
Actuatorが移動する動作をSeekingと呼び、移動にかかる時間をSeek timeと呼びます。そして回転を待つ時間はRotational latencyと呼ばれます。これらは、ドライブがデータを読み取る際に時間がかかる原因となる2つの物理的な要因であり、測定可能な時間です。これが、ハードドライブには物理的な制限があるという意味で、固定された時間内に行える前後の揺れ動きには限界があるのです。
S3のShardStoreとワークロード分散の仕組み
Amazon S3にオブジェクトを保存すると、そのオブジェクトはShardsと呼ばれる断片に分割され、Erasure Codingと呼ばれるアルゴリズムを使用して、ストレージノード全体のディスクフリート上に分散して保存されます。Erasure Codingについては、後ほどJamesが詳しく説明します。重要なのは、送信されたデータを分割し、複数のドライブに冗長的に保存するということです。これらのドライブ上では、ShardStoreと呼ばれるソフトウェアが動作しています。
ShardStoreは、ストレージノードホスト上で実行されるファイルシステムです。私たちが独自に開発したもので、Jamesのセクションで説明される公開論文もあります。基本的には、ログ構造化ファイルシステムで、新しいShardsが書き込まれる際に、それらを連続的に配置します。ドライブの動作を考えると、アクチュエータの往復移動がドライブ使用における遅延とコストの一因となっています。ログ構造化ファイルシステムでは、データをドライブ上で隣接して配置することで、このコストを削減し、書き込みをより効率的にすることができます。
読み取りに関しては、そのような贅沢は許されません。顧客がいつどのデータを読み取りたいかをコントロールできないため、プラッター上のデータがある場所まで移動せざるを得ません。読み取りは最も非効率な処理で、シークと回転待ち時間の両方のコストがかかり、ドライブ上を移動する必要があります。このタイプのI/Oは一般的にランダムI/Oと呼ばれ、通常はシークとドライブの回転の両方が必要です。
ストレージのワークロードは個別に見ると、非常にバースト的な特性を持っています。つまり、長期間アイドル状態が続いた後、突然高負荷になるということです。その良い例がFINRAです。FINRAは証券会社と証券市場を規制する機関で、不適切な行為を監視するために株式とオプション市場のすべての取引を収集、処理、分析する責任があります。これには1日に数千億件のレコードの取り込みが含まれ、8月のピーク時には2日連続で9,060億件のレコードを処理しました。平均して1日約30テラバイトのデータを4時間のSLAで処理しています。彼らのワークロードがバースト的である理由は、この4時間の間に集中的にデータにアクセスし、残りの20時間は比較的アイドル状態(新しいレコードの取り込みは行っていますが)で、すべての大規模処理を4時間の枠内で行っているためです。
FINRAのような例のワークロードで、独自のストレージシステムを構築する場合に必要なドライブ数を計算してみましょう。この例では - これはFINRAの実際の数字ではありませんが - 1ペタバイトのデータを保持し、それを1時間で処理したいという顧客がいるとします。1時間で1ペタバイトにアクセスする場合、想定されるアクセスレートは275ギガバイト/秒です。オブジェクトあたり約1メガバイトとすると、約10億個のオブジェクトとなり、これは妥当な規模のワークロードと言えます。
ハードドライブについて話す時、主に2つの動作があります:Seekingと回転です。7200 RPMのドライブは1分間に7200回転するので、1回転あたり約8ミリ秒かかります。平均的に、データを見つけるには半回転する必要があるため、平均的な読み取りの回転待ち時間は4ミリ秒です。Seek時間は平均してプラッターの半径の半分を移動する必要があり、これにもさらに4ミリ秒かかります。冗長ストレージのためにオブジェクトがShardに分割される仕組みを覚えていますか - この例では、1メガバイトのオブジェクトが0.5メガバイトのShardに分割されています。その0.5メガバイトを読み取るには、ディスクから約0.5メガバイトをスキャンする必要があり、さらに2ミリ秒かかります。合計すると、このランダムな読み取りには10ミリ秒かかります - Seek時間、回転待ち時間、転送時間の合計です。1秒は1000ミリ秒なので、読み取りに10ミリ秒かかるとすると、1秒間に約100回の読み取りができます。これは0.5メガバイトの読み取りなので、1秒間に100回の0.5メガバイト読み取りができるということは、このドライブから1秒間に50メガバイトを読み取れるということになります。
元の数字に戻って考えてみましょう。 ピーク時に275ギガバイト/秒でアクセスしたい1ペタバイトのデータがあります。もしデータを単に保存するだけで、アクセスしないのであれば、現在の1台あたり20テラバイトのドライブサイズで計算すると50台のドライブが必要です。しかし、1台あたり50メガバイト/秒で275ギガバイト/秒のアクセスを実現するには、システムに5000台以上のドライブが必要になります。 ストレージ容量に対してアクセスレートを考慮すると、ドライブ数が100倍に増幅され、この例では1日1時間しか使用しないため、それらのドライブは1日23時間アイドル状態になります。これは非常に非効率です - 100倍の余分な容量に対してお金を払い、その95%の時間は使用していないことになります。
皆さんの中には、これが単一のワークロードの例だとして、S3には何百万もの活発な顧客がいるのだから、S3というサービスにとってはこの問題が実際には100万倍深刻なのではないかと考える方もいるでしょう。 これこそがスケールのメリットの面白いところです。個々のワークロードは非常にバースト的ですが、互いに関係のない独立したワークロードは同時にバーストすることはありません。 集約レベルでは、ストレージの温度は驚くほど予測可能になります。確かに、新しいデータは一般的にホットで、小さいオブジェクトもホットな傾向にあり、私たちは多くの新しい小さなオブジェクトを持っています。4000億個以上のオブジェクトがありますが、古くて大きなオブジェクトも多くあります。
これらのワークロードを重ね合わせると、システム全体としてはより予測可能になります。集約するにつれて、ピーク需要は徐々に増加しますが、 ピークと平均の差は縮小し、ワークロードはより予測可能になります。これは先ほど説明した物理の方程式にとって 実に素晴らしいことです。なぜなら、顧客のデータを、ストレージ容量だけを考えた場合よりもはるかに多くのドライブに分散できるからです。これが可能なのは、あるワークロードのピーク時が、 他のワークロードの静かな時期と重なるからです。
これが、私たちのスケールがお客様にとって有利に働く最初の理由です。この会場にもいらっしゃるかもしれませんが、 何万もの顧客それぞれが、ストレージ容量としては それほど必要としないにもかかわらず、100万台以上のドライブにデータとワークロードを分散させています。これには2つの利点があります:膨大な数のSpindle(ハードドライブの回転ユニット)に分散できるため、 ドライブ容量を完全に使用していなくても、それらのSpindleが提供する総スループットまでバーストすることができます。また、各顧客が個々のディスクの ごく一部しか使用していないため、前のスライドで見たような優れたワークロード分離が実現できます。
Shuffle Shardingによるトラフィック分散とスケールメリット
このワークロード分離が可能なのは、まさに私たちが巨大なスケールを持っているからです。 先ほど私は、お客様が通常のデータ量では必要としない数のドライブからスループットを活用できる方法について詳しく説明しました。この集約されたワークロードの予測可能性は、物語の一部に過ぎません。私たちはまだ、ディスク全体でホットスポットとコールドスポットが発生するのを防ぐため、トラフィックがフリート全体にどのように分散されるかに注意を払う必要があります。
この分散は自動的には起こりません。オブジェクトは集約すると予測可能なパターンに従いますが、時間の経過とともに、新しいデータほどホットなデータとなります。ドライブに書き込む時点ではデータは新しいのですが、1年か2年後には、そのデータの活性は低下します。ドライブは古くなるにつれて冷えていきますが、削除によって生じた隙間に新しいデータが埋められることもあります。このまま放置すると、システムは冷却化する傾向にあります。必要なスループットを提供できないのに容量がいっぱいのスピンドルを抱えるのは非効率なので、私たちはすべてのスピンドル全体でトラフィックを集約し、バランスを取りたいと考えています。
フリート全体で高利用と低利用のポケットが発生する可能性があり、これはIO容量を使い切れていないという点で理想的ではありません。しかし、私たちのスケールは、システム内のあらゆるトラフィックプロファイルのストレージラックを持っているということを意味し、何かが間違っていたり冷却化が見られたりした場合に、再バランスと再エンコードのための大きな余地を提供します。私たちは常にデータを移動させ、ラックやハードドライブ全体でストレージの温度バランスを取っています。これにより、ビットが予想よりもホットだったか、コールドだったか、経年により冷えたか、あるいはインテリジェントな階層化のために移動が必要かといった、初期配置の決定を見直すチャンスが得られます。
つまるところ、Amazon S3は巨大なディスクラックの集合体に過ぎません。昨晩のDave Brownの講演でお聞きになった方もいると思いますが、以前使用していたラックタイプの中には4,500ポンド(2トン以上)の重さのものもありました。現在のラックタイプは、そこまでは重くありませんが、それでも車よりも重い数千ポンドの重さがあります。これらは非常に大きいため、施設の設計時には、積み下ろし場所から最終設置場所までの移動を考慮して、補強された床を設計する必要があります。
これらは私たちがJBOD(Just a Bunch of Disks)と呼んでいるものの実際の写真です。これらのラックには約1,000台のディスクがあり、ディスク1台あたり約20テラバイトです。これらのラックはそれぞれ約20ペタバイトの物理容量を持っており、ノートパソコンに新しいドライブを買い足すのと同じように、私たちは常に新しいラックを導入してAmazon S3の容量を増やしています。しかし、前述したように、新しいデータはシステム内のホットデータなので、新しいトラフィックをすべてこれらのドライブに送ることはできません。ラック内の1,000台のスピンドルを圧倒してしまうからです。1,000台というと相当な数に聞こえますが、スピンドル1台あたり50メガバイト/秒では、ラックからそれほど多くを得ることはできません。すべてが新しい、ホットなデータの場合、ラックはメルトダウンしてしまいます。これはラックがメルトダウンする様子のアーティストによる描写です。
その代わりに、私たちはラックをオンラインにして、既存のAmazon S3のデータをそこに拡散することから始めます。私たちがマイグレーションと呼ぶこのプロセスは、既存の比較的アクセス頻度の低いデータで約80%まで満たし、それを既存のフリートと混ぜ合わせることを含みます。これにより、既存のスピンドル全体で容量が解放され、新しいスピンドルにホットデータを集中させるのではなく、多くのスピンドルにわたってホットデータ用のスペースが作られます。新しいラックの導入は、過去の配置決定を見直す絶好の機会となり、システム内のラックの多様なプロファイルにより、ホットスポットを回避するための強力なツールとなります。システム内には様々なプロファイルのラックがあり、マイグレーションに必要な異なるトラフィックプロファイルを選択できる場所が複数存在します。
ここでの要点は、ストレージシステムは実世界のハードウェアとアクセスパターンの物理的な特性に基づいているということです。Amazon S3で作業を実行する際、お客様は私たちのシステムのスケールと物理的特性を活用しているのです。
非相関化とShuffle Shardingの詳細:James Bornholtの解説
私がここにいられることを嬉しく思います。これは私のre:Inventでの一番のお気に入りのトークです。なぜなら、私たちのチームが実際にシステムをアーキテクトする上で行っている素晴らしい仕事について話す機会だからです。これが皆さんのシステムのアーキテクチャの参考になることを願っていますし、また話題にできる本当にクールな内容になると思います。実は、Sethの話の続きから始めたいと思います。彼はデータを何百万台ものドライブに分散させることの利点について説明していましたが、それがどのように皆さんの利点になるのかについて話していました。私はこの点についてもっと掘り下げたいと思います。なぜなら、これは一回限りのことではなく、私たちが唯一行っていることでもないからです。
Amazon S3を構築していく中で、これが私たちが使用しているアーキテクチャパターンであることに気づきました。これは非相関化という考え方です。私たちはS3のあらゆる部分をワークロードを非相関化するように設計しています。2人のお客様がワークロードを実行している場合、それらの2つのワークロードは互いに非相関であるべきです。さらに、1人のお客様であっても、そのワークロードは自身との間でも非相関であるべきです。これは少し変わった考え方かもしれません。自分自身とどのように非相関化するのか。これについてもっと詳しく説明しましょう。
S3には多くの素晴らしい機能があります - 今朝もたくさんの新機能を発表したばかりですが、基本的にS3はPutとGetに関するものです。S3にバイトデータをPutし、後でそれをGetして取り出します。これらのPutを行う際、私たちは重要な決定を下す必要があります。そのバイトデータをどこに配置すべきかを決めなければなりません。シンプルなアプローチをご紹介します:Putを実行すると、ドライブが割り当てられます。そして、同じバケットに対して更にPutを行うたびに、それらのPutを同じドライブに配置し続けます。これを繰り返します。最終的に、十分なPutがあれば、ドライブは一杯になります。それは問題ありません - 私たちには他のドライブがあります。そこで2台目のドライブを割り当て、そのドライブにもバケットからのデータを格納し始めます。
私の話し方からお察しの通り、これは実際のやり方ではありませんし、特に優れた方法というわけでもありません。ただし、この方法を一概に否定したくはありません。実際、これは割り当てを行うのにかなり良い方法です。特に重要な利点が1つあります:シンプルだということです。シンプルさは素晴らしいものです - 運用が容易で、理解しやすく、計画も立てやすいからです。しかし、いくつかの問題もあります。1つの問題は、ストレージシステムについて考えたことがある人なら恐らく明らかですが、データを1台のドライブにだけ保存することはできないということです。HDDは比較的高い確率で故障するため、データを複数の場所に保存する必要があります。
このアプローチにはさらに微妙な問題があり、その問題はスケールによって異なります。ドライブを満たすほど大きくない小さなBucketがある場合、ドライブ全体を割り当てるのはコスト効率が悪くなります。Sethが話していたように、これらのドライブは20テラバイトもの大きさです。100キロバイトのデータしかない場合、ドライブ全体を割り当てることはできません。小規模なCustomerの場合、他のCustomerとスペースを争うことになり、ドライブを共有して、同じドライブに多くの異なるワークロードを置くことになります。
大規模なCustomerの場合、それは問題ではありません - ドライブ全体を使用でき、それを満たすのに十分なデータがあります。しかし、今度は別の問題が発生します:パフォーマンスが最終的にストレージの容量によって制限されるのです。これはSethが説明していた考え方です。1ペタバイトのデータがある場合、50台のドライブが割り当てられます - それだけのスペースを得られます。しかし、50台のドライブから得られる以上のパフォーマンスが必要かもしれません。これらはどちらも相関関係です。小規模なCustomerの場合、リソースを共有しているため他のCustomerと相関関係があります。大規模なCustomerの場合、自身の容量に縛られているため、自分自身と相関関係があります。
これらの相関関係によって、システムの使用が非常に困難になり、Customerのバランスが時間とともに崩れた場合の対応を考えなければならないため、スケーリングも非常に難しくなります。そこでS3では異なる方法を採用しています。これは実際、Shuffle Shardingと呼ばれる、私たちがよく話題にするパターンの一例です。Shuffle Shardingの考え方はシンプルです - ワークロードをドライブやCPUやGPUなどの他のリソースに静的に割り当てるのではなく、フリート全体のドライブにランダムに分散させます。そのため、Putを実行すると、バイトを保存するためのドライブのセットをランダムに選択します。次回Putを実行する際は、同じBucketや同じKeyであっても、毎回異なるドライブのセットを選択します。
重複する可能性はありますが、使用するドライブについて毎回新しいランダムな選択を行います。これは意図的にシステムに非相関性を組み込んでいるのです。これらのオブジェクトは同じワークロード、同じCustomer、同じBucketのものですが、Bucket内で強制的に非相関にしています。これにより、静的なリソースによる制約を受けなくなります。
ペタバイト規模のデータをお持ちの場合、実際には1ペタバイト分以上のドライブに分散させることができます。Sethが言及していたように、ワークロードに必要な5500台のドライブを使用できるのです。小規模なお客様の場合、Noisy Neighbor効果から保護されています。というのも、あるお客様が特定のオブジェクトに対して頻繁にアクセスするといった高負荷な状況であっても、そのオブジェクトはおそらく他のお客様とは異なるドライブセットに分散されているからです。1台のドライブを共有している場合でも、データを保持している他のドライブにアクセスできるため、高負荷なワークロードとの競合を分離することができます。
この Shuffle Sharding という考え方は、Amazon S3のリソースに関するチームの基本的な考え方です。S3のElasticityについて私たちが考えているのは、私たちが保有する数千万台のドライブすべてを、ワークロードが互いに干渉し合わない限り、どのワークロードでも利用できるということです。例えばFINRAのような場合、1日1時間だけ、ストレージの容量制限を超えて、毎秒テラバイト規模の帯域幅にバーストすることができます。
Shuffle Shardingは、リソースを効率的に共有し、ワークロード間の意図的な分離を実現する強力なメカニズムです。この Shuffle Sharding の概念を理解すると、ドライブだけでなく、あらゆる場所で活用されていることに気付きます。S3にリクエストを送信する際、実際には DNS アドレス(バケット名.s3.amazon.aws.comや、他のリージョンの場合は異なるアドレス)にリクエストを送信しています。これらの名前を実際に解決すると、IPのリストが返されます。以前は1つのIPしか返されませんでしたが、昨年の素晴らしいローンチで、S3にマルチアンサーDNSサポートが追加され、現在では1回の検索で8つのIPが返されるようになりました。
これらのIPはそれぞれ異なるサーバーセットを指し示しており、これがS3のロードバランシングの仕組みです。DNSルックアップを行う時点で、リクエストの送信先を決定しますが、それは他のお客様とは異なる可能性が高いです。これは単一のお客様にとってもメリットがあります。なぜなら、リクエストを複数のホストやサーバーに分散できるからです。これによってホットスポッティングの影響を最小限に抑えることができます。サーバーを他のお客様と共有している場合でも、通常以上の帯域幅にバーストできます。ここでも Shuffle Sharding が行われています。Sethと私が同時に同じバケット名に対してDNS解決を行っても、実際には異なるフロントエンドホストセットと異なるIPセットが返されます。
これらのフロントエンドホスト、つまり返されるIPはステートレスになるように設計されているため、どのホストにリクエストを送信しても、ランダムに割り当てられたすべてのドライブ上のすべてのバイトにアクセスできます。DNS Shuffle Shardingは特に耐障害性の面で重要です。私たちは多くのサーバーを持っており、サーバーの障害は通常のことです。もしデータにアクセスできるサーバー間に相関関係があれば、これは望ましくありません。なぜなら、1台のサーバーに障害が発生した場合、それがバケットの体系的な障害につながってしまうからです。
ステートレスな性質により、障害の影響を最小限に抑えることができます。このような障害が発生するからこそ、リトライが重要なのです。1回目の失敗があっても2回目はほぼ確実に成功します。なぜなら、異なるホストに振り分けられるからです。 このような理由から、すべてのAWS SDKは自動的にリトライを実装しています。実は、SDKはかなり賢く作られていて、どのようなリクエストの失敗やHTTPエラーコードがサーバーの問題に対応するのかを理解しています。403認証エラーと実際のサーバーエラーの違いも判別できます。問題が発生すると、SDKは賢く別のホストにリトライを振り分け、失敗したホストを無視して、意図的に異なるIPにリクエストを送信します。これが可能なのは、ステートレスだからです。バケットは特定のサーバーにマッピングされているわけではないので、どのホストにリクエストが行くかは問題ではありません。つまり、Shuffle Shardingは分散化を実現する素晴らしい方法なのです。私たちはS3全体、クライアントSDK、フロントエンドサーバー、そしてドライブでこれを実践しています。
AWS Common RuntimeとPower of Two Random Choicesの活用
分散化によって、私たちはレジリエンスと安定性を実現しています。これは、私たちが扱っている規模だからこそ可能なことです。Shuffle Shardingは素晴らしいテクニックです。
AWS Common Runtime(CRT)は、 Amazon S3との連携に使用する低レベルのソフトウェアライブラリです。リトライ、リクエストの自動並列化、そしてS3から非常に高いスループット(単一のホストから数百ギガビットのスループット)を得るために必要なすべての機能を実装しています。ほとんどのお客様がCRTを直接使用することはありません。JavaSDKやPython SDKに含まれるライブラリとして、あるいはMount Point for S3のような私たちのオープンソースコネクターを使用することで、CRTの機能を利用することができます。
CRTは、Shuffle Shardingを活用して非常に印象的なことを行っています。すべてのSDKが実現している障害耐性だけでなく、S3との通信性能を実際に向上させているのです。これは、S3へのリクエストレイテンシーを示すグラフで、X軸にレイテンシー、Y軸にレイテンシーの分布を示しています。ほとんどのリクエストは高速ですが、グラフの端に外れ値があることがわかります。分散システムでは、いくつかの遅いリクエストが発生するのは完全に正常です。CRTはこれに対して非常に興味深い対応をしています。CRTは動的にこの分布を追跡し、 実行しているリクエストのテールレイテンシーのリアルタイムな推定値を保持しています。
例えば、S3からの応答のレイテンシーの95パーセンタイル(p95)を追跡しているとします。これを把握すると、CRTは興味深い動作を開始します。このp95を超えるリクエスト、つまりp95より遅いリクエストを意図的にキャンセルしてリトライします。一見すると、これは驚くべきことかもしれません。なぜ、特に応答が近いと思われる遅いリクエストをキャンセルするのでしょうか?作業を無駄にしているように見えます。しかし実際には、Shuffle Shardingがこれを可能にしているのです。 新しいリクエストは、おそらく異なるフロントエンドホストに送信されます。複数のドライブにリクエストを分散させるため、異なるドライブセットに振り分けられる可能性が高いのです。そのため、リトライされたリクエストは非常に高速に処理される可能性が高いのです。
このような分布になります。左側は同じままですが、リクエストのキャンセルと再試行を開始したp95以降の部分では、Tail Latencyを大幅に改善できています。再試行したリクエストのほとんどが非常に高速に処理されたため、Latencyの分布の裾野が改善されたわけです。はっきり言って、これはある種の賭けです。2回目のリクエストの方が速くなるだろうという賭けであり、必ずしも常にうまくいくわけではありません。しかし実際には、Shuffle Shardingによってフォールトトレランスだけでなく、パフォーマンスも向上させる非常に効果的な手法となっています。
Shuffle Shardingは至る所で使われている素晴らしいデザインパターンです。この仕組みについて少し詳しくお話ししましょう。私たちはShuffle Shardingを気に入っていて、できるだけ多くのドライブにデータを分散させるというアイデアを採用しています。では、実際にどのように実現しているのでしょうか?Putリクエストが来た時、どのドライブにデータを保存するかを決める必要があります。この判断をどのように行っているのでしょうか?
一つの方法として、すべてのドライブを調べ、最適なドライブの組み合わせを見つけ出す、例えば最も空きがあるドライブや最も負荷の少ないドライブを選ぶという方法が考えられます。Putリクエストごとにすべてのドライブを確認して、インテリジェントな判断を下すことができます。しかし残念ながら、私たちは膨大な数のドライブを持っています。数千万台規模のドライブがあり、すべてのPutリクエストに対してこれらすべてのドライブを確認することはできません。なぜなら、Putリクエストは1つだけではなく、S3では毎秒何百万ものリクエストを処理しており、それらすべてが同時にこの計算を行おうとするからです。さらに、全員が同時にこのデータを参照している場合、全員が正しい判断を下せているかどうかを確認する調整の問題も発生します。
完璧な方法は実現できません。では、完全にランダムにするのはどうでしょうか?実は、完全にランダムな戦略も良い方法です。最適化を試みず、完全にランダムにドライブを選んでデータを配置する方法です。これは実際にうまく機能し、Shuffle Shardingの精神にも合致した効果的な手法です。ただし、完全にランダムにする場合には問題があります。どういうことか説明させてください。ここでお見せしたいのは、ドライブの容量使用率、つまりドライブ上で使用している容量のシミュレーションです。このグラフでは、X軸が容量のバランスを示すパーセンテージを表しています。Putリクエスト時に完全にランダムな配置を行い、ドライブの組み合わせを完全にランダムに選択した場合、このグラフは容量使用率の分布、つまり容量使用率のバランスがどうなるかを示しています。このグラフから分かるように、完全にランダムに行うと、時間の経過とともに、一部のドライブが他のドライブより10%、場合によっては20%も使用率が低くなってしまう状況に陥ります。これは好ましくありません。というのも、どのドライブが最も使用率が高くてこれ以上データを受け入れるべきでないか、あるいはどのドライブが最も使用率が低くてデータの配置に適しているかを考慮せずに、ランダムな選択を行っているため、容量が無駄になってしまうからです。
データを完全にランダムに配置することは可能ですが、不均衡のリスクがあります。結果として、この不均衡を補うための予備容量を購入する必要が生じ、コストの問題につながってしまいます。
もう1つの素晴らしいテクニックについてお話しします。 それは「Power of Two Random Choices」と呼ばれる手法です。考え方はこうです:容量使用率のような指標を最適化しようとする際、完全にランダムに選ぶのではなく、少し異なるアプローチを取ります - 2台のドライブを選ぶのです。数千万台あるドライブすべてを調べる必要はありません。ランダムに2台のドライブを選び、より使用率の低い方を使用するだけです。数千万台のドライブがある中で2台だけを選ぶことは、大した改善に見えないかもしれません。しかし、これが驚くほど効果的なのです。完全にランダムな選択をする代わりにこの手法を使うと、容量の分布は劇的に改善されます。毎回すべてのドライブを確認する場合とほぼ同じくらい完璧な配置が実現でき、しかもランダム性がもたらすパフォーマンス、効率性、調整不要という利点を維持したまま、わずか2台のドライブを見るだけで済むのです。
私たちはAmazon S3のスタック全体で、ドライブやフロントエンドだけでなく、意図的に相関性を排除するよう設計しています。これはShuffle Shardingを使って、データを異なるストリームに配置し、将来的に互いに干渉しないようにすることで実現しています。そして、Power of Two Random Choicesのようなアルゴリズムを使用して、パフォーマンスを犠牲にすることなく、スケーラビリティと耐障害性を実現しています。Amazon Builder's LibraryにはShuffle Shardingについての非常に興味深い記事がありますので、ぜひご覧ください。また、皆さんのシステムでも、低コストで耐障害性を実現する方法としてShuffle Shardingの実装を検討されることをお勧めします。
Erasure codingによる耐障害性とデータ冗長性の実現
最後にお話ししたいのは、私たちがどのように開発速度を高めているかについてです。Amazon S3での仕事を通じて私が学んだ驚くべきことの1つは、耐障害性のための設計作業が、チームの開発スピードを上げ、より速くデリバリーする上で非常に役立っているということです。 大規模システムでは、障害は日常的に発生します - ドライブはランダムに故障します。これは究極的には分散システムの問題であり、すべての分散システムが抱える問題です。個々のドライブの故障だけでなく、ファンやケーブルの故障といったハードウェアの問題により、ハードドライブやラック全体が故障することもあります。
また、より大きな障害ドメインにも対応できるよう設計しています。特に、複数のAvailability Zoneを使用するAmazon S3のストレージクラスは、データセンター全体が完全に失われても耐えられるようになっています。これらのAvailability Zoneの1つが永久に失われても、すべてのデータは無傷のまま保持されます。この仕組みについては、re:Inventで丸々1セッション使って説明したこともあります。これは簡単なことではありません - アーキテクチャやシステムの冗長性だけの問題ではないのです。この規模での耐障害性は、障害の種類を知ることと同じくらい、エンジニアリング文化やチームがシステムをどう捉えているかということに関係しています。
耐障害性に関する最も難しく微妙な問題の1つは、そもそも何かが故障したことをどうやって知るかということです。これは奇妙に聞こえるかもしれませんが、私たちの規模では、システムが故障したことをどのように判断するかという問題に答えるのは驚くほど難しいのです。 例えば、Amazon S3は数百万台のハードドライブに保存されているすべてのバイトを常にスキャンして、整合性をチェックし、期待された場所にあるか、そして損傷していないかを確認しています。最大の顧客 - 1秒あたりの最もリクエスト数の多い顧客 - のリストを作成すると、このバックグラウンドスキャン作業(どのデータが無傷か、あるいは欠落しているかを見つける作業)は、そのリストの上位に入るほど大規模なものです。私たちは運用の観点からこれを非常に重要視しています。
私たちは、P100(バイトがこのプロセスによってスキャンされずに経過した最長時間)に関するアラームを設定しています。このメトリクスが高くなりすぎると、エンジニアを夜中に起こしてページングを行います。フリート内のすべてのバイトが定期的にスキャンされていることを確認することを非常に重視しているのです。これは、Amazon S3において常に行っている耐障害性の取り組みを考えれば、当然のことと言えるでしょう。
この世界で耐障害性を実現する方法についてお話ししましょう。私たちはこれを「Erasure coding」と呼んでいます。単一ドライブの障害に対する耐久性と回復力を実現する非常にシンプルな方法があります。それは、オブジェクトを複数のドライブに複製することです。ここに1メガのオブジェクトがあり、これをコピーすることができます。例えば、異なるドライブに2つのコピーを保存します。これでドライブ障害に対する耐性ができました。1台のドライブが故障しても、まだ2つのコピーが残っています。2台のドライブが故障しても、まだ1つのコピーが残っています。これらのコピーを異なるAvailability Zoneに配置することで、Availability Zone全体の障害にも耐えられるようになります。つまり、1つのAvailability Zoneが故障しても、他の2つにはデータのコピーが残っているため、オブジェクトを取り戻すことができます。
Replicationには多くの利点があり、特にシンプルであることが魅力です。私たちはシステムでReplicationを使用していますが、1つだけ欠点があります。それは、顧客データ1バイトを保存するのに非常にコストがかかることです。このモデルでは、耐障害性を得るために、物理的に3バイトを保存する必要があります。つまり、保存するバイト数が3倍に増幅されるのです。実際には、設計目標である11ナインの耐久性を達成するには3つ以上のコピーが必要なので、スライドで見るよりもさらに状況は厳しくなります。
では、代わりに何ができるでしょうか?私はErasure codingについて話すのが大好きです。これは、Amazon S3で私たちが楽しく取り組んでいる、とてもクールな数学についてお話しする機会です。考え方はこうです。まず1メガのオブジェクトから始めて、これを5つに分割します。ここまでは何も得られていません - 同じオブジェクトを私たちが「Shard」と呼ぶ5つのチャンクに分割しただけです。ここからが工夫のしどころで、非常に高度な数学を使って「Parity shard」と呼ばれる追加のShardを計算します。これらのShardを計算するための数学により、これら9つのShardのうちどの5つを使っても元のオブジェクトを復元できるという特性が得られます。
もちろん、最初の5つ(元のデータを5つに分割したもの)を見れば、オブジェクトを取り戻して組み立て直すことができます。しかし、別の方法でも可能です。Parity shardを2つと元のShardを3つ使っても、元のオブジェクトを完全に復元できます。どの組み合わせを使うかは問題ではありません。これら9つのShardのうちどの5つを使っても、元のオブジェクト全体を取り戻すことができます。これらのShardを複数のドライブに分散させることで、耐障害性を実現しています。いくつかのドライブが故障しても、元のオブジェクトを復元できます。重要なのは、どのドライブが故障しても構わないということです。どの4台のドライブが故障しても、オブジェクトを復元できます。
このErasure codingは、Availability Zone障害への耐性を確保する上でも有効です。シャードをAvailability Zone全体に分散させる必要があります。数学的にもうまく合致します。スライドには9つのシャードがあり、ほとんどのリージョンには3つのAvailability Zoneがあるので、各AZに3つのシャードを配置します。そうすると、1つのAvailability Zoneが停止しても、6つのシャードが残っており、オブジェクトを復元するのに十分な数となります。どのシャードが失われたかは問題ではありません。素晴らしいのは、レプリケーションと比べてオーバーヘッドがはるかに少ないことです。スライドで示したバージョンでは、4台のドライブが故障しても耐えられます。レプリケーションでこれを実現しようとすると、5つのコピーを保存する必要があります - 故障する可能性のある4つと、オブジェクトを維持するための1つの予備コピーです。つまり、5倍のオーバーヘッドが必要で、論理バイトごとに5つの物理バイトを保存しなければなりません。Erasure codingでは、オリジナルオブジェクトの5つのシャードと同じサイズのパリティシャードを4つ作成するだけで、80%のオーバーヘッド - 論理バイト1つあたり1.8物理バイトで済みます。
Erasure codingを活用した迅速なデプロイとパフォーマンス向上
Erasure codingは、ここで説明する以上に深い魔法のような数学ですが、低オーバーヘッドで障害耐性を実現してくれます。Erasure codingについては以前から聞いたことがあるかもしれません - 実際にはかなり一般的なストレージ技術で、ストレージシステムの基本的な機能の一つと言えます。ここで私が皆さんにお伝えしたいのは、単なる障害耐性を超えたErasure codingの可能性についてです。まず最初に、Erasure codingは開発速度を向上させる非常に強力なメカニズムだということです。これまで、ドライブの故障、ラックの故障、Availability Zoneの故障など、さまざまな種類の障害ドメインについてお話ししてきました。私たちのシステムは、Erasure codingなどの技術を使用してこれらの障害に対応できるように設計されています。しかし、このような障害に対応できるように設計されていれば、実際の障害の原因はそれほど重要ではありません。ドライブのケーブルが断線して1台のドライブが故障した場合も、ドライブ上で動作しているソフトウェアにバグがあって1台のドライブが故障した場合も、システムへの影響は同じです。
この障害耐性を活用することで、より迅速なデプロイが可能になり、変更に対する確信も早く得られます。新しいソフトウェアのデプロイを単一のホストや単一のラックから始めることもできます。新しいタイプのハードドライブを導入する場合も、最初は1台、10台、100台など、非常に小規模な容量からスタートすることができます。
このアプローチにより、Amazon S3に保存されているデータのごく一部だけが新しい障害ドメインにさらされることになります。ソフトウェアが機能しない、あるいはハードドライブに問題がある場合でも、問題ありません。それはごく少量のキャパシティに限定されており、私たちの障害耐性により、そのドライブやソフトウェアが停止しても対応できます。言い換えれば、新しいソフトウェアやハードウェアを、失敗することを想定してデプロイできるのです。これは耐久性について考えることや、システムにバグがないことを確認する必要性を免除するものではありません。私たちのチームは新しいコンポーネントに対して細心の注意を払っているため、実際にはこのような障害は稀です。しかし、このように悲観的に考えることは私たちの助けになっています。
このアプローチは、チームが新しいアイデアを自信を持ってデプロイするのに役立ちます。1台のドライブが故障したらどうなるかと考えて立ち止まることはありません - 1台のドライブが故障しても大丈夫なのです。このような本番環境での経験、つまりできるだけ早くシステムを本番環境に投入することは、Amazon S3の開発において非常に貴重です。たとえ新しいハードウェアやソフトウェアにさらされるのがこれらのシャードの1つだけだとしても、私たちの規模では、それは毎秒数百万のリクエストに対してXバイトのデータが影響を受けることを意味します。データの耐久性にリスクを与えることなく、この経験を得ることができます。このように、障害耐性は私たちにより迅速なデプロイを可能にする速度をもたらしてくれるのです。
これは素晴らしく聞こえますが、先ほどShuffle Shardingとオブジェクトをすべてのドライブにランダムに配置するという考えについてお話しした際に、お気づきかもしれない注意点が1つあります。新しいプラットフォームはフリート内にごくわずかしかありません。私たちが持っている数千万台のドライブのうち、おそらく100台程度、あるいは新しいソフトウェアを搭載したサーバーラック1台分程度です。そのため、あるオブジェクトがその新しい容量に配置される確率は非常に低いのです。1つのオブジェクトをPutする際、いくつかのドライブを選択しますが、それらのドライブが新しいソフトウェアや新しいハードウェアを搭載している可能性は極めて低いわけです。
しかし、ここにBirthday Paradoxと呼ばれる、もう1つの厄介な数学的な問題があります。Birthday Paradoxの考え方は、この部屋で私と同じ誕生日の人がいる可能性は非常に低いのですが、部屋全体を見渡すと、同じ誕生日を持つ2人が存在する確率は非常に高いというものです。Birthday Paradoxは、1つのオブジェクトが障害ドメインに晒される可能性が非常に低くても、大規模になると、少なくとも1つのオブジェクトが過度に晒されることを教えてくれます。ほとんどのオブジェクトは新しいドライブや新しいソフトウェアに1つのShardしか持たないかもしれませんが、システム内のどこかに、非常に運の悪いオブジェクトが存在し、それが5つの新しいドライブやソフトウェアに割り当てられてしまう可能性があるのです。
そのため、より慎重になる必要があります。もはやShardを完全にランダムにフリート全体に分散させることはできません。私たちのShuffle Shardingシステムは、意図的にこれらの新しい障害ドメインの知識を組み込んでいます。新しいソフトウェアや新しいハードウェアについて把握しており、新しいランダムな割り当てを行う際には、意図的に新しいハードウェアに配置するShardを1つだけに制限しています。このガードレール付きのShuffle Shardingの例として、先ほどSethが説明していたShardStoreシステムがあります。これは、私たちのすべてのドライブで動作する最新のストレージノードソフトウェアです。
私たちは実際、過去数年にわたってこのソフトウェアをAmazon S3全体に展開してきました。Erasure Codingによって、これが可能になり、各ステップを安全に進めることができました。ShardStoreの導入は、まずオブジェクトの1つのShardだけをShardStoreに保存することから始めました。しばらくその状態を維持し、チームはその経験から得たデータを学び、ShardStoreを調整し、改善しました。その後、2つのShardに増やし、さらに3つのShardへと増やしていきました。そして、Availability Zone全体のデータ量まで引き上げ、最終的にはガードレールを取り除き、ShardStoreを全面的に展開することができました。お客様に気付かれることなく、Amazon S3のエンジンを変更することができたのです。システムの最も基本的な部分を再構築し、それをErasure Codingによって実現したのです。
Erasure Codingについてもう1つ、これが単なる障害耐性のためだけではないということをお話ししたいと思います。Erasure Codingは、パフォーマンスを向上させるための優れた手段でもあるのです。この考え方は、先ほどお話ししたAWS Common Runtimeがリクエストをキャンセルして再試行する仕組みと非常によく似ています。なぜなら、Erasure Codingされたオブジェクトの場合、オブジェクトを復元するには5つのShardが必要ですが、どの5つであるかは問題ではありません。そのため、これらのShardから5つを選択することができます。もしこれらのShardの1つが何らかの理由で遅くなった場合、例えば他のお客様との競合によってドライブに負荷がかかっている場合でも、問題ありません。
別のシャードを選んで試すことができます。遅すぎるリクエストをキャンセルして、別のシャードを試すことができるのです。 Shuffle Shardingを使って追加のシャードを読み込むことで、最も遅いシャードによって生じるTail Latencyに対処することができます。つまり、フォールトトレランスは単なる障害対策以上のものなのです - それは速度のためでもあり、パフォーマンスのためでもあるのです。これは実に驚くべきことです。
フォールトトレランスの重要性と結論
要するに、Amazon S3チームはフォールトトレランスの計画に多くの時間を費やしているということです。これはシステムに冗長性を持たせるだけの話ではありません。私たちは障害を検知し、素早く対応できるようにするために懸命に取り組んでいます。Erasure Codingのような冗長性スキームも、障害を検知して回復できることを前提に設計しています。エンジニアとして、フォールトトレランスは時として重荷に感じることがあります。障害が起きないシステムを作る方がずっと簡単なのですが、大規模なシステムでは、フォールトトレランスは実は大きな強みとなります。私たちのシステムは障害に対して回復力があるため、チームは自信を持って素早く行動することができるのです。
今日お話ししたかったのは、以上が主な内容です。耐久性のある柔軟なストレージシステムをどのように構築しているか、そしてS3のスケールがどのように私たちの、そして最終的にはお客様の利点となっているかについて、少しでもご理解いただけたのではないかと思います。ストレージチームとして常に大切にしている原則は、お客様が私たちのことを意識する必要がないときが最も幸せだということです。ストレージシステムは単に動くべきであり、S3やその他のことについて考える必要がないはずです。私たちのチームがこの考えをどのように実現しているか、そして皆様が持ち帰れるアイデアをいくつかお伝えできたのではないかと思います。システムをよく見てみると、Shuffle ShardingやPower of Two Choicesの機会が意外なほど頻繁に現れることに気づくでしょう。それらが全く理解できなかったとしても、S3で行っているクールな取り組みについて、技術的な話で盛り上がっていただけたなら幸いです。本日はご参加いただき、ありがとうございました。残りの月もお楽しみください。
※ こちらの記事は Amazon Bedrock を利用することで全て自動で作成しています。
※ 生成AI記事によるインターネット汚染の懸念を踏まえ、本記事ではセッション動画を情報量をほぼ変化させずに文字と画像に変換することで、できるだけオリジナルコンテンツそのものの価値を維持しつつ、多言語でのAccessibilityやGooglabilityを高められればと考えています。
Discussion