a.不要返回過大的結果集。這個建議對一般資料庫都是適用的,如果要獲取大量結果,可以使用search_after api,或者scroll (新版本中已經不推薦)。
b.避免大的檔案。
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中的讀寫分離很類似。
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_score
query。
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"
}
}
}
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來歸檔歷史資料,他們依然可以存取,但是有著更高的儲存效率。
1.將索引分片大小保持在10G~50G之間
2.平均下來每G堆記憶體下不要超過20個分片。