不支持使用TableBatchOperation检索多行?

时间:2013-01-06 04:24:50

标签: azure nosql azure-storage azure-table-storage

这是一段初始化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。 我注意到这个问题从未在网上提出过。最近可能添加了这个约束吗?

修改

我在这里找到了一个相关的问题:Very Slow on Azure Table Storage Query on PartitionKey/RowKey List。 似乎在rowkeys上使用带有“或”的TableQuery会产生全表扫描。 这里真的是一个严重的问题......

7 个答案:

答案 0 :(得分:5)

在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”的所有行,您只需一次调用(或整个调用两次)即可获得所有相关数据。这产生的最大开销是在你的写入中,所以现在当你正确行时,你还必须为每个相关行写入行,并确保数据保持最新(这可能不是问题对于你,如果这是一种写一种情况)。

如果我的任何假设是错误的,或者你有一些示例数据,我可以用更相关的例子来更新这个答案。

答案 1 :(得分:4)

尝试这样的事情:

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

答案 2 :(得分:3)

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

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

  1. (PK&amp;&amp; RK)
  2. (PK&amp;&amp; RK1)|| (PK&amp; RK2)|| ...
  3. PK&amp;&amp; (RK1 || RK2 || ...)
  4. 我使用的是Async 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
    

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

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

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

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

答案 3 :(得分:0)

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

答案 4 :(得分:0)

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

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

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

答案 5 :(得分:0)

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

另一种选择是在一次操作中执行Row Key range query以检索部分分区。基本上,您指定要返回的行键的上限和下限,而不是整个分区。

答案 6 :(得分:0)

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

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

IMAO,微软将Retrieve方法添加到TableBatchOperation类是因为它传达了表存储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)

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

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