このクエリの速度を上げるにはどうすればよいですか?
次のクエリを実行する1-2 minutes
の範囲内に約100人のコンシューマーがいます。これらの各実行は、消費関数の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分以上かかります。
このクエリの速度を上げるにはどうすればよいですか?
検討できることは3つあります。
1。まず、クエリ結果に対して実行する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
ループをActionBlock
メソッドで置き換えることができます Dataflow またはParallelForEachAsync
AsyncEnumerator Nugetパッケージの拡張メソッド 。
2。パフォーマンスの向上が最大で10%であっても、独立した並列クエリを実行して結果をマージすることをお勧めします。これにより、パフォーマンスに最適なクエリを見つける時間が得られます。ただし、すべての制約を含めることを忘れずに、両方の方法をテストして、どちらが問題に適しているかを確認してください。
。それが良い提案かどうかはわかりませんが、それを実行して結果を確認してください。 [〜#〜] msdn [〜#〜] で説明されているように:
テーブルサービスは、次のようにサーバータイムアウトを適用します。
クエリ操作:タイムアウト間隔中に、クエリが最大5秒間実行される場合があります。クエリが5秒以内に完了しない場合、応答には、後続のリクエストで残りのアイテムを取得するための継続トークンが含まれます。詳細については、クエリのタイムアウトとページネーションを参照してください。
挿入、更新、および削除操作:最大タイムアウト間隔は30秒です。 30秒は、すべての挿入、更新、削除操作のデフォルトの間隔でもあります。
サービスのデフォルトのタイムアウトよりも短いタイムアウトを指定すると、タイムアウト間隔が使用されます。
したがって、タイムアウトで遊んで、パフォーマンスの改善があるかどうかを確認できます。
var over1000Results = table.ExecuteQueryAsync(treanslationsQuery).Result.Cast<Translation>();
return over1000Results.Where(x => x.expireAt > DateTime.Now)
.Where(x => x.effectiveAt < DateTime.Now);
これは問題の1つです。クエリを実行し、これらの「場所」を使用してメモリからクエリをフィルタリングしています。クエリを実行する前にフィルターを移動します。これは非常に役立ちます。
次に、データベースから取得する行の制限を提供する必要があります
残念ながら、以下のクエリは全テーブルスキャンを導入します:
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つのパーティションスキャンになり、より効率的に実行されます。
したがって、秘密はコードだけでなく、Azureストレージテーブルのセットアップにもあります。
a)Azureでクエリを最適化するための優れたオプションの1つは、キャッシュを導入することです。これにより、全体的な応答時間が大幅に短縮され、これにより、言及したピーク時のボトルネックが回避されます。
b)また、Azureからエンティティをクエリする場合、PartitionKeyとRowKeyの両方を使用するのが最も速い方法です。これらはテーブルストレージの唯一のインデックス付きフィールドであり、これらの両方を利用するクエリは数ミリ秒で返されます。したがって、PartitionKeyとRowKeyの両方を使用してください。
詳細はこちらをご覧ください: https://docs.Microsoft.com/en-us/Azure/storage/tables/table-storage-design-for-query
お役に立てれば。