这是一段初始化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会产生全表扫描。 这里真的是一个严重的问题......
答案 0 :(得分:5)
在Azure表存储(ATS)中设计分区密钥(PK)和行密钥(RK)方案时,首要考虑因素应该是如何检索数据。正如您所说的那样,您运行的每个查询都需要花钱,但更重要的是时间,因此您需要在一个有效的查询中获取所有数据。您可以在ATS上运行的有效查询属于以下类型:
根据您的评论我猜你有一些类似的数据:
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行
我使用的是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)
如果您的密钥之间的字典(即排序顺序)距离很长,您可能想要并行获取它们。例如,如果您将字母存储在表存储中,那么提取相距很远的a
和z
最好在获取a
,b
时获取并行检索操作。最接近查询的c
是最接近的。获取a
,b
c
和z
将受益于混合方法。
如果您事先了解所有这些,您可以计算出给定一组PK和RK最好的事情。您对基础数据的排序方式了解得越多,结果就越好。我建议对此进行一般性处理,然后尝试应用您从这些不同查询模式中学到的知识来解决您的问题。