Azure Table Storage のスケーラブルなパーティション分割戦略を設計する

この記事では、Azure Table Storage でのテーブルのパーティション分割と、効率的なスケーラビリティを確保するために使用できる戦略について説明します。

Azure には、高可用性と拡張性の高いクラウド ストレージが用意されています。 Azure の基になるストレージ システムは、Azure Blob Storage、Azure Table Storage、Azure Queue Storage、Azure Filesなどの一連のサービスを通じて提供されます。

Azure Table Storage は、構造化データを格納するように設計されています。 Azure Storage サービスでは、無制限の数のテーブルがサポートされています。 各テーブルは、大規模なレベルにスケーリングでき、テラバイト単位の物理ストレージを提供できます。 テーブルを最大限に活用するには、データを最適にパーティション分割する必要があります。 この記事では、Azure Table Storage のデータを効率的にパーティション分割するために使用できる戦略について説明します。

テーブル エンティティ

テーブル エンティティは、テーブルに格納されるデータの単位を表します。 テーブル エンティティは、一般的なリレーショナル データベース テーブルの行に似ています。 各エンティティは、プロパティのコレクションを定義します。 各プロパティは、名前、値、および値のデータ型によってキーと値のペアとして定義されます。 エンティティでは、プロパティ コレクションの一部として、次の 3 つのシステム プロパティを定義する必要があります。

  • PartitionKey: PartitionKey プロパティには、エンティティが属するパーティションを識別する文字列値が格納されます。 後で説明するように、パーティションはテーブルのスケーラビリティに不可欠です。 同じ PartitionKey 値を持つエンティティは、同じパーティションに格納されます。

  • RowKey: RowKey プロパティは、各パーティション内のエンティティを一意に識別する文字列値を格納します。 PartitionKeyRowKey は、エンティティの主キーを形成します。

  • Timestamp: Timestamp プロパティは、エンティティの追跡可能性を提供します。 タイムスタンプは、エンティティが最後に変更された日時を示す日付/時刻値です。 タイムスタンプは、エンティティの バージョンと呼ばれることもあります。 テーブル サービスは、すべての挿入および更新操作中にこのプロパティの値を保持するため、タイムスタンプに対する変更は無視されます。

テーブル主キー

Azure エンティティの主キーは、 PartitionKey プロパティと RowKey プロパティを組み合わせて構成されます。 2 つのプロパティは、テーブル内で単一のクラスター化インデックスを形成します。 PartitionKeyRowKey の値のサイズは最大 1024 文字です。 空の文字列も使用できます。ただし、null 値は許可されません。

クラスター化インデックスは 、PartitionKey を昇順に並べ替え、 RowKey を昇順で並べ替えます。 この並べ替え順は、あらゆるクエリ応答で見られるものです。 並べ替え操作では、辞書式比較が使用されます。 文字列値 "111" は、文字列値 "2" の前に表示されます。 場合によっては、並べ替え順序を数値にすることもできます。 数値と昇順で並べ替えるには、固定長のゼロ埋め込み文字列を使用する必要があります。 前の例では、"002" は "111" の前に表示されます。

テーブルのパーティション

パーティションは、同じ PartitionKey 値を持つエンティティのコレクションを表します。 パーティションは常に 1 つのパーティション サーバーから提供されます。 各パーティション サーバーは、1 つ以上のパーティションを提供できます。 パーティション サーバーが 1 つのパーティションを介して一定時間に提供できるエンティティ数には、レート制限があります。 具体的には、パーティションのスケーラビリティ ターゲットは 1 秒あたり 2000 エンティティです。 このスループットは、ストレージ ノードの最小負荷の間に高くなる可能性がありますが、ノードがホットまたはアクティブになると調整されます。

パーティション分割の概念をよりよく説明するために、次の図は、フット レース イベント登録用のデータの小さなサブセットを含むテーブルを示しています。 この図は、 PartitionKey に 3 つの異なる値が含まれているパーティション分割の概念図を示しています。イベントの名前と 3 つの距離 (フル マラソン、ハーフ マラソン、10 km) を組み合わせたものです。 この例では、2 つのパーティション サーバーを使用します。 サーバー A には、ハーフマラソンと 10 km の距離の登録が含まれています。 サーバー B には、フルマラソン距離のみが含まれます。 RowKey 値はコンテキストを提供するために表示されますが、この例では値は意味がありません。

