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索引設置以繞過此限制。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM