elasticSearch高性能集羣一

第 1-1 課:如何規劃新集羣

當有一個新的業務準備使用 Elasticsearch,尤其是業務首次建設 Elasticsearch 集羣時,往往不知道該如何規劃集羣大小,應該使用什麼樣的服務器?規劃多少個節點纔夠用?

集羣規模當然是越大越好,但是出於成本考慮,還是希望集羣規模規劃的儘量準確,能夠滿足業務需求,又有一些餘量,不建議規劃一個規模“剛剛好”的集羣,因爲當負載出現波動,或者一些其他偶然的故障時,會影響到業務的可用性,因此留一些餘量出來是必要的。

1 規劃數據節點規模

Elasticsearch 節點有多種角色,例如主節點,數據節點,協調節點等,默認情況下,每個節點都同時具有全部的角色。對於節點角色的規劃我們將在下一個小節討論,首先考慮一下我們需要多少個數據節點存儲我們的數據。規劃數據節點數量需要參考很多因素,有一些原則可以幫助我們根據業務情況進行規劃。

1.1 數據總量

數據總量是指集羣需要存儲的數據總大小,如果每天都有新數據入庫,總量隨之相應地增加。我們之所以要考慮數據總量,並非因爲磁盤空間容量的限制,而是 JVM 內存的限制

爲了加快搜索速度,Lucene 需要將每個段的倒排索引都加載到 JVM 內存中,因此每一個 open 狀態的索引都會在 JVM 中佔據一部分常駐內存,這些是 GC 不掉的,而且這部分空間佔用的比較大,並且由於堆內存不建議超過 32G,在磁盤使用率達到極限之前,JVM 佔用量會先到達極限。

按照我們的經驗,Elasticsearch 中 1TB 的 index 大約佔用 2GB 的 JVM 內存,具體和字段數據類型及樣本相關,有些會更多。一般情況下,我們可以按照這個比例來做規劃。如果想要精確計算,業務可以根據自己的樣本數據入庫 1TB,然後查看分段所佔的內存大小(實際上會比 REST 接口返回的值高一些)

curl -X GET "localhost:9200/_cat/nodes?v&h=name,segments.memory"

以 1TB 數據佔用 2GB 內存,JVM 堆內存配置 31G ,垃圾回收器 CMS 爲例,新生代建議配置 10G,old 區 21G,這 21G 內存分配一半給分段內存就已經很多了,想想看還沒有做任何讀寫操作時 old 區就佔用了一半,其他幾個內存大戶例如 bulk 緩衝,Query 緩存,indexing buffer,聚合計算等都可能會使用到 old 區內存,因此爲了保證節點穩定,分段內存不超過 10G 比較好,換算成索引數據量爲5TB。

因此,我們可以按照單個節點打開的索引數據總量不超過 5TB 來進行規劃,如果預計入庫 Elasticsearch 中的數據總量有 100TB 的數據(包括副分片所佔用空間),那麼數據節點的數量至少應該是: 100/5=20,此外出於冗餘方面的考慮,還要多加一些數據節點。冗餘節點的數量則和日增數據量以及故障轉移能力相關

可以看出這樣規劃出來的節點數是相對比較多的,帶來比較高的成本預算。在新的 6.7 及以上的版本 Elasticsearch 增加了凍結索引的特性,這是一種冷索引機制,平時他可以不佔內存,只有查詢的時候纔去加載到內存,雖然查詢慢一些,但是節點可以持有更多的數據總量。

因此,如果你想要節點存儲更多的數據量,在超出上述原則後,除了刪除或 close 索引之外,一個新的選擇是將它變成凍結狀態。

1.2 單個節點持有的最大分片數量

單個節點可以持有的最大分片數量並沒有明確的界限,但是過多的分片數量會造成比較大的管理壓力,官方給出的建議是,單個節點上所持有的分片數按 JVM 內存來計算:每 GB 的內存乘以 20。例如 JVM 內存爲 30GB,那麼分片數最大爲:30*20=600個。當然分片數越少會越穩定。

