天天看點

《深入了解ElasticSearch》——2.7 使用過濾器優化查詢

本節書摘來自華章計算機《深入了解elasticsearch》一書中的第2章,第2.7節,作者:[美] 拉斐爾·酷奇(rafa ku) 馬雷克·羅戈任斯基(marek rogoziński)更多章節内容可以通路雲栖社群“華章計算機”公衆号檢視。

elasticsearch允許使用者建立他們熟知的各種不同的查詢類型。當需要決定哪些文檔與查詢比對并應該傳回時,僅有查詢本身是不夠的。elasticsearch查詢dsl提供的大多數查詢類型都有它們的相似物,并且能将相似物包裝(wrapping)成以下這些查詢類型使用:

constant_score

filtered

custom_filters_score

那麼問題來了:“什麼時候使用過濾器,什麼時候僅需使用查詢類型?”。現在,讓我們揭曉它的答案。

2.7.1 過濾器與緩存

首先,過濾器是很好的緩存候選方案,正如你所預料的那樣,elasticsearch提供了一種特殊的緩存,即過濾器緩存(filter cache),用來存儲過濾器的結果。而且,被緩存的過濾器并不需要消耗過多的記憶體(因為它們隻存儲了哪些文檔能與過濾器相比對的相關資訊),而且可供後續所有與之相關的查詢重複使用,進而極大地提高了查詢性能。想象一下,你執行了下面這個簡單的查詢:

《深入了解ElasticSearch》——2.7 使用過濾器優化查詢

該查詢傳回了在name字段包含joe以及在year字段包含1981的所有文檔。盡管這個查詢很簡單,但是它能查詢出滿足指定姓名和出生年代條件的足球運動員。

在目前這種情形下,隻有同時滿足這兩個條件的查詢才會被緩存起來。是以,如果我們想搜尋名字相同而出生年代不同的足球運動員,那麼之前的查詢就不再适用了。是以現在讓我們想想如何來優化這個查詢:人名有太多種可能,是以它不是完美的緩存候選對象,而出生年代則是(year字段中并沒有很多不同的值,難道不是嗎?)。于是,我們使用另外一種查詢方法,該查詢組合了查詢類型與過濾器:

《深入了解ElasticSearch》——2.7 使用過濾器優化查詢

我們已經使用了filtered查詢來同時包含查詢和過濾器元素。第一次執行該查詢以後,過濾器就會被elasticsearch緩存起來,如果後續的其他查詢也要使用該過濾器,則它将會被重複利用,進而避免elasticsearch重複加載相關資料。

并不是所有過濾都預設被緩存

緩存很有用,但事實上elasticsearch并不是預設緩存所有過濾器。這是因為在elastic-search中某些過濾器使用了字段資料緩存,這是一種特殊的緩存,它可以在基于字段資料的排序時使用,也能在計算切面結果時使用。以下過濾器預設不緩存:

numeric_range

script

geo_bbox

geo_distance

geo_distance_range

geo_polygon

geo_shape

and

or

not

上面提到的過濾器中,最後三個本身并不使用字段緩存,但由于它們操作其他過濾器,因而它們不緩存。而且,它們操作的過濾器在必要的時候已經被緩存起來了。

改變elasticsearch的緩存行為

如果有需要,elasticsearch允許使用者通過設定_cache和_cache_key屬性來開啟或關閉過濾器的緩存機制。回到先前的例子,進行相關配置進而緩存詞項過濾器結果,緩存的key為year_1981_cache:

《深入了解ElasticSearch》——2.7 使用過濾器優化查詢

現在,關閉該查詢的詞項過濾器緩存:

《深入了解ElasticSearch》——2.7 使用過濾器優化查詢

https://yqfile.alicdn.com/9dbe35dbcc501319aed2d0cbc7aef0ca2b89e794.png" >

為什麼要為cache的key命名

也許讀者會有疑問,為什麼非要手動設定_cache_key屬性,難道elasticsearch不會自動處理它嗎?當然,必要時可以讓elasticsearch自動處理,但有時候需要更精細地控制緩存行為,就需要手動處理了。例如,已知某些查詢很少執行,但又想周期性地清除之前查詢的緩存。如果不設定_cache_key,那麼将不得不強制清理全部的過濾器緩存;而如果設定了_cache_key,隻需執行下面的指令:

《深入了解ElasticSearch》——2.7 使用過濾器優化查詢

何時改變elasticsearch過濾器緩存的行為

有些時候,使用者比elasticsearch更清楚自己想要什麼。例如,你也許想在查詢時使用geo_distance過濾器使之隻傳回距離較近的文檔,同時確定該過濾器能與其他多個具有相同腳本過濾器(script filter)參數的查詢一起使用,進而将多次使用同一個腳本。在這種場景中,就值得開啟這些過濾器的緩存。每時每刻使用者都應該問自己:“我會多次使用該過濾器還是隻使用一次?”由于将資料存放在緩存中會消耗資源,因而在不需要時應及時清理資料。

2.7.2 詞項查找過濾器

緩存和各種标準查詢并不是elasticsearch的全部家當。随着elasticsearch 0.90的釋出,又一個精巧的過濾器可供我們使用了,它用于給一個具體的查詢傳遞從elasticsearch取回的多個詞項(與sql的in操作符類似)。