3 つのパーティションを含むテーブルを示す図
3 つのパーティションがあるテーブル

スケーラビリティ

パーティションは常に 1 つのパーティション サーバーから提供され、各パーティション サーバーは 1 つ以上のパーティションを提供できるため、エンティティの提供の効率はサーバーの正常性と相関します。 パーティションのトラフィックが多いサーバーでは、高スループットを維持できない場合があります。 たとえば、前の図では、"2011 ニューヨーク市Marathon__Half" の要求が多数受信されると、サーバー A がホットになりすぎる可能性があります。 ストレージ システムは、サーバーのスループットを向上させるために、パーティションの負荷を他のサーバーに分散させます。 その結果、トラフィックは他の多数のサーバー間で分散されます。 トラフィックの最適な負荷分散を行うには、Azure Table Storage がパーティションをより多くのパーティション サーバーに分散できるように、より多くのパーティションを使用する必要があります。

エンティティ グループ トランザクション

エンティティ グループ トランザクションは、 同じ PartitionKey 値を持つエンティティにアトミックに実装されるストレージ操作のセットです。 エンティティ グループ内のストレージ操作が失敗した場合、エンティティ内のすべてのストレージ操作がロールバックされます。 エンティティ グループ トランザクションは、100 個以下のストレージ操作で構成され、サイズが 4 MiB 以下である可能性があります。 エンティティ グループ トランザクションは、リレーショナル データベースによって提供される原子性、一貫性、分離、持続性 (ACID) セマンティクスの限られた形式で Azure Table ストレージを提供します。

エンティティ グループ トランザクションでは、Azure Table Storage に送信する必要がある個々のストレージ操作の数が減るため、スループットが向上します。 エンティティ グループトランザクションは、経済的利益も提供します。 エンティティ グループ トランザクションは、格納されているストレージ操作の数に関係なく、1 つのストレージ操作として課金されます。 エンティティ グループ トランザクション内のすべてのストレージ操作は 、同じ PartitionKey 値を持つエンティティに影響するため、エンティティ グループ トランザクションを使用する必要があると 、PartitionKey 値の選択が促進される可能性があります。

範囲パーティション

エンティティに一意の PartitionKey 値を使用する場合、各エンティティは独自のパーティションに属します。 使用する一意の値が値の増減の場合、Azure によって範囲パーティションが作成される可能性があります。 範囲パーティションは、範囲クエリのパフォーマンスを向上させるために、連続した一意の PartitionKey 値を持つエンティティをグループ化します。 範囲パーティションがない場合、範囲クエリはパーティション境界またはサーバー境界を超える必要があります。これにより、クエリのパフォーマンスが低下する可能性があります。 PartitionKey のシーケンス値が増加する次の表を使用するアプリケーションについて考えてみましょう。

PartitionKey RowKey
"0001" -
"0002" -
"0003" -
"0004" -
"0005" -
"0006" -

Azure では、最初の 3 つのエンティティを範囲パーティションにグループ化する場合があります。 PartitionKey を条件として使用し、エンティティを "0001" から "0003" に要求するテーブルに範囲クエリを適用すると、エンティティが 1 つのパーティション サーバーから提供されるため、クエリが効率的に実行される可能性があります。 範囲パーティションを作成するタイミングと方法は保証されません。

PartitionKey 値が増減するエンティティを挿入する場合、テーブルの範囲パーティションが存在すると、挿入操作のパフォーマンスに影響する可能性があります。 PartitionKey 値を増やすエンティティの挿入は、追加専用パターンと呼ばれます。 値が小さくなるエンティティの挿入は、先頭に追加のみのパターンと呼ばれます。 挿入要求の全体的なスループットは 1 つのパーティション サーバーによって制限されるため、これらの種類のパターンを使用しないことを検討してください。 これは、範囲パーティションが存在する場合、最初と最後 (範囲) のパーティションに、それぞれ最小および最大 の PartitionKey 値が含まれているためです。 したがって、新しいエンティティ ( PartitionKey 値が順番に小さいか高いエンティティ) の挿入は、終了パーティションの 1 つを対象とします。 次の図は、前の例に基づく範囲パーティションのセットを示しています。 "0007"、"0008"、および "0009" エンティティのセットが挿入された場合、それらは最後の (オレンジ色) パーティションに割り当てられます。

範囲パーティションのセットを示す図
範囲パーティションのセット

挿入操作で、より分散した PartitionKey 値を使用する場合、パフォーマンスに悪影響がないことに注意してください。