但是使用這個參考值也會有些問題,當分片大小爲 40GB 時(單一分片一般大小不超過50GB),節點所持有的數據量爲:40 * 600 = 24TB,按照 1TB 數據量佔用 2GB JVM 內存來計算,所佔用 JVM 內存爲:24 * 2 = 48GB,已經遠超 JVM 大小,因此我們認爲一般情況下不必將單個節點可以持有的分片數量作爲一個參考依據,只需要關心一個原則:讓 JVM 佔用率和 GC 時間保持在一個合理的範圍。

考慮另一個極端情況,每個分片的數據量都很小,同樣不必關心每個節點可以持有多少,對大量分片的管理屬於主節點的壓力。一般情況下,建議整個集羣的分片數量建議不超過 10 萬

2 規劃集羣節點角色

一個 Elasticsearch 節點默認擁有所有的角色,分離節點角色可以讓集羣更加穩定,尤其是更加註重穩定性和可用性的在線業務上,分離節點角色是必要的。

2.1 使用獨立的主節點

集羣有唯一的活躍主節點,他負責分片管理和集羣管理等操作,如果主節點同時作爲數據節點的角色,當活躍主節點失效的時候,例如網絡故障,硬件故障,新的主節點當選後需要重新分配原主節點上持有的分片數據,導致集羣在一段時間內處於 RED 和 YELLOW 狀態。而獨立部署的主節點不需要這個過程,新節點當選後集羣可以迅速 GREEN。

另外,由於數據節點通常有較大的內存佔用,GC 的影響也會導致混合部署的工作受到影響。因此如果集羣很在意穩定性和可用性,我們建議數據節點有 3 個及以上時,應該獨立部署 3 個獨立的主節點,共 6 個節點

node.master:false 
 
node.data:true 
 
node.ingest:false 
 
search.remote.connectfalse 
 

2.2 使用獨立的協調節點

有時候,你無法預知客戶端會發送什麼樣的查詢請求過來,也許他會包括一個深度聚合,這種操作很容易導致節點 OOM,而數據節點離線,或者長時間 GC,都會對業務帶來明顯影響。

亦或者客戶端需要進行許多佔用內存很多的聚合操作,雖然不會導致節點 OOM,但也會導致節點 GC 壓力較大,如果數據節點長時間 GC,查詢延遲就會有明顯抖動,影響查詢體驗。

此時最好的方式就是讓客戶端所有的請求都發到某些節點,這種節點不存儲數據,也不作爲主節點,即使 OOM 了也不會影響集羣的穩定性,這就是僅查詢節點(Coordinating only node)

node.master:false 
 
node.data:false 
 
node.ingest:false 
 
search.remote.connect:false 
 

禁用node.master角色(默認情況下啓用)。

禁用node.data角色(默認情況下啓用)。

禁用node.ingest角色(默認情況下啓用)。

禁用跨羣集搜索(默認情況下啓用)。

 

2.3 使用獨立的預處理節點

Elasticsearch 支持在將數據寫入索引之前對數據進行預處理、內容富化等操作,這通過內部的 processor 和 pipeline 實現,如果你在使用這個特性,爲了避免對數據節點的影響, 我們同樣建議將他獨立出來,讓寫請求發送到僅預處理節點

node.master:false 
 
node.data:false 
 
node.ingest:true 
 
search.remote.connectfalse 
 

獨立節點的各個角色後的集羣結構如下圖所示,其中數據節點也是獨立的。

如果沒有使用預處理功能,可以將讀寫請求都發送到協調節點。另外數據寫入過程最好先進入 Kafka 之類的 MQ,來緩衝一下對集羣的寫入壓力,同時也便於對集羣的後期維護。

總結

本文介紹瞭如何規劃集羣節點數和集羣節點角色,依據這些原則進行規劃可以較好的保證集羣的穩定性,可以適用於組件新集羣時評估集羣規模,以及在現有集羣接入新業務時對集羣資源的評估。

關於索引分片的規劃將在後續章節中介紹。