現在看一個簡單的例子。假設有一個線上書店,并且存儲了書店客戶所購買書籍的相關資訊。索引book看起來非常簡單(索引映射資訊存儲在books.js檔案中):

《深入了解ElasticSearch》——2.7 使用過濾器優化查詢

前面的代碼并沒有什麼稀奇之處,它僅僅列出了書籍的id和标題而已。

現在,我們再看看clients.json檔案,這裡存儲了用于描述索引clients結構的映射資訊:

《深入了解ElasticSearch》——2.7 使用過濾器優化查詢

我們有了客戶id、姓名以及客戶所購買書籍的id清單。除此之外,還索引了一些範例資料:

《深入了解ElasticSearch》——2.7 使用過濾器優化查詢

現在,考慮一下如何擷取某個特定使用者購買的所有書籍,例如,id為1的使用者。當然,我們可以執行下面這個get請求:curl -xget 'localhost:9200/clients/client/1',來擷取存有該客戶資訊的文檔,然後利用文檔中的books字段資訊來執行下面這個查詢:

《深入了解ElasticSearch》——2.7 使用過濾器優化查詢

https://yqfile.alicdn.com/0f941fb897a3b424fd601883bddd56fae1105c70.png" >

幸運的是,我們有更簡捷的做法,elasticsearch 0.90新提供了一個查找過濾器,它允許使用者将前面的兩個查詢合并為一個過濾查詢(filtered query),如下面代碼所示:

《深入了解ElasticSearch》——2.7 使用過濾器優化查詢

請注意參數_cache_key的值。正如你所見,它被設定為terms_lookup_client_1_books,并包含有使用者的id。但值得警惕的是,當在多個不同的查詢中重用該_cache_key的值時,你可能會得到錯誤的或者是預料之外的檢索結果。這是因為elasticsearch會在某個特定的key下緩存某個查詢的結果,并在其他查詢執行時重用這些結果。

現在,來檢視一下前面那個查詢的響應結果:

《深入了解ElasticSearch》——2.7 使用過濾器優化查詢

https://yqfile.alicdn.com/c13f9455411587d96e2396c3205940a2061439c1.png" >

這正是我們想要的。看起來有點不可思議吧?

它是如何工作的

檢視一下我們發送至elasticsearch的查詢。它隻是一個簡單的帶過濾器的查詢,即在一個比對所有文檔的查詢上外加了一個terms過濾器。但是在這裡,詞項過濾器的使用方式略有不同,我們并沒有顯式确定具體的詞項,而是讓elasticsearch去索引中加載它們。

正如你所見,我們感興趣的是查詢的id對象,這是因為它包含多個屬性,且過濾器的執行也依賴于它。具體包含的屬性有:index、type、id和path。index屬性指明了所要加載詞項所在的索引(本範例中是clients索引)。type屬性告訴elasticsearch我們對哪些文檔類型感興趣(本範例中是client)。id屬性訓示了需要加載對象所在文檔的id。path字段用來告訴elasticsearch從哪個字段加載詞項,在我們的範例中,指的是clients索引的books字段。

總而言之,elasticsearch所做的就是從clients索引中id為1,類型為client的文檔的books字段中加載詞項,并将這些詞項用于詞項過濾器中,進而篩選那些books索引(因為我們在該索引中執行查詢)中id字段包括這些詞項的文檔(請注意詞項過濾器的名字:id)。

請記住,為了使詞項查找功能生效,需要先存儲_source字段。

性能考慮

前面的查詢執行在elasticsearch内部已經通過緩存機制優化了,即相關詞項已經被加載到過濾器緩存中且由查詢指定的key下了。除此之外,一旦将這些詞項(即那些書籍的id)加載到緩存,後續的查詢就不會再重複加載,這意味着elasticsearch能提高此類查詢的性能。

如果在詞項查找過程中使用到的資料量不大,建議索引(如範例中的索引clients)隻建立一個分片,并且在所有出現了books索引的節點上為該分片儲存一個副本。之是以這麼建議是因為elasticsearch優先在本地執行詞項查詢以避免不必要的網絡開銷和延遲,進而能提高查詢的性能。

從内部對象中加載詞項

如果客戶資訊的books字段類型由數值類型數組變為内部對象數組,那麼查詢也應該做相應的修改,此時應修改查詢的id屬性,使之包含對象嵌套的相關資訊。例如,将"id":"books"修改為"id":"books.book"。

詞項查詢過濾器緩存設定

前面提到過,為了提供詞項查找功能,elasticsearch引進了一種新的緩存類型,它使用了一種快速的lru(最近最少使用算法)緩存來處理詞項緩存。

為了配置這種緩存,使用者可以在elasticsearch.yml檔案中配置下面這些屬性:

indices.cache.filter.terms.size:預設設定elasticsearch詞項查找緩存的最大記憶體使用量為10mb。對于大多數案例來說,這個預設值夠用了,但如果要加載更多的資料至緩存中,那麼可以調大該參數值。

indices.cache.filter.terms.expire_after_access:該屬性配置了自上次查詢以來的逾時時長。預設不逾時。

indices.cache.filter.terms.expire_after_write:該屬性配置了資料加載至緩存以後多長時間将逾時。預設不逾時。