基本概念
這兩個概念比較像,所以大部分時候會放在一起說。
這兩個概念源於Elasticsearch
(後面簡稱ES)除了強大的搜索功能外,還可以支持排序,聚合之類的操作。搜索需要用到倒排索引,而排序和聚合則需要使用 “正排索引”。說白了就是一句話,倒排索引的優勢在於查找包含某個項的文檔,而反過來確定哪些項在單個文檔裏並不高效。
doc_values和fielddata就是用來給文檔建立正排索引的。他倆一個很顯著的區別是,前者的工作地盤主要在磁盤,而後者的工作地盤在內存。
我整理了一個表格,從不同維度比較這哥倆。
維度 | doc_values | fielddata |
---|---|---|
創建時間 | index時創建 | 使用時動態創建 |
創建位置 | 磁盤 | 內存(jvm heap) |
優點 | 不佔用內存空間 | 不佔用磁盤空間 |
缺點 | 索引速度稍低 | 文檔很多時,動態創建開銷比較大,而且佔內存 |
索引速度稍低這個是相對於fielddata方案的,其實仔細想想也可以理解。那排序舉例,相對於一個在磁盤排序,一個在內存排序。誰的速度快自然不用多說。
在ES 1.x版本的官方說法是,
Doc values are now only about 10–25% slower than in-memory fielddata
雖然速度稍慢,doc_values的優勢還是非常明顯的。一個很顯著的點就是他不會隨着文檔的增多引起OOM
問題。正如前面說的,doc_values在磁盤創建排序和聚合所需的正排索引。這樣我們就避免了在生產環境給ES設置一個很大的HEAP_SIZE
,也使得JVM的GC更加高效,這個又爲其它的操作帶來了間接的好處。
而且,隨着ES版本的升級,對於doc_values的優化越來越好,索引的速度已經很接近fielddata了,而且我們知道硬盤的訪問速度也是越來越快(比如SSD)。所以 doc_values 現在可以滿足大部分場景,也是ES官方重點維護的對象。
所以我想說的是,doc values相比field data還是有很多優勢的。所以 ES2.x 之後,支持聚合的字段屬性默認都使用doc_values,而不是fielddata。
示例
光說不練假把式。上例子吧。我下面的例子是基於ES 7.1版本。
先準備一些數據,
PUT users
{
"mappings" : {
"properties" : {
"name" : {
"type" : "text"
},
"mobile" : {
"type" : "keyword"
},
"age" : {
"type" : "integer"
}
}
}
}
這裏我們新建了一個名爲users的index,設置了mapping。然後插入一些測試數據,
PUT users/_doc/1
{
"name":"tom",
"mobile": "15978866921",
"age": 30
}
PUT users/_doc/2
{
"name":"jerry",
"mobile": "15978866920",
"age": 35
}
PUT users/_doc/3
{
"name":"jack",
"mobile": "15978866922",
"age": 20
}
接着我們搜索的時候,基於年齡排序,
POST users/_search
{
"query": {
"match_all": {}
},
"sort": [
{
"age": {
"order": "desc"
}
}
]
}
結果是正常的,
{
"took" : 3,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 3,
"relation" : "eq"
},
"max_score" : null,
"hits" : [
{
"_index" : "users",
"_type" : "_doc",
"_id" : "2",
"_score" : null,
"_source" : {
"name" : "jerry",
"mobile" : "15978866920",
"age" : 35
},
"sort" : [
35
]
},
{
"_index" : "users",
"_type" : "_doc",
"_id" : "1",
"_score" : null,
"_source" : {
"name" : "tom",
"mobile" : "15978866921",
"age" : 30
},
"sort" : [
30
]
},
{
"_index" : "users",
"_type" : "_doc",
"_id" : "3",
"_score" : null,
"_source" : {
"name" : "jack",
"mobile" : "15978866922",
"age" : 20
},
"sort" : [
20
]
}
]
}
}
對於非text字段類型,doc_values默認情況下是打開的。索引我們上面的操作都沒有問題。現在我們把doc_values關掉再試試。
由於修改mapping需要重建索引,爲了簡單起見,我這裏把index直接刪掉重建。
關閉doc_values,
DELETE users
PUT users
{
"mappings" : {
"properties" : {
"name" : {
"type" : "text"
},
"mobile" : {
"type" : "keyword"
},
"age" : {
"type" : "integer",
"doc_values": false
}
}
}
}
重新插入數據,然後再次搜索排序,發現報錯,
意思就是age
字段不支持排序了,需要打開doc_values纔行。
fielddata現在用的不多,我就不演示了。
原理剖析
簡單分析下doc_values的原理。我們知道搜索的時候需要使用倒排索引,類似下圖這樣,
比如說,所以我們要查找包含brown
的文檔,先在詞項列表中找到 brown,然後掃描所有列,可以快速找到包含 brown 的文檔。
但是如果是要對搜索結果進行排序或者其它聚合操作,倒排索引這種方式就沒真這麼容易了,反而是類下面這種正排索引更方便。doc_values其實是Lucene在構建倒排索引時,會額外建立一個有序的正排索引(基於document => field value的映射列表)。
總結
根據經驗,大部分情況下你都不需要主動打開 fielddata,doc_values能滿足大部分需求。
另外,mapping中的字段我們確定以後不會基於這個字段做排序或者聚合,可以把它關掉,但是一定要非常明確你的這個操作,因爲如果要重新打開doc_values,需要重建索引,這個在生產環境下還是要謹慎。