Weaviate Blog

HFresh: Memory-Efficient Vector Search

8.5内容质量
HFresh: Memory-Efficient Vector Search

TL;DR · AI 摘要

HFresh是Weaviate推出的基于磁盘的向量索引,通过分区和两阶段搜索策略实现低内存使用和大规模数据集的高效查询。

核心要点

  • HFresh将内存使用降低到传统HNSW的1/10,适合资源受限场景
  • 采用分区方法将向量分为小区域(postings),结合LSM存储优化磁盘I/O
  • 继承SPFresh论文思想,支持大规模数据更新无需重建索引

结构提纲

按章节快速跳转。

  1. 介绍HFresh作为Weaviate的磁盘向量索引,解决内存瓶颈问题。

  2. ·HNSW的内存瓶颈

    分析HNSW在大规模数据集中的内存占用问题及其局限性。

  3. 阐述基于分区和LSM存储的磁盘索引设计原理。

  4. 描述内存中心点索引与磁盘详细搜索的协同机制。

  5. 说明如何通过局部更新避免全量重建索引。

思维导图

用一张图看清主题之间的关系。

查看大纲文本(无障碍 / 无 JS 友好)
  • HFresh:内存高效的向量搜索
    • 核心架构
      • 分区方法(postings)
      • LSM存储优化
      • 两阶段搜索
    • 优势特性
      • 内存使用降低10倍
      • I/O可控性
      • 无需重建更新

金句 / Highlights

值得收藏与分享的关键句。

#向量搜索#内存优化#Weaviate#HNSW#LSM树
打开原文

HFresh: 高效内存的向量搜索 | Weaviate

HFresh: 高效内存的向量搜索

2026年9月9日

·

15分钟阅读

Asdine El Hrychy

高级研究工程师

Roberto Esposito

研究工程师

HFresh是Weaviate的基于磁盘的向量索引,适用于优先考虑降低内存使用而非峰值查询吞吐量的应用场景。这种权衡对资源有限的小型应用和大型数据集都具有价值。例如,Weaviate Cloud的免费层级默认通过其成本优化配置使用HFresh。

理解相似性搜索中HNSW的内存瓶颈

在快速查找相似向量方面,HNSW(分层可导航小世界图)已成为黄金标准。它速度快且准确,但随着数据集从数百万向数十亿向量扩展时,HNSW暴露出一个根本性限制:其图结构和向量缓存都存储在内存中。

HNSW是一种基于图的索引,将向量组织成分层结构。顶层是具有长距离连接的稀疏图,帮助快速定位到目标区域。随着向下遍历层级,图结构变得越来越密集,包含更多局部连接,最终在底层引导至最相似的向量。

问题不在于HNSW是否优秀。它确实非常出色。如果需要最低延迟和最高吞吐量,HNSW几乎难以被超越。但许多应用更优先考虑降低内存使用和扩展性,而非峰值查询性能。

这正是基于磁盘的索引变得有趣的地方。如果能够通过牺牲部分延迟来实现更低的内存占用,并具备扩展到更大数据集的能力,会怎样?

介绍HFresh

HFresh是一种现代的基于磁盘的向量索引,专为大规模场景下的高召回率、强更新性能和可控查询I/O而设计。它基于SPFresh研究论文提出的思想,将设计适配到Weaviate已有的成熟组件上。

从高层次来看,HFresh属于基于分区的向量索引家族。与HNSW在全局图中连接所有向量邻居不同,HFresh将向量划分为许多称为"帖子(postings)"的小区域。每个帖子包含向量空间中彼此接近的向量,并以LSM存储结构存储在磁盘上。

为了使这种布局更高效,HFresh采用两阶段搜索策略。

首先,一个紧凑的内存中质心索引确定哪些向量空间区域与查询相关。然后,仅从磁盘获取对应的帖子并进行详细搜索。通过将磁盘读取限制在数据集的小部分,HFresh的设计目标是在数据集扩展到数十亿规模时,仍能保持I/O可控和延迟可预测。

这种结构旨在支持非常大的数据集,同时保持性能的可预测性。

无需重建的更新机制

SPFresh的核心思想(被HFresh继承)是:大多数更新仅影响向量空间的一小部分区域。

在传统基于分区的索引中,更新可能累积导致分区漂移,最终需要完全重建以恢复召回率和延迟,这一过程在大规模场景下可能需要数小时甚至数天。

SPFresh表明,这通常是没有必要的。在结构良好的分区索引中,插入或删除一个向量通常只会对向量空间中的一个小区域产生影响。与其重新构建整个索引,不如通过增量再平衡来维护索引质量,仅使用一组局部操作。

  • 分裂过大的发布列表
  • 合并过小的发布列表
  • 当边界变化时重新分配向量

