你好,stackoverflow社区,
这个问题是关于建模涉及多个实体的一对一关系。
假设我们有一份关于学生的申请。每个Student
都有:
Profile
(姓名、出生日期…)Grades
(数学成绩、地理…)Address
(城市、街道…)
要求:
Profile
、Grades
和Address
每次只属于一个Student
(即一对一)Student
必须具有所有Profile
、Grades
和Address
数据(例如,没有学生没有成绩)- 所有字段都可能发生更新,但配置文件数据大多保持不变
- 我们基于
Student
而不是通过查询地址或其他什么来访问数据(查询可以是"给我学生约翰的成绩",或"给我同学约翰的档案和地址"等) - 所有字段加在一起都低于DynamoDB的400kb阈值
问题是你将如何设计它?将所有数据作为一行/项,还是将其拆分为Profile
、Grades
和Address
项?
我的解决方案是将所有数据保留在由studentId定义的一行中作为PK,其余数据放在一大列中。所以有一项看起来像[studentId, name, birthDate, mathsGrade, geographyGrade, ..., city, street]
。
我发现,像这样,我可以进行跨国插入/更新(缺点是,我总是要处理完整的项目),在查询时,我可以要求每次都需要数据的子集。除此之外,此解决方案符合AWS关于发电机的两个最重要的指南:
- 将所有内容放在一个表中
- 尽可能提供预联接数据
我提出这个问题的原因是,我在stackoverflow中只能找到一个关于DynamoDB中一对一建模的主题,而建议的解决方案(也获得了大量支持)支持将数据保存在单独的表中,这让我想起了关系数据库的设计(请参阅此处的解决方案)。
我知道在这种情况下,作者试图保留一个更通用的用例,并可能支持更复杂的查询,但感觉把所有东西放在一起的选项完全贬值了。
出于这个原因,我想在这里开始讨论,并听取其他意见。
基本实现
考虑到您所描述的数据和访问模式,我会设置一个单独的student-data
表,其中包含一个分区键,允许我按学生进行查询,以及一个排序键,允许我们根据要访问的实体进一步缩小结果范围。一种方法是为学生使用某种标识符,比如studentID
,然后对排序键使用更通用的标识符,比如entityID
,或者简称为SK
。
在应用层,我会将每个Item分类到一个可能的实体(profile
、grades
、address
)下,并将与该实体相关的数据存储在该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设计
请记住这一点,尤其是当您考虑高流量应用程序的成本/性能影响时:
有效设计和使用分区密钥的最佳实践