Skip to content

[Feature]: 媒体内容寻址缓存(xxHash3-128 + LRU),避免 base64 撑爆监控库与会话内存 #2354

Description

@BiFangKNT

这是一个?

现有功能优化

详细描述

现象

跑一段时间后,data/langbot.db 可能涨到几十 GB,容器内存(Beszel 一类面板上的 working set)也长期在 2G+。

不完全是“内存泄漏”。更常见的是:

  1. 监控表里塞进了整段图片 base64
  2. SQLite 又大又慢,page cache 顶上去
  3. 会话历史里如果也一直挂着 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。

比较顺的做法:

  1. 媒体按内容去重后外置
    对解码后的图片 bytes 算 xxHash3-128(不要 hash base64 字符串,避免前缀差异导致同图不同 key)。
    元数据放小表/小记录,实体走现有 storage_providerpkg/storage/...,和 bot_log_images 同一套后端)。
    monitoring_messages 里只留引用(hash / mime / size / storage_key 之类),去掉 base64 字段。

  2. 会话同样只持引用
    只改监控、会话里还挂着原图的话,磁盘会好一点,内存峰值还在。
    历史消息里存引用;真要发给模型或回平台时再按 hash 取回。
    相关位置仍是 chat.py 写入历史、preproc.py 组装上下文。

  3. 必须有 LRU 容量上限
    这是缓存,不是永久图床。
    用访问时间做近似 LRU(写/读 touch,超总大小就踢最久没用的)。
    允许 miss:监控回看时图没了就提示已淘汰,别为了“永远能点开”把磁盘再堆爆。

  4. 落库 / 入会话前统一处理
    最好收口在“即将持久化、即将进入会话历史”的路径上处理消息链,避免每个适配器改一遍。
    监控入口就在 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 存储、只读部署之类约束也可以直接补在评论里。

Metadata

Metadata

Assignees

No one assigned

    Labels

    eh: Featureenhance: 新功能添加 / add new featureseh: Improveenhance: 现有功能的改进 / improve current featuresm: Session会话和消息模块 / Sessions managementpd: Need designpending: 需要进一步设计的功能 / wait for us to design

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions