【问题标题】:Azure Tables - Partition Key and Row Key - Correct ChoiceAzure 表 - 分区键和行键 - 正确选择
【发布时间】:2014-05-27 04:15:11
【问题描述】:

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

我有类似这样的数据:

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

根据我的阅读,CustomerId 是一个好的 PartitionKey 吗?我被卡住的地方是CustomerIdTripId 的组合,它们不会构成唯一的行。我之所以将TripId 作为行键是因为每个查询都是基于CustomerIdTripId 的数据集。

就上下文而言,CustomerId 显然是独一无二的,TripId 代表车辆中的一个旅程,而在该旅程中,JourneyStep 代表该旅程中的一个单位,可能是 10 步或 1000 步。

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

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

更新:

根据要求,该解决方案适用于 Vehicle Telematics,因此请在您自己的汽车中思考自己。 Blackbox 将数据传送到服务器,然后将其传递到 Azure Tables。在关系数据库术语中,我将有一个客户表和一个带有返回客户表的外键的行程表。

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

查询将分为两部分:

  1. 为每个客户显示单个旅程的地图,因此按客户和行程过滤,然后将每一行(旅程步骤)迭代到地图。

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

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

【问题讨论】:

  • 对于特定的CustomerIdTripId 是唯一的吗?
  • Gaurav,否 - 一位客户将有多次旅行。唯一唯一的组合是 CustomerId 和 Time,但从查询的角度来看,很少使用。 'CustomerId 和 TripId 的组合不构成唯一行'
  • 那么很遗憾你不能使用 TripId 作为 RowKey。在一个分区内,RowKey 必须是唯一的。
  • 啊,这就是我所担心的。如果我对唯一的 RowID 使用时间,那么在每个查询都包含 TripID 的情况下,查询性能的最佳方法是什么?我可以在 TripId 上附加一些东西来制作一个唯一的行键,但我想在非常查询中将它们拆分出来。

标签: azure azure-table-storage


【解决方案1】:

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

因此,查看您的域并确定您可能使用的聚合非常重要。

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

【讨论】:

  • 感谢 Frans,今天下午阅读并接受了 Cilerer 的方法,因此将他的答案标记为答案。对于其他阅读者来说,这个答案并不正确,但另一个似乎更适合我的情况。
  • @Steve 不用担心,这完全取决于您的域和查询需求。很高兴您找到了可行的解决方案。
【解决方案2】:

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

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

现在,RowKey 可以是您的tripIdtime。如果我是你,我可能会使用 rowKey 作为yyyyMMddHHmm|tripId 格式,它可以让你根据 startWith 和 endWidth 选项进行查询。

【讨论】:

  • 感谢 Cilerler,明智的解决方案似乎适合我想要的。由于tripId 是一个向导,因此很容易以方法开始/以方法结束,从查询性能的角度来看这是否明智?走 Frans 路线似乎也是可行的,但我每次都必须加入客户表,这似乎不是最理想的?
  • @SteveNewton 实际上取决于您的需求。我的意思是,如果您正在与 Expedia 类型的网站打交道,您将需要采用客户第一、旅行第二的方法。但是在您的情况下,您可能需要与之相反。这实际上取决于您的数据有多大以及您需要如何访问这些数据。
  • 顺便说一下,关于“我每次都必须加入客户表,这似乎不是最理想的”部分。许多文档告诉您,只要您手中有 PrimaryKey,您的查询就会足够快(如果您同时拥有 Primary+Row,它将执行得最好)但是请记住,我们还没有 GetAllPartitionKeys() 方法,这意味着您必须保留另一个表为 yyyyMMddHHmmssfffff 或数字 Id 作为 PartitionKey 和 ClientId 作为 RowKey 以获得一般报告,如 GetAllTrips()
【解决方案3】:

添加到@Frans 答案:

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

【讨论】:

  • 感谢 Gaurav,我认为我们有两种有效的方法,问题的第二部分是鉴于数据量将是巨大的,哪一种提供了最好的查询性能!让我们说 100gb 以供争论,此时的设计至关重要。我总是会询问客户和旅行。
  • 在我回答这个问题之前,我还有一些自己的问题:) - 1) 你会提前知道TripId 吗? 2) TripId 是否需要按日期/时间排序?如果您可以在原始帖子中详细说明您的查询要求,那肯定会有所帮助。
  • 更新了我原来的帖子,希望能澄清我的查询要求。
【解决方案4】:

我建议您在 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 个)

如果你能保证每次Trip中的Times of Steps的唯一性,你甚至不必将JourneyStep放入RowKey,因为它有点不方便

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

HTH

【讨论】:

  • 感谢 Igorek,这是一个非常彻底的答案,我肯定会在我的研究中加入。
【解决方案5】:

表存储的设计是为了优化Azure Tables的两大能力:

  • 可扩展性
  • 搜索效果

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

关于性能,最快的搜索方法是在搜索查询中同时点击 partitionkey+rowkey。与 Amazon DynamoDB 不同,Azure Tables 不支持二级列索引。如果您让搜索查询搜索存储在这两个列之外的列中的属性,Azure 将需要执行全表扫描。

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2014-02-04
    • 1970-01-01
    • 2012-01-01
    • 1970-01-01
    • 2014-06-09
    • 2023-02-21
    • 2014-02-04
    • 2022-10-04
    相关资源
    最近更新 更多