データを分析する

インデックスの管理に使用できるリレーショナル データベース内のテーブルとは異なり、Azure Table Storage 内のテーブルにはインデックスを 1 つだけ含めることができます。 Azure Table Storage のインデックスは、常に PartitionKey プロパティと RowKey プロパティで構成されます。

Azure テーブルでは、インデックスを追加したり、ロールアウト後に既存のテーブルを変更したりすることで、テーブルのパフォーマンスチューニングを行うという贅沢はありません。テーブルの設計時にデータを分析する必要があります。 最適なスケーラビリティとクエリと挿入の効率のために考慮すべき最も重要な側面は、 PartitionKeyRowKey の値です。 この記事では、テーブルのパーティション分割方法に直接関連するため、 PartitionKey の選択方法を強調します。

パーティションのサイズ設定

パーティション サイズとは、パーティションに含まれるエンティティの数です。 「スケーラビリティ」で説明するように、パーティション数を増やすと、負荷分散が向上します。 PartitionKey 値の粒度は、パーティションのサイズに影響します。 最も粗いレベルでは、 PartitionKey として 1 つの値が使用される場合、すべてのエンティティは非常に大きい 1 つのパーティション内にあります。 細分性の最も高いレベルでは、 PartitionKey には各エンティティの一意の値を含めることができます。 その結果、各エンティティのパーティションが存在します。 次の表は、細分性の範囲の長所と短所を示しています。

PartitionKey の粒度 パーティション サイズ 長所 短所
単一値 少数のエンティティ バッチ トランザクションは、任意のエンティティで実行できます。

すべてのエンティティはローカルであり、同じストレージ ノードから提供されます。
単一値 多数のエンティティ エンティティ グループトランザクションは、任意のエンティティで可能な場合があります。 エンティティ グループ トランザクションの制限の詳細については、「エンティティ グループ トランザクション の実行」を参照してください。 スケーリングが制限される。

スループットが単一サーバーのパフォーマンスに制限される。
[複数の値] 複数パーティション

パーティション サイズは、エンティティの分散によって異なります。
一部のエンティティではバッチ トランザクションが可能です。

動的パーティション分割が可能です。

単一要求クエリを使用できます (継続トークンはありません)。

より多くのパーティション サーバー間での負荷分散が可能です。
パーティション間でのエンティティの分散が非常に不均一な場合、より大きくアクティブなパーティションのパフォーマンスが制限される可能性があります。
一意の値の数 多数の小さなパーティション テーブルは非常にスケーラブルです。

範囲パーティションは、パーティション間のクエリのパフォーマンスを向上させる可能性があります。
範囲を含むクエリでは、複数のサーバーへのアクセスが必要になる場合があります。

バッチ トランザクションが実行できない。

追加専用またはプリペンド専用のパターンは、挿入スループットに影響する可能性があります。

次の表は、 PartitionKey 値によるスケーリングの影響を示しています。 より優れた負荷分散を提供するため、小さいパーティションを優先することをお勧めします。 一部のシナリオでは、より大きなパーティションが適している場合があり、必ずしも不利であるとは限りません。 たとえば、アプリケーションでスケーラビリティが必要ない場合は、1 つの大きなパーティションが適切な場合があります。

クエリを決定する

クエリは、テーブルからデータを取得します。 Azure Table Storage 内のテーブルのデータを分析する場合は、アプリケーションで使用されるクエリを検討することが重要です。 アプリケーションに複数のクエリがある場合は、優先順位を付ける必要があるかもしれませんが、決定は主観的なものである可能性があります。 多くの場合、主要なクエリは他のクエリと区別できます。 パフォーマンスの点から、クエリはさまざまなカテゴリに分類されます。 テーブルにはインデックスが 1 つしかないため、クエリのパフォーマンスは通常、 PartitionKey プロパティと RowKey プロパティに関連しています。 次の表は、さまざまな種類のクエリとそのパフォーマンス評価を示しています。

[クエリの種類] PartitionKey の一致 RowKey の一致 パフォーマンス評価
行範囲スキャン [Exact] Partial サイズの小さいパーティションの方が優れています。

非常に大きいパーティションでは不適切です。
パーティション範囲スキャン 部分的 部分的 少数のパーティション サーバーに触れるのに適しています。

