Elasticsearch向量检索插件是阿里云Elasticsearch(简称ES)团队自主开发的向量检索引擎,基于阿里巴巴达摩院proxima向量检索库实现,能够帮助您快速实现图像搜索、视频指纹、人脸识别、语音识别和商品推荐等向量检索场景的需求。

背景信息

目前在阿里巴巴集团内,阿里云ES向量检索引擎已成熟应用于拍立淘、图搜云、优酷视频指纹、趣头条视频指纹、猜你喜欢、搜索个性化、CrossMedia搜索等大规模生产应用场景。

阿里云ES向量检索功能基于ES插件扩展机制实现,能够完全兼容原生ES版本,用户无需任何额外的学习成本即可使用向量检索引擎。向量索引除了支持实时增量写入、NRT查询,还具备了所有原生ES的分布式能力,同时支持多副本、错误恢复、快照等功能。

在算法上,目前向量检索引擎已经支持了hnsw算法以及linear算法,适用于单机数据量小(全内存)的业务场景。两种算法性能对比如下:

表 1. hnsw算法和linear算法性能对比
表格中为阿里云ES 6.7.0版本环境实测数据,测试环境配置如下:
  • 机器配置:数据节点16核64G*2 + 100G ssd云盘。
  • 数据集:sift128维float向量(http://corpus-texmex.irisa.fr/)。
  • 数据总量:2千万。
  • 索引配置:全部采用默认参数。
性能指标 hnsw linear
top10召回率 98.6% 100%
top50召回率 97.9% 100%
top100召回率 97.4% 100%
延迟(p99) 0.093s 0.934s
延迟(p90) 0.018s 0.305s
说明 表中的p表示百分比,例如延迟(p99)表示99%的查询能在多少秒返回。

索引规划

算法 适用场景 是否全内存 其他
hnsw
  • 单机数据量小。
  • 对延迟要求高。
  • 对召回率要求高。
  • hnsw是基于“邻居的邻居可能是邻居”的核心思想,它在距离衡量算法上有一定的限制,需要满足三角形不等式,即三角形的两边之和大于第三边。例如,对于内积向量空间,由于不满足三角形不等式,需要转化为欧式空间或球面空间,才能使用hnsw检索方法。
  • 建议写入结束后,在业务低峰期进行定期forceMerge,有助于降低查询延迟。
linear
  • 暴力检索。
  • 召回率100%。
  • 延迟与数据量成正比。
  • 通常用于效果对照。
无 。

集群规划

规划项 说明
数据节点规格(必须) 生产环境需要数据节点为4核16G及以上,2核8G仅可用于功能测试。
机型(建议) 向量索引主要使用jvm堆外内存,建议选择内存较高的数据节点,例如CPU:Mem为1:4或1:8的机型。
单机最大数据量 数据节点总内存/2。
写入限流 向量索引的构建属于CPU密集型任务,建议业务控制写入流量不要太高。以16核64G的数据节点为例,建议单节点写入峰值控制在5000tps以内。

同时,由于在向量索引的查询过程中,会把索引文件全部加载到系统内存,因此建议在业务查询期间,不要同时进行大批量的写入,避免因节点内存紧张导致shard重启的情况。

安装向量检索插件

注意
  • 向量检索插件只支持6.7及以上版本的阿里云ES实例。
  • 在安装向量检索插件前,请确保实例的数据节点规格为2核8G及以上(2核8G仅可用于功能测试,生产环境需要数据节点为4核16G及以上)。如果不满足,需要首先将实例的数据节点规格升级至2核8G及以上,详情请参见集群升配
登录阿里云Elasticsearch控制台,单击实例ID > 插件配置 > 系统默认插件列表 。在系统默认插件列表列表中安装aliyun-knn插件,详情请参见操作步骤aliyun-knn插件
注意 aliyun-knn插件默认为未安装状态。

使用向量检索插件

安装向量检索插件完成后,登录对应ES实例的Kibana控制台,按照以下步骤使用向量检索插件,完成对应业务需求。

注意 以下示例代码只适用于ES 6.7版本,7.4版本语法请参见官方文档
  1. 创建索引。
    PUT test
    {
      "settings": {
        "index.codec": "proxima",
        "index.vector.algorithm": "hnsw" 
      },
      "mappings": {
        "_doc": {
          "properties": {
            "feature": {
              "type": "proxima_vector", 
              "dim": 2 
            },
            "id": {
              "type": "keyword"
            }
          }
        }
      }
    }
    参数 描述
    index.vector.algorithm 可选值:hnsw、linear。
    type 字段类型。proxima_vector表示向量字段。
    dim 向量维度,支持1~2048维。
  2. 添加文档。
    POST test/_doc
    {
      "feature": [1.0, 2.0], 
      "id": 1
    }
    参数 描述
    feature float数组。数组长度必须与创建索引时,mapping指定的dim保持一致。
  3. 检索。
    GET test/_search
    {
      "query": {
        "hnsw": {  
          "feature": {
            "vector": [1.5, 2.5], 
            "size": 10 
          }
        }
      }
    }
    参数 描述
    hnsw 与创建索引时指定的algorithm一致。
    vector float数组。数组长度必须与创建索引时,mapping指定的dim保持一致。
    size 指定召回的topN。

参数说明

表 2. 算法选择
参数 描述 默认值
index.vector.algorithm 指定索引使用的算法,目前支持hnsw和linear。 hnsw
表 3. 写入参数(hnsw)
参数 描述 默认值
index.vector.hnsw.builder.max_scan_num 用于控制构图过程中的近邻考察范围,保证最坏情况的性能。 100000
index.vector.hnsw.builder.neighbor_cnt hnsw 0层图每个节点的邻居数。建议配置为100。该值越大,离线索引存储消耗越大,图构建质量越高。 100
index.vector.hnsw.builder.upper_neighbor_cnt hnsw上层图(除0层之外)中每个节点的邻居上限数。一般建议配置为neighbor_cnt的一半。 50
index.vector.hnsw.builder.efconstruction 控制图构建过程中近邻扫描区域大小,该值越大,离线构图质量越好,索引构建越慢。建议初始值设置为400。 400
index.vector.hnsw.builder.max_level hnsw总层数,包含0层图和上层图。例如总共1000万文档,scaling_factor为30,那么层数可以以max level=30为底,取1000万的对数向上取整,计算得5。 6
index.vector.hnsw.builder.scaling_factor 下次图是上次图数据的多少倍,呈指数关系。通常设置在10~100之间。scaling_factor越大,实际生成的图层数越低。建议初始配置50。 50
表 4. 查询参数(hnsw)
参数 描述 默认值
ef 用于控制在线检索时,考察的子图范围大小。该值越大,召回越高,性能越差。建议取值[100,1000]。 100

查询示例如下:

GET test/_search
{
  "query": {
    "hnsw": {
      "feature": {
        "vector": [1.5, 2.5],
        "size": 10,
        "ef": 100,
        "max_scan_num": 100000
      }
    }
  }
}
表 5. 熔断参数
参数 描述 默认值
indices.breaker.vector.native.indexing.limit 如果堆外内存使用超过该值,写入操作会被熔断;等待后台构建完成释放内存后,写入恢复正常。出现熔断错误表示当前系统内存消耗已经过高,建议业务上降低写入流量。不建议初学者调整这个参数值。 70%
indices.breaker.vector.native.total.limit 向量索引后台构建时,最多使用的堆外内存比例。如果实际使用的堆外内存超过了这个比例,可能会发生shard重启的情况,不建议初学者调整这个值。 80%

常见问题

  • Q:如何评估查询的召回率?

    A:可以同时创建两个索引,一个为hnsw算法,一个为linear算法,其他配置相同。客户端向两个索引同时推送相同的向量数据,refresh后,用同样的查询向量对比linear索引和hnsw索引召回的文档id,交集的docid个数/召回总数,即为待测向量的召回率。
    说明 交集的docid个数是指两个索引召回的docid的交集。
  • Q:写入期间报错circuitBreakingException,如何处理?

    A:这个错误表明此时系统的堆外内存使用率超过了indices.breaker.vector.native.indexing.limit指定的比例(默认为70%),触发了写入的熔断操作,一般等待后台的索引构建任务完成后会自动释放。建议客户端写入时添加错误重试机制。

  • Q:为什么写入已经停止了,CPU依然在工作?

    A:向量索引的构建发生在refresh或flush期间,虽然写入流量已经停止,但后台的向量索引构建任务可能仍然在继续。等待最后一轮refresh结束后,计算资源就会被释放。