1. 程式人生 > >elasticsearch 深入 —— Scroll滾動查詢

elasticsearch 深入 —— Scroll滾動查詢

Scroll

search 請求返回一個單一的結果“頁”,而 scroll API 可以被用來檢索大量的結果(甚至所有的結果),就像在傳統資料庫中使用的遊標 cursor。

滾動並不是為了實時的使用者響應,而是為了處理大量的資料,例如,為了使用不同的配置來重新索引一個 index 到另一個 index 中去。

client 支援:Perl 和 Python

注意:從 scroll 請求返回的結果反映了 search 發生時刻的索引狀態,就像一個快照。後續的對文件的改動(索引、更新或者刪除)都只會影響後面的搜尋請求。

為了使用 scroll,初始搜尋請求應該在查詢中指定 scroll

引數,這可以告訴 Elasticsearch 需要保持搜尋的上下文環境多久(參考Keeping the search context alive),如 ?scroll=1m

POST /twitter/tweet/_search?scroll=1m 
{
    "query": {
        "match" : {
            "title" : "elasticsearch"
        }
    }
}

使用上面的請求返回的結果中包含一個 scroll_id,這個 ID 可以被傳遞給 scroll API 來檢索下一個批次的結果。

POST /_search/scroll
{
    "scroll" : "1m", 
    "scroll_id" : "c2Nhbjs2OzM0NDg1ODpzRlBLc0FXNlNyNm5JWUc1" 
}
  • GET 或者 POST 可以使用
  • URL不應該包含 index 或者 type 名字——這些都指定在了原始的 search 請求中。
  • scroll 引數告訴 Elasticsearch 保持搜尋的上下文等待另一個 1m
  • scroll_id 引數

每次對 scroll API 的呼叫返回了結果的下一個批次知道沒有更多的結果返回,也就是直到 hits 陣列空了。

為了向前相容,scroll_idscroll 可以放在查詢字串中傳遞。scroll_id 則可以在請求體中傳遞。

curl -XGET 'localhost:9200/_search/scroll?scroll=1m' -d 'c2Nhbjs2OzM0NDg1ODpzRlBLc0FXNlNyNm5JWUc1'

注意:初始搜尋請求和每個後續滾動請求返回一個新的 _scroll_id,只有最近的 _scroll_id 才能被使用。

如果請求指定了聚合(aggregation),僅僅初始搜尋響應才會包含聚合結果。

使用 scroll-scan 的高效滾動

使用 from and size 的深度分頁,比如說 ?size=10&from=10000 是非常低效的,因為 100,000 排序的結果必須從每個分片上取出並重新排序最後返回 10 條。這個過程需要對每個請求頁重複。

scroll API 保持了那些已經返回記錄結果,所以能更加高效地返回排序的結果。但是,按照預設設定排序結果仍然需要代價。

一般來說,你僅僅想要找到結果,不關心順序。你可以通過組合 scrollscan 來關閉任何打分或者排序,以最高效的方式返回結果。你需要做的就是將 search_type=scan 加入到查詢的字串中:

POST /twitter/tweet/_search?scroll=1m&search_type=scan
{
   "query": {
       "match" : {
           "title" : "elasticsearch"
       }
   }
}
  • 設定 search_typescan 可以關閉打分,讓滾動更加高效。

掃描式的滾動請求和標準的滾動請求有四處不同:

  • 不算分,關閉排序。結果會按照在索引中出現的順序返回。
  • 不支援聚合
  • 初始 search 請求的響應不會在 hits 陣列中包含任何結果。第一批結果就會按照第一個 scroll 請求返回。
  • 引數 size 控制了每個分片上而非每個請求的結果數目,所以 size10 的情況下,如果命中了 5 個分片,那麼每個 scroll 請求最多會返回 50 個結果。

如果你想支援打分,即使不進行排序,將 track_scores 設定為 true

保持搜尋上下文存活

引數 scroll (傳遞給 search 請求還有每個 scroll 請求)告訴 Elasticsearch 應該需要保持搜尋上下文多久。這個值(比如說 1m,詳情請見the section called “Time units)並不需要長到可以處理所有的資料——僅僅需要足夠長來處理前一批次的結果。每個 scroll 請求(包含 scroll 引數)設定了一個新的失效時間。

一般來說,背後的合併過程通過合併更小的分段建立更大的分段來優化索引,同時會刪除更小的分段。這個過程在滾動時進行,但是一個開啟狀態的搜尋上下文阻止了舊分段在使用的時候不會被刪除。這就是 Elasticsearch 能夠不管後續的文件的變化,返回初始搜尋請求的結果的原因。

