如何在天蓝色表存储中使用partitionkey加快查询速度

问题描述 投票:10回答:5

我们如何提高查询速度?

在执行以下查询的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个使用者时,如您所见,请求将聚集并形成峰值:

enter image description here

在这些高峰期间,请求通常需要1分钟以上的时间:

enter image description here

我们如何提高查询速度?

c# azure azure-table-storage azure-virtual-network azure-tablequery
5个回答
3
投票
var over1000Results = table.ExecuteQueryAsync(treanslationsQuery).Result.Cast<Translation>(); return over1000Results.Where(x => x.expireAt > DateTime.Now) .Where(x => x.effectiveAt < DateTime.Now);
这里是问题之一,您正在运行查询,然后使用这些“ wheres”从内存中对其进行过滤。将过滤器移至查询运行之前,这会很有帮助。

第二,您必须提供从数据库中检索的行数限制


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的原因。但是,要使其具有更高的性能并利用异步方法的所有优点,您可以在ForEachActionBlock扩展名中将上述Dataflow循环替换为ParallelForEachAsync方法AsyncEnumerator Nuget package中的方法。

2

。执行独立的并行查询然后合并结果是一个不错的选择,即使其性能提高最多为10%。这使您有时间能够找到最佳的性能友好查询。但是,永远不要忘记将所有约束都包括在其中,并测试两种方式以了解哪种更适合您的问题。

3

。我不确定这是否是个好建议,但可以这样做并查看结果。如MSDN中所述:
Table服务强制服务器超时,如下所示:

  • 查询操作:在超时间隔内,查询可能会针对最多五秒钟。如果查询未在以下时间内完成在五秒钟的时间间隔内,响应包括连续令牌用于在后续请求中检索剩余项目。查看查询超时和分页以获取更多信息。
  • 插入,更新和删除操作:最大超时间隔为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) ) );
您应该将其分成两个分区键过滤器并分别查询,这将成为两个

分区扫描

,并且执行效率更高。

1
投票
因此,秘密不仅在代码中,而且在设置Azure存储表中。

a)在Azure中优化查询的主要选择之一是引入缓存。这将大大减少您的总体响应时间,从而避免您提到的高峰时段的瓶颈。

b)此外,在Azure之外查询实体时,最快的方法是使用PartitionKey和RowKey。这些是表存储中唯一的索引字段,使用这两个字段的任何查询都将在几毫秒内返回。因此,请确保同时使用PartitionKey和RowKey。

在此处查看更多详细信息:https://docs.microsoft.com/en-us/azure/storage/tables/table-storage-design-for-query

希望这会有所帮助。


-1
投票
ORM可能正在做一些愚蠢的事情。在进行优化时,可以降低抽象层。因此,我建议用查询语言(SQL?)重写查询,以使其更容易了解正在发生的事情,并且也更易于优化。

优化查找的关键是排序!与对每个查询扫描整个表相比,对表进行排序通常要便宜得多!因此,如果可能,请按查询中使用的键对表进行排序。在大多数数据库解决方案中,这是通过创建索引键来实现的。

如果组合很少,另一种有效的策略是使每个查询作为始终保持最新状态的单独的(内存中的临时)表。因此,当插入某些内容时,它也会“插入”到“视图”表中。一些数据库解决方案将此称为“视图”。

更粗暴的策略是创建只读副本以分配负载。

© www.soinside.com 2019 - 2024. All rights reserved.