后端架构数据存储会话系统成本优化Agent
会话存储设计:Redis、Postgres 与对象存储怎么选(AI/Agent 场景)
AI 会话数据既包含高频读写的短期状态(流式输出、步骤进度),也包含需要审计与复盘的长期记录(事件日志、工具回执),还可能有大对象(附件、长文本、向量)。本文给出一套可落地的分层存储决策框架:按数据粒度/生命周期/一致性/成本选 Redis、Postgres 或对象存储,并提供冷热分层与迁移策略。
2026年3月4日
Synthly 团队
预计阅读 15 分钟

📷 Photo by JÉSHOOTS via Pexels
先把会话数据拆开:你存的不是“聊天”,是“运行系统”
在 Agent 产品里,“会话”包含至少三类东西:
- 交互层数据
- 用户消息、助手最终回复
- 执行层数据
- run 状态、步骤进度、工具调用回执摘要、重试决策
- 审计与运营数据
- 事件日志、错误分类、成本(token/工具调用)
把它们混在一起存,会让:
- 查询很难写
- 热点很难控制
- 成本很难预测
所以第一步是:按粒度与生命周期分层。
一、决策维度:四个问题决定存哪
对每一类数据,问这四个问题:
- 访问模式:高频读写还是低频查询?
- 一致性:需要强一致吗?允许最终一致吗?
- 生命周期:分钟/小时/天/年?需要 TTL 吗?
- 查询方式:需要复杂过滤/聚合/索引吗?还是只要按 key 取?
四问答完,通常答案就出来了。
二、三种存储的“正确定位”
1)Redis:短期状态与控制面(热)
适合存:
- run 状态(running/succeeded/failed)
- 流式输出缓冲(短期)
- 幂等键与去重记录(短期)
- 分布式锁(resource lock)
- 速率限制计数器
不适合存:
- 需要审计的长期日志
- 需要复杂查询的历史数据
一句话:Redis 是“控制面”,不是“事实仓库”。
2)Postgres:事实、审计与查询面(温)
适合存:
- 会话线程(thread)与消息(message)
- 事件日志(event log)
- 工具调用摘要与回执索引
- 关键指标的聚合表(日报/看板)
优势:
- 强一致
- 可索引、可查询
- 审计与权限好做
3)对象存储:大对象与归档(冷)
适合存:
- 大段原始文本归档
- 附件(pdf、图片、音频)
- 导出的报告文件
- 大规模评测与离线分析产物
配套建议:
- 在 Postgres 里存对象元数据与 URL
- 对象本体放对象存储
三、推荐的分层架构:热/温/冷
把数据按温度分三层,会让系统可扩展且成本可控。
热层(Redis)
- TTL:分钟~小时
- 内容:运行时状态、锁、幂等、流式缓冲
温层(Postgres)
- TTL:天~年(按合规)
- 内容:消息、事件日志、回执摘要、指标
冷层(对象存储)
- TTL:按业务与合规
- 内容:大对象、归档、离线产物
迁移策略:
- 热 → 温:run 完成后把关键状态落库
- 温 → 冷:历史归档、压缩存储
四、事件日志表怎么设计:最小可用 schema
你不需要一开始就做复杂的数据湖,但建议至少有一张事件表:
event_idthread_idrun_idseqevent_typepayload_summary(可检索摘要)payload_ref(指向对象存储的原始 payload,可选)created_at
关键点:
seq支持重放payload_summary支持排障与运营分析- 原始大 payload 放对象存储,避免数据库膨胀
五、成本模型:为什么“只用 Postgres”也会很贵
很多团队会说:
Postgres 很强,那就全放 Postgres。
问题在于:
- 事件日志增长极快(每次工具调用、每次 delta 都是事件)
- 大 payload(长文本/回执)会导致表膨胀
- 索引维护成本高
所以建议:
- delta 级别事件不要全落数据库(可聚合/抽样)
- 只落关键里程碑事件(step/tool/done/error)
- 大对象走对象存储
六、上线 Checklist(会话存储分层)
- 数据分层:热(Redis)/温(Postgres)/冷(对象存储)职责明确
- 运行状态:run 状态可重入(断线重连/后台继续)
- 事件日志:至少存 step/tool/error/done 里程碑事件
- 幂等与锁:写操作幂等键、资源锁存 Redis
- 归档策略:历史数据压缩/迁移/TTL 清理
- 审计与权限:按 tenant/user 隔离查询,敏感字段脱敏
常见问题
我需要把流式 token delta 存起来吗?
通常不需要全量存。建议存:
- 最终答案
- 关键里程碑事件
- 必要的调试摘要
全量 delta 既贵又难查。
会话历史要支持“重放”,数据应该怎么存?
重放依赖的是事件序列(seq),而不是消息字符串。你可以存“可重放事件”并在前端用 reducer 还原 UI 状态(见前端篇:/articles/chat-frontend-state-from-messages-to-tool-events)。