问题 不支持使用TableBatchOperation检索多行?


下面是一段初始化TableBatchOperation的代码,用于在一个批处理中检索两行:

 TableBatchOperation batch = new TableBatchOperation();
 batch.Add(TableOperation.Retrieve("somePartition", "rowKey1"));
 batch.Add(TableOperation.Retrieve("somePartition", "rowKey2")); 
 //second call throws an ArgumentException:
 //"A batch transaction with a retrieve operation cannot contain 
 //any other operation"

如上所述,抛出异常,似乎 不支持在单个批次中检索N行。 这对我来说很重要,因为我需要为每个请求检索大约50行。 这个问题与成本明智一样明显。 您可能知道,Azure表存储定价基于事务量,这意味着50个检索操作比单个批处理操作贵50倍。

我错过了什么吗?

边注 我正在使用新的Azure Storage api 2.0。 我注意到这个问题从未在网上提出过。最近可能添加了这个约束吗?

编辑

我在这里找到了一个相关问题: PartitionKey / RowKey列表上的Azure表存储查询速度非常慢。 似乎在rowkeys上使用带有“或”的TableQuery会产生全表扫描。 这里真的是一个严重的问题......


7722
2018-01-06 04:24


起源

我被卡住了...无法找到一个可接受的解决方案......难怪为什么stackoverflow上的azure问题是如此不活跃:然而Azure还没有准备就绪。 - uzul
您是否有一些您尝试查询的数据类型的示例? - knightpfhor
这是简单的Json字符串,非常小,数以百万计。我创建了一个包含字符串的“Data”属性的通用实体......但是现在我认为我应该选择性能明智的blob ...但我仍然无法在一次往返中检索它们... - uzul
我的意思是你如何设计你的PK / RK,你怎么知道你需要检索哪50个项目。数据的分布是随机的,还是以某种方式相关? - knightpfhor
我正在做一个游戏,PK是一个guid,而且这些项目是相互引用的。但是对于这个数据量,关系sql不是一个选项。 - uzul


答案:


在Azure表存储(ATS)中设计分区密钥(PK)和行密钥(RK)方案时,您应首先考虑如何检索数据。正如你所说的那样,你运​​行的每个查询都需要花钱,但更重要的是时间,所以你需要在一个有效的查询中获得所有数据。您可以在ATS上运行的高效查询具有以下类型:

  • 确切的PK和RK
  • 精确PK,RK范围
  • PK范围
  • PK范围,RK范围

根据你的评论我猜你有一些类似的数据:

