Elasticsearch 中有多種查詢緩存,當一個查詢請求執行後,他可能會被緩存下來,但是哪些查詢會被緩存,哪些不會緩存,緩存了什麼内容,什麼時候失效,手冊中并沒有很系統的闡述,并且文檔中也存在一些疑點,導緻整個查詢緩存體系容易讓人迷惑。現在,我們來把他搞清楚。
Shard Request Cache
Shard Request Cache 簡稱 Request Cache,他是分片級别的查詢緩存,每個分片有自己的緩存。該緩存采用 LRU 機制,緩存的 key 是整個用戶端請求,緩存内容為單個分片的查詢結果。如果一個用戶端請求被資料節點緩存了,下次查詢的時候可以直接從緩存擷取,無需對分片進行查詢。緩存的實作在 IndicesRequestCache 類中,緩存的 key 是一個複合結構,主要包括shard,indexreader,以及用戶端請求三個資訊:
(cacheEntity, reader.getReaderCacheHelper().getKey(), cacheKey)
cacheEntity, 主要是 shard資訊,代表該緩存是哪個 shard上的查詢結果。readerCacheKey, 主要用于區分不同的 IndexReader。cacheKey, 主要是整個用戶端請求的請求體(source)和請求參數(preference、indexRoutings、requestCache等)。由于用戶端請求資訊直接序列化為二進制作為緩存 key 的一部分,是以用戶端請求的 json 順序,聚合名稱等變化都會導緻 cache 無法命中。
緩存的 value比較簡單,就是将查詢結果序列化之後的二進制資料。
Request Cache 的主要作用是對聚合的緩存,聚合過程是實時計算,通常會消耗很多資源,緩存對聚合來說意義重大。
緩存政策
并非所有的分片級查詢都會被緩存。
緩存政策指哪些類型的查詢需要緩存,哪些類型的緩存無需緩存。IndicesService#canCache 方法中定義了某個請求是否可以被緩存,簡單的可以了解成隻有用戶端查詢請求中 size=0的情況下才會被緩存,其他不被緩存的條件還包括 scroll、設定了 profile屬性,查詢類型不是 QUERY_THEN_FETCH,以及設定了 requestCache=false等。另外一些存在不确定性的查詢例如:範圍查詢帶有now,由于它是毫秒級别的,緩存下來沒有意義,類似的還有在腳本查詢中使用了 Math.random() 等函數的查詢也不會進行緩存。
查詢結果中被緩存的内容主要包括:aggregations(聚合結果)、hits.total、以及 suggestions等。
由于 Request Cache 是分片級别的緩存,當有新的 segment 寫入到分片後,緩存會失效,因為之前的緩存結果已經無法代表整個分片的查詢結果。是以分片每次 refresh之後,緩存會被清除。
緩存設定
ES 預設情況下最多使用堆記憶體的 1% 用作 Request Cache,這是一個節點級别的配置:
.requests
Request Cache預設是開啟的。你可以為某個索引動态啟用或禁用緩存:
或者在查詢請求級别通過 request_cache 參數來控制本次查詢是否使用 cache,他會覆寫索引級别的設定。
Node Query Cache (Filter Cache)
Shard Request Cache 緩存的是整個查詢語句在某個分片上的查詢結果,而Node Query Cache 緩存的是某個 filter 子查詢語句,在一個 segment 上的查詢結果。如果一個 segment 緩存了某個子查詢的結果,下次可以直接從緩存擷取,無需對 segment 進行查詢。Request Cache 隻對 filter 查詢進行緩存,是以又稱為 Filter Cache。
filter 查詢與普通查詢的主要差別就是不會計算評分。
Query Cache是 Lucene 層面實作的,封裝在 LRUQueryCache 類中,預設開啟。ES 層面會進行一些政策控制和資訊統計。每個 segment 有自己的緩存,緩存的 key 為 filter 子查詢(query clause ),緩存内容為查詢結果,這些查詢結果就是比對到的 document numbers,儲存在位圖 FixedBitSet中。
緩存的建構過程是,對 segment 執行子查詢的結果,先擷取查詢結果中最大的 document number: maxDoc
document number是 lucene 為每個 doc 配置設定的數值編号,fetch 的時候也是根據這個編号擷取文檔内容
然後建立一個大小為 maxDoc的位圖:FixedBitSet,然後周遊查詢命中的 doc,将FixedBitSet中對應的bit 設定為1。
例如查詢結果為[1,2,5],則FixedBitSet中的結果為[1,1,0,0,1],當整個查詢有多個filter子查詢時,交并集直接對位圖做位運算即可。
下面用一個例子來說明 Query Cache結構。如下查詢語句包含兩個子查詢,分别是對 date 和 age 字段的 range 查詢,Lucene 在查詢過程中周遊每個 segment,檢查其各自的 LRUQueryCache 能否命中,在本例中,segment 2命中了對 age 字段的緩存,segment 8 命中了對 age 和 date 兩個字段的緩存,将會直接傳回結果。由于 segment 2 沒有命中 date 字段緩存,将會執行查詢過程。
uniqueQueries 的作用是記錄了分片的所有 segment 中已經被緩存下來的子查詢,并基于 LinkedHashMap 實作 LRU 淘汰機制。
緩存政策
并非所有的 filter 查詢都會被緩存下來。
QueryCache政策決定某個查詢要不要緩存。ES 使用 UsageTrackingQueryCachingPolicy作為預設的緩存政策,在這個政策中,判斷某個查詢要不要緩存,主要關注兩點:
- 某些類型的查詢,永遠不會緩存,目前 shouldNeverCache 方法中定義了以下類型:TermQuery、MatchAllDocsQuery、MatchNoDocsQuery、以及子查詢為空的BooleanQuery、DisjunctionMaxQuery。
- 某條 query 的 通路頻率大于等于特定門檻值之後,該 query結果才會被緩存。對于通路頻率,主要分為2類,一類是通路2次就會被緩存,包括:MultiTermQuery、MultiTermQueryConstantScoreWrapper、TermInSetQuery、PointQuery 在 isCostly方法中定義。其餘類型的查詢通路5次會被緩存。
最近使用次數如何統計的?lucene 裡維護一個大小為256的環形緩沖,最近使用過的 query 會做一下 hash 儲存到這個緩沖裡,當緩沖滿的時候直接覆寫最後一個,被通路一次也不會調整他在緩沖裡的順序。是以“最近通路頻率”可以了解成:在最近的256個曆史記錄裡,query 被通路的次數。
在一些場景中,用戶端請求經常會帶有多個 filter 子查詢,而最終結果是同時比對所有 filter 子查詢的文檔,假設用戶端請求中有兩個子查詢,第一個子查詢比對到了10個文檔,第二個子查詢比對到了1億個文檔,最終取交集的時候Lucene 會從結果數量最少的集合(稱為 leader)開始周遊,隻需要10次比對完成交集的過程,而為第二個子查詢的1億個結果建立緩存卻花費了大量的時間,增大了查詢延遲。如果這個巨大的緩存在後續被通路的次數較少,則緩存帶來的優點得不償失。是以在對一個子查詢進行緩存之前,先檢查 leader 命中的文檔數量,如果子查詢比 leader 的結果集大于某個門檻值,則認為緩存成本過高,不對該子查詢進行緩存。目前的版本中,門檻值是直接對子查詢比對的文檔數量除以 250:
cost 為目前子查詢比對到的文檔數量,skipCacheFactor為固定值 250,在開啟了 indices.queries.cache.all_segments 的情況下會被設定為1。leadCost為 leader子查詢比對到的文檔數量。
所有的子查詢會在每個 segment 上都執行,是以上述計算緩存成本的過程會在每個 segment 上進行。但是這種成本計算方式不會考慮被跳過緩存的子查詢未來被通路的頻率。
攜程大神 wood 叔曾經在社群提到過使用 range 查詢時傳入的數值範圍精度問題,如果傳入的是秒級的時間機關,那麼子查詢被緩存後隻能在1秒内被命中,因為範圍值變化對緩存來說是一個新的 Query。特别是被緩存的結果集巨大時,建構緩存花費很多時間,而在之後又很快不再通路。是以在 range 查詢時指定粗一些的粒度有利于緩存命中,例如時間範圍查詢中使用 now/h,使用小時級别的機關,可以讓緩存在1小時内都可能被通路到。
緩存設定
預設情況下節點的 Query cache最多緩存 10000個子查詢的結果(LRU 的大小),或者最多使用堆記憶體的10%,可以通過配置來調整:
#預設 10000
每個分片有一個 LRUQueryCache 對象,在分片的所有 segment 上已經被緩存的子查詢清單記錄在 uniqueQueries中,10000個子查詢的門檻值就是對 uniqueQueries 大小的限制。例如如果一個子查詢命中了2個 segment,LRU 緩存裡會把兩個 segment 的位圖都緩存起來,但是 cache count 隻算1個。
由于緩存是為每個 segment 建立的,當 segment 合并的時候,被删除的 segment 其關聯 cache 會失效。其次,對于體積較小的 segment 不會建立Query cache,因為他們很快會被合并。是以一個 segment的 doc 數量需要大于10000,并且占整個分片的3%以上才會走 cache 政策。
Query Cache 支援索引級設定來啟用或禁用緩存,但是不支援請求級别的設定。通過 index.queries.cache.enabled 配置來設定某個索引是否開啟緩存,該配置不支援動态調整,要更改索引配置,需要先關閉索引。
Query Cache 和 Request Cache 一個是 Lucene 層面的實作,一個是 ES 層面的實作,其名稱容易讓人迷惑。在緩存機制上都使用 LRU,Request Cache 通路1次會就考慮緩存,Query Cache會根據不同的查詢計算其通路頻率,達到一定頻率才會考慮緩存。Request Cache的主要用途是對聚合結果的緩存,Query Cache的主要用途是對數值類型的 filter 子查詢。我們整理一下兩者主要差別,如下表:
緩存類型 | 查詢對象 | 失效 | 通路頻率 | 緩存 Key | 緩存 Value |
---|---|---|---|---|---|
Query Cache | 分段 | merge | 與頻率有關 | filter 子查詢 | FixedBitSet |
Request Cache | 分片 | refresh | 與頻率無關 | 整個用戶端請求 | 查詢結果序列化 |
其他緩存
- fielddata 對于 text 類型進行聚合等擷取字段值的行為時才會用到 fielddata,他會占用大量記憶體。fielddata預設關閉,因為在 text 類型上執行聚合一般是沒有意義的,是以這裡不深入讨論。
- pagecache 查詢過程中讀取 Lucene 檔案時會被作業系統 pagecache 緩存,pagecache使用類似 LRU 的政策對資料進行淘汰,但是由于 pagecache 對所有檔案一視同仁,并且難以控制,此處也不做深入讨論。
手工清除緩存
對于 ES 和 Lucene 層面的三種緩存,我們可以 REST API手工清除他們,下面的指令會清除指定索引或全部索引的緩存:
POST //_cache/clear #清理指定索引的 cache,支援多個
POST /_cache/clear #清理整個叢集的 cache
上面的指令會全部清除三種緩存。也可以隻清理特定緩存:
POST //_cache/clear?query=true #隻清理 query cache
POST //_cache/clear?request=true #隻清理 request cache
POST //_cache/clear?fielddata=true #隻清理 fields cache
同時可以通過 fields 參數指定清理部分字段的緩存:
POST /my-index/_cache/
不建議在生産環境中進行手動清除 cache,會對查詢性能有較大的影響。手工清除緩存一般隻用于測試和驗證的目的。
監控緩存
對于緩存的監控名額我們通常需要關心緩存占用的空間大小,以及命中率等資訊,一般需要在兩個層面觀測緩存使用情況,一個是節點級别,一個是索引級别。
節點級别
下面的指令可以比較友善的觀測每個節點上緩存占用的空間大小以及 request cache的命中率:
name,queryCacheMemory,fielddataMemory,requestCacheMemory,requestCacheHitCount,requestCacheMissCount
指令輸出如下,緩存命中率需要根據 HitCount和 MissCount 自己計算:
query cache的緩存命中可以通過 Nodes stats API 來檢視:
該指令将對每個節點都輸出以下詳細資訊。
關于 Nodes stats API 和 cat Nodes API 的更多參數請參考官網手冊。
索引級别
索引級别的資訊可以通過 Index stats API來檢視,對于緩存方面的名額可以使用如下指令:
其輸出資訊與 Nodes stats API 類似,不再贅述。
本文基于 Elasticsearch 7.7版本。
最近有很多同學問我 Elasticsearch 應該怎麼學,大部分都是新手小白,或者在使用過程中遇到了問題,想要系統的學習,深入了解、高效使用。Elasticsearch 知識體系龐大而複雜
這張圖出自阮一鳴,他在極客時間的視訊課非常系統的講解了整個知識體系,廣受好評,阮一鳴使用的最新的 Elasticsearch 7.x,内容涵蓋開發,到運維,甚至覆寫了 Elastic認證的全部考點,是非常好的教材,推薦給大家
(掃碼或長按關注本公衆号)