前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【腾讯云ES】基于NGram分词ES搜索性能优化实践

【腾讯云ES】基于NGram分词ES搜索性能优化实践

原创
作者头像
子非鱼i
发布2022-12-05 14:17:05
2.8K0
发布2022-12-05 14:17:05
举报

背景

在商品搜索场景中,需要根据用户输入关键字严格匹配商品数据,而普通的全文检索方式,诸如:match 或者match_pharse,不一定能达到搜索效果。

例如:使用 match api 时,基于 ik_max_word 分词方式对“白色死神”进行分词后,搜索"白色"、"死神"能搜索到,而根据 "白" 进行搜索时,结果确为空。我们可以看看 ik_max_word 策略的分词效果:

代码语言:txt
复制
GET /_analyze
{
  "analyzer": "ik_max_word",
  "text": "白色死神"
}

分词结果:

代码语言:txt
复制
{
  "tokens" : [
    {
      "token" : "白色",
      "start_offset" : 0,
      "end_offset" : 2,
      "type" : "CN_WORD",
      "position" : 0
    },
    {
      "token" : "死神",
      "start_offset" : 2,
      "end_offset" : 4,
      "type" : "CN_WORD",
      "position" : 1
    }
  ]
}

ik_max_word主要是基于词库对文本做最细粒度的拆分,只要你输入的内容能匹配上任何一个分词项,就能将文档返回,虽然召回率很高,但不一定满足严格匹配的场景。

关于严格匹配我们很容易就能想到模糊查询,es本身也是能支持模糊查询的:

方案选择

方案一:模糊查询 wildcard && fuzzy

模糊查询的功能有点类似 mysql 中的 like,可以使用正则表达式的通配符来达到模糊搜索的效果,使用的姿势如下:

代码语言:txt
复制

GET my-index/_search

{

"query": {

代码语言:txt
复制
"wildcard": {
代码语言:txt
复制
  "product_title": {
代码语言:txt
复制
    "value": "*白*"
代码语言:txt
复制
  }
代码语言:txt
复制
}

}

}

代码语言:txt
复制
wildcard 能同时支持 text 和 keyword 两种类型的搜索,但是当输入字符串很长或者搜索数据集很大时,搜索性能很低,原因是ES使用的是基于DFA的文本匹配算法,时间复杂度(M+N),当索引里面的数据量为K时,时间复杂度为(M+N)× K,数据量越大,输入文本越长,模糊搜索的效率就会越低。

###模糊查询性能数据:

对2000万数据进行模糊搜索:**耗时3s+**

"took" : 3714,

"timed_out" : false,

"_shards" : {

代码语言:txt
复制
"total" : 48,
代码语言:txt
复制
"successful" : 48,
代码语言:txt
复制
"skipped" : 0,
代码语言:txt
复制
"failed" : 0

}

代码语言:txt
复制

对2亿数据进行模糊搜索:耗时30s+

代码语言:txt
复制
 "took" : 32714,
  "timed_out" : false,
  "_shards" : {
    "total" : 48,
    "successful" : 48,
    "skipped" : 0,
    "failed" : 0
  }  事实上,当数据量上千万时,使用模糊查询会存在性能瓶颈,集群会出现大量慢查询,甚至把节点内存打爆。方案二:N-gram 分词生产环境我们可以使用 N-gram 来代替 wildcard 实现模糊搜索功能,N-gram 分词器可以通过指定分词步长来对输入文本进行约束切割,本质上也是一种全文搜索。在使用过程中我们可以通过自定义分析器,在创建索引或者更新字段类型时,对它配置使用N-gram进行分词,简单且高效。我们可以看看分词效果:

POST my-index/_analyze

{

"analyzer":"ngram_analyzer",

"text":"理想小韭菜"

}

代码语言:txt
复制

其分词结果为:

代码语言:txt
复制
 {
  "tokens" : [
    {
      "token" : "小",
      "start_offset" : 0,
      "end_offset" : 1,
      "type" : "word",
      "position" : 0
    },
    {
      "token" : "小韭",
      "start_offset" : 0,
      "end_offset" : 2,
      "type" : "word",
      "position" : 1
    },
    {
      "token" : "小韭菜",
      "start_offset" : 0,
      "end_offset" : 3,
      "type" : "word",
      "position" : 2
    },
    {
      "token" : "韭",
      "start_offset" : 1,
      "end_offset" : 2,
      "type" : "word",
      "position" : 3
    },
    {
      "token" : "韭菜",
      "start_offset" : 1,
      "end_offset" : 3,
      "type" : "word",
      "position" : 4
    },
    {
      "token" : "菜",
      "start_offset" : 2,
      "end_offset" : 3,
      "type" : "word",
      "position" : 5
    }
  ]
}##N-gram配置方式配置方式一:创建索引时候直接指定。

PUT test-ngram-v1

