elasticsearch 官方優化建議

2023-04-09 12:01:28

1.一般建議

  a.不要返回過大的結果集。這個建議對一般資料庫都是適用的,如果要獲取大量結果,可以使用search_after api,或者scroll (新版本中已經不推薦)。

  b.避免大的檔案。

2. 如何提高索引速度

  a.使用批次請求。為了達到最好的效果,可以進行測試,遞增地提高bulk的數量,比如從100,到200,再到400,達到一個吞吐量和響應時間的平衡。

  b.使用多執行緒傳送資料。

  c.關閉或者減小refresh_interval。從記憶體快取寫入磁碟快取(memorybuffer -> filesystem cache),這個過程叫做refresh。在這個過程之前記憶體快取裡面的檔案是不可被搜尋的,這也是為什麼es被稱為近實時索引的原因。

    在索引初始化(大量匯入檔案)的時候,可以關閉refresh_interval。當產品允許較大的不可搜尋時間,可以將index.refresh_interval設定為30s,提高索引速度。

  d.初始化時關閉複製分片。索引時設定index.number_of_replicas為0,避免主分片複製資料,索引完畢後再調整到正常的複製分片數。

  e.關閉swapping。swap會極大地降低es的索引速度。

Swap分割區(即交換區)在系統的實體記憶體不夠用的時候,把硬碟空間中的一部分空間釋放出來,以供當前執行的程式使用。
那些被釋放的空間可能來自一些很長時間沒有什麼操作的程式,這些被釋放的空間被臨時儲存到Swap分割區中,等到那些程式要執行時,再從Swap分割區中恢復儲存的資料到記憶體中。

  f.給檔案系統快取分配足夠多的記憶體。檔案系統換行用來處理io操作,至少要將物理機一半的記憶體分配給檔案系統快取。比如物理機記憶體64g,那麼至少分配32g給檔案系統快取,剩下的記憶體才考慮分配給es。

  g.使用自動生成的id。如果使用指定的id,es會檢查這個id是否已經存在,而且隨著檔案數越多,這個判重操作越耗時。索引的時候,如果沒有指定id,es會自動生成id。

{
    "_index": "sales",
    "_type": "_doc",
    "_id": "xb7IY4cB6Rdc8HbDycuE", // auto-generated id
    "_version": 1,
    "result": "created",
    "_shards": {
        "total": 2,
        "successful": 1,
        "failed": 0
    },
    "_seq_no": 10,
    "_primary_term": 1
}

  h.使用更好的硬體。比如SSD,或者Amazon的Elastic Block Storage。

  i.調整索引快取大小。確保每個索引分片能獲得512M的快取,即 indices.memory.index_buffer_size = 512M,大於512M沒有更多提升效果。

  j.使用cross-cluster replication 來實現讀寫分離,這樣讓索引叢集壓力更小。這和mysql中的讀寫分離很類似。

3.如何提到搜尋速度

  a.給檔案系統快取分配足夠多的記憶體。

  b.在linux環境中設定合適的readahead。但是es中的查詢更多的是隨機io,過大的readahead反而使檔案系統的頁快取嚴重抖動,從而使查詢效能下降。

Linux的檔案預讀readahead,指Linux系統核心將指定檔案的某區域預讀進頁快取起來,便於接下來對該區域進行讀取時,不會因缺頁(page fault)而阻塞。因為從記憶體讀取比從磁碟讀取要快很多。
預讀可以有效的減少磁碟的尋道次數和應用程式的I/O等待時間,是改進磁碟讀I/O效能的重要優化手段之一。使用命令lsblk檢視readahead值。

  c.使用更好的硬體。

  d.好的檔案模型。酌情使用nested query, parent query, 避免使用join query。

檔案模型 對比普通查詢
nested query 慢幾倍
parent query 慢幾百倍
join query 應當避免

  e.儘可能少的查詢欄位。在越多的欄位上匹配,查詢速度就越慢。在索引的時候可以將需要查詢的多個欄位聚合到一個欄位中。使用copy_to 可以自動實現這一功能,以下範例將name和plot欄位聚合到name_and_plot欄位中。

PUT movies
{
  "mappings": {
    "properties": {
      "name_and_plot": {
        "type": "text"
      },
      "name": {
        "type": "text",
        "copy_to": "name_and_plot"
      },
      "plot": {
        "type": "text",
        "copy_to": "name_and_plot"
      }
    }
  }
}

  f.預先索引資料。比如如果想對price欄位做range聚合,那麼預先計算出單個檔案的price範圍,那麼就能將range聚合轉化成terms聚合。這樣確實能提高效率,但是不太靈活。

