Files
zhuangang/topics/backend/mongodb/scenarios/conversation-store.md
T

79 lines
3.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
id: backend.mongodb.scenario.conversation-store
title: AI 客服会话存储:海量对话记录的建模、查询、归档和同步
type: scenario
domain: backend
stack: [mongodb, postgresql, elasticsearch]
tags: [对话记录, 文档建模, TTL, 归档, 变更流, 多租户]
level: intermediate
prerequisites: [backend.mongodb.document-model]
est_hours: 2
related: [backend.mongodb.interview, scenario.data-sync, scenario.customer-service-kb]
week: [14]
---
# AI 客服会话存储:海量对话记录的建模、查询、归档和同步
## 业务背景与问题
AI 客服上线后,每天产生大量对话(用户消息、模型回复、工具调用记录、评分),原先存在 PostgreSQL 的一张 JSONB 大表中:
1. 消息结构经常变化(新增工具调用、图片、引用来源),频繁改表;
2. 坐席后台按租户查“待处理会话”越来越慢;
3. 需要按关键词搜索历史对话;
4. 数据只需要在线保留 180 天,之后归档。
## 约束
- 订单、退款等核心交易数据仍在 PostgreSQL,强一致。
- 对话数据允许最终一致的检索。
- 多租户隔离。
## 方案设计
1. **职责划分**:交易数据留在 PostgreSQL;对话记录迁移到 MongoDB(结构灵活、写入量大);全文 / 语义检索交给 Elasticsearch。
2. **建模**:
- `conversations`:会话元数据 + 最近 N 条消息嵌入(列表页和详情首屏一次读取);
- `messages`:完整消息单独集合,按 `conversation_id + seq` 引用(避免无限数组);
- 每个文档都带 `tenant_id`。
3. **索引**:`{tenant_id, status, updated_at: -1}`(坐席列表,ESR)、`{conversation_id, seq}` 唯一(幂等写入和顺序读取)、`messages.created_at` 上的 TTL 索引或按月归档任务。
4. **同步检索**:变更流 → 消费者 → 写入 ES;保存 resume token,断线后续传;消费者幂等(以消息 id 为 ES 文档 id)。
5. **一致性**:转人工(改会话 + 建工单)用事务,或者把工单放在 PostgreSQL 并通过 outbox 模式保证一致。
## 关键代码
```python
# 追加消息:唯一索引保证重复投递不重复写入;同时更新会话的最近消息(保留最后 20 条)
messages.update_one(
{"conversation_id": cid, "seq": seq},
{"$setOnInsert": {"tenant_id": tenant, "role": role, "text": text, "created_at": now}},
upsert=True,
)
conversations.update_one(
{"_id": cid, "tenant_id": tenant},
{"$push": {"recent": {"$each": [{"seq": seq, "role": role, "text": text}], "$slice": -20}},
"$set": {"updated_at": now}},
)
```
可运行的基础示例:[examples/conversations](../examples/conversations/)。
## 取舍
| 选择 | 优点 | 缺点 |
| --- | --- | --- |
| **MongoDB 存对话 + PG 存交易(本方案)** | 各取所长,结构灵活 | 多一种数据库要运维,跨库一致性要设计 |
| 全部留在 PostgreSQL JSONB | 技术栈简单,事务方便 | 写入量大时表膨胀,结构变化仍需维护索引 |
| 对话直接写 ES | 检索方便 | ES 不适合作为主存储(更新代价、一致性) |
## 易错点
- 消息全部嵌入会话文档,长会话超过 16MB。
- 查询忘记带 `tenant_id`,越权读取其他租户数据。
- 变更流消费者没有保存 resume token,重启后丢事件或全量重放。
- 归档前没有确认合规要求(对话内容可能包含个人信息,需要脱敏或加密)。
## 面试怎么讲
> “对话记录结构多变、写入量大,我把它从 PG 的 JSONB 表迁到了 MongoDB,交易数据留在 PG。会话文档嵌入最近 20 条消息,完整消息单独集合用 conversation_id+seq 唯一索引,既避免无限数组又保证幂等。坐席列表按 ESR 原则建复合索引。检索通过变更流同步到 ES,保存 resume token 断点续传。180 天后归档。”