欢迎光临
我们一直在努力

向量库把 C 盘吃到 0 字节:75M 数据撑出 48.6G 索引的清理复盘

一次真实的磁盘事故:应用数据只有 75MB,它的向量库索引目录却悄悄长到 48.6GB。定位、回收、验收的完整过程,以及为什么索引型存储必须纳入磁盘监控。

事故现场

一台开发机 C 盘可用空间归零,系统开始随机弹"磁盘已满"的提示,IDE 保存文件偶尔失败。按老套路逐层找大文件:先看下载目录,正常;再看 IDE 缓存和 npm 缓存,正常;一路用目录体积统计工具往下钻,最后锁定的元凶不在任何"惯犯"路径里,而是一个向量库项目的数据目录:

textbooks.lance/
├── data/ 75 MB ← 业务数据本体
└── _indices/ 48.6 GB ← 索引目录,1492 个子目录

数据与索引的体积比是 1 : 648。直觉判断是索引写坏了,接下来怀疑有人写了循环在疯狂建索引——翻了写入路径、翻了建索引的调用点,都没有。这是一个安静膨胀的典型案例:没有任何报错,没有任何异常日志,它只是每天比前一天更大一点,直到把整块盘吃穿。

定位过程:从"盘满了"到"是索引"

从满盘到锁定目录,实际只用了三步:

  • 按体积排序找目录。 用目录体积统计工具对 C 盘整体扫一遍,异常大的目录一眼可见——不是系统目录,是一个业务数据目录;
  • 目录内部再排序。 进入 textbooks.lance/ 后按子目录体积排,data/ 只有 75MB,_indices/ 里有 1492 个子目录,随便点开几个都在几十 MB 量级;
  • 看时间戳分布。 这 1492 个子目录的修改时间均匀铺开在几个月里,每天三五个——说明不是一次性事故,而是持续行为。
  • 持续、安静、与写入频率相关而不是与数据量相关——这三个特征指向的不是"索引建错了",而是"索引从未被回收"。

    膨胀的机制:每个版本各快照一份索引

    这个项目用的是 LanceDB,表里建了全文检索(FTS)索引。出问题的不是索引本身,而是它的版本化策略:

    • 表每次写入都会产生一个新版本(schema 或数据变更都算);
    • 已建索引的表在新版本上做查询时,索引需要与版本对齐,于是每个写版本都会留下一份索引快照;
    • 只要没人主动清理旧版本,这些快照就一直躺在 _indices/ 下。

    这个项目的写入模式是高频小批量:记录里留下的写版本计数是 1517。也就是说,同一份 FTS 索引,被按版本快照了一千五百多次。做个算术:75MB 的数据乘以一千多的版本留存,48.6GB 就这么攒出来了——膨胀速度跟数据增长几乎无关,只跟写入次数线性相关。

    注意这里没有任何"泄漏"意义上的 bug。每个快照单独看都是合法的、可回溯的版本状态,这是版本化存储的设计行为。问题在于业务侧从来没有人调用过清理接口,版本只增不减,索引自然只增不减。设计给你留了后悔药,但你得自己按时吃。

    补一句怎么确认"版本只增不减":官方 API 里能直接列出表的全部版本和当前指针,一千五百多个版本一目了然。版本列表是这个类型存储里含金量很高的体检项目——它同时回答"写入有多碎"和"垃圾攒了多少"两个问题,建议和目录体积统计一起放进巡检。

    为什么不能手删

    磁盘满的时候,人的直觉就是 rm -rf 掉 _indices/ 下看起来旧的那批目录。这次没有这么做,原因是查了源码之后的后背发凉:

    _indices/ 下每个索引目录对应一个 UUID,而表的 manifest 文件里记录着"当前有效版本引用了哪些索引 UUID"。目录名和 manifest 引用之间的对应关系没有任何外部工具能验证——你不知道哪个目录是当前版本正在引用的。删掉一个看似"旧"的目录,可能恰好删掉当前版本引用的那份,结果是整张表的索引状态损坏:轻则查询时索引失效退化为全表扫描,重则表无法打开,重建索引的成本远高于清磁盘。

    对索引型存储来说,磁盘清理必须走官方 API。手删目录等于对着一份你看不懂的引用清单掷骰子。

    回收:两个参数的 optimize

    LanceDB 提供的官方清理入口是表的 optimize:

    await table.optimize({
    cleanupOlderThan: new Date(), // 把所有早于此刻的旧版本标记过期
    deleteUnverified: true // 连同未校验的残留一起清
    });

    cleanupOlderThan 传当前时间意味着所有历史版本全部过期——对这个纯检索场景的表是安全的,因为它没有"回滚到三小时前"的业务诉求;如果业务有版本回溯的需要,这里应传一个保留窗口(比如七天前),让窗口内的版本继续可用。执行完成后:

    • _indices/ 从 48.6GB 降到 0.1GB 量级,回收约 48.5GB;
    • C 盘可用空间恢复到几十 GB 的正常水位。

    整个执行过程分钟级。真正花时间的不是清理,而是前面"敢不敢跑"的判断。

    验收:清的是索引,不是数据

    删索引类的操作最容易翻车的方式,是磁盘空间回来了、数据却悄悄少了一块。所以验收不能只看 df,要做业务侧对账:

  • 行数对账:清理前后表行数均为 13263,不变;
  • 检索对账:同一组关键词跑 FTS,命中条数与排序和清理前基线一致;
  • 回归测试:项目 288 个测试全绿,git 工作区干净。
  • 三项都对上,才敢说这次清理是"回收了冗余"而不是"删了数据"。尤其第二条:检索行为是索引存在的根本意义,索引清理后的检索结果必须与清理前逐条一致,否则无论空间回收多少都算失败。

    把教训固化成三件小事

    • 索引是数据体积的乘数,不是加数。 容量预估按"数据量 × 版本保留策略"算,只按数据量做的容量规划在索引型存储上必然失真,而且失真的倍数会随写入频率漂移。
    • 高频小写入的表必须配定期 optimize。 写入越碎,版本攒得越快。膨胀速度和业务增长无关,只和写入频率有关——业务没长大,磁盘也可能被吃穿。
    • 磁盘监控要报到目录级。 这次能在一小时内定位,靠的是对数据目录结构的熟悉加上目录级体积统计;如果监控只报"C 盘使用率 100%"而不报到目录,每次事故都要从盘开始人肉钻取。

    事后补了一个简单的巡检:定期统计各数据目录体积写入日志,超阈值告警。一行目录体积统计换一次深夜满盘救火,这笔账怎么算都划算。

    关于桌面端 AI 助手和知识库检索的工程实践,可以参考:

    • 叮当小宝电商客服 AI:叮当小宝CS — 电商AI智能客服,多平台自动回复
    • 知识库自动应答的落地思路:客户总问重复问题? 微信常见问题自动回复搭建指南 | 微客AI助手
    赞(0)
    未经允许不得转载:171主机测评 » 向量库把 C 盘吃到 0 字节:75M 数据撑出 48.6G 索引的清理复盘
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址