第 1-2 課:Elasticsearch 索引設計

Elasticsearch 開箱即用,上手十分容易。安裝、啓動、創建索引、索引數據、查詢結果,整個過程,無需修改任何配置,無需瞭解 mapping,運作起來,一切都很容易。

這種容易是建立在 Elasticsearch 在幕後悄悄爲你設置了很多默認值,但正是這種容易、這種默認的設置可能會給以後帶來痛苦。

例如不但想對 field 做精確查詢,還想對同一字段進行全文檢索怎麼辦?shard 數不合理導致無法水平擴展怎麼辦?出現這些狀況,大部分情況下需要通過修改默認的 mapping,然後 reindex 你的所有數據。

這是一個很重的操作,需要很多的資源。索引設計是否合理,會影響以後集羣運行的效率和穩定性

1 分析業務

當我們決定引入 Elasticsearch 技術到業務中時,根據其本身的技術特點和應用的經驗,梳理出需要預先明確的需求,包括物理需求、性能需求。

在初期應用時,由於對這兩方面的需求比較模糊,導致後期性能和擴展性方面無法滿足業務需求,浪費了很多資源進行調整。

希望我總結的需求方面的經驗能給將要使用 Elasticsearch 的同學提供一些幫助,少走一些彎路。下面分別詳細描述。

1.1 物理需求

根據我們的經驗,在設計 Elasticsearch 索引之前,首先要合理地估算自己的物理需求,物理需求指數據本身的物理特性,包括如下幾方面。

  • 數據總量

業務所涉及的領域對象預期有多少條記錄,對 Elasticsearch 來說就是有多少 documents 需要索引到集羣中。

  • 單條數據大小

每條數據的各個屬性的物理大小是多少,比如 1k 還是 10k。

  • 長文本

明確數據集中是否有長文本,明確長文本是否需要檢索,是否可以啓用壓縮。Elasticsearch 建索引的過程是極其消耗 CPU 的,尤其對長文本更是如此。

明確了長文本的用途併合理地進行相關設置可以提高 CPU、磁盤、內存利用率。我們曾遇見過不合理的長文本處理方式導致的問題,此處在 mapping 設計時會專門討論

  • 物理總大小

根據上面估算的數據總量和單條數據大小,就可以估算出預期的存儲空間大小。

  • 數據增量方式

這裏主要明確數據是以何種方式納入 Elasticsearch 的管理,比如平穩增加、定期全量索引、週期性批量導入。針對不同的數據增量方式,結合 Elasticsearch 提供的靈活設置,可以最大化地提高系統的性能。

  • 數據生命週期

數據生命週期指進入到系統的數據保留週期,是永久保留、還是隨着時間推移進行老化處理?老化的週期是多久?既有數據是否會更新?更新率是多少?根據不同的生命週期,合理地組織索引,會達到更好的性能和資源利用率。

1.2 性能需求

使用任何一種技術,都要確保性能能夠滿足業務的需求,根據上面提到的業務場景,對於 Elasticssearch 來說,核心的兩個性能指標就是索引性能和查詢性能

索引性能需求

Elasticsearch 索引過程需要對待索引數據進行文本分析,之後建立倒排索引,是個十分消耗 CPU 資源的過程。

對於索引性能來說,我們認爲需要明確兩個指標,一個是吞吐量,即單位時間內索引的數據記錄數;另一個關鍵的指標是延時,即索引完的數據多久能夠被檢索到。

Elasticsearch 在索引過程中,數據是先寫入 buffer(數據內存緩衝區) 的,需要 refresh 操作後才能被檢索到,所以從數據被索引到能被檢索到之間有一個延遲時間,這個時間是可配置的,默認值是 1s。這兩個指標互相影響:減少延遲,會降低索引的吞吐量;反之會增加索引的吞吐量。

查詢性能需求

數據索引存儲後的最終目的是查詢,對於查詢性能需求。Elasticsearch 支持幾種類型的查詢,包括:

  • 1. 結構化查詢

結構查詢主要是回答 yes/no,結構化查詢不會對結果進行相關性排序。如 terms 查詢、bool 查詢、range 查詢等。

  • 2. 全文檢索

全文檢索查詢主要回答數據與查詢的相關程度。如 match 查詢、query_string 查詢。

  • 3. 聚合查詢

無論結構化查詢和全文檢索查詢,目的都是找到某些滿足條件的結果,聚合查詢則不然,主要是對滿足條件的查詢結果進行統計分析,例如平均年齡是多少、兩個 IP 之間的通信情況是什麼樣的。

對不同的查詢來說,底層的查詢過程和對資源的消耗是不同的,我們建議根據不同的查詢設定不同的性能需求

2 索引設計

此處索引設計指宏觀方面的索引組織方式,即怎樣把數據組織到不同的索引,需要以什麼粒度建立索引,不涉及如何設計索引的 mapping。(mapping 後文單獨講)

2.1 按照時間週期組織索引

如果查詢中有大量的關於時間範圍的查詢,分析下自己的查詢時間週期,儘量按照週期(小時、日、周、月)去組織索引,一般的日誌系統和監控系統都符合此場景。

按照日期組織索引,不但可以減少查詢時參與的 shard 數量,而且對於按照週期的數據老化備份刪除的處理也很方便,基本上相當於文件級的操作性能。

這裏有必要提一下 delete_by_query這種數據老化方式性能慢,而且執行後,底層並不一定會釋放磁盤空間,後期 merge 也會有很大的性能損耗,對正常業務影響巨大。

2.2 拆分索引

檢查查詢語句的 filter 情況,如果業務上有大量的查詢是基於一個字段 filter,比如 protocol,而該字段的值是有限的幾個值,比如 HTTP、DNS、TCP、UDP 等,最好把這個索引拆成多個索引。

這樣每次查詢語句中就可以去掉 filter 條件,只針對相對較小的索引,查詢性能會有很大提高。同時,如果需要查詢跨協議的數據,也可以在查詢中指定多個索引來實現。

2.3 使用 routing

如果查詢語句中有比較固定的 filter 字段,但是該字段的值又不是固定的,我們建議在創建索引時,啓用 routing 功能。這樣,數據就可以按照 filter 字段的值分佈到集羣中不同的 shard,使參與到查詢中的 shard 數減少很多,極大提高 CPU 的利用率。

2.4 給索引設置別名

我們強烈建議在任何業務中都使用別名,絕不在業務中直接引用具體索引

別名是什麼

索引別名就像一個快捷方式,可以指向一個或者多個索引,我個人更願意把別名理解成一個邏輯名稱。

別名的好處

  • 方便擴展

對於無法預估集羣規模的場景,在初期可以創建單個分片的索引 index-1,用別名 alias 指向該索引,隨着業務的發展,單個分片的性能無法滿足業務的需求,可以很容易地創建一個兩個分片的索引 index-2,在不停業務的情況下,用 alise 指向 index-2,擴展簡單至極。

  • 修改 mapping

業務中難免會出現需要修改索引 mapping 的情況,修改 mapping 後歷史數據只能進行 reindex 到不同名稱的索引,如果業務直接使用具體索引,則不得不在 reindex 完成後修改業務索引的配置,並重啓服務。業務端只使用別名,就可以在線無縫將 alias 切換到新的索引。

2.5 使用 Rollover index API 管理索引生命週期

對於像日誌等滾動生成索引的數據,業務經常以天爲單位創建和刪除索引。在早期的版本中,由業務層自己管理索引的生命週期。

在 Rollover index API 出現之後,我們可以更方便更準確地進行管理:索引的創建和刪除操作在 Elasticsearch 內部實現,業務層先定義好模板和別名,再定期調用一下 API 即可自動完成,索引的切分可以按時間、或者 DOC 數量來進行。

總結

正式接入業務數據之前進行合理的索引設計是一個必要的環節,如果偷懶圖方便用最簡單的方式進行業務數據接入,問題就會在後期暴露出來,那時再想解決就困難許多。

