1:ElasticSearch的查詢過程
2:由ES查詢模式引起的深度分頁問題
3:如何正確遍歷索引中的數據
ElasticSearch的查詢過程
es的數據查詢分兩步:
第一步是的結果是獲取滿足查詢條件的,分布於各個shard上的_doc_id及對應_score;
第二步是根據第一步獲取的所有的_doc_id,去各個shard上獲取數據明細,合並返回客戶端。
在第一步的查詢中,es執行了一個類似map-reduce的查詢模式:在各個shard上執行同樣的查詢,獲取同樣大小的數據(from+size),然后將各個 shard查詢結果的_doc_id,_score返回給接收查詢請求的節點,最終進行一次匯總。注意這里傳輸數據的數量是from+size,而不僅僅是size大小。
由ES查詢模式引起的深度分頁問題
通過ES的查詢過程我們已經可以看出問題,對大數據量的Index進行遍歷操作,如果使用from size的方式,將存在極大的資源浪費(因為from過大),當然如果size過大則問題更為嚴重,服務器內存會被吃光。因此很多站點的查詢,都不會允許查看過大的page,深度分頁的問題在關系數據庫中同樣存在。
如何正確遍歷索引中的數據
在ES中遍歷索引的推薦方式是采用scroll操作,或者在不需要排序而僅僅需要遍歷的情況下,可以采用scroll_scan進一步提升性能。
詳細可見:Scroll的官方說明
簡單描述一下Scroll的工作原理:
1:對需要遍歷(或獲取某個滿足條件的子集)的索引,規划一個合理的size作為分頁大小;
2:在POST的查詢中,增加?scroll=1m(expire time)的query string;
3:在該次查詢中,返回結果中除了查詢結果以外,還有一個scroll_id(很長),這個scroll_id可以看做是第一次查詢時目標索引的快照的游標;
4:之后直接在GET請求中將scroll_id作為query string傳遞,或在POST請求中加入scroll_id即可獲取下一批數據,直到沒有數據為止,如此實現對所有目標數據的遍歷。
(注:在使用scroll遍歷的過程中,不需要再指定各種查詢條件比如索引名,size大小什么的,只需要指定scroll_id即可,還可以指定scroll:1m之類的過期時間,相同條件下有效期內的scroll操作,返回的scroll_id是不會變的,僅僅是scroll_id這個游標向前走,返回下一批數據而已)
scroll_id可以被顯式刪除,如果你需要重置對查詢目標的遍歷過程的話。
如果遍歷過程不需要對數據進行排序,可以使用更為高效的scroll_scan方式進行,如下:
POST ip:port/my_index/my_type/_search?search_type=scan&scroll=1m&size=50 { "query": { "match_all": {}} }
這里需要注意的是,search_type指定為scan,即無需排序,而size=50是指每個shard上的size是50,最終返回數據是shard*size。
初次以外,scroll_scan與普通的scroll還有如下三點不同:
- Scroll-Scan 結果沒有排序,按 index 順序返回,沒有排序,可以提高取數據性能。
- 初始化時只返回 _scroll_id,沒有具體的 hits 結果。
- size 控制的是每個分片的返回的數據量而不是整個請求返回的數據量
關於scroll操作是否線程安全的問題,es的客戶端(java&c#)均為線程安全,因此我認為通過客戶端來進行的scroll請求也是線程安全的。如果有錯誤請指出。
2016-09-14補充:
利用scroll_id獲取數據的同時,ES將返回當前查詢的scroll_id,在其失效或被刪除之前,這個scroll_id都不變,如果希望程序的可靠性更高,可以用每次返回的scroll_id更新查詢POST所發送的scroll_id;同時,對通過scroll_id查詢返回的結果集進行判空,以確定當前scroll_id是否已失效,從而需要重新獲取scroll_id。