互联网浪潮中,科技改变生活,真切地让我们感受到工作的提效、生活的便利、娱乐的多样化。伴随更多的业务需求和极速的系统膨胀,作为核心价值的数据存储方案设计也变得愈发重要,NoSQL (Not only SQL) 也逐渐深入人心。亚马逊云科技秉承专门构建的理念,为客户提供完整的云原生数据战略体系,为不同业务类型,提供最优解。
![image.png](https://dev-media.amazoncloud.cn/eca7a339d3c6472789abeccb861d5b5f_image.png "image.png")
### 01 Why DynamoDB?
自 2007 年的第一篇“Dynamo 研究论文”,到 2012 年 [Amazon DynamoDB](https://aws.amazon.com/cn/dynamodb/?trk=cndc-detail) 服务的推出。现在,亚马逊云科技在大型非关系数据库和云服务技术领域 18 年持续投入,带给我们一款为互联网规模的应用程序而生、快速、高度可靠且具有成本效益的 NoSQL 数据库服务。
[Amazon DynamoDB](https://aws.amazon.com/cn/dynamodb/?trk=cndc-detail) 是一种完全托管式、[无服务器](https://aws.amazon.com/cn/serverless/?trk=cndc-detail)的 NoSQL 键值数据库,旨在运行任何规模的高性能应用程序。每天持续处理超过 10 万亿个请求。且可提供无限的可扩展性,稳定的个位数毫秒级性能和高达 99.999% 的可用性。
![image.png](https://dev-media.amazoncloud.cn/4093097bf0ba4fb7b4503e18a1d4d6e3_image.png "image.png")
基于 [Amazon DynamoDB](https://aws.amazon.com/cn/dynamodb/?trk=cndc-detail) 的能力和使用场景,如何以最佳实践的方式在 AI 场景发挥作用,是很多客户关心的话题。
### 02 业务场景描述
AIGC(AI-Generated Content,人工智能生成内容)风靡全球,而 LLM (Large Language Model) 创造力和智慧是最惊艳的部分。但是在工程化的过程中,我们希望数字人可以有“记忆”,来延续对话的上下文。那么会话的记忆存储,就需要一个可以承载高并发、低延迟的数据库来支撑业务。
#### 2.1 架构图详解
根据业务需求,将 [Amazon Bedrock](https://aws.amazon.com/cn/bedrock/?trk=cndc-detail) 的 Claude2 模型和 [Amazon DynamoDB](https://aws.amazon.com/cn/dynamodb/?trk=cndc-detail) 结合,来实现智能“问答”和“记忆”。
问答流程:
1. user 发起访问
2. 基于 [Amazon Elastic Kubernetes Service](https://aws.amazon.com/cn/eks/?trk=cndc-detail) ([Amazon EKS](https://aws.amazon.com/cn/eks/?trk=cndc-detail)) 运行的 AI 数字人应用,接收对话请求
3. APP 查询 DynamoDB 会话的历史记录,并结合当前会话内容,整合信息
4. 发送整合后信息到 [Amazon Bedrock](https://aws.amazon.com/cn/bedrock/?trk=cndc-detail) Claude2,获取“回答”
5. APP 发送“回答”给 user
![image.png](https://dev-media.amazoncloud.cn/105e3c5825504727ad5355d8a369241b_image.png "image.png")
### 03 数据查询/存储方案
在理清业务需求和技术实现架构之后,我们需要针对会话历史信息的存储和查询,进行详尽的技术拆解。
#### 3.1 表设计和实体关系详解
- 聊天会话表用于记录用户和 AI 数字人之间的聊天 ID (chat_id)。
- 应用程序会根据用户 ID (user_id) 和 AI 数字人 (ai_id),获取当前#ACTIVE#的 chat_id。
- 用户可以选择删除旧的 chat_id(软删除,在数据库要保留这条记录,通过 delete_time 时间戳等信息来表示历史聊天)。
- 用户也可以重启聊天会话,同时删除旧的聊天会话,创建一个新的聊天会话。
- 因此,一个用户跟 AI 数字人之间允许有多条聊天会话记录,但是同一时间有且仅有一条处于活跃状态#ACTIVE#的聊天会话记录。
##### 3.1.1 ERD(数据库实体关系图)
![image.png](https://dev-media.amazoncloud.cn/565d20cf0a9a4de9abf3f78498857151_image.png "image.png")
涉及的实体包括:用户(user_id),AI 数字人(ai_id),聊天会话(chat_id)
用户跟 AI 数字人之间是多对多的关系:
- 用户可以跟多个 AI 数字人存在多条聊天会话记录;
- AI 数字人也可以跟多个用户存在多条聊天会话记录;
##### 3.1.2 访问模式(聊天会话的增删改查)
- 创建新的聊天会话 – CreateChat
- 获取指定聊天会话 – GetChatByUser_Id_and_AI_Id
- 删除聊天会话 – DeleteChat
- 重启聊天会话 – RenewChat
- 更新指定会话 AI_Version – UpdateAIVersionByChat_Id
### 04 DynamoDB 数据建模&表格设计
#### 4.1 表格设计
我们需要一个基表存储数据,同时需要一个 GSI (Global Secondary Index) 辅助查询。
- 基表名称:chat_session
- 全局二级索引名称:GSI1_chat(选择 Keys_Only 模式)
表格定义:
![image.png](https://dev-media.amazoncloud.cn/18db86ebf959408fb24b808932b71059_image.png "image.png")
#### 4.2 表格设计技巧
[Amazon DynamoDB](https://aws.amazon.com/cn/dynamodb/?trk=cndc-detail) 是一个全托管,且简单易用的数据库。但是如何发挥其性能优势,也是需要我们知其善用。
这里我们就罗列一些小技巧:
- 有效利用 DynamoDB 排序键的特性:
例如标记活跃状态的 SK 前缀是#ACTIVE#,以#字符开头,让这条记录在排序过程中保持置顶。
- 兼顾业务和技术的 Key 设计:
场景 1. chat_id,我们可以使用 ULID (一种全局唯一且可以按字典序排序的标识符格式),其比随机 UUID 增加了时间戳,查询性能会更好。
场景 2. 在以 user_id & ai_id 作为查询条件,且以时间维度排序所有历史聊天会话的场景,ULID 就可以提供更好的查询性能。
![image.png](https://dev-media.amazoncloud.cn/f53666725eb441ba947ce311acac7c64_image.png "image.png")
#### 4.3 表数据样例
- 基表插入数据后的样式
![image.png](https://dev-media.amazoncloud.cn/aa9effc4f252441dbc95a52727cf0005_image.png "image.png")
- GSI 的数据样式
![image.png](https://dev-media.amazoncloud.cn/9f062ce9c859425cbbe17ab9f1f6cffa_image.png "image.png")
### 05 访问模式实现
在完成表格设计之后,如何消费数据,以满足业务需求?后续内容会详细介绍。
#### 5.1 创建新的聊天会话 – CreateChat
在基表 chat_session 上执行 putItem 操作,通过条件判断表达式(ConditionExpression),确定当前表中不存在这条记录,才执行写入新的聊天会话。如果条件判断检查失败,则返回应用端 ConditionalCheckFailedException,表示数据库中存在这条记录。需要应用端做业务逻辑判断再操作。
```js
put_item_params = {
'TableName': 'chat_session',
'Item': {
'PK': {'S': user_id},
'SK': {'S': '#ACTIVE#' + ai_id},
'chat_id': {'S': chat_id},
'create_time': {'S': create_timestamp},
'GSI1PK': {'S': chat_id}
},
'ConditionExpression': 'attribute_not_exists(PK) AND attribute_not_exists(SK)'
}
```
#### 5.2 获取指定聊天会话 – GetChatByUser_Id_and_AI_Id
在基表 chat_session 上执行 getItem 操作,指定 PK=user_id and SK=#ACTIVE#ai_id,获取返回结果。
#### 5.3 标记删除聊天会话 – DeleteChat
组合操作完成这个场景,首先 getItem 获取到当前活跃聊天会话记录的所有属性。然后事务写入,插入一条有 delete_time 的历史会话记录,并删除当前活跃聊天会话记录。相关步骤参数见下面:
**第一步**:getItem PK=user_id and SK=#ACTIVE#ai_id, 可以复用上面的 getChatByUser_id_and_ai_id 函数。获取到这条 Item 所有信息记录下来。
**第二步**:事务写入
putItem PK=user_id and SK=ai_id#OLD#chat_id,其他属性包括 chat_id, ai_version,create_time,delete_time.
deleteItem PK=user_id and SK=#ACTIVE#ai_id
#### 5.4 重新启动聊天会话 – RenewChat
组合操作完成这个场景,跟上面软删除很类似,只是在事务写入时候,使用 updateItem。首先 getItem 获取到当前活跃聊天会话记录的所有属性。然后事务写入,插入一条有 delete_time 的历史会话记录,然后更新当前活跃聊天会话记录,为最新的 ai_version,chat_id。相关步骤参数见下面:
**第一步**:getItem PK=user_id and SK=#ACTIVE#ai_id, 可复用 getChatByUser_id_and_ai_id 函数。获取到这条 Item 所有信息记录下来。
**第二步**:事务写入
putItem PK=user_id and SK=ai_id#OLD#chat_id,其他属性包括 chat_id,ai_version,create_time,delete_time
updateItem PK=user_id and SK=#ACTIVE#ai_id,其他属性包括 chat_id,ai_version,create_time,GSI1PK
#### 5.5 更新指定会话 AI_Version – UpdateAIVersionByChat_Id
**第一步**:GSI1_chat 中 getItem PK=chat_id;拿到基表的 PK,SK
**第二步**:基表 chat_session 中 updateItem,PK=user_id,SK=#ACTIVE#ai_id
#### 5.6 访问模式和实现方法汇总
为便于理解,我们将前面的访问模式,结合具体的实现方法,通过表格的形式进行汇总。
![image.png](https://dev-media.amazoncloud.cn/584fdf20923044d4a5d5d252ff46658f_image.png "image.png")
### 总结
通过以上内容,我们希望你理解“专门构建”的含义,不再根据数据库设计业务,而是通过业务选择适合的数据库。在类似场景下,DynamoDB 作为 Key-Value 数据库,可以充分发挥高并发、低延迟、高稳定性的特点,支撑核心业务的稳定运行。也希望通过本文,带来更清晰的 DynamoDB 的建模方法、建表模式,以及高性能查询的最佳实践。
### 参考链接
- [Amazon DynamoDB](https://docs.aws.amazon.com/zh_cn/amazondynamodb/latest/developerguide/GettingStartedDynamoDB.html?trk=cndc-detail)
- [Gaming profile schema design in DynamoDB](https://docs.aws.amazon.com/zh_cn/amazondynamodb/latest/developerguide/data-modeling-schema-gaming-profile.html?trk=cndc-detail)
![开发者尾巴.gif](https://dev-media.amazoncloud.cn/9312d9b2a6644ded83a8a23466e93a1c_%E5%BC%80%E5%8F%91%E8%80%85%E5%B0%BE%E5%B7%B4.gif "开发者尾巴.gif")