下一節我們開始介紹索引層面之下的分片設計

第 1-3 課:Elasticsearch 分片設計

Elasticsearch 的一個分片對應 Lucene 的一個索引,Elasticsearch 的核心就是將這些 Lucene 索引分佈式化,提供索引和檢索服務。可見,如何設計分片是至關重要的。

一個索引到底該設置幾個主分片呢?由於單個分片只能處於 Elasticsearch 集羣中的單個節點,分片太少,影響索引入庫的併發度,以及以後的橫向擴展性,如果分片過大會引發查詢、更新、遷移、恢復、平衡等性能問題

1 主分片數量確定

我們建議綜合考慮分片物理大小因素、查詢壓力因素、索引壓力因素,來設計分片數量。

1.1 物理大小因素

建議單個分片的物理大小不大於 50GB(30G~50G都是可以的,而一臺節點上的分配的合理索引總大小不超過5TB,也就是能存儲分片102~170個分片,集羣分片一般不超過10萬個),之所以這樣建議,基於如下幾個因素:

  • 更快的恢復速度

集羣故障後,更小的分片相對大分片來講,更容易使集羣恢復到 Green 狀態。

  • merge 過程中需要的資源更少

Lucene 的 segment merge 過程需要兩倍的磁盤空間,如果分片過大,勢必需要更大的臨時磁盤空間用於 merge,同時,分片過大 merge 過程持續時間更長,將對 IO 產生持續的壓力。

  • 集羣分片分佈更容易均衡

分片過大,Elasticsearch 內部的平衡機制需要更多的時間。

  • 提高 update 操作的性能

對 Elasticsearch 索引進行 update 操作,底層 Lucene 採用的是先查找,再刪除,最後 index 的過程。如果在分片比較大的索引上有比較多的 update 操作,將會對性能產生很大的影響。

  • 影響緩存

節點的物理內存是有限的,如果分片過大,節點不能緩存分片必要的數據,對一些數據的訪問將從物理磁盤加載,可想而知,對性能會產生多大的影響。

1.2 查詢壓力因素

單個 shard 位於一個節點,如果索引只有一個 shard,則只有一個節點執行查詢操作。如果有多個 shard 分部在不同的節點,多個節點可以並行執行,最後歸併。

但是過多的分片會增加歸併的執行時間,所以考慮這個因素,需要根據業務的數據特點,以貼近真實業務的查詢去測試,不斷加大分片數量,直到查詢性能開始降低。

1.3 索引壓力因素

單個 shard 只能位於一塊單個節點上,索引過程是 CPU 密集型操作,單個節點的入庫性能是有限的,所以需要把入庫的壓力分散到多個節點來滿足寫入性能。單純考慮索引性能,可以根據單個節點的索引性能和需要索引的總性能來估算分片數量。

2 副本數量

副本是主分片的拷貝,可以響應查詢請求、防止數據丟失、提高集羣可用性等,但是副本不是“免費”的,需要佔用與主分片一樣的資源,包括 CPU、內存、磁盤,副本數量的確定等涉及多方面的因素。

PUT fieldpower-2017/_settings

{

      "index.number_of_replicas":"1" //修改副本的數量,可以動態的修改

}

2.1 數據可靠性

明確自己的業務需要多高的可靠性和可用性。依據 Elasticsearch 的內部分片分佈規則,同一索引相同編號的分片不會處於同一個 node,多一份副本就多一份數據安全性保障。

2.2 索引性能

副本和主分片在索引過程中執行和主分片一樣的操作,如果副本過多,有多少副本就會有幾倍的 CPU 資源消耗在索引上,會拖累整個集羣的索引吞吐量,對於索引密集型的業務場景影響巨大。所以要在數據安全型和索引性能上做權衡處理來確定副本的數量。

2.3 查詢性能

