« 【AWS発表】IAMに新機能が追加。利用料、レポートのみ閲覧可能なビジネス利用向けのアカウントも作成可能に。 | メイン | 【AWS新発表】 Relational Database Serviceの自動バックアップ期間を35日間に延長 »

Amazon S3のパフォーマンスをあげるコツ

今日は、AWSストレージ・オペレーション・ディレクタのダグ・グリスモアの記事をベースに、大谷晋平 (@shot6)がお送りします。Amazon S3で可能な限りのパフォーマンスを出すためのコツになります!

玉川憲 (@KenTamagawa)


AWSは、ありがたいことに非常に多くのお客様からAmazon S3を利用して頂いており、そのデータのアップロード・ダウンロードを通じて、膨大な負荷をこの数年間にわたって経験しています。その経験を活かして、S3のいくつかのベストプラクティスをご紹介します。

まず最初に言っておきますが、S3で小さな負荷(秒間50リクエスト以下)では下記のプラクティスはあてはまらないです。それがどれだけのオブジェクト数があっても(!)、です。なぜなら、S3はバックグラウンドで複数の自動化したエージェントをもっているためです。これらエージェントは、S3のリソースを均等にかつクールに割り振るため、限りない多様な負荷に対して負荷をシステム全体でならしてくれます。たとえ負荷が秒間100リクエスト以上になったとしても、お客様がそれを事前に我々に通知を頂く必要もありません。S3はそのようなリクエストを常に受け続けられるようにデザインされています、そういつまでもです。S3は実践的で、真にスケールアウトする設計がされています。

S3は短期間でも長期間でも、これよりずっとずっと大きな負荷に耐えられるようにスケールします。私たちのお客様の中にはS3に対して秒間数千以上のリクエストを継続的に実施している方々がいます、しかも年中毎日です。お客様の中には、S3のストレージと検索システムがどのように動くかを自分たちで"推測"する方もいれば、または似たようなロジックを使ったネームスペースを分割する別のシステムからS3に来られる方もいます。それとは別に、S3上で基本的に無限にスケールするシステムデザインが出来るよう、プレミアムサポート経由でお手伝いしているお客様もいます。そこで今日は、その中から皆さんにとって有用なガイダンスを公開しようと思います。

何故このアプローチがうまくいくかを説明するためにS3の高レベルなデザインコンセプトをここで説明しておく必要があります。S3はバケット内のオブジェクト名の'map'と'key'を管理します。旧来からのやり方では、このようなmapを使ってスケールアウトさせるにはパーティショニングの方法が使われます。S3が辞書順序ソートでリストするAPIをサポートするとするならば、キー名そのものがmapとパーティションスキーマ両方で何らかの方法で使われるでしょう。そして実際まさにそうなっています。各キーの'keymap'(我々は内部ではこう呼んでいます)はS3に最初にオブジェクトが保存された時に与えられた名前によって保存されて検索されます。どういう事かというと、最初にあなたが指定したオブジェクト名が、S3でどのようにkeymapを管理するかを実際に指示する事になるのです。

内部的には、S3内の全てのキーは文字列で以下のようになります:

bucketname/keyname

更に、S3のキーはプレフィックスによって分割されます。

先ほど言及したように、S3はキースペース内で分割が必要なエリアを継続的に探すように自動化されています。パーティションは、継続的な高いリクエストレートか、大量のキー数を保持するような場合(パーティション内の検索の遅延の原因になるためです)に、分割されます。新しく作ったパーティションへの移動は勿論オーバヘッドがあります。しかし、リクエストレートを低く抑えることに魔法はないですし、S3はそのパーティション分割の操作中でさえ、適度に高いパフォーマンスを維持することが出来ます。この分割操作はS3で一日の間で頻繁に発生しており、パフォーマンス観点ではお客様からすれば全く気付かれることもないのです。しかし、単一パーティションでリクエストレートが急激に上昇した場合、パーティション分割はリクエストのパフォーマンスにとって弊害になり始めます。では、どのようにそのような非常に高い負荷と毎回うまく付き合えばいいのでしょうか?それには賢いキー名の付け方が重要なのです。

