Elasticsearch
前言
最近工作中用到了Elasticsearch
,但是遇到幾個挺坑的點,還是記錄下。
深度分頁的問題
es中的普通的查詢from+size
,存在查詢數量的10000條限制。
index.max_result_window
The maximum value of from + size for searches to this index. Defaults to 10000. Search requests take heap memory and time proportional to from + size and this limits that memory. See Scroll or Search After for a more efficient alternative to raising this.
es為了減少內存的使用,限制了內存中索引數據的加載,默認10000
。也就是
from 10000 size 1
這樣的查詢就是不行的,將會報錯
Result window is too large, from + size must be less than or equal to:[10000] but was [10500]. See the scroll api for a more efficient way to requestlarge data sets. This limit can be set by changing the[index.max_result_window] index level parameter
如何解決
修改默認值
通過設置index 的設置參數max_result_window的值,來改變查詢條數的限制。
curl -XPUT http://127.0.0.1:9200/book/_settings -d '{ "index" : { "max_result_window" : 200000000}}'
使用search_after方法
例子可參考官方https://www.elastic.co/guide/en/elasticsearch/reference/6.8/search-request-search-after.html
如何使用呢,就是設置一個全局唯一的字段,然后在查看的時候加上這個字段的排序。這樣第二次查詢,search_after第一次查詢最后一條的對應的唯一值的值。有點繞哈,看例子
GET twitter/_search
{
"size": 10,
"query": {
"match" : {
"title" : "elasticsearch"
}
},
"sort": [
{"accessId": "asc"}
]
}
比如我的accessId
是全局唯一,並且自增的,第二次查詢search_after
最新的accessId
就好了
GET twitter/_search
{
"size": 10,
"query": {
"match" : {
"title" : "elasticsearch"
}
},
"search_after": [10],
"sort": [
{"accessId": "asc"}
]
}
后面的查詢依次類推
scroll 滾動搜索
scroll
查詢可以用來對Elasticsearch
有效地執行大批量的文檔查詢,而又不用付出深度分頁那種代價。
游標查詢允許我們 先做查詢初始化,然后再批量地拉取結果。 這有點兒像傳統數據庫中的cursor
。
游標查詢會取某個時間點的快照數據。查詢初始化之后索引上的任何變化會被它忽略。它通過保存舊的數據文件來實現這個特性,結果就像保留初始化時的索引 視圖 一樣。
深度分頁的代價根源是結果集全局排序,如果去掉全局排序的特性的話查詢結果的成本就會很低。游標查詢用字段_doc
來排序。 這個指令讓 Elasticsearch
僅僅從還有結果的分片返回下一批結果。
啟用游標查詢可以通過在查詢的時候設置參數 scroll
的值為我們期望的游標查詢的過期時間。游標查詢的過期時間會在每次做查詢的時候刷新,所以這個時間只需要足夠處理當前批的結果就可以了,而不是處理查詢結果的所有文檔的所需時間。 這個過期時間的參數很重要,因為保持這個游標查詢窗口需要消耗資源,所以我們期望如果不再需要維護這種資源就該早點兒釋放掉。設置這個超時能夠讓Elasticsearch
在稍后空閑的時候自動釋放這部分資源。
GET /old_index/_search?scroll=1m // 設置查詢窗口一分鍾
{
"query": { "match_all": {}},
"sort" : ["_doc"], // 使用_doc字段排序
"size": 1000
}
這個查詢的返回結果包括一個字段_scroll_id
,它是一個base64編碼的長字符串 。現在我們能傳遞字段_scroll_id
到_search/scroll
查詢接口獲取下一批結果:
GET /_search/scroll
{
"scroll": "1m", // 時間
"scroll_id" : "cXVlcnlUaGVuRmV0Y2g7NTsxMDk5NDpkUmpiR2FjOFNhNnlCM1ZDMWpWYnRROzEwOTk1OmRSamJHYWM4U2E2eUIzVkMxalZidFE7MTA5OTM6ZFJqYkdhYzhTYTZ5QjNWQzFqVmJ0UTsxMTE5MDpBVUtwN2lxc1FLZV8yRGVjWlI2QUVBOzEwOTk2OmRSamJHYWM4U2E2eUIzVkMxalZidFE7MDs="
} // scroll_id是上次返回的
之后的查詢依次類推
這個游標查詢返回的下一批結果。 盡管我們指定字段size
的值為1000,我們有可能取到超過這個值數量的文檔。 當查詢的時候, 字段 size 作用於單個分片,所以每個批次實際返回的文檔數量最大為size * number_of_primary_shards
。
es中的近似聚合
對於es來講,其中的去重計數。是個近似的值,不像mysql中的是精確值,存在5%的誤差,不過可以通過設置precision_threshold
來解決少量數據的精准度
GET /cars/transactions/_search
{
"size" : 0,
"aggs" : {
"distinct_colors" : {
"cardinality" : {
"field" : "color",
"precision_threshold" : 100 // precision_threshold 接受 0–40,000 之間的數字,更大的值還是會被當作 40,000 來處理。
}
}
}
}
示例會確保當字段唯一值在 100 以內時會得到非常准確的結果。盡管算法是無法保證這點的,但如果基數在閾值以下,幾乎總是100%
正確的。高於閾值的基數會開始節省內存而犧牲准確度,同時也會對度量結果帶入誤差。
對於指定的閾值,HLL
的數據結構會大概使用precision_threshold * 8
字節的內存,所以就必須在犧牲內存和獲得額外的准確度間做平衡。
在實際應用中,100
的閾值可以在唯一值為百萬的情況下仍然將誤差維持5%
以內。
總結
當我們選型es時候,要充分考慮到上面的幾點。