Elasticsearch調優篇 05 - Elasticsearch 搜索層面最全優化


1、盡量少的字段

  elasticsearch 的搜索引擎嚴重依賴於底層的 filesystem cache,你如果給 filesystem cache 更多的內存,盡量讓內存可以容納所有的 indx segment file 索引數據文件,那么你搜索的時候就基本都是走內存的,性能會非常高。

  比如說,你的 es 節點有3台機器,每台機器 64G,總內存 64 * 3。每台機器給 es jvm heap 是32G,那么剩下來留給 filesystem cache 的就是每台機器才32g,總共集群里給 filesystem cache的就是 32 * 3 = 96gb 內存。如果此時你整個磁盤上索引數據文件,在3台機器上一共占用了1T的磁盤容量,es 數據量是 1t。filesystem cache 的內存才100g,十分之一的數據可以放內存,其他的都在磁盤,然后你執行搜索操作,大部分操作都是走磁盤,性能肯定差。
  歸根結底,你要讓 es 性能好,最佳的情況下,就是你的機器的內存,至少可以容納你的總數據量的一半 。比如說,你一共要在 es 中存儲 1T 的數據,那么你的多台機器留給 filesystem cache 的內存加起來綜合至少要到 512G,至少半數的情況下,搜索是走內存的,性能一般可以到幾十毫秒。

  如果最佳的情況下,我們自己的生產環境實踐經驗,最好是用 es 就存少量的數據,就是你要用來搜索的那些索引,內存留給 filesystem cache 的就 100G,那么你就控制在 100gb 以內,相當於你的數據幾乎全部走內存來搜索,性能非常之高,一般可以在10ms以內。
  所以在 es 里就存儲必須用來搜索的數據,比如說你現在有一份數據,有100個字段,其實用來搜索的只有10個字段,建議是將10個字段的數據存入 es,剩下90個字段的數據,可以放 mysql,hadoop,hbase 等都可以。 這樣的話,es數據量很少,10個字段的數據,都可以放內存,就用來搜索,搜索出來一些id,通過 id 去 mysql,hbase 里面去查詢明細的數據。

2、document 模型設計

  document模型設計是非常重要的,不要在搜索的時候才想去執行各種復雜的亂七八糟的操作。

  es能支持的操作就是那么多,不要考慮用es做一些它不好操作的事情。如果真的有那種操作,盡量在 document 模型設計的時候,寫入的時候就完成。

  另外對於一些太復雜的操作,比如 join,nested,parent-child 搜索都要盡量避免,性能都很差的。

  在搜索/查詢的時候,要執行一些業務強相關的特別復雜的操作:

  1. 在寫入數據的時候,就設計好模型,加幾個字段,把處理好的數據寫入加的字段里面
  2. 自己用java程序封裝,es能做的,用es來做,搜索出來的數據,在java程序里面去做,比如說我們基於es用java封裝一些特別復雜的操作

3、預索引數據

  例如電商平台,有很多 range aggregation 操作,但有很多其范圍是可以確定下來的,那么在索引的時候就增加相關的字段。

  例如,要做一個按照價格區間進行分類統計的,但是這個價格區間基本上是固定的,形如 0 ~ 100,100 ~ 200, 200 ~ 500 ......,那么我們再索引的時候針對價格就可以明確確定下來該商品到底是屬於哪個價格區間的,打上對應的標簽,這樣再聚合和查詢的時候可以直接通過該字段來快速的進行定位,提升搜索性能。

4、日期查詢

  這里針對日期查詢單獨提出來,是因為日期查詢寫的不規范時會導致性能急劇下降。

  盡量不要使用 now 這種內置函數來執行日期查詢,因為默認 now 是到毫秒級的,是無法緩存結果,盡量使用一個階段范圍。

  比如 now/m,就是到分鍾級,那么如果一分鍾內,都執行這個查詢,是可以取用查詢緩存的

5、禁用動態類型映射

  默認的動態 string 類型映射會將 string 類型的 field 同時映射為 text 類型以及 keyword 類型,這會浪費磁盤空間,因為我們不一定兩種都需要。

  通常來說,id field 這種字段可能只需要 keyword 映射,而 body field 可能只需要 text field。

  映射一個content,content: text,content.內置字段: keyword

  可以通過手動設置 mappings 映射來避免字符串類型的field被自動映射為 text 和 keyword:

PUT index
{
 "mappings": {
   "type": {
     "dynamic_templates": [
       {
         "strings": {
           "match_mapping_type": "string",
           "mapping": {
              "type":"keyword"
           }
         }
       }
     ]
    }
  }
}

