ES调优建议

查询模块

保证ES节点有充足的内存

ES默认分配节点一半的内存给JVM(最多会分配30GB),剩余的内存用于向量索引和pagecache,节点内存不足会导致频繁触发向量索引的驱逐和加载,导致查询延迟较高。

因此需要确保数据节点有足够的内存,使向量索引常驻内存。可以通过以下方式获取节点向量数据的内存使用情况。

GET /_bpack/_knn/stats
GET /_bpack/_knn/nodeId1,nodeId2/stats // 获取指定节点nodeId1,nodeId2的内存使用情况,使用逗号进行分割

如果发现内存不足,建议参考资源评估建议进行集群容量规划

首次查询之前把数据进行预加载

默认情况下,ES的向量索引构建完成后,不会主动加载到内存中。并且在首次查询中会触发加载,只有向量数据全部加载到内存中才能进行查询。

在首次查询前,可以通过如下指令提前预热数据,降低延迟:

GET /_bpack/_knn/warmup/my_index1, my_index2

尽量减少segment的数量

ES中的索引的基础存储单元为segment。在KNN算法中,每个segment都会单独构建向量索引,因此在搜索过程中需要检索每个segment

如果索引中segment的划分过于琐碎,会影响检索的速度。一般情况下,可以通过BES的定时任务,定期把琐碎的segment合并。

当然也可以在写入结束后的业务低峰期,通过force_merge强制执行合并,减少segment的数量,提高检索的效率。

POST /my-index-000001/_forcemerge?max_num_segments=3

尽量避免从_source中查询向量字段

在构建索引过程中默认会把原始JSON存储在_source字段中,因此搜索结果中的每一次命中索引都包含完整的_source内容。

当索引包含高维度向量字段时,_source可能比较大,加载成本就很高,这可能会显著降低KNN搜索的速度。

尽量使用doc_value存储数据

doc_value_source存储的内容是一致的,但是doc_value是面向列式存储的,因此会拥有更好的排序和聚合效率。

创建索引过程中,不需要特地打开 doc_value 开关,ES对于绝大多数类型的数据会自动构建 doc_value 。

对于 text 类型的数据,不会构建 doc_value 而是存储在 _source 中。
因此如果可以的话,建立索引过程中尽量使用 keyword 类型代替 text 类型。

查询过程中,可以指定查询doc_value中字段,减少加载_source数据,加速查询效率。

查询方式如下所示:

 
GET /my_index/_search
{
  "query": {
    "hnsw": {
      "vec": {
        "vector": [
          0.495662,
          
          ...
         
          -0.10869
        ],
        "k": 10,
        "ef": 200
      }
    }
  },
  "size": 10,
  "_source":false, // 规避加载 _source 中的数据,加速查询效率
  "docvalue_fields":[
    "id",
    "tag_keyword"
  ] // 填写需要从 doc_value 中查询的字段
}

关闭默认的自动构建mapping开关

对于字符串类型的字段,自动构建的mapping会默认同时构建textkeyword类型数据,比较浪费磁盘空间。 如果不需要对字段进行全文检索,则只需要设置keyword类型即可。

也可以通过下面配置,设置默认构建单一类型:

 
PUT index
{
  "mappings": {
    "dynamic_templates": [
      {
        "strings": {
          "match_mapping_type": "string",
          "mapping": {
            "type": "keyword"
          }
        }
      }
    ]
  }
}

规避使用_source存储数据,减少查询过程中的数据加载量

在构建索引的过程中,可以通过如下方式禁用_source字段:

 
PUT my-index-000001
{
  "mappings": {
    "_source": {
      "enabled": false
    }
  }
}

但是禁用_source的操作需要您深思熟虑后进行,因为禁用_source后以下功能将无法使用

  • 无法使用updateupdate_by_query以及reindex的API,也无法从一个集群的索引reindex到另一个集群
  • 无法使用高亮
  • 不能修改索引的mappingsanalysis