副本可以減輕對主分片的查詢壓力,這裏可能說查詢次數更爲合理。節點加載副本以提供查詢服務和加載主分片消耗的內存資源是完全相同的,增加副本的數量勢必增加每個 node 所管理的分片數,因此會消耗更多的內存資源,Elasticsearch 的高速運行嚴重依賴於操作系統的 Cache。

如果節點本身內存不充足,副本數量的增加會導致節點對內存的需求的增加,從而降低 Lucene 索引文件的緩存效率,使 OS 產生大量的換頁,最終影響到查詢性能。當然,在資源充足的情況下,擴大副本數是肯定可以提高集羣整體的 QPS。

3 分片分佈

ELasticsearch 提供的關於 shard 平衡的兩個參數是 cluster.routing.allocation.balance.shard 

 cluster.routing.allocation.balance.index

第一個參數的意思是讓每個節點維護的分片總數儘量平衡,第二個參數的意思是讓每個索引的的分片儘量平均的分散到不同的節點。

如果集羣中有不同類型的索引,而且每個類型的索引的索引方式、物理大小不一致,很容易造成節點間磁盤佔用不均衡、不同節點間堆內存佔用差異大的問題,從而導致集羣不穩定。

所以我們建議儘量保證不同索引的 shard 大小盡量相近,以獲得實質意義上的均衡分片分佈

4 集羣分片總數控制

由於 Elasticsearch 的集羣管理方式還是中心化的,分片元信息的維護在選舉出來的 master 節點上,分片過多會增加查詢結果合併的時間,同時增加集羣管理的負擔。

根據我們的經驗,單個集羣的分片超過 10 萬時,集羣維護相關操作例如創建索引、刪除索引等就會出現緩慢的情況。所以在實踐中,儘量控制單個集羣的分片總數在 10 萬以內

5 【案例分析】大分片數據更新引發的 IO 100% 異常

分片大小對某些業務類型來講會產生致命的影響,這裏介紹一個我們遇到的一個案例,由於分片不合理導致了很嚴重的性能問題。

5.1 問題背景

有一個集羣由很多業務部門公用,該集羣爲 10 個節點,單節點 128G 內存,CPU 爲 40 線程,硬盤爲 4T*12 的配置,存儲使用總量在 20%。業務 A 反應他的 bulk 操作很慢,需要分鐘級才能完成。

經過與業務溝通後,瞭解到他們單次 bulk 1000 條數據,單條數據大小爲 1k,這種 bulk 並不大,因此速度慢肯定是不正常的現象。

5.2 問題分析

有了以上的背景信息,開始問題排查。該索引大小爲 1.2T,5 個主分片(一個分片的大小爲200多G)。登錄到集羣服務器後臺,在業務運行過程中,通過 top 查看,CPU 利用率在 30%,在正常範圍內。但是 iowait 一直持續在 20% 左右,問題初步原因應該在 IO 上。

隨即通過 iostat 觀察磁盤的狀況,發現有一塊盤的 IO 持續 100%。登錄其他幾個服務器,發現每個服務器都有一塊盤的 IO 處於 100% 狀態。

之後通過分析該索引的分佈情況,發現 IO 利用率高的磁盤都有這個索引對應的 shard,難道是這個索引導致的?

因爲這些磁盤上還有其他的索引,我們現在也只是推測。打開 iotop,發現有些線程的 io 持續有大量讀取。

將線程 tid 轉換成 16 進制,通過 jtack 查詢對應的線程,發現是 Lucene 的 refresh 操作觸發的。但是隻通過線程堆棧扔無法確認是由該索引的 bulk 操作引起。之後通過跟蹤系統調用:

strace -t -T -y -p $tid

發現每秒有數百次的 pread 系統調用,而且讀取的目錄全部爲該索引所在目錄,使得磁盤 IO 一直處於滿負荷狀態。bulk 是寫操作,不會引起大量的讀。

有一種情況是,如果待索引的數據的 id 是應用程序自己生成的,底層 Lucene 在索引時,要去查找對應的文檔是否存在。

跟業務人員溝通後,id 確實是由應用程序自己控制。這樣問題就清晰了,在這個索引的單個分片上 bulk 200 條數據,底層 Lucene 要進行 200 次查找以確定對應的數據是否存在。