6、局部預熱

  如果我們重啟了es,或者重建了一個新的集群,那么 filesystem cache 就會變為空殼了,就需要不斷的查詢才能重新讓 filesystem cache 熱起來。

  我們可以先手動對一些數據進行查詢。比如說,你本來一個查詢,要用戶點擊以后才執行,才能從磁盤加載到 filesystem cache 里,第一次執行要 1s,以后每次就幾十毫秒。

  你完全可以起一個程序執行那個查詢,預熱,數據就加載到 filesystem cahce,程序執行的時候是 1s,以后用戶真的來看的時候就才幾十毫秒。

7、適當增大副本

  一般副本量大會增加搜索的吞吐量,但是也會降低索引性能,所以在實際場景中會根據不同的業務場景來設置合理的副本數量,一般我們設置副本數量常為  2。

  如果更新不是很頻繁而查詢量比較大的時候,可以適當增大副本數量,例如增大的 3、4、5 等。

  如果更新比較頻繁,而查詢的 qps 不高的話,可以將副本數量設置為 1。

8、用性能更好的硬件設備

  可以使用性能更好的 SSD 磁盤來替代 機械磁盤,尤其是這種隨機讀取的性能按照廠商給到的數據會比機械磁盤快 100 倍。

9、避免稀疏數據

  lucene的內核結構,跟稠密的數據配合起來,性能會更好。

  舉個例子,比如有100個document,每個document都有20個field,20個field都有值,這就是稠密的數據。

  但是如果100個document,每個document的field都不一樣,有的document有2個field,有的 document 有 50個field,這就是稀疏的數據。

  原因就是,lucene在內部會通過 doc id 來唯一標識一個document,這個 doc id 是integer類型,范圍在 0到索引中含有的document數量之間。

  這些 doc id 是用來在 lucene 內部的 api 之間進行通信的,比如說,對一個 term 用一個 match query 來進行搜索,就會產生一個 doc ids 集合,然后這些 doc ids 會用來獲取對應的norm值,以用來計算每個doc的相關度分數。

  而根據 doc id 查找 norm 的過程,是通過每個document的每個field保留一個字節來進行的一個算法,這個過程叫做norm查找,norm就是每個document的每個field保留的一個字節。

  對於每個doc id對應的那個norm值,可以通過讀取es一個內置索引,叫做doc_id的索引中的一個字節來獲取。這個過程是性能很高的,而且可以幫助lucene快速的定位到每個document的norm值,但是同時這樣的話document本身就不需要存儲這一個字節的norm值了。

  在實際運行過程中,這就意味着,如果一個索引有100個document,對於每個field,就需要100個字節來存儲norm值,即使100個document中只有10個document含有某個field,但是對那個field來說,還是要100個字節來存儲norm值。這就會對存儲產生更大的開銷,存儲空間被浪費的一個問題,而且也會影響讀寫性能。

下面有一些避免稀疏數據的辦法:

(1)避免將沒有任何關聯性的數據寫入同一個索引

  我們必須避免將結構完全不一樣的數據寫入同一個索引中,因為結構完全不一樣的數據,field是完全不一樣的,會導致index數據非常稀疏。

  最好將這種數據寫入不同的索引中,如果這種索引數據量比較少,那么可以考慮給其很少的primary shard,比如1個,避免資源浪費。

(2)對document的結構進行規范化/標准化

  即使我們真的要將不同類型的document寫入相同的索引中,還是有辦法可以避免稀疏性,那就是對不同類型的document進行標准化。

  比如說,如果所有的document都有一個時間戳field,不過有的叫做timestamp,有的叫做creation_date,那么可以將不同document的這個field重命名為相同的字段,盡量讓documment的結構相同。

  另外一個,就是比如有的document有一個字段,叫做goods_type,但是有的document沒有這個字段,此時可以對沒有這個字段的document,補充一個goods_type給一個默認值,比如default。

(3)避免使用多個types存儲不一樣結構的document

  很多人會很喜歡在一個index中放很多個types來存儲不同類型的數據。

  但是其實不是這樣的,最好不要這么干,如果你在一個index中有多個type,但是這些type的數據結構不太一樣,那么這些type實際上底層都是寫到這個索引中的,還是會導致稀疏性。

  如果多個type的結構不太一樣,最好放入不同的索引中,不要寫入一個索引中。

(4)對稀疏的field禁用norms和doc_values

  如果上面的步驟都沒法做,那么只能對那種稀疏的field,禁止 norms 和 doc_values 字段,因為這兩個字段的存儲機制類似,都是每個field有一個全量的存儲,對存儲浪費很大。

  如果一個field不需要考慮其相關度分數,那么可以禁用norms,如果不需要對一個field進行排序或者聚合,那么可以禁用doc_values字段。 .

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM