Elasticsearch搜索調優權威指南 (1/3)

英文原文:https://qbox.io/blog/elasticsearch-search-tuning-5-0-ultimate-guide
作者:Adam Vanderbush
譯者:楊振濤

目錄

  1. 文檔建模
  2. 全局序列號和延遲
  3. 多代關係
  4. 爲文件系統緩存分配內存

Elasticsearch搜索調優權威指南,是QBOX在其博客上發佈的系列文章之一,本文是該系列的第一篇,主要從文檔建模、內存分配、文件系統緩存、GC和硬件等方面介紹了優化查詢性能的一些經驗。

Elasticsearch 5.0.0確實是在2.x之後的一個大版本,爲大家帶來了許多新東西。Elasticsearch現在作爲Elastic Stack中的一員,與整個技術棧的其他產品的版本號已經對齊,現在Kibana、Logstash、Beats和Elasticsearch全都是5.0版本了。

這個版本的Elasticsearch是目前爲止最快、最安全、最彈性,也是最易用的,而且還帶來了很多的改進和新特性。

我們已經通過“Elasticsearch性能調優權威指南”系列,介紹了一些性能調優的基本經驗和方法,解釋了每一步最關鍵的系統設置和衡量指標。該系列共分下列3個部分:

  • The Authoritative Guide to Elasticsearch Performance Tuning (Part 1) 
  • The Authoritative Guide to Elasticsearch Performance Tuning (Part 2) 
  • The Authoritative Guide to Elasticsearch Performance Tuning (Part 3)

索引決策也很重要,它對如何搜索數據有很大的影響。如果是一個字符串字段,是否需要分詞或歸一化?如果是,怎麼做?如果是一個數值型屬性,需要哪種精度?還有很多其他類型,比如date-time、geospatial shape以及父子關係等,需要更多特別的考慮。

我們也通過一個系列教程討論了“Elasticsearch索引性能優化”,介紹了一些通用的技巧和方法,來最大化索引的吞吐量並降低監控和管理的負載。該教程分如下3個部分:

本文旨在推薦一些搜索調優技術、策略以及Elasticsearch 5.0及以上的推薦特性。

Elasticsearch搜索調優權威指南 (1/3)

1.文檔建模

內部對象屬性數組並不像期望的那樣工作。Lucene 中沒有內部對象的概念,所以Elasticsearch把對象層次展開到一個由屬性名稱和屬性值組成的簡單列表中。以下列文檔爲例:

curl -XPUT 'localhost:9200/my_index/my_type/1?pretty' -H 'Content-Type: application/json' -d '{
 "group" : "fans",
 "user" : [
   {
     "first" : "John",
     "last" :  "Smith"
   },
   {
     "first" : "Alice",
     "last" :  "White"
   }
 ]
}'

該請求會在內部轉換爲如下的文檔形式:

{
  "group" :        "fans",
  "user.first" : [ "alice", "john" ],
  "user.last" :  [ "smith", "white" ]
}

如果需要索引對象數組,並維護數組中每個對象的依賴關係,應當使用內嵌數據類型而不是對象數據類型。內嵌對象在內部會把數組中的每個對象當作單獨的隱藏文檔來索引,即使用下述內嵌查詢,可以單獨查詢每個內嵌對象:

curl -XPUT 'ES_HOST:ES_PORT/my_index?pretty' -H 'Content-Type: application/json' -d '{
 "mappings": {
   "my_type": {
     "properties": {
       "user": {
         "type": "nested"
       }
     }
   }
 }
}'

curl -XPUT 'ES_HOST:ES_PORT/my_index/my_type/1?pretty' -H 'Content-Type: application/json' -d '{
 "group" : "fans",
 "user" : [
   {
     "first" : "John",
     "last" :  "Smith"
   },
   {
     "first" : "Alice",
     "last" :  "White"
   }
 ]
}'

