歡迎光臨
每天分享高質量文章

別再說你不會 ElasticSearch 調優了,都給你整理好了

  1. 第一部分:調優索引速度
  2. 第二部分-調優搜尋速度
  3. 第三部分:通用的一些建議

英文原文:https://www.elastic.co/guide/en/elasticsearch/reference/current/how-to.html

ES釋出時帶有的預設值,可為es的開箱即用帶來很好的體驗。全文搜尋、高亮、聚合、索引檔案 等功能無需使用者修改即可使用,當你更清楚的知道你想如何使用es後,你可以作很多的最佳化以提高你的用例的效能,下麵的內容告訴你 你應該/不應該 修改哪些配置

第一部分:調優索引速度

(https://www.elastic.co/guide/en/elasticsearch/reference/current/tune-for-indexing-speed.html)

  1. 使用批次請求批次請求將產生比單檔案索引請求好得多的效能。

為了知道批次請求的最佳大小,您應該在具有單個分片的單個節點上執行基準測試。 首先嘗試索引100個檔案,然後是200,然後是400,等等。 當索引速度開始穩定時,您知道您達到了資料批次請求的最佳大小。 在配合的情況下,最好在太少而不是太多檔案的方向上犯錯。 請註意,如果群集請求太大,可能會使群集受到記憶體壓力,因此建議避免超出每個請求幾十兆位元組,即使較大的請求看起來效果更好。

  1. 傳送端使用多worker/多執行緒向es傳送資料
    傳送批次請求的單個執行緒不太可能將Elasticsearch群集的索引容量最大化。 為了使用叢集的所有資源,您應該從多個執行緒或行程傳送資料。 除了更好地利用叢集的資源,這應該有助於降低每個fsync的成本。

請確保註意TOO_MANY_REQUESTS(429)響應程式碼(Java客戶端的EsRejectedExecutionException),這是Elasticsearch告訴您無法跟上當前索引速率的方式。 發生這種情況時,應該再次嘗試暫停索引,理想情況下使用隨機指數回退。

與批次調整大小請求類似,只有測試才能確定最佳的worker數量。 這可以透過逐漸增加工作者數量來測試,直到叢集上的I / O或CPU飽和。

  1. 調大 refresh interval
    預設的index.refresh_interval是1s,這迫使Elasticsearch每秒建立一個新的分段。 增加這個價值(比如說30s)將允許更大的部分flush並減少未來的合併壓力。
  2. 載入大量資料時禁用refresh和replicas
    如果您需要一次載入大量資料,則應該將index.refresh_interval設定為-1並將index.number_of_replicas設定為0來禁用掃清。這會暫時使您的索引處於危險之中,因為任何分片的丟失都將導致資料 丟失,但是同時索引將會更快,因為檔案只被索引一次。 初始載入完成後,您可以將index.refresh_interval和index.number_of_replicas設定回其原始值。
  3. 設定引數,禁止OS將es行程swap出去
    您應該確保作業系統不會swapping out the java行程,透過禁止swap
    (https://www.elastic.co/guide/en/elasticsearch/reference/current/setup-configuration-memory.html)
  4. 為filesystem cache分配一半的物理記憶體
    檔案系統快取將用於緩衝I / O操作。 您應該確保將執行Elasticsearch的計算機的記憶體至少減少到檔案系統快取的一半。
  5. 使用自動生成的id(auto-generated ids)
    索引具有顯式id的檔案時,Elasticsearch需要檢查具有相同id的檔案是否已經存在於相同的分片中,這是昂貴的操作,並且隨著索引增長而變得更加昂貴。 透過使用自動生成的ID,Elasticsearch可以跳過這個檢查,這使索引更快。
  6. 買更好的硬體
    搜尋一般是I/O 密集的,此時,你需要
    a.為filesystem cache分配更多的記憶體
    b.使用SSD硬碟
    c.使用local storage(不要使用NFS、SMB 等remote filesystem)
    d.亞馬遜的 彈性塊儲存(Elastic Block Storage)也是極好的,當然,和local storage比起來,它還是要慢點
    如果你的搜尋是 CPU-密集的,買好的CPU吧
  7. 加大 indexing buffer size
    如果你的節點只做大量的索引,確保index.memory.index_buffer_size足夠大,每個分割槽最多可以提供512 MB的索引緩衝區,而且索引的效能通常不會提高。 Elasticsearch採用該設定(java堆的一個百分比或絕對位元組大小),並將其用作所有活動分片的共享緩衝區。 非常活躍的碎片自然會使用這個緩衝區,而不是執行輕量級索引的碎片。

預設值是10%,通常很多:例如,如果你給JVM 10GB的記憶體,它會給索引緩衝區1GB,這足以承載兩個索引很重的分片。

  1. 禁用_field_names欄位
    _field_names欄位引入了一些索引時間開銷,所以如果您不需要執行存在查詢,您可能需要禁用它。
    (_field_names:https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping-field-names-field.html)
  2. 剩下的,再去看看 “調優 磁碟使用”吧
    (https://www.elastic.co/guide/en/elasticsearch/reference/current/tune-for-disk-usage.html)中有許多磁碟使用策略也提高了索引速度。

第二部分-調優搜尋速度

  1. filesystem cache越大越好
    為了使得搜尋速度更快, es嚴重依賴filesystem cache
    一般來說,需要至少一半的 可用記憶體 作為filesystem cache,這樣es可以在物理記憶體中 保有 索引的熱點區域(hot regions of the index)
  2. 用更好的硬體
    搜尋一般是I/O bound的,此時,你需要
    a.為filesystem cache分配更多的記憶體
    b.使用SSD硬碟
    c.使用local storage(不要使用NFS、SMB 等remote filesystem)
    d.亞馬遜的 彈性塊儲存(Elastic Block Storage)也是極好的,當然,和local storage比起來,它還是要慢點
    如果你的搜尋是 CPU-bound,買好的CPU吧
  3. 檔案模型(document modeling)
    檔案需要使用合適的型別,從而使得 search-time operations 消耗更少的資源。咋作呢?
    答:避免 join操作。具體是指
    a.nested 會使得查詢慢 好幾倍
    b.parent-child關係 更是使得查詢慢幾百倍
    如果 無需join 能解決問題,則查詢速度會快很多
  4. 預索引 資料
    根據“搜尋資料最常用的方式”來最最佳化索引資料的方式
    舉個例子:
    所有檔案都有price欄位,大部分query 在 fixed ranges 上執行 range aggregation。你可以把給定範圍的資料 預先索引下。然後,使用 terms aggregation
  5. Mappings(能用 keyword 最好了)
    數字型別的資料,並不意味著一定非得使用numeric型別的欄位。
    一般來說,儲存識別符號的 欄位(書號ISBN、或來自資料庫的 標識一條記錄的 數字),使用keyword更好(integer,long 不好哦,親)
    6.避免執行指令碼
    一般來說,指令碼應該避免。 如果他們是絕對需要的,你應該使用painless和expressions引擎。
  6. 搜尋rounded 日期
    日期欄位上使用now,一般來說不會被快取。但,rounded date則可以利用上query cache
    rounded到分鐘等
  7. 強制merge只讀的index
    只讀的index可以從“merge成 一個單獨的 大segment”中收益
  8. 預熱 全域性序數(global ordinals)
    全域性序數 用於 在 keyword欄位上 執行 terms aggregations
    es不知道 哪些fields 將 用於/不用於 term aggregation,因此 全域性序數 在需要時才載入進記憶體
    但,可以在mapping type上,定義 eager_global_ordinals==true,這樣,refresh時就會載入 全域性序數
  9. 預熱 filesystem cache
    機器重啟時,filesystem cache就被清空。OS將index的熱點區域(hot regions of the index)載入進filesystem cache是需要花費一段時間的。
    設定 index.store.preload 可以告知OS 這些檔案需要提早載入進入記憶體

11使用索引排序來加速連線
索引排序對於以較慢的索引為代價來加快連線速度非常有用。在索引分類檔案中閱讀更多關於它的資訊。

12.使用preference來最佳化高速快取利用率
有多個快取可以幫助提高搜尋效能,例如檔案系統快取,請求快取或查詢快取。然而,所有這些快取都維護在節點級別,這意味著如果連續執行兩次相同的請求,則有一個或多個副本,並使用迴圈(預設路由演演算法),那麼這兩個請求將轉到不同的分片副本,阻止節點級別的快取幫助。

由於搜尋應用程式的使用者一個接一個地執行類似的請求是常見的,例如為了分析索引的較窄的子集,使用標識當前使用者或會話的優選值可以幫助最佳化高速快取的使用。

13.副本可能有助於吞吐量,但不會一直存在
除了提高彈性外,副本可以幫助提高吞吐量。例如,如果您有單個分片索引和三個節點,則需要將副本數設定為2,以便共有3個分片副本,以便使用所有節點。

現在假設你有一個2-shards索引和兩個節點。在一種情況下,副本的數量是0,這意味著每個節點擁有一個分片。在第二種情況下,副本的數量是1,這意味著每個節點都有兩個碎片。哪個設定在搜尋效能方面表現最好?通常情況下,每個節點的碎片數少的設定將會更好。原因在於它將可用檔案系統快取的份額提高到了每個碎片,而檔案系統快取可能是Elasticsearch的1號效能因子。同時,要註意,沒有副本的設定在發生單個節點故障的情況下會出現故障,因此在吞吐量和可用性之間進行權衡。

那麼複製品的數量是多少?如果您有一個具有num_nodes節點的群集,那麼num_primaries總共是主分片,如果您希望能夠一次處理max_failures節點故障,那麼正確的副本數是max(max_failures,ceil(num_nodes / num_primaries) – 1)。

14.開啟自適應副本選擇
當存在多個資料副本時,elasticsearch可以使用一組稱為自適應副本選擇的標準,根據包含分片的每個副本的節點的響應時間,服務時間和佇列大小來選擇資料的最佳副本。這可以提高查詢吞吐量並減少搜尋量大的應用程式的延遲。

第三部分:通用的一些建議

1、不要 傳回大的結果集
es設計來作為搜尋引擎,它非常擅長傳回匹配query的top n檔案。但,如“傳回滿足某個query的 所有檔案”等資料庫領域的工作,並不是es最擅長的領域。如果你確實需要傳回所有檔案,你可以使用Scroll API

2、避免 大的doc。即,單個doc 小了 會更好
given that(考慮到) http.max_context_length預設==100MB,es拒絕索引操作100MB的檔案。當然你可以提高這個限制,但,Lucene本身也有限制的,其為2GB
即使不考慮上面的限制,大的doc 會給 network/memory/disk帶來更大的壓力;
a.任何搜尋請求,都需要獲取 _id 欄位,由於filesystem cache工作方式。即使它不請求 _source欄位,獲取大doc _id 欄位消耗更大
b.索引大doc時消耗記憶體會是 doc本身大小 的好幾倍
c.大doc的 proximity search, highlighting 也更加昂貴。它們的消耗直接取決於doc本身的大小

3、避免 稀疏
a.不相關資料 不要 放入同一個索引
b.一般化檔案結構(Normalize document structures)
c.避免型別
d.在 稀疏 欄位上,禁用 norms & doc_values 屬性

稀疏為什麼不好?
Lucene背後的資料結構 更擅長處理 緊湊的資料
text型別的欄位,norms預設開啟;numerics, date, ip, keyword,doc_values預設開啟
Lucene內部使用 integer的doc_id來標識檔案 和 內部API互動。
舉個例子:
使用match查詢時生成doc_id的迭代器,這些doc_id被用於獲取它們的norm,以便計算score。當前的實現是每個doc中保留一個byte用於儲存norm值。獲取norm值其實就是讀取doc_id位置處的一個位元組
這非常高效,Lucene透過此值可以快速訪問任何一個doc的norm值;但,給定一個doc,即使某個field沒有值,仍需要為此doc的此field保留一個位元組
doc_values也有同樣的問題。2.0之前的fielddata被現在的doc_values所替代了。
稀疏性 最明顯的影響是 對儲存的需求(任何doc的每個field,都需要一個byte);但是呢,稀疏性 對 索引速度和查詢速度 也是有影響的,因為:即使doc並沒有某些欄位值,但,索引時,依然需要寫這些欄位,查詢時,需要skip這些欄位的值
某個索引中擁有少量稀疏欄位,這完全沒有問題。但,這不應該成為常態
稀疏性影響最大的是 norms&doc;_values ,但,倒排索引(用於索引 text以及keyword欄位),二維點(用於索引geo_point欄位)也會受到較小的影響

如何避免稀疏呢?
1、不相關資料 不要 放入同一個索引
給個tip:索引小(即:doc的個數較少),則,primary shard也要少
2、一般化檔案結構(Normalize document structures)
3、避免型別(Avoid mapping type)
同一個index,最好就一個mapping type
在同一個index下麵,使用不同的mapping type來儲存資料,聽起來不錯,但,其實不好。given that(考慮到)每一個mapping type會把資料存入 同一個index,因此,多個不同mapping type,各個的field又互不相同,這同樣帶來了稀疏性 問題
4、在 稀疏 欄位上,禁用 norms & doc_values 屬性
a.norms用於計算score,無需score,則可以禁用它(所有filtering欄位,都可以禁用norms)
b.doc_vlaues用於sort&aggregations;,無需這兩個,則可以禁用它
但是,不要輕率的做出決定,因為 norms&doc;_values無法修改。只能reindex

秘訣1:混合 精確查詢和提取詞乾(mixing exact search with stemming)
對於搜尋應用,提取詞乾(stemming)都是必須的。例如:查詢 skiing時,ski和skis都是期望的結果
但,如果使用者就是要查詢skiing呢?
解決方法是:使用multi-field。同一份內容,以兩種不同的方式來索引儲存
query.simple_query_string.quote_field_suffix,竟然是 查詢完全匹配的

秘訣2:獲取一致性的打分
score不能重現
同一個請求,連續執行2次,但,兩次傳回的檔案順序不一致。這是相當壞的使用者體驗

如果存在 replica,則就可能發生這種事,這是因為:
search時,replication group中的shard是按round-robin方式來選擇的,因此兩次運行同樣的請求,請求如果打到 replication group中的不同shard,則兩次得分就可能不一致

那問題來了,“你不是整天說 primary和replica是in-sync的,是完全一致的”嘛,為啥打到“in-sync的,完全一致的shard”卻算出不同的得分?

原因就是標註為“已刪除”的檔案。如你所知,doc更新或刪除時,舊doc並不刪除,而是標註為“已刪除”,只有等到 舊doc所在的segment被merge時,“已刪除”的doc才會從磁碟刪除掉

索引統計(index statistic)是打分時非常重要的一部分,但,由於 deleted doc 的存在,在同一個shard的不同copy(即:各個replica)上 計算出的 索引統計 並不一致

個人理解:
a. 所謂 索引統計 應該就是df,即 doc_freq
b. 索引統計 是基於shard來計算的

  1. 搜尋時,“已刪除”的doc 當然是 永遠不會 出現在 結果集中的
  2. 索引統計時,for practical reasons,“已刪除”doc 依然是統計在內的

假設,shard A0 剛剛完成了一次較大的segment merge,然後移除了很多“已刪除”doc,shard A1 尚未執行 segment merge,因此 A1 依然存在那些“已刪除”doc

於是:兩次請求打到 A0 和 A1 時,兩者的 索引統計 是顯著不同的

如何規避 score不能重現 的問題?使用 preference 查詢引數
發出搜尋請求時候,用 標識字串 來標識使用者,將 標識字串 作為查詢請求的preference引數。這確保多次執行同一個請求時候,給定使用者的請求總是達到同一個shard,因此得分會更為一致(當然,即使同一個shard,兩次請求 跨了 segment merge,則依然會得分不一致)
這個方式還有另外一個優點,當兩個doc得分一致時,則預設按著doc的 內部Lucene doc id 來排序(註意:這並不是es中的 _id 或 _uid)。但是呢,shard的不同copy間,同一個doc的 內部Lucene doc id 可能並不相同。因此,如果總是達到同一個shard,則,具有相同得分的兩個doc,其順序是一致的

score錯了
score錯了(Relevancy looks wrong)
如果你發現

  1. 具有相同內容的檔案,其得分不同
  2. 完全匹配 的查詢 並沒有排在第一位
    這可能都是由 sharding 引起的
  3. 預設情況下,搜尋檔案時,每個shard自己計算出自己的得分。
  4. 索引統計 又是打分時一個非常重要的因素。

如果每個shard的 索引統計相似,則 搜尋工作的很好
檔案是平分到每個primary shard的,因此 索引統計 會非常相似,打分也會按著預期工作。但,萬事都有個但是:

  1. 索引時使用了 routing(檔案不能平分到每個primary shard 啦)
  2. 查詢多個索引
  3. 索引中檔案的個數 非常少
    這會導致:參與查詢的各個shard,各自的 索引統計 並不相似(而,索引統計對 最終的得分 又影響巨大),於是 打分出錯了(relevancy looks wrong)

那,如何繞過 score錯了(Relevancy looks wrong)?

如果資料集較小,則,只使用一個primary shard(es預設是5個),這樣兩次查詢 索引統計 不會變化,因而得分也就一致啦
另一種方式是,將search_type設定為:dfs_query_then_fetech(預設是query_then_fetch)
dfs_query_then_fetch的作用是

  1. 向 所有相關shard 發出請求,要求 所有相關shard 傳回針對當前查詢的 索引統計
  2. 然後,coordinating node 將 merge這些 索引統計,從而得到 merged statistics
  3. coordinating node 要求 所有相關shard 執行 query phase,於是 發出請求,這時,也帶上 merged statistics。這樣,執行query的shard 將使用 全域性的索引統計
    大部分情況下,要求 所有相關shard 傳回針對當前查詢的 索引統計,這是非常cheap的。但,如果查詢中 包含 非常大量的 欄位/term查詢,或者有 fuzzy查詢,此時,獲取 索引統計 可能並不cheap,因為 為了得到 索引統計 可能 term dictionary 中 所有的term都需要被查詢一遍
贊(0)

分享創造快樂