这是一个?
现有功能优化
详细描述
现象
跑一段时间后,data/langbot.db 可能涨到几十 GB,容器内存(Beszel 一类面板上的 working set)也长期在 2G+。
不完全是“内存泄漏”。更常见的是:
- 监控表里塞进了整段图片 base64
- SQLite 又大又慢,page cache 顶上去
- 会话历史里如果也一直挂着 base64,多轮对话时内存峰值再叠一层
我们这边实际摸过一版:monitoring_messages 大概一万行量级,库文件却到了约 21GB;抽样里接近一半消息带 data:image / base64,单条内容十几 MB 都有。写库时还能看到 database is locked,错误日志里 SQL 参数被截断到一千多万字符。
代码上是怎么串起来的
收图(以 OneBot/aiocqhttp 为例)会先把图下载并转成 base64,挂到 Image 上:
pkg/platform/sources/aiocqhttp.py
收到 image 后 qq_image_url_to_base64(...),再 Image(base64=f'data:image/...;base64,...')
监控入库时,整条消息链原样 JSON 序列化进库,没有像 tool payload 那样做长度限制:
pkg/pipeline/monitoring_helper.py
json.dumps(query.message_chain.model_dump(), ...)
再 monitoring_service.record_message(...)
pkg/api/http/service/monitoring.py
record_message 直接把 message_content 写入 monitoring_messages
(同文件里 tool 结果倒是有 _serialize_tool_payload(..., max_length=20000),媒体这条路没对齐)
会话侧也会把用户消息和回复 append 进对话历史,预处理时再把 Image.base64 塞进模型上下文:
pkg/pipeline/process/handlers/chat.py
using_conversation.messages.append / extend
pkg/pipeline/preproc/preproc.py
ContentElement.from_image_base64(me.base64)
所以问题不只在“库大”,会话路径一样会长期拿着大图。
对比一下:事件日志其实已经在走“存文件、只留 key”:
pkg/platform/logger.py
img.get_bytes() → storage_provider.save('bot_log_images/...') → 日志里只留 key
监控 / 会话还没跟这套思路对齐。
链路(方便对一下)
平台收图 (aiocqhttp 等)
└─ Image.base64 = 整图 data-url
│
├─ monitoring_helper.model_dump() ──► monitoring_messages(SQLite TEXT,巨大)
│
├─ conversation.messages 长期持有 ──► 多轮 prompt / 内存峰值
│
└─ preproc → from_image_base64() ──► 调模型时再带一遍
想怎么改(方向,不是接口草案)
核心就一句话:会长期存、会反复序列化的地方,不要再抱着 base64。
比较顺的做法:
-
媒体按内容去重后外置
对解码后的图片 bytes 算 xxHash3-128(不要 hash base64 字符串,避免前缀差异导致同图不同 key)。
元数据放小表/小记录,实体走现有 storage_provider(pkg/storage/...,和 bot_log_images 同一套后端)。
monitoring_messages 里只留引用(hash / mime / size / storage_key 之类),去掉 base64 字段。
-
会话同样只持引用
只改监控、会话里还挂着原图的话,磁盘会好一点,内存峰值还在。
历史消息里存引用;真要发给模型或回平台时再按 hash 取回。
相关位置仍是 chat.py 写入历史、preproc.py 组装上下文。
-
必须有 LRU 容量上限
这是缓存,不是永久图床。
用访问时间做近似 LRU(写/读 touch,超总大小就踢最久没用的)。
允许 miss:监控回看时图没了就提示已淘汰,别为了“永远能点开”把磁盘再堆爆。
-
落库 / 入会话前统一处理
最好收口在“即将持久化、即将进入会话历史”的路径上处理消息链,避免每个适配器改一遍。
监控入口就在 monitoring_helper;会话入口在 pipeline 写 history 前后。
另外异常日志别再把整段 SQL 参数(含 base64)打出来。
不打算做成啥
- 不是对外 CDN / 永久图床
- 不要求先重写所有适配器的收图方式(适配器短暂持有 base64 可以接受,重点是后面别长期存)
- 算法先按 xxHash3-128(缓存语义 + 去重);真要换成 blake3 也行,但不是这个 issue 的重点
预期效果
langbot.db 不再跟着群聊图片线性涨到几十 GB
- 容器 working set / swap 下来一截
- monitoring 写入锁、超大错误日志少很多
- 多轮带图对话的内存曲线平稳一些
还想一起定的
- 默认缓存上限大概定多少、是否可配置
- 监控引用被 LRU 清掉后,前端怎么展示
- 监控和会话是否共用同一套 media cache
- 第一期是否只覆盖 Image,Voice/File 是否一起
有多实例、S3 存储、只读部署之类约束也可以直接补在评论里。
这是一个?
现有功能优化
详细描述
现象
跑一段时间后,
data/langbot.db可能涨到几十 GB,容器内存(Beszel 一类面板上的 working set)也长期在 2G+。不完全是“内存泄漏”。更常见的是:
我们这边实际摸过一版:
monitoring_messages大概一万行量级,库文件却到了约 21GB;抽样里接近一半消息带data:image/ base64,单条内容十几 MB 都有。写库时还能看到database is locked,错误日志里 SQL 参数被截断到一千多万字符。代码上是怎么串起来的
收图(以 OneBot/aiocqhttp 为例)会先把图下载并转成 base64,挂到
Image上:pkg/platform/sources/aiocqhttp.py收到 image 后
qq_image_url_to_base64(...),再Image(base64=f'data:image/...;base64,...')监控入库时,整条消息链原样 JSON 序列化进库,没有像 tool payload 那样做长度限制:
pkg/pipeline/monitoring_helper.pyjson.dumps(query.message_chain.model_dump(), ...)再
monitoring_service.record_message(...)pkg/api/http/service/monitoring.pyrecord_message直接把message_content写入monitoring_messages(同文件里 tool 结果倒是有
_serialize_tool_payload(..., max_length=20000),媒体这条路没对齐)会话侧也会把用户消息和回复 append 进对话历史,预处理时再把
Image.base64塞进模型上下文:pkg/pipeline/process/handlers/chat.pyusing_conversation.messages.append / extendpkg/pipeline/preproc/preproc.pyContentElement.from_image_base64(me.base64)所以问题不只在“库大”,会话路径一样会长期拿着大图。
对比一下:事件日志其实已经在走“存文件、只留 key”:
pkg/platform/logger.pyimg.get_bytes()→storage_provider.save('bot_log_images/...')→ 日志里只留 key监控 / 会话还没跟这套思路对齐。
链路(方便对一下)
想怎么改(方向,不是接口草案)
核心就一句话:会长期存、会反复序列化的地方,不要再抱着 base64。
比较顺的做法:
媒体按内容去重后外置
对解码后的图片 bytes 算 xxHash3-128(不要 hash base64 字符串,避免前缀差异导致同图不同 key)。
元数据放小表/小记录,实体走现有
storage_provider(pkg/storage/...,和bot_log_images同一套后端)。monitoring_messages里只留引用(hash / mime / size / storage_key 之类),去掉 base64 字段。会话同样只持引用
只改监控、会话里还挂着原图的话,磁盘会好一点,内存峰值还在。
历史消息里存引用;真要发给模型或回平台时再按 hash 取回。
相关位置仍是
chat.py写入历史、preproc.py组装上下文。必须有 LRU 容量上限
这是缓存,不是永久图床。
用访问时间做近似 LRU(写/读 touch,超总大小就踢最久没用的)。
允许 miss:监控回看时图没了就提示已淘汰,别为了“永远能点开”把磁盘再堆爆。
落库 / 入会话前统一处理
最好收口在“即将持久化、即将进入会话历史”的路径上处理消息链,避免每个适配器改一遍。
监控入口就在
monitoring_helper;会话入口在 pipeline 写 history 前后。另外异常日志别再把整段 SQL 参数(含 base64)打出来。
不打算做成啥
预期效果
langbot.db不再跟着群聊图片线性涨到几十 GB还想一起定的
有多实例、S3 存储、只读部署之类约束也可以直接补在评论里。