curl -XGET 'ES_HOST:ES_PORT/my_index/_search?pretty' -H 'Content-Type: application/json' -d '{
 "query": {
   "nested": {
     "path": "user",
     "query": {
       "bool": {
         "must": [
           { "match": { "user.first": "Alice" }},
           { "match": { "user.last":  "Smith" }}
         ]
       }
     }
   }
 }
}'

curl -XGET 'ES_HOST:ES_PORT/my_index/_search?pretty' -H 'Content-Type: application/json' -d '{
 "query": {
   "nested": {
     "path": "user",
     "query": {
       "bool": {
         "must": [
           { "match": { "user.first": "Alice" }},
           { "match": { "user.last":  "White" }}
         ]
       }
     },
     "inner_hits": {
       "highlight": {
         "fields": {
           "user.first": {}
         }
       }
     }
   }
 }
}'

當有一個主實體比如一篇博客文章,帶有一些有一定關係但又不是非常重要的其他實體比如評論時,內嵌對象會非常有用。如果能根據評論內容來查詢到博客文章,那就很不錯,而且內嵌查詢和過濾器一起提供了更快的join查詢能力。

內嵌對象模型的缺點如下:

爲了 增加 、修改 或 刪除 一個內嵌對象文檔,整個文檔必須重建索引;這就導致內嵌文檔越多開銷就越大。

搜索請求返回整個文檔,而不是隻返回匹配的內嵌文檔。雖然已經以後計劃支持返回根文檔的部分最配內嵌文檔,但目前仍然不支持。

有時候可能需要把主文檔和其關聯實體分離,這種分離由父子關係來提供。

通過建立另一個文檔的父類型mapping,可以在相同索引的文檔之間建立父子關係:

curl -XPUT 'ES_HOST:ES_PORT/my_index?pretty' -H 'Content-Type: application/json' -d '{
 "mappings": {
   "my_parent": {},
   "my_child": {
     "_parent": {
       "type": "my_parent"
     }
   }
 }
}'

curl -XPUT 'ES_HOST:ES_PORT/my_index/my_parent/1?pretty' -H 'Content-Type: application/json' -d '{
 "text": "This is a parent document"
}'

curl -XPUT 'ES_HOST:ES_PORT/my_index/my_child/2?parent=1&pretty' -H 'Content-Type: application/json' -d '{
 "text": "This is a child document"
}'

curl -XPUT 'ES_HOST:ES_PORT/my_index/my_child/3?parent=1&refresh=true&pretty' -H 'Content-Type: application/json' -d '{
 "text": "This is another child document"
}'

curl -XGET 'ES_HOST:ES_PORT/my_index/my_parent/_search?pretty' -H 'Content-Type: application/json' -d '{
 "query": {
   "has_child": {
     "type": "my_child",
     "query": {
       "match": {
         "text": "child document"
       }
     }
   }
 }
}'

父子join對管理實體關係非常有用,尤其是在索引時間比檢索時間很重要的情形下,但是它會帶來較大的開銷;父子查詢比同等的內嵌查詢要慢5到10倍。

2.全局序列號和延遲

父子關係使用了全局序列號來加速join操作。無論父子map是否使用了內存緩存或磁盤上的doc value,全局序列號仍然需要在索引發生任何改變時進行重建。

分片中的父代越多,全局序列號構建就越耗時。相對於需要父代和較少的子代, 父子關係最適合每個父代有很多子代的情形。

全局序列號默認是 延遲 構建:refresh後的第一個父子查詢或聚合請求將會觸發構建全局序列號。這會讓用戶感知到一個明顯的潛在峯值。可以使用eager_global_ordinals 來把查詢期構建全局序列號的成本轉移到refresh期,通過如下方式mapping _parent屬性:

curl -XPUT 'ES_HOST:ES_PORT/company -d ‘{
  "mappings": {
    "branch": {},
    "employee": {
      "_parent": {
        "type": "branch",
        "fielddata": {
          "loading": "eager_global_ordinals"
        }
      }
    }
  }
}’

這裏,_parent屬性的全局序列號將會在一個新的段搜索可見時被構建。

對於很多的父代,全局序列號要花費數秒鐘來構建。此時,需要增加refresh_interval,以便refresh的頻率更低,而全局序列號保持可用的時間更長。這將大幅減少每秒鐘重建全局序列號的CPU消耗。

3.多代關係

對多代數據的Join(參考Grandparents and Grandchildren)能力聽起來很吸引人,但需要思考其代價:

  • Join越多,性能越差。
  • 每一個父代都需要把自己的string _id屬性保存在內存,這可能會消耗大量RAM。
  • 當考慮關係型方案及父子關係是否適合時,可參考下列關於父子關係的建議:
  • 保守使用父子關係,僅當子代比父代多很多時才考慮。
  • 避免在單個查詢中使用多父子關係來join。
  • 避免對使用has_child過濾器,或score_mode爲 none 的has_child查詢來打分。
  • 父ID儘量簡短,以便在doc value中更好地壓縮,從而在瞬時加載時消耗更少的內存。

4.爲文件系統緩存分配內存

對於運行中Elasticsearch,內存是需要密切監控的重要資源之一。Elasticsearch和Lucene通過JVM堆內存和文件系統緩存兩種方式來消耗內存。由於Elasticsearch運行在Java虛擬機(JVM)中,所以JVM的GC週期和頻率也需要重點監控。

JVM堆內存

對於Elasticsearch一個“剛好合適”的JVM堆大小是非常重要的——不能設置過大或過小,原因見後文。一般來說Elasticsearch的經驗值是分配少於50%的可用RAM給JVM堆,且不要超過32GB。

爲Elasticsearch分配過少的堆內存,那麼就會留給Lucene更多內存,而Lucene重度依賴於文件系統緩存來快速處理請求。不管怎樣也不能設置過小的堆內存,因爲當應用由於頻繁GC而面臨短時中斷時,可能會遭遇內存溢出錯誤或吞吐量下降。

Elasticsearch默認安裝時設置的JVM堆大小爲1GB,這在大多數情況下都偏小。可以通過環境變量來設置期望的對大小並重啓Elasticsearch:

export ES_HEAP_SIZE=10g

設置JVM堆大小的另一種方式(相當於設置一樣的最小值和最大值,以防止重新調整堆大小),是在每次啓動Elasticsearch時通過命令行參數指定:

ES_HEAP_SIZE="10g" ./bin/elasticsearch

這兩種示例方式都是設置了10GB的堆大小,爲了驗證是否設置成功,執行:

curl -XGET http://ES_HOST:9200/_cat/nodes?h=heap.max

返回的輸出會顯示已正確地更新了最大堆內存。

垃圾回收

Elasticsearch依靠GC過程來釋放堆內存。由於GC本身也要消耗資源(爲了釋放資源!),所以應當留意GC頻率和持續時間,以確認是否需要調整堆內存大小。設置過大的堆內存,換來的是更長的GC時間;這種過多的停頓非常危險,因爲可能導致集羣誤認爲該節點網絡異常而失聯。

因此,Elasticsearch重度依賴文件系統緩存來加速搜索。一般需要保證至少有一半的可用內存用於文件系統緩存,這樣Elasticsearch才能保持索引數據的熱點區域都在物理內存中。

使用更快的硬件

如果搜索受限於I/O,應當考慮爲文件系統緩存分片更多內存(參考前文),或者購買更快的驅動。特別地,SSD公認地比機械磁盤性能好很多。儘可能使用本地存儲,避免使用像 NFS 或 SMB 之類的遠程或網絡文件系統,也要注意像Amazon EBS這樣的虛擬化存儲。

Elasticsearch使用虛擬化存儲工作是沒有問題的,它因爲快速和安裝簡單而受歡迎,但同樣不幸的是,在基礎上與專用的本地存儲相比它天生就比較慢。如果在EBS上創建了一個索引庫,請確認使用預分配的IOPS,否則很快就會被限流。

如果搜索受限於CPU,那麼應當考慮購買更快的CPU。

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