插入檔案:

PUT index/_doc/1
{
  "designation": "spoon",
  "price": 13
}

range聚合查詢:

GET index/_search
{
  "aggs": {
    "price_ranges": {
      "range": {
        "field": "price",
        "ranges": [
          { "to": 10 },
          { "from": 10, "to": 100 },
          { "from": 100 }
        ]
      }
    }
  }
}

另一種做法,預先計算price_range:

PUT index
{
  "mappings": {
    "properties": {
      "price_range": {
        "type": "keyword"
      }
    }
  }
}

PUT index/_doc/1
{
  "designation": "spoon",
  "price": 13,
  "price_range": "10-100"
}

使用terms聚合:

GET index/_search
{
  "aggs": {
    "price_ranges": {
      "terms": {
        "field": "price_range"
      }
    }
  }
}

  g.儘可能將欄位自定義為keyword。對於數位型別的欄位,es對其range查詢做了優化。在term層級的查詢下,keyword欄位比數位型別要好。

    在以下兩種情況下可以考慮將數位型別定義為keyword:

      1.不需要對這些資料進行range查詢

      2.有很高的查詢速度要求。

    如果實在不清楚哪個好,可以用 multi-field為數位型別的欄位同時定義數位型別和keyword型別。

  h.避免使用指令碼。如果可能,避免使用指令碼排序,使用指令碼聚合,以及script_scorequery。

  i.使用四捨五入的日期。這樣有助於es進行快取,精確到秒級別的查詢有時候並無必要。

實時查詢(秒級):

PUT index/_doc/1
{
  "my_date": "2016-05-11T16:30:55.328Z"
}

GET index/_search
{
  "query": {
    "constant_score": {
      "filter": {
        "range": {
          "my_date": {
            "gte": "now-1h",
            "lte": "now"
          }
        }
      }
    }
  }
}

分鐘級查詢:

GET index/_search
{
  "query": {
    "constant_score": {
      "filter": {
        "range": {
          "my_date": {
            "gte": "now-1h/m",
            "lte": "now/m"
          }
        }
      }
    }
  }
}

  j.對唯讀索引進行force-merge。在時序索引中,過期的索引都是唯讀的,將其合併成一個段能加快查詢速度。

  k.預熱global ordinals。ordinals 是doc values的具體儲存形式。一般情況下一個欄位的global ordinals是懶載入的。如果某個欄位在聚合上用到很多,我們可以先將其預熱(載入到heap),當做field data cache.的一部分。

PUT index
{
  "mappings": {
    "properties": {
      "foo": {
        "type": "keyword",
        "eager_global_ordinals": true
      }
    }
  }
}

  l.預熱檔案系統快取。設定index.store.preload引數即可。注意,必須確保檔案系統快取足夠大,否則會讓查詢變得更慢。

  m.使用索引排序來加速連線查詢。比如我們要進行過濾 a AND b AND …​,然後a是low-cardinality(低區分度)。那麼我們可以先對a進行排序,那麼一旦a的某個值不匹配這個表示式,那麼有相同的值的檔案都可以跳過。

  n.使用preference進行快取使用優化。es中有非常多的快取,比如檔案系統快取(最重要),請求快取,查詢快取,但是這些快取都是在節點層面。預設情況下es會使用round-robin演演算法分配查詢到不同的分片上去,這樣快取就失效了。

    如果可以,使用preference引數將使用者的請求和對應的分片或者節點繫結起來,這樣快取就不會失效。例如:

GET /_search?preference=_shards:2,3
{
    "query": {
        "match": {
            "title": "elasticsearch"
        }
    }
}

  o.更多的複製分片會提升吞吐量(但並不一定)。在系統資源充足的情況下,複製分片越多吞吐量會越高。但是過多的分片會讓故障恢復變得更慢。

  p.使用profile api優化查詢語句。和mysql中的explain類似,例如:

GET /my-index-000001/_search
{
  "profile": true,
  "query" : {
    "match" : { "message" : "GET /search" }
  }
}

{
  "took": 25,
  "timed_out": false,
  "_shards": {
    "total": 1,
    "successful": 1,
    "skipped": 0,
    "failed": 0
  },
  "hits": {
    "total": {
      "value": 5,
      "relation": "eq"
    },
    "max_score": 0.17402273,
    "hits": [...] 
  },
  "profile": {
    "shards": [
      {
        "id": "[2aE02wS1R8q_QFnYu6vDVQ][my-index-000001][0]",
        "searches": [
          {
            "query": [
              {
                "type": "BooleanQuery",
                "description": "message:get message:search",
                "time_in_nanos" : 11972972,
                "breakdown" : {
                  "set_min_competitive_score_count": 0,
                  "match_count": 5,
                  "shallow_advance_count": 0,
                  "set_min_competitive_score": 0,
                  "next_doc": 39022,
                  "match": 4456,
                  "next_doc_count": 5,
                  "score_count": 5,
                  "compute_max_score_count": 0,
                  "compute_max_score": 0,
                  "advance": 84525,
                  "advance_count": 1,
                  "score": 37779,
                  "build_scorer_count": 2,
                  "create_weight": 4694895,
                  "shallow_advance": 0,
                  "create_weight_count": 1,
                  "build_scorer": 7112295
                },...

  q.使用 index_phrases 加速phrase query。index_phrases,會將兩個單詞的組合單獨索引,這樣可以加速phrase query。

  r.使用 index_phrases 加速prefix query。同上。

  s.使用constant_keyword加速過濾。如果某個欄位的大多數情況下的值是個常數,但是我們又經常要對其進行過濾,我們可以將其拆分成兩個索引,一個使用constant_keyword,一個不使用。

mapping如下:

UT bicycles
{
  "mappings": {
    "properties": {
      "cycle_type": {
        "type": "constant_keyword",
        "value": "bicycle"
      },
      "name": {
        "type": "text"
      }
    }
  }
}

PUT other_cycles
{
  "mappings": {
    "properties": {
      "cycle_type": {
        "type": "keyword"
      },
      "name": {
        "type": "text"
      }
    }
  }
}

查詢語句:

GET bicycles,other_cycles/_search
{
  "query": {
    "bool": {
      "must": {
        "match": {
          "description": "dutch"
        }
      },
      "filter": {
        "term": {
          "cycle_type": "bicycle"
        }
      }
    }
  }
}

在查詢bicycles索引時,es會將查詢語句自動轉換為:

GET bicycles,other_cycles/_search
{
  "query": {
    "match": {
      "description": "dutch"
    }
  }
}

4.磁碟優化

  a.禁用不需要的特性。

    比如數位型別的欄位如果不需要進行過濾,可以不對其進行索引。

PUT index
{
  "mappings": {
    "properties": {
      "foo": {
        "type": "integer",
        "index": false
      }
    }
  }
}

    es會對text型別的欄位儲存一些打分資訊,如果不需要對這些欄位進行打分,可以將其設定為match_only_text型別

  b.不要使用預設動態字串對映。預設動態字串對映會將字串型別對映為text和keyword型別,這樣很浪費空間。可以預先設定所有字串對映型別為keyword。

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

  c.監控分片大小。越大的分片能更有效地儲存資料。但是分片越大,故障恢復也會越慢。

  d.禁用_source欄位。_source會儲存原始的json資料,如果不需要,就將其禁用。

  e.使用best_compression進行壓縮。es預設使用 LZ4 進行壓縮,使用best_compression可以提升壓縮比率,但是會影響資料存取效能。

  f.force-merge.強制合併段能提升儲存效率。注意,force-merge應當在沒有檔案寫入後進行,  比如在過期的時序索引節點上。

  g.shrink 索引。即收縮索引,將當前索引重新索引成分片數更少的索引。分片越大,儲存效率越高。

    shrink索引有如下條件。

    1.索引必須唯讀。

    2.節點必須包含索引的所有分片(主分片,或者複製分片都可以)

    3.索引狀態必須是健康的。

  h.使用能滿足需求的最小的數位型別。比如能用byte, 不用short。這個在其他db比如mysql中也適用。

  i.使用索引排序來提升檔案的壓縮效能。排序後相似的檔案會放在一起,es能根據他們的特性有效地進行壓縮。

    設定索引排序:

PUT my-index-000001
{
  "settings": {
    "index": {
      "sort.field": "date", 
      "sort.order": "desc"  
    }
  },
  "mappings": {
    "properties": {
      "date": {
        "type": "date"
      }
    }
  }
}

  j.索引檔案時保證json欄位順序一致。es在儲存的時候將多個檔案壓縮成一成block,如果json檔案順序一致,es能更好的對更長的相同的字串進行壓縮。

  k.roll-up歷史資料。使用roll up api來歸檔歷史資料,他們依然可以存取,但是有著更高的儲存效率。

5.分片大小

  1.將索引分片大小保持在10G~50G之間

  2.平均下來每G堆記憶體下不要超過20個分片。