也可以对向量字段不构建_source,只对标量字段构建

 
PUT my-index-000001
{
  "mappings": {
    "_source": {
      "excludes": [
        "vec",  // 可以直接指定不构建_source的字段
        "vec.*" // 也可以通过通配符进行模糊匹配
      ]
    }
  }
}

写入模块

合理配置shard数量

为了使shard分配的更加均匀,从而避免写入热点,建议把shard数量设置成节点数量的整数倍。

shard数量设置方法如下所示:

 
PUT /my_index
{
  "settings": {
    "index": {
      "number_of_shards": 3 // 索引的分片数量,创建后无法修改。
    }
  }
}

合理设置replica的数量

没有副本意味着节点丢失可能会导致数据丢失,因此多副本对于数据安全十分重要,以便在出现问题时可以重新加载数据。

并且适度增加副本数量,可以增大查询过程中的并行程度,从而提高查询效率。

每增加一个副本都会增加集群磁盘成本,并且副本数不能大于节点的数量。

 
在多副本情况下,写入过程可以先设置副本数 index.number_of_replicas 为0,增加写入的速度。
PUT /my_index/_settings
{
  "number_of_replicas": 0 // 分片的副本数量,取值范围[0, 节点数-1],可随时修改
}
在写入完成后,再把副本调整到合适的数量。

合理调整refresh间隔

refresh_interal是用来控制多久把内存里的数据刷出segment的。ES会对刷出的segment进行合并,如果合并不过来会阻止写入。

所以把refresh_interval调大,也可以把刷出的segment变大,降低合并的频率,提升导入性能。

调整refresh_interval方式如下:

 
PUT /my_index/_settings
{
 	"index.refresh_interval": "10s"
}

写入速度流程优化

使用bulk请求写入数据

bulk请求写入效率比单个索引请求性能更好,尽量使用bulk请求进行数据写入。

为了知道批量请求的最佳大小,可以在具有单index的单节点集群进行上进行测试。
首先尝试一个批次包含100个document,之后测试一个批次包含200个,以此类推,在每次测试过程中将批量请求中的document数量增加一倍。
当索引构建的速度开始趋于平稳时,批量请求需要包含的document数据就达到了的最佳大小。
增加索引构建并发

当构建较大数据量的向量索引时,可能会出现build较慢的情况。 可以根据分片数和节点CPU核数,在写入数据前适当调整"bpack.knn.hnsw.index_thread_qty" 。

例如,1kw数据量,1节点2分片,节点为16核CPU,我们可以把"bpack.knn.hnsw.index_thread_qty" 设置为4-6(如果设置为8,会使CPU满载,生产环境可能有风险),可以提高构建索引效率。

调节方式如下:

 
PUT /_cluster/settings
{
    "persistent" : {
        "bpack.knn.hnsw.index_thread_qty":3
    }
}

"bpack.knn.hnsw.index_thread_qty" 参数设置偏大,会导致构建时启动线程过多。在负载比较高的集群,不建议调整这个参数,以免集群满载。

如果写入和构建向量索引偏慢,可以通过临时减少集群负载(减少其他写入和查询),并调大"bpack.knn.hnsw.index_thread_qty"的方式来加快构建 ,等到构建结束,再将"bpack.knn.hnsw.index_thread_qty" 调整回1。

自动生成DocumentID

当写入索引过程中指定 _id 时,ES需要检查同一分片中是否已存在具有相同 _id 的document,这是一项成本高昂的操作,并且随着索引的增长,成本会变得更高。

如果使用场景允许,建议写入过程中不指定_id, ES可以跳过检查,从而加快索引速度。

 
POST _bulk
{ "create" : { "_index" : "test1"} }  // 不指定 _id
{ "create" : { "_index" : "test2", "_id" : "3" } } // 指定 _id
 
 

posted on 2024-11-13 11:42  ExplorerMan  阅读(1)  评论(0编辑  收藏  举报

导航