Azure表 - 分区键和行键 - 正确的选择

时间:2013-12-26 13:00:23

标签: azure azure-table-storage

我是Azure表的新手并阅读了很多文章,但是鉴于其基础,我希望对上述内容有所保证。

我的数据类似于:

CustomerId, GUID
TripId, GUID
JourneyStep, GUID
Time, DataTime
AverageSpeed, int

根据我所读到的,CustomerId是一个好的PartitionKey吗?我陷入困境的地方是CustomerIdTripId的组合,它们没有形成唯一的行。我TripId作为行键的理由是因为每个查询都是基于CustomerIdTripId的数据集。

仅针对具体情况,CustomerId显然是独一无二的,TripId表示车辆中的一次旅行,在此旅程中,JourneyStep代表该旅程中的一个单位,可能是10个步骤或1000.

目的是将数据汇总到更多表中,每个级别用于不同的目的。在最集中的级别,客户将获得一些分数。

数据量显然很大,因此需要从一开始就考虑查询性能。

更新

根据要求,解决方案适用于车辆远程信息处理,因此请在您自己的车中考虑自己。 Blackbox将数据发送到服务器,然后服务器将数据传递给Azure Tables。在关系数据库术语中,我将有一个Customer表和一个带有外键的trip表返回到customer表。

黑匣子会自动生成tripId。从查询的角度来看,TripId不需要按日期时间存储,但从查询性能的角度来看可能是相关的。

查询将分为两部分:

  1. 显示每位客户的单程旅行地图,然后按客户过滤,然后按行至然后将每一行(journeystep)迭代到地图。

  2. 根据客户的说法,我会对每次旅行进行评分,然后检索上个月的旅行,以汇总分数。我确实有SQL数据库来丰富客户记录等数据,但对于卷数据(旅行数据),我希望使用Azure表。

  3. 来自第二个查询的聚合可能会存储在一个单独的表中,因此,如果某人在一个月内进行了10次旅行,我会运行第二个查询,该查询将对每次旅行进行评分,然后为该月的所有旅行生成一个分数。存储两个答案,因此可能是一个旅行聚合表和一个月度聚合表。

5 个答案:

答案 0 :(得分:3)

关于分区密钥的事情是它代表一个逻辑分组;例如,您无法插入跨多个分区键的数据。同样,具有相同分区的行可能存储在同一服务器上,从而可以快速检索给定分区键的所有数据。

因此,重要的是要查看您的域并找出您可能使用的聚合。

如果我正确理解您的域模型,我实际上很想将TripId用作分区键,将JourneyStep用作行键。 您需要单独设置一个表,列出属于给定客户的所有旅行ID - 这种情况有意义,因为您可能希望在这样的表格中存储一些数据,例如“旅行名称”等。

答案 1 :(得分:2)

您的设计必须与您的查询相关。您可以根据2列PartitionKey和RowKey过滤数据。 PartitionKey是您最重要的列,因为您的查询将首先点击该列。

在您的情况下,CustomerId应该是您的 PartitionKey ,因为大部分时间您都会尝试根据客户来获取数据。 (您可能还需要为您的客户列表保留另一个表)

现在,RowKey可以是您的tripIdtime。如果我是你,我可能会使用rowKey作为yyyyMMddHHmm|tripId格式,这将允许您基于startWith和endWidth选项进行查询。

答案 2 :(得分:2)

添加到@Frans回答:

您可以做的一件事是为每个客户创建一个单独的表。所以你可以把表命名为Customer。这样,每个客户的数据都可以很好地分隔到不同的表中。然后,您可以使用TripId作为PartitionKey,然后使用JourneyStep作为RowKey,如@Frans所示。为了存储一些关于旅行的元数据,而不是进入一个单独的表,我仍然会使用相同的表,但在这里我会将RowKey保持为空,并在那里提供有关旅行的其他信息。

答案 3 :(得分:2)

我建议您考虑以下PK / RK设计方法。我相信它会为您概述的查询带来最佳性能:

PartitionKey:CustomerId和TripId的组合。

string.Format("{0}_{1}", customerId.ToString(), tripId.ToString())

RowKey:DateTime.MaxValue.Ticks的组合 - Time.Ticks格式化为带有JourneyStep的大型0填充字符串。

string.Format("{0}_{1}", (DateTime.MaxValue.Ticks - Time.Ticks).ToString("00000000000000000"), JourneyStep.ToString())

此类组合将允许您“快速”执行以下查询。

  • 仅通过CustomerId获取数据。示例:context.Trips.Where(n=>string.Compare(id + "_00000000-0000-0000-0000-000000000000", n.PartitionKey) <= 0 && string.Compare(id+"_zzzzzzzz-zzzz-zzzz-zzzz-zzzzzzzzzzzz") >=0).AsTableServiceQuery(context);
  • 通过CustomerId和TripId获取数据。示例:context.Trips.Where(n=>n.PartitionKey == string.Format("{0}_{1}", customerId, tripId).AsTableServiceQuery(context);
  • 如果您使用“Take”功能通过CustomerId或CustomerId / TripId进行搜索,则获取最后X个旅程步骤
  • 通过将时间戳转换为Ticks
  • ,通过日期范围查询获取数据
  • 使用单个存储事务将数据保存到行程中(假设步骤少于100个)

如果您可以保证每次旅行中的步骤时间的唯一性,您甚至不必将JourneyStep放入RowKey,因为它有点不方便

此架构的唯一缺点是无法在不知道其时间和ID的情况下检索特定的单个旅程步骤。但是,除非你有非常具体的用例,否则下载一个行程中的所有步骤然后从列表中选择一个特定的步骤应该不会那么糟糕。

HTH

答案 4 :(得分:1)

表存储的设计是优化Azure表的两个主要功能的功能:

  • 可扩展性
  • 搜索效果

正如@Frans用户已经指出的那样,Azure表使用partitionkey来决定如何在多个存储服务器节点上扩展数据。因此,我建议不要使用唯一的分区密钥,因为从理论上讲,您将使用Azure来覆盖只能为一个客户提供服务的存储节点。我说“理论上”,因为在实践中,Azure使用智能算法来识别分区键中是否存在模式,从而能够对它们进行分组(例如,如果您的ID是连续数字)。您不希望陷入这种情况,因为您的存储的可扩展性将是不可预测的,并且由将要做出这些决策的模糊算法提供。有关可伸缩性的更多信息,请参阅HERE

关于性能,最快的搜索方法是在搜索查询中同时使用partitionkey + rowkey。与Amazon DynamoDB相反,Azure表不支持辅助列索引。如果您的搜索查询搜索除了这两个列之外的列中存储的属性,Azure将需要执行全表扫描。

我遇到了类似于你的情况,其中分区/行键的设计并不简单。最后,我们扩展了我们的数据模型以包含更多信息,因此我们可以设计我们的表格,使得所有搜索查询的约80%可以与分区+行键匹配,而剩下的20%需要表扫描。我们决定包含用户的位置,因此我们的分区键是用户的国家/地区,rowkey是客户唯一ID。这意味着我们的数据模型必须扩展到包括用户的国家,这不是什么大问题。也许你可以做同样的事情?按细分,按地点或电子邮件地址SMTP域对客户进行分组?