这些操作主要在后台异步执行,持续修复局部的小规模不平衡,防止其累积成全局性问题。最终结果是一个能够长期保持新鲜度和良好平衡的索引,无需经历破坏性的重建周期。

HFresh如何基于SPFresh改进

HFresh借鉴了SPFresh的核心思想,并根据Weaviate的架构进行了适配。目标不是逐个组件复现论文内容,而是保留使该设计如此有趣的关键特性:

  • 采用本地维护而非重建
  • 对查询I/O进行可控管理
  • 明确区分内存路由层和基于磁盘的发布列表

在此基础上,我们做出了一系列务实的选择。我们没有引入全新的ANN机制,而是复用了Weaviate中已验证的成熟组件,并调整它们以适应新的架构。这意味着在保持SPFresh整体理念的同时,重新思考部分组件以更好地匹配Weaviate在索引、过滤、压缩和更新方面的优势。

HNSW作为中心点索引

HFresh的一个关键设计选择是使用HNSW作为中心点索引,而非SPTAG。

SPTAG是微软原始SPANN设计中使用的ANN索引,并延续到SPFresh中。它结合了分区树和图结构,使查询能够快速导航到最近的中心点,再进一步找到正确的发布列表。然而,对于Weaviate来说,HNSW是更自然的选择:它已经很好地承担了这一角色。

HNSW是Weaviate中最广泛使用的向量索引,也是系统中经过验证最成熟的组件之一。我们在生产环境中对其行为有深入理解,覆盖了各种工作负载和数据集规模。将HNSW用于中心点搜索,使HFresh能够基于已验证的基础设施进行构建,而非引入全新的机制。

HNSW与中心点索引的实际需求也非常契合。在HFresh中,中心点层需要快速且准确地将查询路由到正确的发布列表。这意味着它必须保持紧凑性、低延迟,并支持随着发布列表演变而进行的频繁更新。

中心点并非静态不变:分割、合并和重新分配操作会持续重塑向量空间的分区结构,因此中心点索引必须能够吸收大量插入和删除操作,而无需昂贵的重建过程。

另一个优势是中心点索引本身可以进行量化。由于中心点层仅用于识别向量空间中具有潜力的区域,HFresh可以对这些向量进行足够激进的压缩,在保持高召回率所需精度的同时减少内存使用。事实上,HFresh使用带有RQ8的HNSW,将中心点的内存使用量降低了4倍。

使用 HNSW 也意味着对 HNSW 的改进会自动惠及 HFresh。一个很好的例子是 ACORN,它通过提高部分数据集匹配过滤器时的图遍历效率,从而改善了过滤搜索。由于 HFresh 依赖 HNSW 作为中心点层,这些改进并非孤立存在:它们增强了 HFresh 的查询路径。

量化

HFresh 在两个不同压缩级别上使用旋转量化。

原因在于搜索的两个阶段承担着不同职责。中心点索引需要将查询路由到正确的条目,因此需要足够的精度以避免将查询发送到向量空间的错误区域。另一方面,条目用于生成候选结果,其近似分数并非最终排名,因为 HFresh 会使用原始未压缩向量对最佳候选结果进行重新评分。

旋转量化通过将向量旋转到更容易压缩的表示形式,然后降低每个维度的精度来实现。HFresh 使用:

  • RQ8 用于中心点,将中心点向量内存减少 4 倍
  • RQ1 用于条目,与 32 位浮点数相比,最多可将存储的向量数据减少 32 倍

中心点索引的 RQ8

HFresh 搜索的第一阶段使用基于中心点的 HNSW 索引。这个内存索引会识别哪些条目可能包含查询的最近邻。

HFresh 在此处使用 RQ8 是因为路由错误代价高昂。如果中心点搜索未能定位到正确区域,后续的条目扫描可能永远无法发现真正的最近邻。RQ8 提供了良好的平衡:在开销之前将中心点向量负载减少约 4 倍,同时保持足够的精度以实现精确路由。

这尤其有效,因为中心点索引远小于完整的向量数据集。HFresh 可以为中心点使用更高精度的压缩格式,同时仍保持内存层的紧凑性。

条目的 RQ1

在 HFresh 选出最有希望的条目后,会扫描其中存储的向量。这一阶段具有不同的权衡。

条目存储在磁盘上,因此其大小直接影响存储成本和查询 I/O。HFresh 使用 RQ1 存储条目向量,其中每个维度用单个比特表示。与 32 位浮点向量相比,这可将向量存储减少最多 32 倍。