さらに悪いことに、より多くのサーバーがタッチされています。
テーブル全体スキャン 部分的に一致か、まったく一致せず 部分的に一致か、まったく一致せず さらに悪いことに、パーティションのサブセットがスキャンされます。

すべてのパーティションがスキャンされ、最悪です。

注意

上の表は、相対的なパフォーマンス評価を示しています。 パーティションの数とサイズによって、最終的にクエリの実行方法が決まる場合があります。 たとえば、多数の大きなパーティションを持つテーブルのパーティション範囲スキャンは、少数の小さなパーティションを持つテーブルの完全なテーブル スキャンと比較してパフォーマンスが低下する可能性があります。

前の表に示したクエリの種類は、パフォーマンス評価に基づいて、使用するクエリの最適な種類から最悪の種類までの進行状況を示しています。 ポイント クエリは、テーブルのクラスター化インデックスを最大限に活用するため、使用するのに最適なタイプのクエリです。 次のポイント クエリでは、フィート レース登録テーブルのデータを使用します。

http://<account>.windows.core.net/registrations(PartitionKey=”2011 New York City Marathon__Full”,RowKey=”1234__John__M__55”)  
  

アプリケーションで複数のクエリを使用する場合、そのすべてをポイント クエリにすることはできません。 パフォーマンスの点で、範囲クエリはポイント クエリに次ぐ評価となっています。 範囲クエリには、行範囲スキャンとパーティション範囲スキャンの 2 つのタイプがあります。 行範囲スキャンでは、1 つのパーティションだけを指定します。 操作は 1 つのパーティション サーバーで行われるため、通常、行範囲スキャンはパーティション範囲スキャンよりも効率的です。 ただし、行範囲スキャンのパフォーマンスの重要な要因は、クエリの選択性です。 クエリの選択度は、一致行を見つけるために反復処理が必要な行数を決定します。 クエリの選択度が高いほど、行範囲スキャンの効率が高くなります。

クエリの優先順位を評価するには、各クエリの頻度と応答時間の要件を検討します。 頻繁に実行されるクエリの優先順位が高くなる場合があります。 ただし、重要だが使用頻度の低いクエリでは、優先順位リストの上位にランク付けできる待機時間要件が低くなる可能性があります。

PartitionKey の値を選択する

テーブルの設計の中核となるのは、スケーラビリティ、アクセスに使用されるクエリ、ストレージ操作の要件です。 選択する PartitionKey 値によって、テーブルのパーティション分割方法と、使用できるクエリの種類が決まります。 ストレージ操作 (特に挿入) も 、PartitionKey 値の選択に影響する可能性があります。 PartitionKey 値の範囲は、単一の値から一意の値までです。 また、複数の値を使用して作成することもできます。 エンティティ プロパティを使用して 、PartitionKey 値を形成できます。 または、アプリケーションで値を計算することもできます。 次のセクションでは、重要な考慮事項について説明します。

エンティティ グループ トランザクション

開発者はまず、アプリケーションでエンティティ グループ トランザクション (バッチ更新) を使用するかどうかを検討する必要があります。 エンティティ グループ トランザクションでは、エンティティに同じ PartitionKey 値が必要です。 また、バッチ更新はグループ全体に対して行われるため、 PartitionKey 値の選択が制限される場合があります。 たとえば、現金のトランザクションが維持される銀行アプリケーションでは、現金のトランザクションをテーブルに原子的に挿入する必要があります。 現金取引は、借方と貸方の両方を表し、ネットをゼロにする必要があります。 この要件は、トランザクションの両側で異なるアカウント番号が使用されるため、アカウント番号を PartitionKey 値のどの部分としても使用できないことを意味します。 代わりに、トランザクション ID を選択することをお勧めします。

パーティション

パーティション番号とサイズは、負荷がかかっているテーブルのスケーラビリティに影響します。 また、 PartitionKey 値の粒度によっても制御されます。 特に値の分布を予測するのが難しい場合は、パーティション サイズに基づいて PartitionKey を決定するのは困難な場合があります。 経験則からは、サイズが小さい複数のパーティションを使用することが推奨されます。 多くのテーブル パーティションを使用すると、Azure Table Storage でパーティションが提供されるストレージ ノードを簡単に管理できます。