私たちは、S3上でユーザIDやゲームID、または似たようなおおよそ単体では意味を成しえない識別子を使ってコンテンツが形成されているところで新しい負荷がよく発生しているのを見かけます。このような識別子でよくあるのは数値を増加させていくか、複数の数値で構成される日付の形です。この命名方法の残念なところは、S3のスケーリングにおいて2つの懸念になってしまうところです。1つ目は全ての新しいコンテンツが単一パーティション(上のリクエストレートの部分を思い出してください)に保持されてしまうということです。2つ目として、少しだけ古いコンテンツ(一般的にはあまり'hot'でないもの)を持つ全てのパーティションは、その他の命名規則を使った場合よりもより早くコールド状態になってしまいます。これは、全ての古いコンテンツをコールドにしていくことを助長してしまい、実際に各パーティションがサポートできるはずの秒間操作量を無駄にしてしまうことになります。

どんなリクエストレートであってもS3の仕組みを効率よく動かす最も単純な方法は、識別子の数値部分の順序をシンプルに逆にしてあげることです(日付や時間ベースの識別子は秒単位の数値を使います)。こうすることで、識別子はランダムな数値として効率的になります。そして、その識別子が使われるときは、(潜在的な)子パーティションをまたがってトランザクションを展開させることになります。これらの各子パーティションは、(そのうちのいくつかのコンテンツがよりアクセスが集中していたり、逆にそうでなかったとしても)線形とほぼ言い切れるほどスケールして、秒間あたりの性能を無駄にすることもありません。事実、S3ではこのような書き込みパターンの並列性を発見するアルゴリズムさえ持っており、一斉に同一親パーティションから複数の子パーティションを自動的に生成します。これにより、リクエストヒートが発見されたとき、システムの秒間性能を増加させるのです。

Example 1
S3バケット'mynewgame'にゲームIDを徐々に増加させている下記の小さなサンプルを考えてみます。

2134857/gamedata/start.png
2134857/gamedata/resource.rsrc
2134857/gamedata/results.txt
2134858/gamedata/start.png
2134858/gamedata/resource.rsrc
2134858/gamedata/results.txt
2134859/gamedata/start.png
2134859/gamedata/resource.rsrc
2134859/gamedata/results.txt

このような全ての書き込みと読み込みは基本的には常に同一パーティションに向かうのですが、ここで識別子を反転させたとすると以下のようになります。

7584312/gamedata/start.png
7584312/gamedata/resource.rsrc
7584312/gamedata/results.txt
8584312/gamedata/start.png
8584312/gamedata/resource.rsrc
8584312/gamedata/results.txt
9584312/gamedata/start.png
9584312/gamedata/resource.rsrc
9584312/gamedata/results.txt

このパターンはS3に対して下記のようなパーティションを作れと言うのと同義です。

mynewgame/7
mynewgame/8
mynewgame/9

キー数やリクエストレートは時間と共に増加するにつれて、これらパーティションは自動的に更に分割されます。 

鋭い読者の方は、このコツをこれだけで使うのはキーが(現在サポートされている唯一の方法である)辞書順序にリストされるのは使えない事に、お気づきかもしれません。多くのS3の利用事例においては、それは問題になりません。しかしそれ以外のものでは、キーのグループが簡単にリストできるようにするためにはもう少し複雑なスキーマが必要になります。このスキーマはより構造化されたオブジェクトネームスペースでもうまく機能します。ここでのコツは短いハッシュ値を計算して(注意:ここでは衝突は問題にならないので、みせかけのランダムさがあればよいです)、つけたいオブジェクト名の先頭に追加するだけです。このやり方によっても、操作は複数のパーティションに展開されます。一般的なプレフィクスを持つキーでリストするには、複数のリスト操作を並列で実行することで得られます。その場合、各リスト操作は、ハッシュ値でつけたユニークな各文字列のプレフィクスを持つリストに対して行います。

Example 2
下記のようなS3バケット名が'myserverblogs'という名前スキーマがある場合を考えてみます。

