本地向量库索引把 C 盘塞爆 48.6G:一次 LanceDB 膨胀排查与根治实录

发布时间:2026/9/29 12:23:34
本地向量库索引把 C 盘塞爆 48.6G:一次 LanceDB 膨胀排查与根治实录
本地向量库索引把 C 盘塞爆 48.6G一次 LanceDB 膨胀排查与根治实录热榜上在聊存储引擎的工程审阅我翻到自己两个月前的工单记录决定也交一份事故实录。主角是 LanceDB——一个嵌入式向量数据库我们用它给本地知识库做检索。事故结果一张 75MB 的表索引目录膨胀到 48.6GB把生产机的 C 盘干到了 0 字节。倍的数先放这儿索引体积是数据本身的 648 倍。下面是完整过程。发现不是报警是满盘那天生产机突然各种诡异地失败——日志写不进去、临时文件创建失败。一看 C 盘可用空间 0。清理临时目录挤出来 2G转眼又被吃掉。顺着大目录往上摸最后停在一个 LanceDB 表目录textbooks.lance/_indices底下 1492 个子目录合计 48.6G。而表本身呢13263 行数据75MB。第一反应是索引坏了删掉重建就行。幸好没删——往下看你会知道为什么这一步差点酿成二次事故。定位三个因素叠出来的风暴事后看三个独立的设计决策严丝合缝地凑在了一起单独任何一个都闹不出这个动静。因素一建索引不幂等。我们的项目在初始化脚本里调用 createIndex 建全文检索索引。问题是 LanceDB 的这个 API 当时并不幂等——索引已经存在再调一次不会报错也不会跳过而是老老实实又建一份约 33MB。而我们的 init.js 是每次进程启动都会跑的。因素二全项目没有任何地方调 optimize。建了索引从没人清理旧版本。LanceDB 是 COPY-ON-WRITE 的设计每次写入产生一个新版本旧版本不会立刻消失要靠显式的 optimize/cleanup 去回收。我们两天攒了 1517 个写入版本。因素三每个版本都带着一份索引快照。这是压垮骆驼的那根稻草——旧版本不只是一个 manifest 记录它引用的索引段文件也一起留着。1517 个版本 × 每个版本的 FTS 索引快照 48.6G。三个因素单独看都不算 bugAPI 行为符合它的设计不调清理也怪不着数据库快照机制是为了版本一致性。但叠在一起就是一个每次启动都长大一点、永不收缩的怪物。为什么不敢手删定位到 _indices 目录后第二反应是写个脚本把旧目录批量删了。动手前做了个验证然后停手了。LanceDB 的 manifest 通过 UUID 引用索引段目录名和 manifest 里的 UUID 的对应关系没有官方文档保证稳定也没有现成工具验证哪些目录是活引用、哪些是死垃圾。手删一旦误删活引用整张表的索引直接报废而那时 C 盘已经 0 字节连重建索引的临时空间都紧张。事故现场的正确姿势是先用官方 API 做清理哪怕它慢。修复一行 optimize 回收 48.5Gawait table.optimize({ cleanupOlderThan: new Date(), // 清理所有过期版本 deleteUnverified: true, // 连未验证的孤儿文件一起清 });两个参数都有讲究deleteUnverified: true 是必需的。官方默认只清理 7 天以上的未验证文件这是一个安全保护。但我们实测不带这个参数跑 optimizebytesRemoved 返回 0——等于白做。对于已经确认要清理的场景必须显式打开。cleanupOlderThan 要设到分钟级。默认的保留期是为可能有并发读者还在读旧版本的场景设计的对我们这种单进程写入、停机维护时清理的场景保留到分钟级就够。跑完回收 48.5G行数 13263 不变全文检索命中结果和清理前基线完全一致近 300 个测试全绿。防复发两道闸 一个维护服务修完只是把水抽干堤还得重修第一道闸init.js 不再无脑建索引。先 listIndices() 查已有索引存在就跳过。幂等性这事API 不给你自己保证。第二道闸定时维护服务。新增了一个每 6 小时跑一次的维护任务但它不是无脑执行 optimize——带了三道前置检查没有正在灌库的进程、库静默超过 30 分钟、索引体积超过数据体积 3 倍。三道全过才动手任何一道没过就走短重试而不是干等下一个整点。还有个部署层的坑顺带记下生产环境是 Docker 跑的改完代码如果只 restart 容器而不 docker compose build容器里跑的还是旧镜像——我们第一次验证防复发逻辑时就被这个坑了本地测试全绿生产上没生效。其实是压根没部署。教训清单索引不是免费的。每一种加速结构都有自己的生命周期管理成本选型时除了看查询性能还要看它的垃圾回收机制要不要你自己调。初始化脚本里的写操作默认假设它会被跑一万次。幂等性要么 API 自带要么自己包一层。清理类的默认值是给小库设计的。7 天保护、默认保留期在 75MB 的表上无感在膨胀到 48G 时就是optimize 跑了等于没跑。满盘是兜底防线。等你看到 0 字节一切排查动作本身都在制造新的空间压力。容量监控要在它之前。事故现场不做没有验证过的删除。手删索引目录看着爽验证不了引用关系的删除都是赌博。我们的电商 AI 客服产品叮当小宝CS官网dingdang.asia也在生产环境里跑着类似的本地存储这套维护闸门后来成了我们所有本地数据服务的标配。