Scroll
Version:6.1
英文原文地址:Scroll
當一個搜索請求返回單頁結果時,可以使用 scroll API 檢索體積大量(甚至全部)結果,這和在傳統數據庫中使用游標的方式非常相似。
不要把 scroll 用於實時請求,它主要用於大數據量的場景。例如:將一個索引的內容索引到另一個不同配置的新索引中。
Client support for scrolling and reindexing
一些官方支持的客戶端提供了一些輔助類,可以協助滾動搜索和索引之間的文檔重索引:
Perl
參閱 Search::Elasticsearch::Client::5_0::Bulk 和 Search::Elasticsearch::Client::5_0::Scroll
Python
NOTE:從 scroll 請求返回的結果反映了初始搜素請求生成時的索引狀態,就像時間快照一樣。對文檔的更改(索引、更新或者刪除)只會影響以后的搜索請求。
為了使用 scroll ,初始的搜索請求應該在查詢字符串中指定 scroll 參數,這個參數會告訴 Elasticsearch 將 “search context” 保存多久。例如:?scroll=1m
POST /twitter/tweet/_search?scroll=1m
{
"size": 100,
"query": {
"match" : {
"title" : "elasticsearch"
}
}
}
上面的請求返回的結果里會包含一個 _scroll_id ,我們需要把這個值傳遞給 scroll API ,用來取回下一批結果。
POST (1) /_search/scroll (2)
{
"scroll" : "1m", (3)
"scroll_id" : "DXF1ZXJ5QW5kRmV0Y2gBAAAAAAAAAD4WYm9laVYtZndUQlNsdDcwakFMNjU1QQ==" (4)
}
(1) GET 或者 POST 都可以
(2) URL 不能包含 index 和 type 名稱,原始請求中已經指定了
(3) scroll 參數告訴 Elasticsearch 把搜索上下文再保持一分鍾
(4) scroll_id 的值就是上一個請求中返回的 _scroll_id 的值
size 參數允許我們配置沒批結果返回的最大命中數。每次調用 scroll API 都會返回下一批結果,直到不再有可以返回的結果,即命中數組為空。
IMPORTANT:初始的搜索請求和每個 scroll 請求都會返回一個新的
_scroll_id,只有最近的_scroll_id是可用的
NOTE:如果請求指定了過濾,就只有初始搜索的響應中包含聚合結果。
NOTE:Scroll 請求對
_doc排序做了優化。如果要遍歷所有的文檔,而且不考慮順序,_doc是最高效的選項。
GET /_search?scroll=1m
{
"sort": [
"_doc"
]
}
Keeping the search context alive
scroll 參數告訴了 Elasticsearch 應當保持搜索上下文多久。它的值不需要長到能夠處理完所有的數據,只要足夠處理前一批結果就行了。每個 scroll 請求都會設置一個新的過期時間。
通常,為了優化索引,后台合並進程會把較小的段合並在一起創建出新的更大的段,此時會刪除較小的段。這個過程在 scrolling 期間會繼續進行,但是一個打開狀態的索引上下文可以防止舊段在仍需要使用時被刪除。這就解釋了 Elasticsearch 為什么能夠不考慮對文檔的后續修改,而返回初始搜索請求的結果。
TIP:使舊段保持活動狀態意味着需要更多的文件句柄。請確保你已將節點配置為擁有足夠的可用的文件句柄。詳情參閱 File Descriptors
你可以使用 nodes stats API 查看有多少搜索上下文處於開啟狀態
GET /_nodes/stats/indices/search
Clear scroll API
當超出了 scroll timeout 時,搜索上下文會被自動刪除。但是,保持 scrolls 打開是有成本的,當不再使用 scroll 時應當使用 clear-scroll API 進行顯式清除。
DELETE /_search/scroll
{
"scroll_id" : "DXF1ZXJ5QW5kRmV0Y2gBAAAAAAAAAD4WYm9laVYtZndUQlNsdDcwakFMNjU1QQ=="
}
可以使用數組傳遞多個 scroll ID
DELETE /_search/scroll
{
"scroll_id" : [
"DXF1ZXJ5QW5kRmV0Y2gBAAAAAAAAAD4WYm9laVYtZndUQlNsdDcwakFMNjU1QQ==",
"DnF1ZXJ5VGhlbkZldGNoBQAAAAAAAAABFmtSWWRRWUJrU2o2ZExpSGJCVmQxYUEAAAAAAAAAAxZrUllkUVlCa1NqNmRMaUhiQlZkMWFBAAAAAAAAAAIWa1JZZFFZQmtTajZkTGlIYkJWZDFhQQAAAAAAAAAFFmtSWWRRWUJrU2o2ZExpSGJCVmQxYUEAAAAAAAAABBZrUllkUVlCa1NqNmRMaUhiQlZkMWFB"
]
}
使用 _all 參數清除所有的搜索上下文
DELETE /_search/scroll/_all
也可以使用 query string 參數傳遞 scroll_id ,多個值使用英文逗號分割
DELETE /_search/scroll/DXF1ZXJ5QW5kRmV0Y2gBAAAAAAAAAD4WYm9laVYtZndUQlNsdDcwakFMNjU1QQ==,DnF1ZXJ5VGhlbkZldGNoBQAAAAAAAAABFmtSWWRRWUJrU2o2ZExpSGJCVmQxYUEAAAAAAAAAAxZrUllkUVlCa1NqNmRMaUhiQlZkMWFBAAAAAAAAAAIWa1JZZFFZQmtTajZkTGlIYkJWZDFhQQAAAAAAAAAFFmtSWWRRWUJrU2o2ZExpSGJCVmQxYUEAAAAAAAAABBZrUllkUVlCa1NqNmRMaUhiQlZkMWFB
Sliced Scroll
如果 scroll 查詢返回的文檔數量過多,可以把它們拆分成多個切片以便獨立使用
GET /twitter/tweet/_search?scroll=1m
{
"slice": {
"id": 0, (1)
"max": 2 (2)
},
"query": {
"match" : {
"title" : "elasticsearch"
}
}
}
GET /twitter/tweet/_search?scroll=1m
{
"slice": {
"id": 1,
"max": 2
},
"query": {
"match" : {
"title" : "elasticsearch"
}
}
}
(1) 切片的 id
(2) 最大切片數量
上面的栗子,第一個請求返回的是第一個切片(id : 0)的文檔,第二個請求返回的是第二個切片的文檔。因為我們設置了最大切片數量是 2 ,所以兩個請求的結果等價於一次不切片的 scroll 查詢結果。默認情況下,先在第一個分片(shard)上做切分,然后使用以下公式:slice(doc) = floorMod(hashCode(doc._uid), max) 在每個 shard 上執行切分。例如,如果 shard 的數量是 2 ,並且用戶請求 4 slices ,那么 id 為 0 和 2 的 slice 會被分配給第一個 shard ,id 為 1 和 3 的 slice 會被分配給第二個 shard 。
每個 scroll 是獨立的,可以像任何 scroll 請求一樣進行並行處理。
NOTE:如果 slices 的數量比 shards 的數量大,第一次調用時,slice filter 的速度會非常慢。它的復雜度時 O(n) ,內存開銷等於每個 slice N 位,其中 N 時 shard 中的文檔總數。經過幾次調用后,篩選器會被緩存,后續的調用會更快。但是仍需要限制並行執行的 sliced 查詢的數量,以免內存激增。
為了完全避免此成本,可以使用另一個字段的 doc_values 來進行切片,但用戶必須確保該字段具有以下屬性:
- 該字段是數字類型
- 該字段啟用了
doc_values - 每個文檔應當包含單個值。如果一份文檔有指定字段的多個值,則使用第一個值
- 每個文檔的值在創建文檔時設置了之后不再更新,這可以確保每個切片獲得確定的結果
- 字段的基數應當很高,這可以確保每個切片獲得的文檔數量大致相同
GET /twitter/tweet/_search?scroll=1m
{
"slice": {
"field": "date",
"id": 0,
"max": 10
},
"query": {
"match" : {
"title" : "elasticsearch"
}
}
}
NOTE:默認情況下,每個 scroll 允許的最大切片數量時 1024。你可以更新索引設置中的
index.max_slices_per_scroll來繞過此限制。
