我们如何提高查询速度?
在执行以下查询的1-2 minutes
范围内我们大约有[<100个消费者。这些运行中的每个运行都代表一个消耗函数的运行。
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分钟以上的时间:
我们如何提高查询速度?
var over1000Results = table.ExecuteQueryAsync(treanslationsQuery).Result.Cast<Translation>();
return over1000Results.Where(x => x.expireAt > DateTime.Now)
.Where(x => x.effectiveAt < DateTime.Now);
这里是问题之一,您正在运行查询,然后使用这些“ wheres”从内存中对其进行过滤。将过滤器移至查询运行之前,这会很有帮助。第二,您必须提供从数据库中检索的行数限制
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 package中的方法。2
。执行独立的并行查询然后合并结果是一个不错的选择,即使其性能提高最多为10%。这使您有时间能够找到最佳的性能友好查询。但是,永远不要忘记将所有约束都包括在其中,并测试两种方式以了解哪种更适合您的问题。3
。我不确定这是否是个好建议,但可以这样做并查看结果。如MSDN中所述:Table服务强制服务器超时,如下所示:因此,您可以玩超时并检查是否有任何性能改进。
- 查询操作:在超时间隔内,查询可能会针对最多五秒钟。如果查询未在以下时间内完成在五秒钟的时间间隔内,响应包括连续令牌用于在后续请求中检索剩余项目。查看查询超时和分页以获取更多信息。
- 插入,更新和删除操作:最大超时间隔为30秒。三十秒也是所有设备的默认间隔插入,更新和删除操作。
如果您指定的超时时间小于服务的默认超时时间,则会使用您的超时时间间隔。
全表扫描:
TableQuery<T> treanslationsQuery = new TableQuery<T>()
.Where(
TableQuery.CombineFilters(
TableQuery.GenerateFilterCondition("PartitionKey", QueryComparisons.Equal, sourceDestinationPartitionKey)
, TableOperators.Or,
TableQuery.GenerateFilterCondition("PartitionKey", QueryComparisons.Equal, anySourceDestinationPartitionKey)
)
);
您应该将其分成两个分区键过滤器并分别查询,这将成为两个,并且执行效率更高。分区扫描
a)在Azure中优化查询的主要选择之一是引入缓存。这将大大减少您的总体响应时间,从而避免您提到的高峰时段的瓶颈。
b)此外,在Azure之外查询实体时,最快的方法是使用PartitionKey和RowKey。这些是表存储中唯一的索引字段,使用这两个字段的任何查询都将在几毫秒内返回。因此,请确保同时使用PartitionKey和RowKey。
在此处查看更多详细信息:https://docs.microsoft.com/en-us/azure/storage/tables/table-storage-design-for-query
希望这会有所帮助。
优化查找的关键是排序!与对每个查询扫描整个表相比,对表进行排序通常要便宜得多!因此,如果可能,请按查询中使用的键对表进行排序。在大多数数据库解决方案中,这是通过创建索引键来实现的。
如果组合很少,另一种有效的策略是使每个查询作为始终保持最新状态的单独的(内存中的临时)表。因此,当插入某些内容时,它也会“插入”到“视图”表中。一些数据库解决方案将此称为“视图”。
更粗暴的策略是创建只读副本以分配负载。