phase 5 part 3: mongodb, elasticsearch hybrid search, data-pipeline, nodejs BFF topics with verified examples

This commit is contained in:
Shikui Wang committed 2026-10-23 16:38:52 +08:00
1 parent 9eeb10aede
commit 075872e31e
27 files changed
+2405 -52

No files matched your search

@@ -0,0 +1,78 @@
---
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]
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 天后归档。”