service_log.2012-02-27-23.hostname1.mydomain.com
service_log.2012-02-27-23.hostname2.mydomain.com
service_log.2012-02-27-23.hostname3.mydomain.com
service_log.2012-02-27-23.hostname4.mydomain.com
service_log.2012-02-27-23.john.myotherdomain.com
service_log.2012-02-27-23.paul.myotherdomain.com
service_log.2012-02-27-23.george.myotherdomain.com
service_log.2012-02-27-23.ringo.myotherdomain.com
service_log.2012-02-27-23.pete.myotherdomain.com

数千または数万のサーバが1時間にログを送ってくるような場合では、このスキーマは最初の例と同様に支持されなくなります。代わりに、ハッシュ値とドメイン識別子を反対にしたものを連結します。スキーマは下記のようになり、パフォーマンスとリストの柔軟性の両方でベストなバランスを提供します。 

c/service_log.2012-02-27-23.com.mydomain.hostname1
4/service_log.2012-02-27-23.com.mydomain.hostname2
9/service_log.2012-02-27-23.com.mydomain.hostname3
2/service_log.2012-02-27-23.com.mydomain.hostname4
b/service_log.2012-02-27-23.com.myotherdomain.john
7/service_log.2012-02-27-23.com.myotherdomain.paul
2/service_log.2012-02-27-23.com.myotherdomain.george
0/service_log.2012-02-27-23.com.myotherdomain.ringo
d/service_log.2012-02-27-23.com.myotherdomain.pete

これらプレフィクスはASCII文字列に対してのmod-16操作やお好みのどんなハッシュ関数でも構いません。上記の例ではそのポイントだけを表してみました。しかしながら、このスキーマの利点は現状でも明らかです。それは、全ての集合に対してプレフィックスでのリストが可能な点です。全ドメインにまたがって1時間ごと、または単一ドメインで1時間ごとなどです。また、このkeymapパーティションに対して定常的に秒間1500以上の読み込みと書き込みが行うことが出来ます(下記の省略した正規表現で全部で16まで可能です)。

myserverlogs/[0-9a-f]

この時間帯の、mydomain.comの全ログへの16個のプレフィックス化された読み込みは下記のようになります。

http://myserverlogs.s3.amazonaws.com?prefix=0/service_log.2012-02-27-23.com.mydomain
http://myserverlogs.s3.amazonaws.com?prefix=1/service_log.2012-02-27-23.com.mydomain
...
http://myserverlogs.s3.amazonaws.com?prefix=e/service_log.2012-02-27-23.com.mydomain
http://myserverlogs.s3.amazonaws.com?prefix=f/service_log.2012-02-27-23.com.mydomain

見て分かる通り、適切な名前構造を与えることで、データからのとても有用な選択によりアクセスしやすくなります。一般的なパターンは、パーティション可能なハッシュ後に、左側に自分がつけたいキー名の要素からかけ離れているものを持つようなキーで名前付けしてください。


ところで、ハッシュ値は2、3あれば、プレフィックス文字としては十分です。何故かというと、仮に保守的に秒間100操作でパーティション毎に2000万オブジェクトを目標とすると、バケットまたはサブバケットのネームスペースに16進で4文字のハッシュパーティションがあれば、ハッシュで5文字目が必要になる前に、理論的には秒間数百万の操作と1兆以上のユニークキーを支える事が出来るからです。


このハッシュの'トリック'はオブジェクト名がアプリケーションにとって意味がないときにも使えます。最も左側が効果的にランダムな文字列であればどんなUUID(例えばbase64エンコード等です)でもうまく動きます。もしbase64を使うならば、URLセーフな実装を使うことと、'+'や'-'は避ける事、'-'(dash)や'_'(underscore)は置換することをお奨めします。

より詳細に個別のサポートが必要であれば、AWSプレミアムサポートチームが助けてくれます。S3でやりたいことについてお客様が安心して頂けるように、プレミアムサポートチームはお客様がやりたいこと同じようなことが依然に実現されたのを見たことがあるか、よりよいやり方を知っている、またはあなたがそれを見つけるのをお手伝いする事ができます。ぜひご活用ください。

大谷晋平 (@shot6


コメント

トラックバック

この記事のトラックバックURL:
http://www.typepad.com/services/trackback/6a00d8341c534853ef016763d88ef7970b

Amazon S3のパフォーマンスをあげるコツを参照しているブログ:

Featured Event

2016年3 月

    1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30 31