一、doc_values
默認情況下,大部分字段是索引的,這樣讓這些字段可被搜索。倒排索引(inverted index)允許查詢請求在詞項列表中查找搜索項(search term),並立即獲得包含該詞項的文檔列表。
倒排索引(inverted index):
如果我們想要獲得所有包含 brown 的文檔的詞的完整列表,我們會創建如下查詢:
GET /my_index/_search
{
"query" : {
"match" : {
"body" : "brown"
}
},
"aggs" : {
"popular_terms": {
"terms" : {
"field" : "body"
}
}
}
}
倒排索引是根據詞項來排序的,所以我們首先在詞項列表中找到 brown,然後掃描所有列,找到包含 brown 的文檔。我們可以快速看到 Doc_1 和 Doc_2 包含 brown 這個 token。
然後,對於聚合部分,我們需要找到 Doc_1 和 Doc_2 裡所有唯一的詞項。用倒排索引做這件事情代價很高: 我們會迭代索引裡的每個詞項並收集 Doc_1 和 Doc_2 列裡面 token。這很慢而且難以擴展:隨著詞項和文檔的數量增加,執行時間也會增加。
Doc values 通過轉置兩者間的關係來解決這個問題。倒排索引將詞項映射到包含它們的文檔,doc values 將文檔映射到它們包含的詞項:
當數據被轉置之後,想要收集到 Doc_1 和 Doc_2 的唯一 token 會非常容易。獲得每個文檔行,獲取所有的詞項,然後求兩個集合的並集。
Doc values 可以使聚合更快、更高效並且內存友好。Doc values 的存在是因為倒排索引只對某些操作是高效的。
倒排索引的優勢:在於查找包含某個項的文檔,而對於從另外一個方向的相反操作並不高效,即:確定哪些項是否存在單個文檔裡,聚合需要這種訪問模式。
在 Elasticsearch 中,Doc Values 就是一種列式存儲結構,默認情況下每個字段的 Doc Values 都是激活的,Doc Values 是在索引時創建的。當字段索引時,Elasticsearch 為了能夠快速檢索,會把字段的值加入倒排索引中,同時它也會存儲該字段的 `Doc Values`。
Elasticsearch 中的 Doc Values 常被應用到以下場景:
- 對一個字段進行排序
- 對一個字段進行聚合
- 某些過濾,比如地理位置過濾
- 某些與字段相關的腳本計算
因為文檔值(doc values)被序列化到磁盤,我們可以依靠操作系統的幫助來快速訪問。當 working set 遠小於節點的可用內存,系統會自動將所有的文檔值保存在內存中,使得其讀寫十分高速;當其遠大於可用內存,操作系統會自動把 Doc Values 加載到系統的頁緩存中,從而避免了 jvm 堆內存溢出異常。
因此,搜索和聚合是相互緊密纏繞的。搜索使用倒排索引查找文檔,聚合操作收集和聚合 doc values 裡的數據。
doc values 支持大部分字段類型,但是text 字段類型不支持(因為analyzed)。
(1) status_code 字段默認啟動 doc_values 屬性;
(2) session_id 顯式設置 doc_values = false,但是仍然可以被查詢;
如果確信某字段不需要排序或者聚合,或者從腳本中訪問字段值,那麼我們可以設置 doc_values = false,這樣可以節省磁盤空間。
二、fielddata
與 doc values 不同,fielddata 構建和管理 100% 在內存中,常駐於 JVM 內存堆。這意味著它本質上是不可擴展的。
fielddata可能會消耗大量的堆空間,尤其是在加載高基數(high cardinality)text字段時。一旦fielddata已加載到堆中,它將在該段的生命週期內保留。此外,加載fielddata是一個昂貴的過程,可能會導致用戶遇到延遲命中。這就是默認情況下禁用fielddata的原因。
如果需要對 text 類型字段進行排序、聚合、或者從腳本中訪問字段值,則會出現如下異常:
Fielddata is disabled on text fields by default. Set fielddata=true on [your_field_name] in order to load fielddata in memory by uninverting the inverted index. Note that this can however use significant memory.
但是,在啟動fielddata 設置之前,需要考慮為什麼針對text 類型字段進行排序、聚合、或腳本呢?通常情況下,這是不太合理的。
text字段在索引時,例如New York,這樣的詞會被分詞,會被拆成new、york 2個詞項,這樣當搜索new 或 york時,可以被搜索到。在此字段上面來一個terms的聚合會返回一個new的bucket和一個york的bucket,但是你可能想要的是一個單一new york的bucket。
怎麼解決這一問題呢?
你可以使用 text 字段來實現全文本查詢,同時使用一個未分詞的 keyword 字段,且啟用doc_values,來處理聚合操作。
(1) 使用my_field 字段用於查詢;
(2) 使用my_field.keyword 字段用於聚合、排序、或腳本;
可以使用 PUT mapping API 在現有text 字段上啟用 fielddata,如下所示:
閱讀更多 軟件架構 的文章