1

你好 stackoverflow 社区,

这个问题是关于对涉及的多个实体的一对一关系进行建模。

假设我们有一个关于学生的应用程序。每个Student都有:

  • Profile(姓名、出生日期……)
  • Grades(数学成绩,地理......)
  • Address(城市,街道......)。

要求:

  1. 和唯一每次Profile都属于一个(即一对一)。GradesAddressStudent
  2. AStudent必须具有 allProfile和数据(例如GradesAddress没有学生没有成绩)。
  3. 所有字段都可能发生更新,但配置文件数据大多保持不变。
  4. 我们基于 aStudent而不是通过查询地址或其他内容来访问数据(查询可以是“给我学生 John 的成绩”,或“给我学生 John 的个人资料和地址”等)。
  5. 所有字段加在一起都低于 DynamoDB 的 400kb 阈值。

问题是你会如何设计它?将所有数据作为单行/项目或将其拆分为Profile,GradesAddress项目?

4

2 回答 2

1

一个基本的实现

考虑到您描述的数据和访问模式,我将设置一个student-data带有分区键的表,允许我按学生查询,以及一个排序键,允许我根据我的实体进一步缩小结果范围想要访问。这样做的一种方法是为学生使用某种标识符,例如studentID,然后为排序键使用更通用的标识符entityID,或者简单地说SK

在应用程序层,我会将每个项目分类到一个可能的实体 ( profile, grades, address) 下,并将与该实体相关的数据存储在该项目上我需要的任意数量的属性中。

该数据如何查找名为 john smith 的学生的示例:

{ studentId: "john", entityId: "profile", firstName: "john", lastName: "smith" }

{ studentId: "john", entityId: "grades", math2045: 96.52, eng1021:89.93 }

{ studentId: "john", entityId: "address", state: "CA", city: "fresno" }

使用此架构,您的所有访问模式都可用:

“给我约翰学生的数学成绩”

PartitionKey = "john", SortKey = "grades"

如果您将地址存储在学生profile实体中,则可以一次性完成“给我学生约翰的个人资料和地址” (应尽可能避免多次查询)

PartitionKey = "john", SortKey = "profile"

考虑

请记住,在设计表格时,您需要考虑读取/写入数据的频率。这是一个非常基本的设计,可能需要进行调整以确保您不会为未来的重大成本或性能问题做好准备。

此实现展示的基本思想是,非规范化数据(在这种情况下,跨您已建立的不同实体)可能是利用 DynamoDB 速度的一种非常有效的方式,同时也为您提供了多种有效访问数据的方法.

问题与局限

具体到您的应用程序,有一个突出的潜在问题,即grades项目开始膨胀到无法管理并且读取/写入/更新变得昂贵的程度似乎非常可行。随着您开始存储越来越多的学生,并且每个学生都学习越来越多的课程,您的grades实体将随着他们而扩展。假设普通学生参加 35-40 节课并为每节课打分,如果不需要,您不想管理一个项目的 35-40 个属性。每次询问学生的成绩时,您也可能不希望返回每个成绩。也许您开始在每个grade实体上存储更多数据,例如:

{ math1024Grade: 100, math1024Instructor: "Dr. Jane Doe", math1024Credits: 4 }

现在对于每个类,您至少要存储 2 个额外的属性。那个有35-40属性的项目刚刚跃升到105-120属性。

除了性能和成本问题之外,您的访问模式可能会开始演变并变得更加苛刻。您可能只想要学生专业的成绩,或者当前不可用的特定类型的课程,如人文、科学等。你永远只能从每个学生那里得到每一个成绩。您可以将 aFilterExpression应用于您的请求并删除一些不需要的项目,但您仍然需要为您阅读的所有数据付费

使用当前的解决方案,我们在性能、灵活性、可维护性和成本方面的优化方面留下了很多东西。

优化

解决查询缺乏灵活性和grades实体可能膨胀的一种方法是使用composite sort key. 使用复合排序键可以帮助您进一步分解实体,使它们更易于更新,并在查询时为您提供更大的灵活性。此外,您最终会得到更小且更易于管理的物品,尽管您存储的物品数量会增加,但您将节省成本和性能。通过更优化的查询,您将只获得所需的数据,因此您无需为丢弃的数据支付额外的读取单元。单个查询请求可以返回的数据量也是有限的,因此您可以减少往返次数。

该复合排序键可能看起来像这样,因为grades

{ studentId: "john", entityId: "grades#MATH", math2045: 96.52, math3082:91.34 }

{ studentId: "john", entityId: "grades#ENG", eng1021:89.93, eng2203:93.03 }

现在,您可以说“给我所有约翰的数学课程成绩”,同时仍然能够获得所有成绩(通过begins_with在查询时使用排序键上的操作)。

如果您认为要开始在grades实体下存储更多课程信息,您可以在复合排序键后加上课程名称、编号、标识符等。现在您可以获得所有学生的成绩,所有学生的成绩在其中一个科目,以及有关学生在该科目内成绩的所有数据,例如其讲师、学分、所学年份、学期、开始日期等。

这些优化都是可能的解决方案,但可能不适合您的应用程序,因此请再次记住这一点。

资源

这里有一些资源可以帮助您提出自己的解决方案,或者调整我在上面提供的解决方案以更好地适合您的方法。

AWS re:Invent 2019:使用 Amazon DynamoDB 进行数据建模 (CMY304)

AWS re:Invent 2018:Amazon DynamoDB 深入探讨:DynamoDB 的高级设计模式 (DAT401)

使用排序键组织数据的最佳实践

DynamoDB 的 NoSQL 设计

请记住这一点,尤其是当您考虑对高流量应用程序的成本/性能影响时:

有效设计和使用分区键的最佳实践

于 2021-09-15T20:18:53.183 回答
1

我的解决方案是将所有数据保留在由 studentId 定义为 PK 的一行中,其余数据位于一大组列中。所以一个项目看起来像[studentId, name, birthDate, mathsGrade, geographyGrade, ..., city, street]

我发现像这样我可以进行跨国插入/更新(当然,缺点是我总是必须使用完整的项目)并且在查询时我可以每次都询问所需的数据子集。除此之外,该解决方案还符合有关 dynamo 的两个最重要的 AWS 准则:

  1. 把所有东西都放在一张桌子上
  2. 尽可能预加入数据。

我提出问题的原因是,我只能在 stackoverflow 中找到一个关于 DynamoDB 中的一对一建模的主题,而建议的解决方案(也被大量投票)支持将数据保存在单独的表中,这让我想起了一种关系型数据库设计(请参阅此处的解决方案)。

我知道在这种情况下,作者试图保留一个更通用的用例并可能支持更复杂的查询,但感觉将所有东西放在一起的选择完全贬值了。

出于这个原因,我想在这里开始讨论并听取其他意见。

于 2021-09-14T10:56:55.677 回答