Elasticsearch重要文章之五:預加載fielddata

Elasticsearch 是默認延遲加載fielddata到內存裏的。當elasticsearch第一次遇到一個查詢須要一個指定field的fielddata的時候,就會把索引的每一個段中整個field加載到內存。對於小段,這是個能夠忽略不計的時間,可是若是你有一些5G大小的段而且須要加載10GB的fielddata到內存裏,這個過程須要數十秒,習慣於秒內響應時間的用戶會被網突如其來的遲鈍所打擊。java

有三種方法應對這種延遲尖峯:
Eagerly load fielddata(餓漢式(預)加載fielddata)
Eagerly load global ordinals(餓漢式(預)加載全局序數)
Prepopulate caches with warmers (使用warmer提早加載緩存)。json

全部這些都是一個意思:預先加載fielddata到內存裏,這樣當用戶須要執行一個搜索的時候就感覺不到延遲了。api

餓漢式(預)加載fielddata緩存

首先是預先加載(而不是默認的延遲加載),當一個新的段造成時(無亂是刷新,寫入或者是合併),能夠預先加載的field會提早把段的fielddata加載到內存裏,在這段能夠用於搜索以前。數據結構

這意味着當你第一次查詢的時候,若是碰到在這個段上,你不須要再觸發加載fielddata的操做,它們已經在內存中了,這會防止你的用戶遇到一些冷到緩存而發生延遲尖峯。app

預先加載是基於每一個field的,因此你能夠控制哪些field進行預加載。electron

PUT /music/_mapping/_song
{
  "price_usd": {
    "type": "integer",
    "fielddata": {
      "loading" : "eager"
    }
  }
}

備註: 經過設置fielddata.loading: eager,告訴elasticsearch預先加載這個field的內容到內存裏。elasticsearch

fielddata的加載能夠設置成餓漢模式(預先加載)仍是懶惰模式(延遲加載),使用update-mapping的api。ide

預先加載是簡單對fielddata加載的開銷的轉移,從查詢時間轉義到刷新時刻。性能

大段的刷新時間會比小段的時間長,一般大段的產生都是由哪些已經可搜素的小段合併而來的,因此慢一點的刷新時間不是那麼重要(譯者注:意思是大段的刷新時間長不影響你的搜索,在大段合併成前的小段能夠用於搜索)。

全局序數

其中一項用於減小string類型的fielddata佔用內存的技術叫作序數。

假設咱們有十億條文檔,每一個文檔都有一個status的field,只有三個值:status_pending, status_published, status_deleted,若是咱們把全部的status加載到內存裏,每一個文檔須要14-16byte,也就是說15GB

相反,咱們能夠確認這三個特殊的字符串,對他們排序,依次編號0,1,2

Ordinal | Term
-------------------
0       | status_deleted
1       | status_pending
2       | status_published

序號對應的字符串值要在序號列表中存儲一次,每一個文檔只要使用他們的編號來表示他們所包含的值就能夠了。

Doc     | Ordinal
-------------------------
0       | 1  # pending
1       | 1  # pending
2       | 2  # published
3       | 0  # deleted

這個能夠把15GB的內存佔用減小到小於1GB

可是這裏有個問題,記住fielddata緩存是針對每一個段的,若是一個段包含兩個狀態-status_deleted 和 status_published,這個序號是(0 和1),若是其它段中有三個不一樣的狀態,那相同的序號的含義在兩個段中是不一樣的。

若是咱們試圖執行一個status 的field的彙總操做(aggregation ),咱們須要在實際的字符串上進行彙總。覺得着咱們要在全部的段上識別出有相同值的文檔,一個單純的方式就是在每一個段上進行彙總操做,從每一個段上返回字符串的值,而後合併他們獲得最終的結果,這樣是可行的,這會很慢很耗CPU。

想反,咱們實驗了一個數據結構叫作全局序數,全局序號只是在fielddata之上,一小部份內存數據結構,把全部的段的惟一值存儲在一個序號列表裏,就像前面咱們描述的那樣。

如今,咱們的彙總操做只要在全局序號上進行彙總,把序號轉換成實際字符串,只要在最後一個彙總中進行就能夠了,這大大提升了在只要三四個值的field上進行彙總(還有排序)操做的性能。

構建全局序數

固然,天下沒有免費的午飯,全局序數是針對一個索引的全部段的,因此若是增長了一個段或者刪除了一個段,這個全局序數都要進行重建,長袖須要讀取每一個段的每一個惟一term。存儲基數越大,惟一tems越多,這個過程會更長。

全局序數是建立在內存裏的fielddata和doc values之上的,實際上,他們也是doc values性能表現的主要緣由。

和fielddata的加載同樣,全局序數的構建默認也是延遲進行的,當第一個請求須要fielddata時候,會觸發全局序數的構建,和你的field的基數有關,這可能致使用戶的一個延遲響應。一旦全局序數構建完成,他們將被使用,直到索引中的段發生變化:刷新,寫入和合並。

餓漢式(預先加載)全局序數

PUT /music/_mapping/_song
{
  "song_title": {
    "type": "string",
    "fielddata": {
      "loading" : "eager_global_ordinals" 
    }
  }
}

注:設置爲eager_global_ordinals 也表明實現了預先加載fielddata。

就像預先加載fielddata同樣,全局序數會在一個新的段可進行搜索以前進行構建。

注意:序數只用於字符串類型的field,數值類型(整型,地理位置,時間等)都不須要一個序數映射,由於他們自身就是一個內存的序數映射。

因此,你只能開啓字符串類型的全局序數的預加載。

文檔的值也能夠有他們的全局序數:

PUT /music/_mapping/_song
{
  "song_title": {
    "type":       "string",
    "doc_values": true,
    "fielddata": {
      "loading" : "eager_global_ordinals"
    }
  }
}

注:這種狀況,fielddata就不加載到內存裏了,而是文檔的values會被加載到系統級緩存裏。

和fielddata的預加載不一樣,對全局序數的預加載會對數據的實時性有影響,隊友一個很大基數的field,構建全局序數會致使數秒的延遲刷新,是把時間花在每次刷新上呢,仍是每次刷新以後的第一次查詢上面這是一個選擇。若是你插入數據頻繁,查詢不多,最好是把代價花在查詢時刻,而不是每次刷新上。

注意:讓你的全局序號本身處理,若是你有一個很大基數的field,致使很長時候構建,你能夠增長refresh_interval 配置,這樣回增長全局序數的有效時間,減小CPU的利用率,一樣構建全局序數的次數也會減小。

Index warmers(索引熱身)

最後,咱們到了index warmers,wamers能夠提前加載fielddata和全局序數,可是他還有一個目的。一個索引的warmer容許你指定一個查詢或者匯聚操做在你的新段可被查詢以前執行,這個idea就叫作預熱、或者叫暖身,緩存等,那你的用戶就永遠不會遇到延遲尖峯時刻了。

期初,這個warmers的最重要的用處是爲了確保fielddata的預先加載,這一般是開銷最大的部分,這是咱們以前討論的利用這個技術控制的最好的地方,然而,warmers能夠用於構建filter 緩存,也能夠用於預加載fielddata,這個目的隨你選擇。

先讓咱們註冊一個warner再去討論發生了什麼:

PUT /music/_warmer/warmer_1 
{
  "query" : {
    "filtered" : {
      "filter" : {
        "bool": {
          "should": [
            { "term": { "tag": "rock"        }},
            { "term": { "tag": "hiphop"      }},
            { "term": { "tag": "electronics" }}
          ]
        }
      }
    }
  },
  "aggs" : {
    "price" : {
      "histogram" : {
        "field" : "price",
        "interval" : 10
      }
    }
  }
}

warmers是註冊在一個特定的索引上面的,每個warmer都有一個惟一的id,由於你能夠爲一個索引指定多個warmer。

而後你能夠之低昂一個查詢,或者多個查詢,他包含查詢語句,filters,aggregations,sort,腳本等有效的查詢語句。重點是要註冊那些你的用戶將要進行,有表明性的查詢,從而適當的緩存能夠被預先加載。

當一個新的段產生的時候,elasticsearch就會執行你這些warmers裏的查詢語句。執行這些查詢會致使緩存進行加載,只有索引warmers都被執行了,新的段纔會變成可搜素狀態。

注意:相似於預先加載,warmers 把對冷緩存的開銷轉移到了刷新時刻。當你註冊了warmers,你必須是通過明智判斷的。你能夠增長成千上萬個wamer確保預熱全部的緩存,可是這將極大的增長一個新的段從建立變爲可搜索的時間。

實踐中,選擇幾條表明大部分用戶的查詢語句進行註冊。

一些管理方面的細節(例如獲取存在的warmer,刪除warmer)就不在這裏過多解釋了,閱讀warmer相關文檔,獲取其它細節。

相關文章
相關標籤/搜索