一文帶你徹底弄懂ES中的doc_values和fielddata

基本概念

這兩個概念比較像,所以大部分時候會放在一起說。

這兩個概念源於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,需要重建索引,這個在生產環境下還是要謹慎。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章