5.3 問題解決

通過以上分析,在目前的情況下,緊急縮小單個分片的物理大小,增加該索引的 shard 數到 200(將每個分片的大小變爲50G左右),使數據均衡到更多的磁盤,對舊索引的數據進行 reindex。遷移完成後,同樣的 bulk 操作在 1s 左右執行完成。

總結

沒有不好的技術,只有不合理的使用。通過減少單個分片的物理大小,將數據分散到更多的 shard,從而將 IO 壓力分散到了集羣內的所有磁盤上,暫時可以解決目前 bulk 慢的問題。

但是考慮到 Lucene 的技術特點,並不適用於有大量更新的業務場景,還是需要重構業務,以追加的方式寫入新數據,通過數據的版本或者時間戳來查詢最新的數據,並定期對數據進行整理,以達到最優的性能。

第 1-4 課:如何設計映射

1 映射 (mapping) 基礎

mapping 定義了文檔的各個字段如何被索引以及如何存儲。我們可以把 Elasticsearch 的 mapping 看做 RDBMS 的 schema。

雖然 Elasticsearch 可以根據索引的數據動態的生成 mapping,我們仍然建議在創建索引時明確的定義自己的 mapping,不合理的 mapping 會引發索引和查詢性能降低,磁盤佔用空間變大。錯誤的 mapping 會導致與預期不符的查詢結果。

2 選擇合適的數據類型

2.1 分清 text 和 keyword

text 類型

  • 用於存儲全文搜索數據,例如:郵箱內容、地址、代碼塊、博客文章內容等。
  • 默認結合 standard analyzer(標準解析器)對文本進行分詞、倒排索引。
  • 默認結合標準分析器進行詞命中、詞頻相關度打分。

keyword 類型

  • 用於存儲需要精確匹配的數據。例如手機號碼、主機名、狀態碼、郵政編碼、標籤、年齡、性別等數據。
  • 用於篩選數據(如 select * from x where status=‘open’)、排序、聚合(統計)。
  • 直接將完整的文本保存到倒排索引中,並不會對字段的數據進行分詞。

如果 keyword 能滿足需求,儘量使用 keyword 類型。

3 mapping 和 indexing

mapping 定義得是否合理,將直接影響 indexing 性能,也會影響磁盤空間的使用。

3.1 mapping 無法修改

Ealsticsearch 的 mapping 一旦創建,只能增加字段,不能修改已有字段的類型

3.2 幾個重要的 meta field

1. _all

雖然在 Elasticsearch 6.x 中,_all 已經是 deprecated,但是考慮到 6.x 之前的版本創建的索引 _all 字段是默認啓用的,這裏有必要詳細說說明下該字段的含義。

_all 字段是一個 text 字段,它將你索引的單個文檔的所有字段連接成一個超級串,之後進行分詞、索引。如果你不指定字段,query_string 查詢和 simple_query_string 查詢默認查詢 _all 字段

_all 字段不是“免費”的,索引過程會佔用額外的 CPU 資源,根據測試,在我們的數據集上,禁用 _all 字段後,索引性能可以提高 30%+!!,所以,如果您在沒有明確 _all 含義的情況下,歷史索引沒有禁用 _all 字段,建議您重新審視該字段,看是否需要禁用,並 reindex,以獲取更高的索引性能以及佔用更少的磁盤空間。如果 _all 提供的功能對於您的業務必不可少,考慮使用 copy_to 參數代替 _all 字段

2. _source

_source 字段存儲了你的原始 JSON 文檔 _source 並不會被索引,也就是說你並不能直接查詢 _source 字段,它主要用於查詢結果展示。所以啓用該字段並不會對索引性能造成很大的影響。

除了用於查詢結果展示,Ealsticsearch 的很多功能諸如 Reindex API、高亮、update_by_query 都依賴該字段。