PartitionKey に対して一意の値またはより細かい値を選択すると、パーティションは小さくなりますが、より多くなります。 これは、システムが多数のパーティションを負荷分散して、多くのパーティションに負荷を分散できるため、一般的に有利です。 ただし、多数のパーティションの存在が、パーティションをまたぐ範囲クエリにどのように影響するかを考慮する必要があります。 これらの種類のクエリは、クエリを満たすために複数のパーティションにアクセスする必要があります。 パーティションが多数のパーティション サーバーに分散されている可能性があります。 クエリがサーバーの境界をまたぐ場合には、継続トークンが返される必要があります。 継続トークンは、次の PartitionKey または RowKey 値を指定して、クエリの次のデータ セットを取得します。 言い換えると、継続トークンはサービスに対する要求を少なくとも 1 つ表します。これにより、クエリの全体的なパフォーマンスが低下する可能性があります。

クエリの選択度は、クエリのパフォーマンスに影響するもう 1 つの要因です。 クエリの選択度とは、各パーティションに対して反復される行数の指標です。 クエリの選択度が高いほど、必要な行を返すクエリの効率が高くなります。 範囲クエリの全体的なパフォーマンスは、タッチする必要があるパーティション サーバーの数や、クエリの選択性によって異なります。 また、テーブルにデータを挿入する場合は、追加専用または先頭に付加のみのパターンを使用しないようにする必要があります。 これらのパターンを使用する場合、小さいパーティションと多数のパーティションを作成する場合でも、挿入操作のスループットが制限される可能性があります。 追加専用パターンとプリペンド専用パターンについては、「 範囲パーティション」で説明します。

クエリ

使用するクエリを把握すると、 PartitionKey 値に対して考慮すべき重要なプロパティを判断するのに役立ちます。 クエリで使用するプロパティは、 PartitionKey 値の候補です。 次の表に、 PartitionKey 値を決定する方法の一般的なガイドラインを示します。

エンティティに... 操作
キー プロパティが 1 つある PartitionKey として使用します。
キー プロパティが 2 つある 1 つを PartitionKey として使用し、もう 1 つを RowKey として使用します。
キー プロパティが 2 つより多い 連結された値の複合キーを使用します。

同じように優勢なクエリが複数ある場合は、必要な異なる RowKey 値を使用して、情報を複数回挿入できます。 アプリケーションでは、2 番目 (または 3 番目など) の行を管理します。 この種類のパターンを使用して、クエリのパフォーマンス要件を満たすことができます。 次の例では、フット レース登録の例のデータを使用します。 これには、次の 2 つの主要なクエリがあります。

  • BIB ナンバー (ゼッケン番号) によるクエリ
  • 年齢によるクエリ

重要なクエリを両方とも実行するには、2 つの行をエンティティ グループ トランザクションとして挿入します。 次の表に、このシナリオの PartitionKey プロパティと RowKey プロパティを示します。 RowKey 値は、アプリケーションが 2 つの値を区別できるように、bib と age のプレフィックスを提供します。

PartitionKey RowKey
2011 New York City Marathon__Full BIB:01234__John__M__55
2011 New York City Marathon__Full AGE:055__1234__John__M

この例では、 PartitionKey の値が同じであるため、エンティティ グループ トランザクションが可能です。 グループ トランザクションは、挿入操作のアトミック性を提供します。 このパターンは異なる PartitionKey 値で使用できますが、同じ値を使用してこの利点を得ることをお勧めします。 それ以外の場合は、異なる PartitionKey 値を使用するアトミック トランザクションを確保するために、追加のロジックを記述する必要がある場合があります。

ストレージ操作

Azure Table Storage 内のテーブルでは、クエリからの読み込みだけでなく、読み込みが発生する可能性があります。 また、挿入、更新、削除などのストレージ操作からの読み込みも発生する可能性があります。 テーブルに対して実行するストレージ操作の種類と速度を検討します。 これらの操作を頻繁に実行しない場合は、それらの操作について心配する必要がない場合があります。 ただし、短時間で多数の挿入を実行するなどの頻繁な操作の場合は、選択した PartitionKey 値の結果としてこれらの操作がどのように処理されるかを考慮する必要があります。 重要な例は、追加専用パターンと前付け専用パターンです。 追加専用パターンとプリペンド専用パターンについては、「 範囲パーティション」で説明します。

追加専用または前置専用のパターンを使用する場合は、後続の挿入時に PartitionKey に一意の昇順または降順の値を使用します。 このパターンを頻繁な挿入操作と組み合わせると、テーブルは優れたスケーラビリティで挿入操作を処理できなくなります。 Azure では操作要求を他のパーティション サーバーに負荷分散できないため、テーブルのスケーラビリティが影響を受けます。 その場合は、GUID 値など、ランダムな値の使用を検討してください。 その後、パーティション サイズは小さいままで、ストレージ操作中も負荷分散を維持できます。

