写入和查询数据
写入数据
bulk写入
用户创建向量索引后,即可使用ES BULK API写入数据。不同的index_type
与space_type
,在数据写入时没有区别。
在以下示例中,我们向my_index
索引的field_hnsw
和field_linear
字段分别写入样例向量数据。
POST my_index/_bulk
{"index":{}}
{"field_hnsw":[3.5,4.5,6.5,6.5], "field_linear":[3.5,4.5,6.5,6.5]}
{"index":{}}
{"field_hnsw":[3.5,4.5,6.5,6.5], "field_linear":[3.5,4.5,6.5,6.5]}
{"index":{}}
{"field_hnsw":[5.5,6.5,6.5,6.5], "field_linear":[3.5,4.5,6.5,6.5]}
...
集群参数配置
数据写入以后,BES会在后台为向量字段构建向量索引,用户可以通过bpack.knn.index_thread_qty
参数设置构建向量索引的并发度,默认值是1,取值范围[1, 32]。并发度越高,构建索引的速度越快,不过CPU消耗也会越高,需要注意过高的bpack.knn.index_thread_qty
值可能会导致CPU资源耗尽,影响同时间的检索请求。
bpack.knn.index_thread_qty
参数的设置方式如下:
PUT /_cluster/settings
{
"persistent" : {
"bpack.knn.index_thread_qty" : 4
}
}
查询数据
hnsw、hnsw_sq8、hnsw_pq 查询示例
因为hnsw、hnsw_sq8、hnsw_pq 类型字段的查询参数相同,所以我们用一个示例进行说明。
在以下示例中,我们基于 my_index
索引的 field_hnsw
和file-type
字段进行检索,其中file-type
字段是keyword
类型,存储了图片类型;field_hnsw
字段是bpack_vector
类型,存储了图像的embedding向量。
GET my-index/_search
{
"size": 3, // 期望的查询结果集大小限制
"_source": ["id"], // 期望返回的字段列表
"query": {
"knn": {
"field_hnsw": { // 要检索的向量字段
"vector": [2, 3, 5, 6], // 查询向量
"k": 10, // 查询的最近邻的数量
"ef": 256, // hnsw算法的ef_search参数,表示检索最近邻时的动态扫描队列大小
"filter": { // 填写所需的filter,不需要filter则省略
"term": {
"file-type": "png"
}
}
}
}
}
}
参数说明
参数 | 类型 | 是否必填 | 说明 |
---|---|---|---|
ef | int | 否 | hnsw算法的ef_search参数,表示检索最近邻时的动态扫描队列大小。提高ef_search参数可以降低检索陷入局部最优解的概率,提高召回率,相应的,也会消耗更多cpu和内存资源,使查询性能降低。取值需大于k,一般建议200以上。 |
hnsw + filter 查询示例
以下示例展示了一个较复杂的filter过滤条件+hnsw检索的查询DSL,其中file-type
字段是keyword
类型,存储了图片类型;description
是text类型,存储了图片的描述;size
是long
类型,存储了图片大小;user_id
是keyword类型,存储了图片的用户标识;field_hnsw
字段是bpack_vector
类型,存储了图像的embedding向量。
GET my-index/_search
{
"size": 3, // 期望的查询结果集大小限制
"_source": ["id"], // 期望返回的字段列表
"query": {
"knn": {
"field_hnsw": { // 要检索的向量字段
"vector": [2, 3, 5, 6], // 查询向量
"k": 10, // 查询的最近邻的数量
"ef": 256, // hnsw算法的ef_search参数,表示检索最近邻时的动态扫描队列大小
"filter": { // 填写用户所需的filter,不需要filter则省略
"bool": {
"must": [
{
"term": {
"file-type": "png"
}
},
{
"match": {
"description": {
"query": "little cat"
}
}
},
{
"range": {
"size":{
"gte": 1024
}
}
}
],
"must_not": [
{
"terms": {
"user_id": ["aaa", "bbb"]
}
}
]
}
}
}
}
}
}
ivf、ivf_pq 查询示例
因为ivf、ivf_pq 类型字段的查询参数相同,所以我们用一个示例进行说明。
在以下示例中,我们基于 my_index
索引的 field_ivf
字段进行检索,field_ivf
字段是bpack_vector
类型,存储了图像的embedding向量。
GET my-index/_search
{
"size": 3, // 期望的查询结果集大小限制
"_source": ["id"], // 期望返回的字段列表
"query": {
"knn": {
"field_ivf": { // 要检索的向量字段
"vector": [2, 3, 5, 6], // 查询向量
"k": 10, // 查询的最近邻的数量
"nprobes": 10 // ivf系索引检索时扫描的桶数量
}
}
}
}
参数说明
参数 | 类型 | 是否必填 | 说明 |
---|---|---|---|
nprobes | int | 否 | ivf系索引检索时扫描的桶数量,nprobes越大,recall越高,但查询性能越差。可以从 nlist / 32 开始调整。再参照用户目标召回率来调整,如果取值等于nlist,则查询性能会跟暴力检索效果相似。 |
ivf_hnsw、ivf_hnsw_pq 查询示例
因为ivf_hnsw、ivf_hnsw_pq 类型字段的查询参数相同,同时包括ivf索引的参数和hnsw索引的参数,所以我们用一个示例进行说明。
在以下示例中,我们基于 my_index
索引的 field_ivf_hnsw
进行检索,field_ivf_hnsw
字段是bpack_vector
类型,存储了图像的embedding向量。
GET my-index/_search
{
"size": 3, // 期望的查询结果集大小限制
"_source": ["id"], // 期望返回的字段列表
"query": {
"knn": {
"field_ivf_hnsw": { // 要检索的向量字段
"vector": [2, 3, 5, 6], // 查询向量
"k": 10, // 查询的最近邻的数量
"nprobes": 10, // ivf系索引检索时扫描的桶数量
"ef": 256 // hnsw算法的ef_search参数,表示检索最近邻时的动态扫描队列大小
}
}
}
}
精确检索查询示例
bpack_vector类型字段均可以通过指定 linear:true
参数进行精确检索,会遍历全量数据进行暴力计算,确保结果的召回率。不过查询延迟会随着数据量近似线性增长,以hnsw索引字段为例:
GET my-index/_search
{
"size": 3,
"_source": ["id"],
"query": {
"knn": {
"field_hnsw": { // 要检索的向量字段
"vector": [2, 3, 5, 6], // 查询向量
"k": 10,
"linear": true, // true代表直接进行精确检索,即扫描全量数据进行暴力计算,默认false
"filter": { // 填写所需的filter,不需要filter则省略
"term": {
"file-type": "png"
}
}
}
}
}
}
此外,也可以通过ES的script_score
查询通过脚本计算进行精确检索,也是遍历全量数据进行暴力计算。
BES通过自定义script engine实现了简化的script计算逻辑,通过指定"lang":"knn"
和"source":"bpack_knn_script"
可以使用BES预定义的计算逻辑,只需要在params中指定向量字段和距离算法等参数即可。具体的距离计算公式和score计算公式请参考文档:算法介绍。示例如下:
POST my-index/_search
{
"size": 3,
"_source": ["id"],
"query": {
"script_score": {
"query": { // 填写所需要的filter,不需要filter则使用match_all
"match_all": {}
},
"script": {
"source": "bpack_knn_script", //固定参数
"lang": "knn", // 固定参数
"params": {
"space": "cosine", // 距离计算算法,支持 l2/cosine/innerproduct
"field": "field1", // 向量字段
"vector": [3.5, 2.5] // 查询向量
}
}
}
}
}
如果需要更灵活的计算逻辑,也可以直接使用默认的painless脚本语言,在source调用距离计算函数,完成复杂的计算。示例如下:
GET my-index/_search
{
"size": 3,
"_source": ["id"],
"query": {
"script_score": {
"query": { // 填写所需要的filter,不需要filter则使用match_all
"match_all": {}
},
"script": {
"source": "(1.0 + cosineSimilarity(params.query_vector, doc[params.field])) / 2", // 支持的距离计算函数包括,l2Squared/cosineSimilarity/innerProduct
"params": {
"field": "field1", // 向量字段
"query_vector": [3.5, 2.5] // 查询向量
}
}
}
}
}