所以實踐中,我們不建議禁用該字段。如果磁盤空間是瓶頸,我們建議優先考慮禁用 _all 字段,_all 禁用達不到預期後,考慮提高索引的壓縮級別,併合理使用 _source 字段的 includes 和 excludes 屬性。

3.3 幾個重要的 mapping 參數

1. index 和 store

首先明確一下,index 屬性關乎該字段是否可以用於查詢,而 store 屬性關乎該字段是否可以在查詢結果中單獨展示出來通過跟一些業務開發人員接觸,發現經常有對這兩個屬性不明確的情況。

index 後的字段可以用於 search默認你定義到 mapping 裏的字段該屬性的值都爲 “true”indexing 過程耗費 cpu 資源,不需要查詢的字段請將該屬性值設爲 “false”在業務部門使用過程中,常見的一個問題是一半以上的字段不會用於查詢,但是並沒有明確設置該屬性,導致索引性能下降。

由於 Elasticsearch 默認提供了 _source 字段,所以,大部分情況下,你無須關心 store 屬性,默認的 “false” 能滿足需求

enabled

對於不需要查詢,也不需要單獨 fetch 的字段,enable 可以簡化 mapping 的定義。

例如:

"session_data": { 
    "enabled": false
}

等同於

"session_data": { 
    "type": "text",
    "index": false,
    "store": false
}

不要使用 fielddata

自從 Elasticsearch 加入 doc_value 特性後,fielddata 已經沒有使用的必要了。

有兩點原因,首先,對於非 text 字段,doc_value 可以實現同樣的功能,但是佔用的內存資源更少。

其次,對於 text 字段啓用 fielddata,由於 text 字段會被分詞,即使啓用了 fielddata,在其上進行聚合、排序通常是沒有意義的,得到的結果也並不是期望的結果。如果確實需要對 text 字段進行聚合,通常使用 fields 會得到你想要的結果。

PUT my_index
{
    "mappings": {
    "my_type": {
            "properties": {
                "city": {
                    "type": "text",
                    "fields": {
                        "raw": { 
                            "type""keyword"
                    }
                    }
                }
            }
        }
    }
}

之後通過 city 字段進行全文查詢,通過 city.raw 字段進行聚合和排序。

2. doc_values

Elasticsearch 默認對所有支持 doc_values 的類型啓用了該功能,對於不需要聚合、排序的字段,建議禁用以節省磁盤空間。

PUT my_index
{
  "mappings": {
    "_doc": {
      "properties": {
        "session_id": { 
          "type": "keyword",
          "doc_values": false
        }
      }
    }
  }
}

3.4 null_value

null 不能索引,也不能用於檢索。null_value 可以讓你明確的指定 null 用什麼值代替,之後可以用該替代的值進行檢索。

4 mapping 和 searching

4.1 預熱 global ordinals

global ordinals(預熱) 主要用於加快 keyword 類型的 terms 聚合,由於 global ordinals 佔用內存空間比較大,Elasticsearch 並不知道要對哪些字段進行聚合,所以默認情況下,Elasticsearch 不會加載所有字段的 global ordinals。可以通過修改 mapping 進行預加載。如下所示:

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

4.2 將數字標識映射成 keyword 類型

Elasticsearch 在索引過程中,numbers 類型對 range 查詢進行了優化,keyword 類型對 terms 查詢進行了優化,如果字段字面上看是 numbers 類型,但是並不會用於 range 查詢,只用於 terms 查詢,將字段映射成 keyword 類型。例如 isbn、郵政編碼、數據庫主鍵等。

總結

mapping 定義的是否合理,關係到索引性能、磁盤使用效率、查詢性能,本章着重討論了影響這些的關鍵 mapping 參數和 Elasticsearch 的 meta fileds,深入理解本章的內容後,mapping 應該不會成爲系統瓶頸的主因。

第一部分的內容到此結束,這部分內容的目的介紹如何“合理地”使用 Elasticsearch,只要使用方式合理,Elasticsearch 本身問題並不多。很多時候遇到的問題都是初期的規劃,使用不當造成的。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章