テーブル パーティションストレステスト

PartitionKey の値が複雑な場合、または他の PartitionKey マッピングとの比較が必要な場合は、テーブルのパフォーマンスをテストする必要がある場合があります。 このテストでは、ピーク負荷時のパーティションのパフォーマンスを調べる必要があります。

ストレス テストを実施するには

  1. テスト テーブルを作成します。
  2. ターゲットとする PartitionKey 値を持つエンティティが含まれるように、テスト テーブルにデータを読み込みます。
  3. アプリケーションを使用して、テーブルへのピーク時の負荷をシミュレートします。 手順 2 の PartitionKey 値を使用して、1 つのパーティションをターゲットにします。 この手順はアプリケーションごとに異なりますが、シミュレーションには必要なすべてのクエリとストレージ操作を含める必要があります。 1 つのパーティションを対象とするようにアプリケーションを調整する必要がある場合があります。
  4. テーブル上で GET 操作または PUT 操作のスループットを確認します。

スループットは、1 つのサーバー上の 1 つのパーティション対して指定されている制限と実際の値とを比較して確認します。 パーティションは、1 秒あたり 2,000 個のエンティティに制限されます。 パーティションのスループットが 1 秒あたり 2,000 エンティティを超える場合、運用設定でサーバーがホットすぎる可能性があります。 この場合、 PartitionKey の値が粗すぎるため、十分なパーティションが存在しないか、パーティションが大きすぎる可能性があります。 パーティションがより多くのサーバーに分散されるように PartitionKey の値を変更する必要がある場合があります。

負荷分散

パーティションレイヤーでの負荷分散は、パーティションが熱くなりすぎると発生します。 パーティションがホットすぎると、パーティション (特にパーティション サーバー) はターゲットのスケーラビリティを超えて動作します。 Azure Storage の場合、各パーティションのスケーラビリティ ターゲットは 1 秒あたり 2000 エンティティです。 負荷分散は、分散ファイル システム (DFS) レイヤーでも行われます。

DFS レイヤーでの負荷分散は I/O 負荷を処理し、この記事の範囲外です。 スケーラビリティ ターゲットを超えた直後は、パーティション レイヤーでの負荷分散は行われません。 代わりに、システムは数分待ってから負荷分散プロセスを開始します。 これにより、パーティションは実際に過負荷になります。 システムが自動的にタスクを実行するため、負荷分散をトリガーする生成された負荷を持つパーティションを準備する必要はありません。

テーブルに特定の負荷が設定されている場合、システムは実際の負荷に基づいてパーティションのバランスを取ることができ、パーティションの分散が大幅に異なる可能性があります。 パーティションを準備する代わりに、タイムアウトエラーとサーバービジーエラーを処理するコードを記述することを検討してください。 エラーは、システムが負荷分散を行っているときに返されます。 再試行戦略を使用してこれらのエラーを処理することで、アプリケーションはピーク時の負荷をより適切に処理できます。 再試行戦略については、次のセクションで詳細に説明します。

負荷分散が行われると、パーティションは数秒間オフラインになります。 オフライン期間中、システムはパーティションを別のパーティション サーバーに再割り当てします。 データはパーティション サーバーによって格納されていないことに注意することが重要です。 代わりに、パーティション サーバーは、DFS レイヤーからエンティティを提供します。 データはパーティション レイヤーに格納されないため、パーティションを別のサーバーに移動することは高速なプロセスです。 この柔軟性により、アプリケーションで発生する可能性があるダウンタイムが大幅に制限されます。

再試行戦略

データ更新を失わないよう、アプリケーションでストレージ操作の失敗を処理することが重要です。 一部のエラーでは、再試行戦略は必要ありません。 たとえば、401 Unauthorized エラーを返す更新プログラムは、401 エラーを解決する再試行の間でアプリケーションの状態が変わらない可能性があるため、操作を再試行してもメリットがありません。 ただし、サーバーのビジー状態やタイムアウトなどのエラーは、テーブルのスケーラビリティを提供する Azure の負荷分散機能に関連しています。 エンティティにサービスを提供するストレージ ノードがホットになると、Azure はパーティションを他のノードに移動することで負荷のバランスを取ります。 この間、パーティションにアクセスできない可能性があり、その結果、サーバーのビジー状態またはタイムアウト エラーが発生します。 最終的に、パーティションが再び有効になり、更新が再開されます。