PK    RK     Data
Guid1 A      {Data:{...}, RelatedRows: [{PK:"Guid2", RK:"B"}, {PK:"Guid3", RK:"C"}]}
Guid2 B      {Data:{...}, RelatedRows: [{PK:"Guid1", RK:"A"}]
Guid3 C      {Data:{...}, RelatedRows: [{PK:"Guid1", RK:"A"}];}

你已经在Guid1上检索了数据,现在你需要加载Guid2和Guid3。我也假设这些行没有共同点,就像它们都是同一个用户一样。考虑到这一点,我会创建一个额外的“索引表”,它看起来像这样:

PK      RK      Data
Guid1-A Guid2-B {Data:{....}}
Guid1-A Guid3-C {Data:{....}}
Guid2-B Guid1-A {Data:{....}}
Guid2-B Guid1-A {Data:{....}}

其中PK是父亲的组合PK和RK,而RK是子行的组合PK和RK。然后,您可以运行一个查询,该查询返回PK =“Guid1-A”的所有行,您只需一次调用(或整个调用两次)即可获得所有相关数据。这产生的最大开销是在你的写入中,所以现在当你正确行时,你还必须为每个相关行写入行,并确保数据保持最新(这可能不是问题对于你,如果这是一种写一种情况)。

如果我的任何假设是错误的,或者如果您有一些示例数据,我可以使用更相关的示例更新此答案。


5
2018-01-08 03:33





尝试这样的事情:

TableQuery<DynamicTableEntity> query = new TableQuery<DynamicTableEntity>()
                                                .Where(TableQuery.CombineFilters(
                                                    TableQuery.GenerateFilterCondition("PartitionKey", QueryComparisons.Equal, "partition1"),
                                                    TableOperators.And,
                                                    TableQuery.CombineFilters(
                                                        TableQuery.GenerateFilterCondition("RowKey", QueryComparisons.Equal, "row1"),
                                                        TableOperators.Or,
                                                        TableQuery.GenerateFilterCondition("RowKey", QueryComparisons.Equal, "row2"))));

4
2017-09-16 20:55



您知道您可以拥有多少过滤条件的限制吗? - TomSelleck


我知道这是一个老问题,但由于Azure STILL不支持二级索引,它似乎有一段时间是相关的。

我遇到了同样的问题。在我的场景中,我需要在同一个分区中查找数百个项目,其中有数百万行(想象GUID为行键)。我测试了几个选项来查找10,000行

  1. (PK && RK)
  2. (PK && RK1)|| (PK&RK2)|| ...
  3. PK &&(RK1 || RK2 || ...)

我使用的是异步API,最大并行度为10度(最多10个未完成请求)。我还测试了几种不同的批量(10行,50,100)。

Test                        Batch Size  API calls   Elapsed (sec)
(PK && RK)                  1           10000       95.76
(PK && RK1) || (PK && RK2)  10          1000        25.94
(PK && RK1) || (PK && RK2)  50          200         18.35
(PK && RK1) || (PK && RK2)  100         100         17.38
PK && (RK1 || RK2 || … )    10          1000        24.55
PK && (RK1 || RK2 || … )    50          200         14.90
PK && (RK1 || RK2 || … )    100         100         13.43

注意:这些都在同一个分区内 - 只有多个rowkeys。

我很乐意减少API调用的数量。但是作为一个额外的好处,经过的时间也明显减少了,节省了计算成本(至少在我的最后!)。

不足为奇的是,100行的批次提供了最佳的流逝性能。显然还有其他性能方面的考虑,特别是网络使用(例如,#1几乎不使用网络,而其他人则更难推动)

编辑 查询许多rowkeys时要小心。 (或当然)查询的URL长度限制。如果超过长度,查询仍将成功,因为该服务无法判断URL是否被截断。在我们的例子中,我们将组合查询长度限制为大约2500个字符(URL编码!)


3
2018-05-29 19:08



这个限制适用于C#查询对吗?有什么方法可以判断查询有多长?你在这里进行的最后一次测试是在@Kiran Madipally的回答中实现的吗? - TomSelleck


Azure表存储不支持批处理“获取”操作。支持的操作包括:添加,删除,更新和合并。您需要将查询作为单独的请求执行。为了加快处理速度,您可能希望并行执行这些查询。


0
2018-01-06 06:29



谢谢你的回答,但我不能认为这是一个可以接受的解决方案。 - uzul
我可以知道为什么吗? - Gaurav Mantri
您的解决方案意味着每个用户请求“50个线程+ 50次往返”。而且我期待每秒数百个请求。这对您来说是否可扩展?认真!我简直无法相信Azure团队没有考虑过需要在同一个分区上检索N行。多可惜!!! - uzul
鉴于您总是在单个PartitionKey上查询,一种可能的解决方案可能是仅对PartitionKey执行查询,并在RowKey上对客户端进行过滤,而不是在PartitionKey / RowKey组合上发送50个查询。但是,这取决于分区中的实体数量。如果您的RowKey值定义良好,您还可以在查询中包含RowKey范围和PartitionKey。 - Gaurav Mantri
实体数量巨大,可能是数百万。 - uzul


您最好的办法是创建一个Linq / OData选择查询...它将获取您正在寻找的内容。

为了获得更好的性能,您应该为每个分区进行一次查询并同时运行这些查

我没有亲自测试过,但认为它会起作用。


0
2018-01-07 13:13





每个分区有多少个实体?通过一次检索操作,您可以为每个查询提取最多1000条记录。然后,您可以对内存集进行行密钥过滤,只需支付1次操作。

另一种选择是做一个 行键范围查询 在一个操作中检索部分分区。基本上,您指定要返回的行键的上限和下限,而不是整个分区。


0
2018-01-07 13:59





好的,所以批处理检索操作,最好的情况是表查询。不太理想的情况需要并行检索操作。

根据您的PK,RK设计,您可以基于(PK,RK)列表找出您需要执行的最小/最有效的检索/查询操作集。然后,您可以并行获取所有这些内容并整理出客户端的确切答案。

IMAO,这是微软设计小姐添加的 Retrieve 方法 TableBatchOperation class,因为它传达了表存储API不支持的语义。

现在,我不想写一些超级高效的东西,所以我只想在这里留下这个超级简单的解决方案。

var retrieveTasks = new List<Task<TableResult>>();

foreach (var item in list)
{
    retrieveTasks.Add(table.ExecuteAsync(TableOperation.Retrieve(item.pk, item.rk)));
}

var retrieveResults = new List<TableResult>();

foreach (var retrieveTask in retrieveTasks)
{
    retrieveResults.Add(await retrieveTask);
}

这个异步代码块将获取实体 list 并行并将结果存储在 retrieveResults 保留订单。如果您需要获取连续的实体范围,则可以使用范围查询来改善这一点。

有一个最佳点(通过测试你必须找到)是查询更多实体的速度可能比特定批次检索所需的更快/更便宜,然后丢弃您不需要的检索结果。

如果你有一个小分区,你可能会受益于这样的查询:

where pk=partition1 and (rk=rk1 or rk=rk2 or rk=rk3)

如果您的密钥之间的字典(即排序顺序)距离很长,您可能希望并行获取它们。例如,如果将字母存储在表存储中,则获取 a 和 z 相距很远最好是在获取时进行并行检索操作 ab 和 c 最接近的是查询。取 ab  c,和 z 将受益于混合方法。

如果您事先了解所有这些,您可以计算出给定一组PK和RK最好的事情。您对基础数据的排序方式了解得越多,结果就越好。我建议对此进行一般性处理,然后尝试应用您从这些不同查询模式中学到的知识来解决您的问题。


0
2018-01-30 18:22