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


10

我们如何提高查询速度?

在执行以下查询的范围内,我们大约有100个消费者1-2 minutes。这些运行中的每个运行都代表一个消耗函数的运行。

        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

那些“翻译”对象大吗?您为什么不喜欢像整个数据库那样获取一些参数而不是gettin`?
平泽唯

@HirasawaYui不,他们很小
l --''''''---------''''''''''''

您应该进行更多过滤,提取5000个结果似乎毫无意义。在不知道您的数据的情况下无法告诉您,但我想您需要找出一种以更有意义的方式对数据进行分区的方法,或者在查询中引入某种过滤方式
4c74356b41

有多少个不同的分区?
彼得·邦斯

Answers:


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))
));

因为您要检索的数据量很大,所以最好并行运行查询。因此,您应该使用我基于Stephen Toub Parallel.While编写的方法替换do whileloop内部ExecuteQueryAsync方法。这样,它将减少查询的执行时间。这是一个不错的选择,因为您可以在调用此方法时将其删除,但是在代码的这一部分之后我将讨论它有一个局限性:Parallel.ForEachResult

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的方法,数据流ParallelForEachAsync从扩展方法AsyncEnumerator NuGet包

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)
      )
     );

您应该将其拆分为两个“分区键”过滤器,然后分别进行查询,这将成为两次分区扫描,并且执行效率更高。


我们看到此效果可能会提高10%,但这还不够
l --''''''---------''''''''''

1

因此,秘诀不仅在于代码,还在于设置Azure存储表。

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

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

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

希望这可以帮助。


-1

注意:这是常规的数据库查询优化建议。

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

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

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

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.