这种激进的压缩使每个条目更小且读取成本更低。它还使第一阶段的距离计算足够快速,从而能够扫描大量候选结果。

HFresh 不依赖 RQ1 分数进行最终排名。相反,RQ1 用于构建候选集。HFresh 随后会获取顶级候选的原始未压缩向量,并在重新评分时重新计算精确距离。这在不牺牲最终排名质量的前提下,保持了磁盘读取的高效性。

背景操作

HFresh 能够在无需重建的情况下长期保持平衡,原因在于维护功能直接集成到索引中。与让不平衡累积后通过一次大型离线任务修复不同,HFresh 将维护分解为可连续处理的小型后台任务。

实际上,大多数前台写入操作保持简单:向量快速追加,后续工作被推送到后台队列。这些任务存储在专用磁盘队列中,因此能够跨重启持续存在,并可通过调度器逐步处理。

HFresh 将这项工作分为三种主要的后台任务类型:拆分(split)、合并(merge)和重新分配(reassign)。

拆分:当某个 posting 的规模变得过大时,HFresh 会对其进行拆分。系统会加载该 posting,清理过期条目,使用平衡 K 均值算法将其向量分为两个均衡的组,然后创建两个新的中心点来替代原来的中心点。这种机制可以防止 posting 过度增长,从而避免增加磁盘读取负担并降低路由精度。

合并:与之相反的问题是 posting 过于微小。这种情况可能在删除操作后发生,也可能随着数据分布的演变而出现。在这些情况下,HFresh 会寻找一个邻近的 posting,该 posting 能够吸收较小的 posting 而不会自身变得过大。如果找到合适的候选对象,系统会合并这两个 posting 并移除多余的中心点。这可以防止索引碎片化为过多微小的 posting。

重新分配:在拆分或合并操作后,某些向量可能不再适合当前存储它们的 posting。这时就需要重新分配机制。SPFresh 论文描述了一种称为 LIRE(轻量级增量再平衡)的协议:在拆分后,系统会检查某些向量是否更适合归入新生成的中心点,甚至邻近的 posting;在合并后,会检查被吸收的向量是否应该被转移到其他位置。这些重新分配操作使 HFresh 能够逐步修正错误,而不是试图一次性达到完美状态。

这些后台操作共同构成了一个持续的平衡循环。插入操作带来局部变化,而索引会在之后悄然进行整理。最终结果是一个能够随着时间保持更新的索引,而无需进行破坏性的重建周期。

带过滤的搜索

带过滤的向量搜索增加了另一个约束条件:搜索结果不仅要与查询向量相似,还必须满足过滤条件。例如在电商搜索中,这可能意味着要查找特定品牌、价格区间内且当前有库存的相似产品。

HFresh 使用一个允许列表(以位图形式表示)来跟踪满足过滤条件的文档 ID。然后根据匹配向量的数量选择两种搜索策略中的一种。

对于高度选择性的过滤条件,运行完整的 HFresh 流程的成本可能高于直接搜索匹配子集。当允许列表包含的 ID 数量少于 5000 个时,HFresh 会跳过中心点路由和 posting 扫描,直接获取这些 ID 的原始向量,并在它们之间计算精确距离。当前 5000 个 ID 的阈值是一个固定的内部启发式规则,而非可配置的阈值或通用调优建议。

基于 posting 的过滤

对于更宽泛的过滤条件,HFresh 使用其标准的两阶段搜索,并采用一种基于 posting 的预过滤方式。过滤器识别匹配对象,但中心点 HNSW 会将查询路由到 posting 而非单个对象。HFresh 通过记录每个 posting 包含哪些向量 ID 的元数据,在这两个层级之间建立关联。

具体流程如下:

  • 使用 posting 元数据将对象级允许列表转换为 posting 的资格状态。
  • 使用 ACORN 在中心点 HNSW 中导航,选择至少能贡献一个匹配向量的 posting。
  • 读取选定的 posting,并在扫描其压缩向量时重新应用原始允许列表。
  • 使用原始未压缩向量对候选结果进行重新评分。

由于 HFresh 可以在多个 posting 中复制向量,它还会跟踪哪些匹配向量已经对 posting 选择做出过贡献。这避免了仅仅因为包含相同匹配的副本而读取多个 posting。通过 posting 级别选择和向量级别检查的结合,可以减少不必要的磁盘读取,同时确保最终结果满足过滤条件。

如何使用 HFresh

使用 HFresh 时,在创建集合时将其配置为向量索引。

以下是使用 Python 客户端的示例:

