DynamoDB one-to-one



你好,stackoverflow社区,

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

假设我们有一份关于学生的申请。每个Student都有:

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

要求:

  1. ProfileGradesAddress每次只属于一个Student(即一对一)
  2. Student必须具有所有ProfileGradesAddress数据(例如,没有学生没有成绩)
  3. 所有字段都可能发生更新,但配置文件数据大多保持不变
  4. 我们基于Student而不是通过查询地址或其他什么来访问数据(查询可以是"给我学生约翰的成绩",或"给我同学约翰的档案和地址"等)
  5. 所有字段加在一起都低于DynamoDB的400kb阈值

问题是你将如何设计它?将所有数据作为一行/项,还是将其拆分为ProfileGradesAddress项?

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

我发现,像这样,我可以进行跨国插入/更新(缺点是,我总是要处理完整的项目),在查询时,我可以要求每次都需要数据的子集。除此之外,此解决方案符合AWS关于发电机的两个最重要的指南:

  1. 将所有内容放在一个表中
  2. 尽可能提供预联接数据

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

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

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

基本实现

考虑到您所描述的数据和访问模式,我会设置一个单独的student-data表,其中包含一个分区键,允许我按学生进行查询,以及一个排序键,允许我们根据要访问的实体进一步缩小结果范围。一种方法是为学生使用某种标识符,比如studentID,然后对排序键使用更通用的标识符,比如entityID,或者简称为SK

在应用层,我会将每个Item分类到一个可能的实体(profilegradesaddress)下,并将与该实体相关的数据存储在该Item上所需的任意数量的属性中。

一个关于数据如何查找名为johnsmith的学生的例子:

{ 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实体中,则可以实现";给我学生John的简介和地址一次完成(尽可能避免多次查询)

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个属性。

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

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

优化

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

对于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:亚马逊DynamoDB深度挖掘:DynamoDB(DAT401)的高级设计模式

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

DynamoDB 的NoSQL设计

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

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

最新更新