優化磁盤使用量與建立索引時的映射參數和索引元數據字段密切相
關,在介紹具體的優化措施之前,我們先介紹這兩方面的基礎知識。
元數據字段
- _source:原始的JSON文檔數據。
- _all:索引所有其他字段值的一種通用字段,這個字段中包含了所有其他字段的值。
允許在搜索的時候不指定特定的字段名,意味着“從全部字段中搜索”,例如:
http://localhost:9200/website/_search?q=keyword
_all字段是一個全文字段,有自己的分析器。從ES 6.0開始該字段被禁用。之前的版本默認啓用,但字段的store屬性爲false,因此它不能被查詢後取回顯示。
索引mapping參數
index:控制字段值是否被索引。它可以設置爲true或false,默認爲true。未被索引的字段不會被查詢到,但是可以聚合。除非禁用doc_values。
doc values:默認情況下,大多數字段都被索引,這使得它們可以搜索。倒排索引根據term找到文檔列表,然後獲取文檔原始內容。但是排序和聚合,以及從腳本中訪問某個字段值,需要不同的數據訪問模式,它們不僅需要根據term找到文檔,還要獲取文檔中字段的值。這些值需要單獨存儲。doc_values 就是用來存儲這些字段值的。
它是一種存儲在磁盤上的列式存儲,在文檔索引時構建,這使得上述數據訪問模式成爲可能。它們以面向列的方式存儲與_source相同的值,這使得排序和聚合效率更高。幾乎所有字段類型都支持doc_values,但被分析(analyzed)的字符串字段除外(即text類型字符串)。doc_values默認啓用。
store:默認情況下,字段值會被索引使它們能搜索,但它們不會被存儲(stored)。意味着可以通過這個字段查詢,但不能取回它的原始值。但這沒有關係。因爲字段值已經是_source字段的一部分,它是被默認存儲的。如果只想取回一個字段或少部分字段的值,而不是整個
_source,則可以通過source filtering達到目的。
在某些情況下,存儲字段是有意義的。例如,如果有一個包含標題、日期和非常多的內容字段的文檔,則可能希望只檢索標題和日期,而不需要從大型_source字段中提取這些字段。
還有一種情況可能用到存儲字段,就是不在_source中出現的字段(例如,copy_to字段)。
doc_values和存儲字段("stored":ture)都屬於正排內容,兩者的設計初衷不同。
stored fields被設計爲優化存儲,doc_values被設計爲快速訪問字段值。搜索可能會訪問很多doc values中的字段,所以必須能夠快速訪問,我們將doc_values用於聚合、排序,以及腳本中。現在,ES中的許多特性都會自動使用doc_values。
另一方面,stored fields僅用於返回前幾個最匹配文檔的字段值,默認情況下 ES 只將其用於這種情況,解壓存儲字段,將其發送給客戶端。爲少量文檔獲取存儲字段還好。它不能在查詢的時候使用,否則會讓查詢變得非常慢。腳本中可以訪問存儲字段,但最好不要那麼做。
禁用對你來說不需要的特性
默認情況下,ES爲大多數的字段建立索引,並添加到doc_values,以便使之可以被搜索和聚合。但是有時候不需要通過某些字段過濾,例如,有一個名爲 foo 的數值類型字段,需要運行直方圖,但不需要在這個字段上過濾,那麼可以不索引這個字段:
Text(動態的string類型應該也可以配置,keyword類型默認關閉norms)類型的字段會在索引中存儲歸一因子(normalizationactors),以便對文檔進行評分,如果只需要在文本字段上進行匹配,而不關心生成的得分,則可以配置 ES 不將 norms 寫入索引:
text類型的字段默認情況下也在索引中存儲頻率和位置。頻率用於計算得分,位置用於執行短語(phrase)查詢。如果不需要運行短語查詢,則可以告訴ES不索引位置,及index_options僅配置頻率:
在text類型的字段上,index_options的默認值爲positions。
index_options參數用於控制添加到倒排索引中的信息。
freqs 文檔編號和詞頻被索引,詞頻用於爲搜索評分,重複出現的詞條比只出現一次的詞條評分更高。
positions 文檔編號、詞頻和位置被索引。positions被用於鄰近查詢(proximity queries)和短語查詢(phrase queries)。
此外,如果也不關心評分,則可以將ES配置爲只爲每個term索引匹配的文檔。仍然可以在這個字段上搜索,但是短語查詢會出現錯誤,評分將假定在每個文檔中只出現一次詞彙。
禁用doc values
所有支持doc value的字段都默認啓用了doc value。如果確定不需要對字段進行排序或聚合,或者從腳本訪問字段值,則可以禁用doc value以節省磁盤空間:
謹慎使用默認的dynamic字符串映射
默認的動態字符串映射會把字符串類型的字段同時索引爲 text 和keyword。如果只需要其中之一,則顯然是一種浪費。通常,id字段只需作爲 keyword類型進行索引,而content字段只需作爲text類型進行索引。
要禁用默認的動態字符串映射,則可以顯式地指定字段類型,或者在動態模板中指定將字符串映射爲text或keyword。下例將字符串字段映射爲keyword:
管理shard大小
較大的分片可以更有效地存儲數據。爲了增加分片大小,可以在創建索引的時候設置較少的主分片數量(規則參考index的shard規劃原則章節),或者使用shrink API來修改現有索引的主分片數量。
但是較大的分片也有缺點,例如,較長的索引恢復時間。
科學使用_source
_source 字段存儲文檔的原始內容。對於部分不需要存儲的字段,可以通過 includes excludes過濾,減少_source字段的存儲量。如果不需要訪問它,則可以將其禁用。但是,需要訪問_source的API將無法使用,至少包括下列情況:
- update、update_by_query、reindex;
- 高亮搜索;
- 重建索引(包括更新mapping、分詞器,或者集羣跨大版本升級可能會用到)
- 調試聚合查詢功能,需要對比原始數據
使用best_compression
_source和設置爲"store": true的字段佔用磁盤空間都比較多。默認情況下,它們都是被壓縮存儲的。默認的壓縮算法爲LZ4,可以通過使用best_compression來執行壓縮比更高的算法:DEFLATE。但這會佔用更多的CPU資源。
PUT index
{
"settings": {
"index": {
"codec": "best_compression"
}
}
}
兩種壓縮特點比較:
|
壓縮比 |
壓縮速度 |
解壓速度 |
壓縮內存佔用 |
解壓內存佔用 |
DEFLATE |
高 |
慢 |
慢 |
少 |
少 |
LZ4 |
低 |
快 |
快 |
多 |
多 |
兩種壓縮的壓縮比測試:
Test |
String fields |
_all |
index size /w LZ4 |
index size /w DEFLATE |
expansion ratio /w LZ4 |
expansion ratio /w DEFLATE |
Impact of DEFLATE |
Structured data file. Original file size: 67644119 |
|
|
|
|
|
|
|
1 |
analyzed and not_analyzed |
enabled |
63047579 |
53131592 |
0.932 |
0.785 |
-0.157 |
2 |
analyzed and not_analyzed |
disabled |
48271433 |
38327106 |
0.713 |
0.566 |
-0.206 |
3 |
not_analyzed |
disabled |
38920800 |
29014796 |
0.575 |
0.428 |
-0.254 |
3b |
not_analyzed, except for 'message' field which is retained and analyzed |
disabled |
65382872 |
49532858 |
0.966 |
0.732 |
-0.242 |
4 |
not_analyzed, except for 'agent' field which is analyzed |
disabled |
43083702 |
32063602 |
0.636 |
0.474 |
-0.255 |
Semi-structured data file. |
|
|
|
|
|
|
|
1 |
analyzed and not_analyzed |
enabled |
100478376 |
82132782 |
1.339 |
1.094 |
-0.182 |
2 |
analyzed and not_analyzed |
disabled |
75238480 |
56911638 |
1.002 |
0.758 |
-0.243 |
3 |
not_analyzed |
disabled |
71866672 |
53553561 |
0.957 |
0.713 |
-0.254 |
3b |
not_analyzed, except for 'message' field which is retained and analyzed |
disabled |
104638750 |
83824398 |
1.394 |
1.117 |
-0.198 |
4 |
not_analyzed, except for 'agent' field which is analyzed |
disabled |
72925624 |
54603882 |
0.971 |
0.727 |
-0.251 |
根據結果比較,使用best_compression之後壓縮比會提升15%-25%
爲只讀索引執行force-merge
爲不再更新的只讀索引執行force merge,將Lucene索引合併爲單個分段,可以提升查詢速度。當一個Lucene索引存在多個分段時,每個分段會單獨執行搜索再將結果合併;另外執行force_merge後也會釋放無法被GC的segmentCache(另一種是close掉索引)。所以將只讀索引強制合併爲一個Lucene分段不僅可以優化搜索過程,減少內存佔用,對索引恢復速度也有好處。
基於日期使用rollover創建的索引的舊數據一般都不會再更新。此前的章節中說過,應該避免持續地寫一個固定的索引,直到它巨大無比,而應該按一定的策略,例如,每天生成一個新的索引,然後用別名關聯,或者使用索引通配符。這樣,可以每天凌晨對昨天的索引執行force-merge、Shrink等操作。
Shrink Index
Shrink API允許減少索引的分片數量,結合上面的Force Merge API,可以顯著減少索引的分片和Lucene分段數量。
合理選擇數值類型長度
爲數值類型選擇的字段類型也可能會對磁盤使用空間產生較大影響,整型可以選擇byte、short、integer或long,浮點型可以選擇scaled_float、float、double、half_float,每個數據類型的字節長度是不同的,爲業務選擇夠用的最小數據類型,可以節省磁盤空間。
使用索引排序來排列類似的文檔
當 ES 存儲_source 時,它同時壓縮多個文檔以提高整體壓縮比。例如,文檔共享相同的字段名,或者它們共享一些字段值,特別是在具有低基數或 zipfian 分佈(參考https://en.wikipedia.org/wiki/Zipf%27s_law)的字段上。
默認情況下,文檔按照添加到索引中的順序壓縮在一起。如果啓用了索引排序,那麼它們將按排序順序壓縮。對具有相似結構、字段和值的文檔進行排序可以提高壓縮比。
關於索引排序的詳細內容請參考官方手冊:
https://www.elastic.co/guide/en/elasticsearch/reference/master/index-modules-index-sorting.html。
測試結果總結
筆者沒有自己測試,找了一些公開的測試結果,效果總結如下:
-
- 禁用_source,空間佔用量下降30%左右;
- 禁用doc values,空間佔用量下降10%左右;
- 壓縮算法將LZ4改爲Deflate,空間佔用量可以下降15%~25%。
本文的測試結果可作一定參考,但是實際業務最好使用自己的樣本數據進行壓力測試以獲取準確的結果。
終極版優化方案詳見:Elasticsearch配置優化方案最終完整版