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

3.8 KiB
Raw Blame History

id, title, type, domain, stack, tags, level, prerequisites, est_hours, related, week
id title type domain stack tags level prerequisites est_hours related week
backend.mongodb.scenario.conversation-store AI 客服会话存储:海量对话记录的建模、查询、归档和同步 scenario backend
mongodb
postgresql
elasticsearch
对话记录
文档建模
TTL
归档
变更流
多租户
intermediate
backend.mongodb.document-model
2
backend.mongodb.interview
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 模式保证一致。

关键代码

# 追加消息:唯一索引保证重复投递不重复写入;同时更新会话的最近消息(保留最后 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。

取舍

选择 优点 缺点
MongoDB 存对话 + PG 存交易(本方案) 各取所长,结构灵活 多一种数据库要运维,跨库一致性要设计
全部留在 PostgreSQL JSONB 技术栈简单,事务方便 写入量大时表膨胀,结构变化仍需维护索引
对话直接写 ES 检索方便 ES 不适合作为主存储(更新代价、一致性)

易错点

  • 消息全部嵌入会话文档,长会话超过 16MB。
  • 查询忘记带 tenant_id,越权读取其他租户数据。
  • 变更流消费者没有保存 resume token,重启后丢事件或全量重放。
  • 归档前没有确认合规要求(对话内容可能包含个人信息,需要脱敏或加密)。

面试怎么讲

“对话记录结构多变、写入量大,我把它从 PG 的 JSONB 表迁到了 MongoDB,交易数据留在 PG。会话文档嵌入最近 20 条消息,完整消息单独集合用 conversation_id+seq 唯一索引,既避免无限数组又保证幂等。坐席列表按 ESR 原则建复合索引。检索通过变更流同步到 ES,保存 resume token 断点续传。180 天后归档。”