{

代码语言:txt
复制
    "settings": {
代码语言:txt
复制
    "index.max_ngram_diff": 10, //核心参数:ngram最大步长,可以手动配置,默认为1。
代码语言:txt
复制
    "analysis": {
代码语言:txt
复制
      "analyzer": {
代码语言:txt
复制
        "ngram_analyzer" : {
代码语言:txt
复制
          "tokenizer" : "ngram_tokenizer" // 配置ngram分词器。
代码语言:txt
复制
        }
代码语言:txt
复制
      },
代码语言:txt
复制
      "tokenizer": {
代码语言:txt
复制
      "ngram_tokenizer" : {
代码语言:txt
复制
          "token_chars" : [ //指定生成的token应该包含哪些字符.对没有包含进的字符进行分割,默认为[],即保留所有字符。
代码语言:txt
复制
            "letter", // 
代码语言:txt
复制
            "digit"  // 
代码语言:txt
复制
          ],
代码语言:txt
复制
          "min_gram" : "1", // 指定最小步长,按需配置。
代码语言:txt
复制
          "type" : "ngram", 
代码语言:txt
复制
          "max_gram" : "10" // 指定最大步长 ,按需配置,不能超过"index.max_ngram_diff"。
代码语言:txt
复制
        }
代码语言:txt
复制
      }
代码语言:txt
复制
    }
代码语言:txt
复制
},
代码语言:txt
复制
"mappings" : {
代码语言:txt
复制
    "properties" : {
代码语言:txt
复制
      "messsage" : {
代码语言:txt
复制
        "type" : "text",
代码语言:txt
复制
         "norms" : false,
代码语言:txt
复制
        "fields" : {
代码语言:txt
复制
          "ngram" : {
代码语言:txt
复制
            "type" : "text",
代码语言:txt
复制
            "analyzer" : "ngram_analyzer" // 配置类型
代码语言:txt
复制
          }
代码语言:txt
复制
        }
代码语言:txt
复制
      },
代码语言:txt
复制
       "level" : {
代码语言:txt
复制
      "type" : "keyword"
代码语言:txt
复制
    }
代码语言:txt
复制
    }
代码语言:txt
复制
}

}

代码语言:txt
复制

配置方式二:通过索引模版指定。

通过索引模版可以对指定的字段配置ngram分词器,通过 template 中的"match"来指定需要配置的字段,能支持字段类型、字段名、路径、正则等多种匹配条件,也可以配置filter来对分词后token进行后置处理。具体使用方式可以参考:https://www.elastic.co/guide/en/elasticsearch/reference/7.10/dynamic-templates.html

代码语言:txt
复制
PUT _index_template/analyzer-template
{
  "priority": 1,
  "template": {
    "settings": {
      "index": {
        "max_result_window": "200000",
        "codec": "zstandard",
        "max_ngram_diff": "10",
        "analysis": {
          "analyzer": {
            "my_analyzer": {
              "tokenizer": "my_tokenizer"
            }
          },
          "tokenizer": {
            "my_tokenizer": {
              "token_chars": [
                "letter",
                "digit"
              ],
              "min_gram": "1", // 按需修改
              "type": "ngram",
              "max_gram": "5" //按需修改
            }
          }
        }
      }
    },
    "mappings": {
      "dynamic_templates": [
        {
          "strings": {
            "mapping": {
              "type": "keyword"
            },
            "match_mapping_type": "string"
          }
        },
        {
          "ngram_analyzer": {
            "mapping": {
              "type": "text",
              "fields": {
                "ngram": {
                  "analyzer": "ngram_analyzer",
                  "type": "text"
                }
              }
            },
            "match": [
              "message" // 按需配置
            ]
          }
        }
      ]
    }
  },
  "index_patterns": [
    "*"
  ]
}

##NGram查询性能:

2亿数据查询性能:P99 < 200ms

代码语言:txt
复制

"took" : 98,

"timed_out" : false,

"_shards" : {

代码语言:txt
复制
"total" : 48,
代码语言:txt
复制
"successful" : 48,
代码语言:txt
复制
"skipped" : 0,
代码语言:txt
复制
"failed" : 0

},

代码语言:txt
复制

#总结 :

1. 使用 wildcard 不需要做分词,不需要额外占用磁盘,但数据量大时搜索性能很差,小规模业务可以使用。

2. Ngram搜索性能要远远高于 wildcard,但会额外消耗10%左右磁盘(并不明显),可以配合一些数据压缩策略使用。

3. Ngram 能够同时支持 match 与 term 查询,重建索引后,客户端无需变动。

4. 直接使用 Ngram 分词,单个关键字命中即返回,召回错误率太高,可以搭配使用 match_phrase,通过设定slot偏移量,可以减少智能分词结果差异导致的召回率低的问题,提升搜索准确率。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 背景
  • 方案选择
    • 方案一:模糊查询 wildcard && fuzzy
      • 配置方式二:通过索引模版指定。
相关产品与服务
Elasticsearch Service
腾讯云 Elasticsearch Service(ES)是云端全托管海量数据检索分析服务,拥有高性能自研内核,集成X-Pack。ES 支持通过自治索引、存算分离、集群巡检等特性轻松管理集群,也支持免运维、自动弹性、按需使用的 Serverless 模式。使用 ES 您可以高效构建信息检索、日志分析、运维监控等服务,它独特的向量检索还可助您构建基于语义、图像的AI深度应用。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档