作为AWS众多云服务的核心成员之一,DynamoDB得到了非常广泛的应用。下面就通过一系列教程来介绍一下DynamoDB的使用。本次主要介绍一下DynamoDB中主键Primary Key的设计考虑因素。在DynamoDB中,主键的选择太重要了,因为它会影响到之后访问数据的方式。如果设计的有问题,对于某些数据的访问将变得较为困难,或者影响性能,亦或影响使用DynamoDB的费用。

AWS DynamoDB系列教程:
- AWS DynamoDB系列之一:简介
- AWS DynamoDB教程之二:主键的设计及GSI
- AWS DynamoDB系列之三:Streams
- AWS DynamoDB系列之四:在Node.js中访问DynamoDB
- AWS DynamoDB系列之五:在本地安装DynamoDB
- AWS DynamoDB教程之六:如何使用APIGateway Service Proxy访问DynamoDB数据
- AWS DynamoDB教程之七:DynamoDB的访问控制
- AWS DynamoDB教程之八:数据备份/恢复及导出
- AWS DynamoDB教程之九:性能监测和调优,Audit Table及TTL
Partition Key及Sort Key的选择
Partition分区其实上应该理解为一个逻辑上的划分。当然,在实际存储的时候可能会由于分区不同而存储在不同的存储设备上。分区的作用就是为了能够快速定位到数据。因此,在设计Partition Key的时候,应尽量将数据打散,避免hot partition,同一个Partition上的数据不宜过大,以保证高性能和避免Throttling(对于每个Partition,Up to 3000 RCU+1000 WCU)。
如果分配给表200 Read Capacity Unit (RCU),那么就意味着不同分区竟会共享这200 RCU。
如果同时选择了Sort Key,那么在同一个分区中,DynamoDB会根据Sort Key进行排序。在进行查询的时候,Sort Key可以帮助我们筛选数据。
在考虑是否需要Sort Key的时候:
- 如果Partition Key唯一,且每次在访问DynamoDB的时候都事先知道Partition Key,那么就没有必要添加Sort Key了。
- 如果Partition Key不是唯一的,那毫无疑问的,肯定需要Sort Key作为辅助了。
- 如果需要根据某个字段在某个范围内查询,则需要添加一个Sort Key。
- 对于Sort Key本身,也可以使用一个组合字符串来构建。比如:国家#省份#城市#街道。
有些时候,为了提高性能,需要尽量将Partition Key均匀分布,避免”hot key”。
Partition Key和Sort Key的组合就是对数据进行分类,分类,再分类的过程,这样才能保证DynamoDB的高性能和扩展性。
需要注意,PK字段中是区分大小写的,因此在应用程序中应统一进行处理(转为大/小写)之后再保存。
再考虑DynamoDB使用时,可以将其视为一个字典,因此在查询时可以把查字典作为类比:
- 查找所有以Dec开始的条目,非常快
- 查找所有以ber结束的条目,非常慢
- 查找所有包含abc的条目,非常慢
GSI
GSI的全称是Global Secondary Index,它的作用就是提供不同的访问路径,或者说从不同维度来获取数据。
先来说说这个GSI是做什么用的。还是以前面的例子来说。
Student ID, Subject, Score, Date
--------------------------------
9901, Math, 90, 2021-11-01
9901, English, 80, 2021-11-02
9902, Math, 92, 2021-11-01
9903, Math, 63, 2021-11-01
9904, Math, 90, 2021-11-01
在上面这个表中,主键是由Student ID(Partition Key)及Subject(Sort Key)组成的。之后可以针对这个两个字段进行查询。
但问题来了,如何通过日期字段查询?比如:返回所有在2021-11-01这天登录的考试成绩。这个时候,PK/SK组合就不够用了。只能通过创建一个GSI来达到这个目的。
创建一个GSI,就相当于又创建了一个当前表的镜像。只不过在镜像中,PK就是这个GSI。这就不难理解为什么可以通过GSI进行快速查询数据了。当然增加GSI的代价是cost。
在主表和GSI表之间的数据同步可能会有延迟,因此如果主表更新后,通过GSI可能查询不到最新数据。
建议在GSI的WCU应高于主表的WCU,这是因为所有对主表的更新操作,必然需要在GSI表中同步。推荐将GSI表设置为autoscaling。
LSI
LSI的全称是Local Secondary Index。
LSI和GSI的区别
| GSI | LSI |
|---|---|
| 主键可以只包含分区键,也可以是分区键和索引键组合 | 只能是组合键(分区键+索引键) |
| 分区键可以与基表分区键不同 | 分区键必须和主表相同 |
| GSI没有大小限制 | LSI有大小限制,比如10GB |
| 可以在任何时候创建GSI | 只能在创建表时创建LSI |
| 通过GSI,可以跨分区进行查询 | 只能针对指定分区进行查询 |
| 对GSI,仅支持最终读取一致性(可能有延迟) | 可选择强制一致性 |
使用LSI时的注意事项
- 只有在创建表的时候可以定义LSI
- 和GSI不同,使用LSI并不会产生额外费用
- 每个表只能定义最多5个LSI(软性限制)
- 一旦创建表,无法新增,删除,更改LSI
- 使用LSI的查询会比针对主表的查询要快很多
DynamoDB数据库设计
DynamoDB的设计理念就是要在应用程序中保留尽可能少的表。如果设计得当,通常一个表就足够了。
DynamoDB是schemaless的,因此在设计的时候纯粹是通过访问模式来确定其设计的。
由于经常会将不同类型的数据保存到同一个DynamoDB表中,因此,属性的名字和其值可能没有关系。在设计好的DynamoDB中,经常会使用非常通用的PK/SK作为属性名称,而不是StudentID, Subject等更为具体的名称。
| PK | SK | Data | Produced_at | Sales_ID |
|---|---|---|---|---|
| PRODUCT_ID | GB | P Name | 2021-10-02 | |
| ORDER_ID | PRODUCT_ID | CATEGORY_ID | 100058 | |
| EMPLOYEE_ID | MANAGER_ID | Paul Bounce |
Index overloading
将不同类别的数据保存在同一列中看似杂乱,但实则非常有用,这种方式叫做index overloading。
Sparse indexes稀疏索引
全局二级索引默认属于稀疏型。将全局二级索引设计为稀疏索引,可以配置低于基表的写入吞吐量,同时仍实现出色的性能。
关于一对多关系的设计
DynamoDB的设计是完全摒弃RDB中的规范化的,比如:
- 关于复杂属性(违反RDB的规范化)的设计:
- 确保不需要直接针对复杂属性的某个部分进行访问
- 注意DynamoDB对每个item的大小都有限制,为 < 400KB
- 关于数据的重复(违反RDB的规范化)
- 通常在数据为只读时重复
- 如果数据不经常更改,或者不会有过多份Copy
通常使用Composite Primary Key来实现一对多的关系。比如通过Partition Key来限定一组数据,然后再通过Sort Key来展现数据的不同方面。
有些时候需要Composite Sort Key,这在大于两级的item时候非常有用。比如Country#State#City#PostCode,之后就可以进行灵活的查询了。这个时候可以查询某个item以及所有下级item。
不适合单表设计的场景
- 如果选择使用GraphQL,则只能选择多表。
- 需要更多灵活性的时候
参考资料
https://www.youtube.com/watch?v=BnDKD_Zv0og
https://www.youtube.com/watch?v=AT2zkWoqQ5o
https://www.alexdebrie.com/posts/dynamodb-single-table/
https://www.trek10.com/blog/dynamodb-single-table-relational-modeling/