再試行戦略は、サーバービジーまたはタイムアウト エラーに適しています。 ほとんどの場合、再試行ロジックから 400 レベルのエラーと 500 レベルのエラーを除外できます。 除外できるエラーには、501 未実装と 505 HTTP バージョンがサポートされていません。 その後、サーバービジー (503) やタイムアウト (504) など、最大 500 レベルのエラーの再試行戦略を実装できます。

アプリケーションの 3 つの一般的な再試行方法から選択できます。

  • [再試行なし]: 再試行は行われません。
  • バックオフの修正: 操作は、一定のバックオフ値を使用して N 回再試行されます。
  • 指数バックオフ: 指数バックオフ値を使用して、操作が N 回再試行されます。

再試行なし戦略は、操作の失敗処理としては簡単な (そして回避的な) 方法です。 ただし、再試行なし戦略はあまり役に立ちません。 再試行をまったく課さないという戦略には、操作が失敗した後、データが正しく格納されないという明白な危険があります。 より良い戦略は、固定バックオフ戦略を使用する方法です。 は、同じバックオフ期間で操作を再試行する機能を提供します。

ただし、この戦略は、拡張性の高いテーブルを処理するために最適化されていません。 多くのスレッドまたはプロセスが同じ期間待機している場合は、競合が発生する可能性があります。 推奨される再試行戦略は、各再試行が最後の試行よりも長い指数バックオフを使用する方法です。 これは、イーサネットなどのコンピューター ネットワークで使用される衝突回避アルゴリズムに似ています。 指数バックオフでは、ランダムな要素を使用して算出した差分を追加することによって、次の再試行間隔を求めます。 また、バックオフ値は上限値と下限値の間に制限されます。 次の計算式を使用すると、指数アルゴリズムに基づいて次回のバックオフ値を計算できます。

y = Rand(0.8z, 1.2z)(2x-1

y = Min(zmin + y, zmax

各値の説明:

z = 既定のバックオフ (ミリ秒)

zmin = 既定の最小バックオフ (ミリ秒)

zmax = 既定の最大バックオフ (ミリ秒)

x = 再試行回数

y = バックオフ値 (ミリ秒)

Rand (random) 関数で使用される 0.8 乗数と 1.2 乗数は、元の値の±20% 以内の既定のバックオフのランダム分散を生成します。 ±20% の範囲は、ほとんどの再試行戦略で許容され、それ以上の競合を防ぎます。 数式は、次のコードを使用して実装できます。

int retries = 1;  
  
// Initialize variables with default values  
var defaultBackoff = TimeSpan.FromSeconds(30);  
var backoffMin = TimeSpan.FromSeconds(3);  
var backoffMax = TimeSpan.FromSeconds(90);  
  
var random = new Random();  
  
double backoff = random.Next(  
    (int)(0.8D * defaultBackoff.TotalMilliseconds),   
    (int)(1.2D * defaultBackoff.TotalMilliseconds));  
backoff *= (Math.Pow(2, retries) - 1);  
backoff = Math.Min(  
    backoffMin.TotalMilliseconds + backoff,   
    backoffMax.TotalMilliseconds);  
  

まとめ

Azure Table Storage のアプリケーションでは、多数のストレージ ノード間でパーティションが管理および再割り当てされるため、大量のデータを格納できます。 データをパーティション分割することで、テーブルのスケーラビリティを制御できます。 テーブル スキーマを定義するときに事前に計画して、効率的なパーティション分割戦略を確実に実装します。 具体的には、 PartitionKey 値を選択する前に、アプリケーションの要件、データ、クエリを分析します。 システムがトラフィックに応答すると、各パーティションが異なるストレージ ノードに再割り当てされる場合があります。 パーティション ストレス テストを使用して、テーブルに正しい PartitionKey 値があることを確認します。 このテストは、パーティションがホットすぎる時期を判断するのに役立ち、必要なパーティション調整を行うのに役立ちます。

アプリケーションで断続的なエラーが処理され、データが永続化されるようにするには、バックオフを使用して再試行戦略を使用します。 Azure Storage クライアント ライブラリで使用される既定の再試行ポリシーには、競合を回避し、アプリケーションのスループットを最大化する指数バックオフがあります。