保持舊的分段存活意味著會產生更多的檔案控制代碼。確保你配置了節點有空閒的檔案控制代碼。參考File Descriptors

你可以檢查有多少搜尋上下文開啟了,

GET /_nodes/stats/indices/search?pretty

清除 scroll API

搜尋上下文當 scroll 超時就會自動移除。但是保持 scroll 存活需要代價,如在前一節講的那樣,所以 scrolls 當scroll不再被使用的時候需要被用 clear-scroll 顯式地清除:

DELETE /_search/scroll
{ 
  "scroll_id" : ["c2Nhbjs2OzM0NDg1ODpzRlBLc0FXNlNyNm5JWUc1"]
}

多個 scroll ID 可按照資料傳入:

DELETE /_search/scroll 
{ 
  "scroll_id" : ["c2Nhbjs2OzM0NDg1ODpzRlBLc0FXNlNyNm5JWUc1", "aGVuRmV0Y2g7NTsxOnkxaDZ"]
}

所有搜尋上下文可以通過 _all 引數而清除:

DELETE /_search/scroll/_all

scroll_id 也可以使用一個查詢字串的引數或者在請求的body中傳遞。多個scroll ID 可以使用逗號分隔傳入:

DELETE /_search/scroll/DXF1ZXJ5QW5kRmV0Y2gBAAAAAAAAAD4WYm9laVYtZndUQlNsdDcwakFMNjU1QQ==,DnF1ZXJ5VGhlbkZldGNoBQAAAAAAAAABFmtSWWRRWUJrU2o2ZExpSGJCVmQxYUEAAAAAAAAAAxZrUllkUVlCa1NqNmRMaUhiQlZkMWFBAAAAAAAAAAIWa1JZZFFZQmtTajZkTGlIYkJWZDFhQQAAAAAAAAAFFmtSWWRRWUJrU2o2ZExpSGJCVmQxYUEAAAAAAAAABBZrUllkUVlCa1NqNmRMaUhiQlZkMWFB

Sliced Scroll 

對於返回大量文件的滾動查詢,可以將滾動分割為多個切片,可以單獨使用:

GET /twitter/_search?scroll=1m
{
    "slice": {
        "id": 0, ①
        "max": 2 ②
    },
    "query": {
        "match" : {
            "title" : "elasticsearch"
        }
    }
}
GET /twitter/_search?scroll=1m
{
    "slice": {
        "id": 1,
        "max": 2
    },
    "query": {
        "match" : {
            "title" : "elasticsearch"
        }
    }
}

  切片的id

  最大切片數

每個滾動都是獨立的,可以像任何滾動請求一樣並行處理。第一個請求的結果返回屬於第一個slice(id:0)的文件,第二個請求的結果返回屬於第二個slice的文件。由於最大切片數設定為2,因此兩個請求的結果的並集等效於沒有切片的滾動查詢的結果。預設情況下,首先在shard集合上進行分割,然後在每個shard上使用_uid欄位在本地進行分割,其公式如下: slice(doc) = floorMod(hashCode(doc._uid), max) 例如,如果shard數等於2且使用者請求4個slice,則分配片0和2到第一個shard,切片1和3分配給第二個shard

如果slice的數量大於shard的數量,則切片過濾器在第一次呼叫時非常慢,它具有O(N)的複雜度,並且儲存器成本等於每個slice的N倍,其中N是在shard中的文件的總數。在幾次呼叫之後,快取過濾器在後續呼叫應該更快,但是您應該限制並行執行的切片查詢的數量以避免記憶體溢位。

為了完全避免這種成本,可以使用doc_values另一個欄位來進行切片,但使用者必須確保該欄位具有以下屬性:

  • 該欄位是數字。
  • doc_values 在該欄位上啟用
  • 每個文件都應包含單個值。如果文件具有指定欄位的多個值,則使用第一個值。
  • 建立文件時永遠不會更新每個文件的值。這可確保每個切片獲得確定性結果。
  • 該領域的基數應該很高。這可確保每個切片獲得大致相同數量的文件。
GET /twitter/_search?scroll=1m
{
    "slice": {
        "field": "date",
        "id": 0,
        "max": 10
    },
    "query": {
        "match" : {
            "title" : "elasticsearch"
        }
    }
}

對於僅附加基於時間的索引,timestamp可以安全地使用該欄位。

預設情況下,每個滾動允許的最大切片數限制為1024.您可以更新index.max_slices_per_scroll索引設定以繞過此限制。