Azureテーブルストレージでパーティションキーを使用してクエリを高速化する方法


10

このクエリの速度を上げるにはどうすればよいですか?

次のクエリを実行するスパン内に約100のコンシューマーがあり1-2 minutesます。これらの各実行は、消費関数の1つの実行を表します。

        TableQuery<T> treanslationsQuery = new TableQuery<T>()
         .Where(
          TableQuery.CombineFilters(
            TableQuery.GenerateFilterCondition("PartitionKey", QueryComparisons.Equal, sourceDestinationPartitionKey)
           , TableOperators.Or,
            TableQuery.GenerateFilterCondition("PartitionKey", QueryComparisons.Equal, anySourceDestinationPartitionKey)
          )
         );

このクエリにより、約5000件の結果が得られます。

完全なコード:

    public static async Task<IEnumerable<T>> ExecuteQueryAsync<T>(this CloudTable table, TableQuery<T> query) where T : ITableEntity, new()
    {
        var items = new List<T>();
        TableContinuationToken token = null;

        do
        {
            TableQuerySegment<T> seg = await table.ExecuteQuerySegmentedAsync(query, token);
            token = seg.ContinuationToken;
            items.AddRange(seg);
        } while (token != null);

        return items;
    }

    public static IEnumerable<Translation> Get<T>(string sourceParty, string destinationParty, string wildcardSourceParty, string tableName) where T : ITableEntity, new()
    {
        var acc = CloudStorageAccount.Parse(Environment.GetEnvironmentVariable("conn"));
        var tableClient = acc.CreateCloudTableClient();
        var table = tableClient.GetTableReference(Environment.GetEnvironmentVariable("TableCache"));
        var sourceDestinationPartitionKey = $"{sourceParty.ToLowerTrim()}-{destinationParty.ToLowerTrim()}";
        var anySourceDestinationPartitionKey = $"{wildcardSourceParty}-{destinationParty.ToLowerTrim()}";

        TableQuery<T> treanslationsQuery = new TableQuery<T>()
         .Where(
          TableQuery.CombineFilters(
            TableQuery.GenerateFilterCondition("PartitionKey", QueryComparisons.Equal, sourceDestinationPartitionKey)
           , TableOperators.Or,
            TableQuery.GenerateFilterCondition("PartitionKey", QueryComparisons.Equal, anySourceDestinationPartitionKey)
          )
         );

        var over1000Results = table.ExecuteQueryAsync(treanslationsQuery).Result.Cast<Translation>();
        return over1000Results.Where(x => x.expireAt > DateTime.Now)
                           .Where(x => x.effectiveAt < DateTime.Now);
    }

これらの実行中、100のコンシューマーがある場合、リクエストがクラスター化してスパイクを形成することがわかります。

ここに画像の説明を入力してください

これらの急増中、リクエストは通常​​1分以上かかります。

ここに画像の説明を入力してください

このクエリの速度を上げるにはどうすればよいですか?


5000の結果は、クエリでほぼ十分にフィルタリングされていないようです。5000結果をコードに転送するだけでは、膨大なネットワーク時間がかかります。後でフィルタリングを行うことを気にしないでください。| クエリでは常に、できるだけ多くの処理を実行します。理想的には、インデックスを取得した行、および/または計算ビューの結果である行に対して。
Christopher