code
from weaviate.classes.config import Configure, VectorDistances
collection = client.collections.create(
    name="Article",
    vector_config=Configure.Vectors.self_provided(
        name="Title",
        vector_index_config=Configure.VectorIndex.hfresh(
            distance_metric=VectorDistances.COSINE,
        ),
    ),
)

HFresh 参数调优

建议从默认配置开始。如果召回率过低,可以增加以下参数:

  • search_probe:每个查询搜索更多 posting。
  • quantizer.rescore_limit:使用全精度向量对更多候选对象进行重新评分。

这两个参数都可以在不重建索引的情况下进行修改。

实验结果

我们在 DBpedia OpenAI 1M 数据集上对未压缩 HNSW、RQ1 压缩 HNSW、RQ8 压缩 HNSW 和 HFresh 进行了基准测试。HNSW 索引使用 efConstruction=256 和 maxConnections=16 进行构建。

堆内存使用

这些测量结果报告的是 Go 堆内存使用量,而非总进程或系统内存使用量。HFresh 的磁盘数据依赖于操作系统管理的缓存,这部分内存使用量不会体现在堆内存统计中。

最显著的差异体现在静态状态下的堆内存使用。HFresh 使用 239 MB 堆内存,而未压缩 HNSW 需要 6.67 GB。

HFresh 的堆内存使用量也低于量化 HNSW。RQ1 压缩 HNSW 使用 715 MB,约为 HFresh 的 3 倍;RQ8 压缩 HNSW 使用 2.38 GB,约为 HFresh 的 10 倍。

这种差异直接源于 HFresh 的架构设计。HFresh 将 RQ8 压缩的中心点索引和支持元数据保留在内存中,同时将压缩后的 posting 存储在磁盘上。Posting 成员关系、向量版本和删除状态以及 posting 大小也会消耗堆内存,并随着数据集增长而增加,但这种布局减少了索引数据所需的堆内存。

查询吞吐量

QPS-召回率曲线展示了 HFresh 堆内存优势在查询性能方面的表现。

在可比召回率下,HNSW 及其量化变体的吞吐量显著高于 HFresh。这是预期结果:HNSW 搜索的是内存中的图结构,而 HFresh 需要从磁盘读取选定的 posting 并获取原始向量进行重新评分。

这些结果明确了选择不同索引的场景。当最低延迟和最高吞吐量最为关键时,HNSW 仍然是更优选择。而 HFresh 专为那些将完整索引保留在内存中成本过高、且可接受较低吞吐量以换取更小堆内存占用的工作负载而设计。

扩展到十亿级向量

除了 DBpedia 基准测试外,我们还测试了 HFresh 在 10 亿个随机生成的 256 维向量上的索引构建。我们使用默认的索引构建设置,以 12 个 worker 每次导入 1000 个向量批次。

| 指标 | 结果 | |------|------| | 数据集 | 10 亿个 256 维向量 | | 计算资源 | 32 vCPU,256 GB RAM (n2-highmem-32) | | 存储 | 4 TB SSD 持久化磁盘 (pd-ssd) | | 峰值 VM 内存使用 | 204 GB | | 重启后 VM 内存使用 | 54 GB | | 重启后 Go 堆内存 | 47 GB | | 峰值磁盘使用 | 3.09 TB | | 导入后磁盘使用 | 2.28 TB |

运行成功构建了十亿向量的 HFresh 索引。此次规模测试未收集召回率或 QPS 数据。

VM 内存测量数据来自 GCP 系统指标,而堆内存测量数据来自 Go 堆分析。

结论

HFresh 为 Weaviate 提供了基于磁盘的索引,实现从小型应用到大型可变数据集的内存高效向量搜索。通过维护紧凑的质心索引、在内存中支持元数据,并逐步维护磁盘驻留的倒排索引,它在不依赖完整索引重建的情况下减少了索引数据所需的堆内存。当最低延迟和最高吞吐量最为关键时,HNSW 仍是更优选择;HFresh 则专为那些较小堆内存占用可接受较低查询吞吐量的工作负载而设计。

HFresh 作为技术预览版在 Weaviate 1.36 中引入,于 Weaviate 1.38 正式发布。请查看文档开始使用。

如果您正在评估 HFresh 用于大规模工作负载,我们希望了解您的数据集、性能需求和测试结果。

准备开始构建?

查看 [快速入门教程](#),或注册免费的 Weaviate Cloud 账户。

[GitHub](#)

[论坛](#)

[X (Twitter)](#)

不想错过下一篇博客?

订阅我们的双周通讯以保持更新!

通过提交,我同意

[服务条款](#)

[隐私政策](#)