Elasticsearch 查詢革新:探索 Wildcard 類型的高效模糊匹配策略
1、背景
在生產(chǎn)使用中,Elasticsearch 除了精確匹配的要求,也會有模糊查詢的場景。
2、解決方案探討
面對這種問題 ,傳統(tǒng)的解決方案有兩種:
2.1 方案一:ngram 分詞器
使用 ngram 分詞器對存入的數(shù)據(jù)進行精細化的拆分,利用細顆粒度的 token 進行快速的召回。
這是一個利用空間換時間的方案,細化查詢所需的詞根內(nèi)容,利用精確匹配結(jié)果大范圍的命中來達到模糊效果。
PUT test-005
{
"settings": {
"index.max_ngram_diff": 10,
"analysis": {
"analyzer": {
"my_analyzer": {
"tokenizer": "my_tokenizer"
}
},
"tokenizer": {
"my_tokenizer": {
"type": "ngram",
"min_gram": 3,
"max_gram": 10,
"token_chars": [
"letter",
"digit"
]
}
}
}
},
"mappings": {
"properties": {
"title": {
"type": "text",
"analyzer": "my_analyzer",
"fields": {
"keyword": {
"type": "keyword"
}
}
}
}
}
}
POST test-005/_bulk
{"index":{"_id":1}}
{"title":"英文官網(wǎng)承認劉強東一度被捕的原因是涉嫌性侵"}
{"index":{"_id":2}}
{"title":"別提了朋友哥哥劉強東窗事發(fā)了"}
{"index":{"_id":3}}
{"title":"劉強東施效顰,沒想到竟然收獲了流量"}
{"index":{"_id":4}}
{"title":"劉強東是誰?我不認識"}
POST test-005/_search
{
"query": {
"match_phrase": {
"title": "劉強東"
}
}
}
- 優(yōu)點:召回快,性能消耗?。?/li>
- 缺點:有不小的空間消耗,顆粒度越細,消耗越大。同時,有一定的學(xué)習(xí)成本,需要對分詞器有成熟的了解,不適合新手。
這里有個明顯的使用案例,如下圖所示,使用 ngram 的 test2 索引比原來使用 keyword 的索引空間大小大了接近10倍。
圖片
2.2 方案二:wildcard 查詢
使用 wildcard 查詢,這是一項支持通配符的模糊檢索功能,有點類似 SQL 中的 like 匹配。
為了實現(xiàn)通配符和正則表達式的查詢,Ealsticsearch 依賴的 Lucene4.0 會將輸入的字符串模式構(gòu)建成一個DFA (Deterministic Finite Automaton),而帶有通配符的pattern構(gòu)造出來的DFA可能會很復(fù)雜,開銷很大。
具體分析:
https://elasticsearch.cn/article/171
https://elasticsearch.cn/article/186
- 優(yōu)點:使用簡單,也不需要額外的存儲資源。
- 缺點:性能消耗巨大,濫用則可能會造成線上事故。
面對兩個各有所長,甚至有點“臥龍鳳雛”的方案,ES 在 7.9 版本推出了 wildcard 字段類型來解決模糊匹配的場景需求。
3、wildcard 類型使用詳解
Elasticsearch 的 wildcard 字段類型最早在 7.9 版本中引入。這個版本加入了對 wildcard 類型的支持,旨在改善模糊匹配的查詢效率和性能,特別是在處理大量文本數(shù)據(jù)時。這一新特性主要針對了之前版本中 wildcard 查詢的性能問題,提供了更高效的方式來處理通配符和正則表達式的搜索需求。
圖片
https://www.elastic.co/guide/en/elasticsearch/reference/7.9/release-highlights.html
我們先來看下 wildcard 類型怎么使用:
先定義一個 wildcard 類型的字段
PUT my-index-000001
{
"mappings": {
"properties": {
"my_wildcard": {
"type": "wildcard"
}
}
}
}
為其寫入一個文檔
PUT my-index-000001/_doc/1
{
"my_wildcard" : "This string can be quite lengthy"
}
然后使用 wildcard 查詢?nèi)缦滤荆?/p>
GET my-index-000001/_search
{
"query": {
"wildcard": {
"my_wildcard": "*quite*lengthy"
}
}
}
結(jié)果為
{
"took" : 6,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 1,
"relation" : "eq"
},
"max_score" : 3.8610575,
"hits" : [
{
"_index" : "my-index-000001",
"_type" : "_doc",
"_id" : "1",
"_score" : 3.8610575,
"_source" : {
"my_wildcard" : "This string can be quite lengthy"
}
}
]
}
}
有時候我們需要忽略大小寫,可以在 wildcard 查詢使用 case_insensitive 參數(shù)。
GET my-index-000001/_search
{
"query": {
"wildcard": {
"my_wildcard": {
"value": "*Quite*lengthy",
"case_insensitive": true
}
}
}
}
4、wildcard 原理
關(guān)于 wildcard 字段的實現(xiàn),官方在推出該字段的時候發(fā)布了相關(guān)的說明:
新的 wildcard 字段使用以下兩種數(shù)據(jù)結(jié)構(gòu)以這種方式自動加速通配符和正則表達式搜索:
- 字符串中所有3個字符序列的 n-gram 索引。
- 完整原始文檔值的 “二進制 doc value” 存儲。第一點,底層還是 ngram 的分詞去實現(xiàn)模糊查詢的場景,但是這里的 ngram 顆粒度是 3,從功能上滿足了模糊查詢的需求和保證了 wildcard 查詢的高性能。
第二點,使用了 ES 中常見的正排+列存數(shù)據(jù)存儲格式 doc value,在這里一個主要的效果就是在自動查詢驗證由 n-gram 語法匹配產(chǎn)生匹配候選的同時利用了doc value格式相對較高的壓縮比。
5、測試
現(xiàn)在來看下 wildcard 實際的表現(xiàn)。
5.1 空間大小
如下圖所示,可以看到使用 wildcard 字段的索引與原索引相差不大。
圖片
5.2 查詢效率
查詢dsl | keyword類型 | wildcard類型 |
wildcard:”紅豆” | 715ms | 71ms |
wildcard:”006-612014” | 633ms | 22ms |
wildcard:”55” | 584ms | 188ms |
wildcard:”11” | 1359ms | 357ms |
注:這里省卻了索引詳細信息,只需知道是同一個索引的比對測試。
綜上所述,在模糊搜索字段區(qū)分度很低的情況下 如:模糊查詢單個數(shù)字,此時優(yōu)化效率rt大概是之前的1/3左右,區(qū)分度高的場景rt大概是之前的1/15左右,有明顯效果。
6、小結(jié)
1.可以說 wildcard 字段類型滿足了模糊查詢的主要需求,同時也提供了相對較高的查詢性能;
2.wildcard 針對于 ngram 分詞器有著不小的空間優(yōu)勢。
3.wildcard 雖然有著不小的優(yōu)勢,但是查詢效率與數(shù)據(jù)的區(qū)分度有著很強的關(guān)聯(lián),在一些區(qū)分度較低的場景下效率與性能消耗依舊很嚴重。
4.相比 ES 在精確查詢場景優(yōu)秀的性能表現(xiàn)(即 term keyword 的高效,平穩(wěn)在毫秒級的返回),wildcard 字段在模糊查詢場景下的使用還是需要研發(fā)人員根據(jù)實際場景測試選擇。
7、作者介紹
金多安,Elastic 認證專家,Elastic資深運維工程師,死磕Elasticsearch知識星球嘉賓,星球Top活躍技術(shù)專家,搜索客社區(qū)日報責任編輯
銘毅天下審稿并做了部分微調(diào)。