これらの「翻訳」オブジェクトは大きいですか?db全体のようにgettin`ではなく、いくつかのパラメータを取得したくないのですか?
平沢唯

@HirasawaYui no彼らは小さい
l --''''''--------- '' '' '' '' '' ''

より多くのフィルタリングを行う必要があります。5000件の結果を取得しても意味がありません。データを知らないと分からないのですが、より意味のある方法でデータを分割する方法を理解するか、クエリに何らかのフィルタリングを導入する必要があります
4c74356b41

パーティションはいくつありますか?
Peter Bons

回答:


3
  var over1000Results = table.ExecuteQueryAsync(treanslationsQuery).Result.Cast<Translation>();
        return over1000Results.Where(x => x.expireAt > DateTime.Now)
                           .Where(x => x.effectiveAt < DateTime.Now);

これが問題の1つです。クエリを実行してから、これらの「場所」を使用してメモリからクエリをフィルタリングします。クエリを実行する前にフィルターを移動すると、非常に役立ちます。

次に、データベースから取得する行数に制限を設ける必要があります


これは違いを
生まなかった

3

検討できることは3つあります。

。まずWhere、クエリ結果に対して実行する句を削除します。クエリに句をできるだけ含めることをお勧めします(テーブルにインデックスがある場合は、それらも含めます)。今のところ、次のようにクエリを変更できます。

var translationsQuery = new TableQuery<T>()
.Where(TableQuery.CombineFilters(
TableQuery.CombineFilters(
    TableQuery.GenerateFilterCondition("PartitionKey", QueryComparisons.Equal, sourceDestinationPartitionKey),
    TableOperators.Or,
    TableQuery.GenerateFilterCondition("PartitionKey", QueryComparisons.Equal, anySourceDestinationPartitionKey)
    ),
TableOperators.And,
TableQuery.CombineFilters(
    TableQuery.GenerateFilterConditionForDate("affectiveAt", QueryComparisons.LessThan, DateTime.Now),
    TableOperators.And,
    TableQuery.GenerateFilterConditionForDate("expireAt", QueryComparisons.GreaterThan, DateTime.Now))
));

取得するデータが大量にあるため、クエリを並行して実行することをお勧めします。したがって、do whileループ内のExecuteQueryAsyncメソッドParallel.ForEachを、Stephen Toub Parallel.Whileに基づいて記述したものに置き換える必要があります。これにより、クエリの実行時間が短縮されます。これは、Resultこのメソッドを呼び出すときに削除できるため、適切な選択ですが、コードのこの部分の後で説明することには少し制限があります。

public static IEnumerable<T> ExecuteQueryAsync<T>(this CloudTable table, TableQuery<T> query) where T : ITableEntity, new()
{
    var items = new List<T>();
    TableContinuationToken token = null;

    Parallel.ForEach(new InfinitePartitioner(), (ignored, loopState) =>
    {
        TableQuerySegment<T> seg = table.ExecuteQuerySegmented(query, token);
        token = seg.ContinuationToken;
        items.AddRange(seg);

        if (token == null) // It's better to change this constraint by looking at https://www.vivien-chevallier.com/Articles/executing-an-async-query-with-azure-table-storage-and-retrieve-all-the-results-in-a-single-operation
            loopState.Stop();
    });

    return items;
}

そして、あなたはあなたのGetメソッドでそれを呼び出すことができます:

return table.ExecuteQueryAsync(translationsQuery).Cast<Translation>();

ご覧のとおり、メソッド自体は非同期ではなく(名前を変更する必要があります)Parallel.ForEach、非同期メソッドを渡すことと互換性がありません。これがExecuteQuerySegmented代わりに使用した理由です。ただし、パフォーマンスを向上させ、非同期メソッドのすべての利点を活用するには、上記のForEachループをDataflowのメソッドまたはAsyncEnumerator Nugetパッケージの拡張メソッドActionBlockで置き換えることができますParallelForEachAsync

2。パフォーマンスの向上が最大で10%であっても、独立した並列クエリを実行して結果をマージすることをお勧めします。これにより、パフォーマンスに最適なクエリを見つける時間が得られます。ただし、すべての制約をそれに含めることを忘れないでください。どちらの方法をテストして、どちらが問題に適しているかを確認してください。

。それが良い提案かどうかはわかりませんが、それを実行して結果を確認してください。MSDNで説明されているように:

テーブルサービスは、次のようにサーバータイムアウトを適用します。

  • クエリ操作:タイムアウト間隔中に、クエリが最大5秒間実行される場合があります。クエリが5秒以内に完了しない場合、応答には、後続のリクエストで残りのアイテムを取得するための継続トークンが含まれます。詳細については、クエリのタイムアウトとページネーションを参照してください。

  • 挿入、更新、および削除操作:最大タイムアウト間隔は30秒です。30秒は、すべての挿入、更新、削除操作のデフォルトの間隔でもあります。

サービスのデフォルトのタイムアウトよりも短いタイムアウトを指定すると、タイムアウト間隔が使用されます。

したがって、タイムアウトを試して、パフォーマンスの向上があるかどうかを確認できます。


2

残念ながら、以下のクエリは全表スキャンを導入します

    TableQuery<T> treanslationsQuery = new TableQuery<T>()
     .Where(
      TableQuery.CombineFilters(
        TableQuery.GenerateFilterCondition("PartitionKey", QueryComparisons.Equal, sourceDestinationPartitionKey)
       , TableOperators.Or,
        TableQuery.GenerateFilterCondition("PartitionKey", QueryComparisons.Equal, anySourceDestinationPartitionKey)
      )
     );

それを2つのパーティションキーフィルターに分割し、個別にクエリする必要があります。これにより、2つのパーティションスキャンとなり、より効率的に実行されます。


これでおそらく10%の改善が見られましたが、それだけでは十分ではありません
l --''''''--------- '' '' '' '' '' ''

1

したがって、秘密はコードだけでなく、Azureストレージテーブルのセットアップにもあります。

a)Azureでクエリを最適化するための優れたオプションの1つは、キャッシュを導入することです。これにより、全体的な応答時間が大幅に短縮され、これにより、言及したピーク時のボトルネックが回避されます。

b)また、Azureからエンティティをクエリする場合、PartitionKeyとRowKeyの両方を使用するのが最も速い方法です。これらはテーブルストレージの唯一のインデックス付きフィールドであり、これらの両方を利用するクエリは数ミリ秒で返されます。したがって、PartitionKeyとRowKeyの両方を使用してください。

詳細については、こちらをご覧くださいhttps : //docs.microsoft.com/en-us/azure/storage/tables/table-storage-design-for-query

お役に立てれば。


-1

注:これは一般的なDBクエリ最適化のアドバイスです。

ORMが愚かなことをしている可能性があります。最適化を行う場合、抽象化レイヤーをステップダウンすることは問題ありません。そのため、何が起こっているのかを簡単に確認でき、最適化もしやすいように、クエリ言語(SQL?)でクエリを書き直すことをお勧めします。

ルックアップを最適化する鍵は、ソートです!テーブルをソートしたままにしておくと、通常、すべてのクエリでテーブル全体をスキャンするよりもはるかに安価です。そのため、可能であれば、クエリで使用されるキーでテーブルを並べ替えてください。ほとんどのデータベースソリューションでは、これはインデックスキーを作成することで実現されます。

組み合わせが少ない場合にうまく機能する別の戦略は、各クエリを常に最新の個別の(メモリ内の一時)テーブルとして持つことです。したがって、何かが挿入されると、「ビュー」テーブルにも「挿入」されます。一部のデータベースソリューションでは、これを「ビュー」と呼んでいます。

より総当たり的な戦略は、読み込み専用のレプリカを作成して負荷を分散することです。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.