日期:2026-08-09
客户是一家大型制造企业,知识库项目做了两年多,向量数据从最初的几百万条涨到了三亿八千万条。数据来源很杂:产品手册、工艺文件、设备维修记录、质量报告、历年的技术方案、还有全部的售后工单。
问题是成本。他们的向量库最初是全内存索引,检索快,P95 在四十毫秒左右,用起来很舒服。但随着数据量涨上去,内存需求也水涨船高,到项目第二年,光是向量库这块的服务器成本每年就要七位数。IT 那边做预算的时候把这一项拎出来问了好几次,业务部门解释不清楚为什么存这么多东西还这么贵。
他们自己试过全部换成磁盘索引,成本立刻降下来了,但检索延迟从四十毫秒涨到了将近八百毫秒。前端问答的体验一下就垮了,用户反馈"以前问一句马上出,现在要等一会儿"。试了两周就退回去了。
我们去看数据的时候统计了一下访问分布,结果挺典型。近半年产生的数据占总量百分之十四,却承担了百分之八十一的检索命中。三年以上的老数据占了百分之四十七的存储,命中率不到百分之三。这种分布摆在这儿,分层是很自然的选择,问题是怎么分得准、迁得稳、查得快。
我们在客户 AI 底座的向量数据库层上做了一套冷热分层,对上层的 RAG 检索保持接口不变,业务代码不用改。整体分成四块。
访问热度统计负责给每个数据分区打分。统计维度包括最近访问时间、访问频次、访问频次的变化趋势、数据本身的创建时间和业务标记。统计粒度不是单条向量,是按业务分区,比如"某产线 2023 年的维修记录"是一个分区。单条粒度统计的开销划不来,分区粒度足够用。
冷热分层与自动迁移把数据放在三层。热层是内存索引,放最近三个月加上高频访问的历史数据;温层是本地 SSD 索引,放半年到两年的数据;冷层是对象存储加轻量索引,放两年以上且低频的数据。迁移由调度任务在业务低峰期执行。
磁盘索引优化针对温层和冷层。同样是磁盘,索引结构和参数调得好不好,延迟能差好几倍。这块我们花的时间最多。
跨层召回合并解决的是一次检索可能跨越多层的情况。检索请求先打热层,根据结果的置信度决定是否继续往下查,最后把多层结果合并重排。
热度判定的抖动是第一个坑。最初的判定规则很简单,最近三十天访问次数低于阈值就降级到温层。跑了两周之后发现有一批分区在热层和温层之间来回跳,一周迁下去,过几天又迁上来。查了才明白,这些是有明显周期性的数据,比如月度报表相关的资料,每月初集中访问几天,其余时间没人碰。
改进的做法是引入滞后区间和最短驻留时间。降级的阈值比升级的阈值低一档,中间形成一个缓冲带,处在缓冲带内的分区维持现状不动。同时规定任何分区迁移后至少驻留十四天才能再次迁移。另外对识别出周期性访问模式的分区单独打标,这类分区不参与自动降级,由人工确认。这几条加上之后,月均迁移次数从两千三百多次降到一百八十次,抖动基本消失。
分层迁移的一致性是我最担心的部分。迁移过程中数据既在源层又在目标层,如果这时候有检索请求进来,可能查到重复结果;如果迁移中途失败,可能两边都不完整。我们的方案是双写加版本标记:迁移开始时目标层写入,写完校验条数和抽样校验向量内容,校验通过后原子切换路由指针,最后延迟一段时间再删除源层数据。切换期间检索会带上版本号,保证同一个请求看到的是一致的视图。
这套流程跑了几个月,出过一次问题。有个分区在迁移到冷层时,对象存储那边写入成功但索引构建失败,校验环节只校验了条数没校验索引可用性,切换后那个分区检索一直返回空。上线前的测试没覆盖到这种情况,因为测试环境的对象存储很小很快,从来没有索引构建失败过。修复方式是在校验环节加了一步真实检索测试,用几条已知向量去查,能查到才算通过。这个教训让我对"校验要校验最终效果而不是中间状态"这句话有了很具体的理解。
冷层检索延迟是最难压的。对象存储的访问延迟本身就有几十毫秒,加上索引加载和计算,第一版实测 P95 到了一千二百毫秒,完全不可用。优化做了几件事。索引结构上,冷层不用图索引,改用倒排加乘积量化的组合,索引体积小很多,可以整体加载进内存做粗筛,只有精排阶段才回源取原始向量。数据布局上,把同一分区的向量按聚类结果重排存储,让一次精排需要读取的对象数从平均三十多个降到四五个。访问路径上加了一层本地磁盘缓存,最近访问过的冷层数据块缓存在本地,命中率大约百分之四十。三项做完,冷层 P95 降到二百六十毫秒。这个数字比热层慢很多,但考虑到冷层只承担百分之三的流量,整体加权下来影响可以接受。
跨层召回的合并要注意分数可比性。不同层用的索引类型不同,相似度分数的分布不完全一致,直接混在一起排序会出问题。我们的处理是各层召回后统一走一遍精排模型,用原始向量重算相似度,以精排分数为准。精排的计算量不大,因为召回数量已经收敛到几十条。
还有个策略上的取舍。理论上每次检索都应该查全部三层才能保证召回率,但那样延迟就被最慢的一层拖住了。我们做的是自适应:先查热层,如果热层结果的最高分超过设定阈值且结果数足够,就直接返回不再往下查;否则继续查温层,以此类推。实测下来百分之八十三的请求只查了热层,百分之十四查到温层,只有百分之三查到冷层。这个策略会损失一点召回率,我们评估过大概是零点九个百分点,客户认为可以接受。
案例片段(已脱敏):分层策略与迁移规则配置。
yaml tiering: layers: hot: {storage: memory, index: HNSW, target_p95_ms: 50} warm: {storage: nvme, index: DiskANN, target_p95_ms: 150} cold: {storage: object, index: IVF_PQ, target_p95_ms: 300} scoring: window_days: 30 weights: {recency: 0.35, freq: 0.40, trend: 0.15, biz_tag: 0.10} promote_threshold: 62 # 升级阈值 demote_threshold: 38 # 降级阈值(中间为滞后缓冲带) min_residency_days: 14 periodic_pattern: detect: true action: PIN_CURRENT_LAYER # 周期性分区不自动降级 migration: window: "02:00-05:30" concurrency: 4 verify: [count_match, vector_sample, live_query_probe] switch: atomic_pointer source_purge_delay_h: 48案例片段(已脱敏):一次冷层检索延迟优化的分解记录。
``` baseline (naive IVF on object storage): P95 = 1218ms
step1 索引改 IVF_PQ,粗筛索引常驻内存 object read 次数 34 -> 34 P95 = 946ms (-272) step2 分区内按聚类重排存储,精排读合并 object read 次数 34 -> 4.6 P95 = 407ms (-539) step3 本地 SSD 块缓存 (LRU 256GB) cache hit rate = 41.3% P95 = 261ms (-146)
遗留: 冷启动首次访问仍有 ~600ms 长尾,占冷层请求 8.7% 评估后接受,未继续优化(成本收益不划算) ```
方案全量运行五个月,数据如下。
存储成本降幅百分之六十一点四。内存节点从原来的十六台缩到五台,多出来的容量由 SSD 和对象存储承接。按年化算,这块省下的钱超过了整个项目的实施投入。
整体 P95 检索延迟从原来全内存时的四十一毫秒变成六十七毫秒。慢了二十六毫秒,业务侧做过盲测,用户感知不到差异。这个结果比客户预期好,他们原本做好了延迟翻倍的心理准备。
召回率变化是负零点九个百分点,从百分之九十四点二降到百分之九十三点三。这个损失来自自适应查询策略。客户做过一轮人工评估,认为对最终问答质量的影响在噪声范围内。
迁移准确率百分之九十九点九七。出错的那部分是前面提到的索引构建失败那次,涉及一个分区,修复后补迁成功。加了实检探针之后再没出过问题。
热层命中率百分之八十三点一,温层百分之十四,冷层百分之二点九,与最初的访问统计吻合。月均迁移一百八十次左右,全部在低峰窗口完成。
以上数据为脱敏后的项目复盘口径。
分层这个思路本身不新鲜,数据库领域用了几十年了,搬到向量库上的难点在于向量检索的特性。传统数据库冷数据查得慢无非是慢一点,向量检索如果冷层召回不全,影响的是回答质量,用户看不到延迟但能感觉到答得不对。所以召回率的损失一定要量化出来给客户看,不能含糊过去。
热度判定的抖动问题会浪费大量迁移资源,滞后区间和最短驻留时间这两条几乎是必须的。我们是踩了两周的坑才加上,如果重来一次会在第一版就设计进去。周期性访问模式的识别也很有价值,制造业、财务、报表这类业务里周期性数据特别多。
校验要校验最终效果。那次索引构建失败的事故说明,只校验中间状态(条数、大小、返回码)是不够的,必须有一个端到端的探针去验证"这个数据现在真的能被查到"。这条现在被我们写进了内部的数据迁移检查单。
冷层的优化重点是减少 IO 次数,不是加速单次 IO。三步优化里收益最大的是把对象读取次数从三十四次降到四点六次,想清楚数据布局比调参数管用。
自适应查询这类会损失召回率的策略,要跟客户明确沟通并且给出量化数字。我们提前做了对比评估,把零点九个百分点这个数字摆在桌上让客户决定,而不是自己替客户做主。这种事情如果先斩后奏,出问题的时候很被动。
客户新提的需求是按业务标签强制驻留,比如合规文档不管访问频率多低都要放在温层以上。改起来不难,主要是跟档案管理制度对齐分类口径,还在梳理。