如何在Elasticsearch中進行深分頁


如何在Elasticsearch中進行深分頁

業務背景

在傳統業務系統中,一個常見的信息展現方式就是“分頁列表”,隨着數據量的增大,就會遇到“深分頁”問題。比如用戶一頁一頁的翻,一直翻到第5萬頁。比如導出全部列表數據到excel,實現時一頁一頁的把數據追加到excel,直到導出全部數據。“深分頁”通常的一個問題就是:隨着頁數越來越大,ES或者關系數據庫響應越來越慢,甚至內存溢出OOM!其中的原理是什么呢?如何在ES中進行深分頁呢?

技術原理

  • 分頁的本質
    分頁的本質是從“大的數據集”中取出一部分。比如10000條記錄,每頁10條數據。取第二頁即第11條到20條數據。ES或者數據庫怎么知道哪些數據是第二部分(第2頁),哪些是第三部分(第3頁)呢?答案是ES或者數據庫不知道,所以正確的分頁必須要指定分頁的順序,即要有order by或者sort語句

  • 單機數據庫系統分頁
    單機數據庫系統有一種分頁實現叫做“先正序排后倒排序排”。即先對"offset+limit"的數據集根據order字段正序排列,然后再倒序找到limit條數據。

在這里插入圖片描述

  • 分布式數據庫系統分頁

分布式數據庫系統相對於單機數據庫系統,在各個節點取出limit條數據后,還要將各個節點的"limit"條數據匯總到master節點。由master節點對limit*N(節點數)再排序,找到最終的limit條數據返回給應用程序。所以在深分頁時,offset+limit過大,要排序的數據過多,對於內存分頁數據庫很容易超過進程的內存限制,產生OOM!

分頁方式

在ES中有三種方式可以實現分頁:from+size、scroll、search_after

方式一: from+size

ES的標准分頁方法是from+size。from相當於postgresql的offset,size相當於limit的作用。每頁10條數據,獲取第11頁的數據,其語法如下:

POST rzfx-sqlinfo/sqlinfo/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "term": {
            "architect.keyword": {
              "value": "郭鋒"
            }
          }
        },
        {
          "range": {
            "NRunTime": {
              "lte": 100
            }
          }
        }
      ]
    }
  },
  "size": 10,
  "from": 100
}

ES為了保證分頁不占用大量的堆內存,避免OOM,參數 index.max_result_window 設置了 from+size的最大值為10000。即每頁10條的話,最多可以翻到1000頁。index的全部參數可以通過以下語句查看:

GET /rzfx-sqlinfo/_settings?flat_settings=true&include_defaults=true

對於結構比較簡單、size比較小的文檔,可以適當的擴大index.max_result_window參數,部分實現深分頁。

方式二:scroll

scroll api提供了一個全局深度翻頁的操作,首次請求會返回一個scroll_id,使用該scroll_id可以順序獲取下一批次的數據

案例

初始的搜索請求在查詢字符串中指定 scroll 參數,這個參數會告訴 Elasticsearch 將 “search context” 保存多久。
例如:?scroll=5m

GET /db10/_search?&scroll=5m
{
  "query": {
    "match_all": {}
  }
  , "sort": [
    {
      "_doc": {
        "order": "desc"
      }
    }
  ], "size": 2
}
反回結果

上面的請求返回的結果里會包含一個 _scroll_id ,我們需要把這個值傳遞給 scroll API ,用來取回下一批結果。

 "_scroll_id" : "DXF1ZXJ5QW5kRmV0Y2gBAAAAAAAAzVUWazVqSWpwSm1UTUc5U1Y4OGN5SWN6QQ==",
  "took" : 0,
執行下一頁查詢
GET _search/scroll
{
  "scroll":"5m",
  "scroll_id" : "DXF1ZXJ5QW5kRmV0Y2gBAAAAAAAAy_kWazVqSWpwSm1UTUc5U1Y4OGN5SWN6QQ=="

}
刪除scroll

當超出了 scroll timeout 時,搜索上下文會被自動刪除。
保持 scrolls 打開是有成本的,當不再使用 scroll 時應當使用 clear-scroll API 進行顯式清除

DELETE _search/scroll
{
   "scroll_id" : "DXF1ZXJ5QW5kRmV0Y2gBAAAAAAABZOkWazVqSWpwSm1UTUc5U1Y4OGN5SWN6QQ=="
}

性能

案例DSL

scroll=5m 滾動分頁標識

GET /filebeat-7.4.0-2019.10.17-000001/_search?&scroll=5m
{
  "query": {
    "match_all": {}
  }
  , "sort": [
    {
      "_doc": {
        "order": "desc"
      }
    }
  ], "size": 10
}
結果

獲得_scroll_id

觀察took,此處消耗 15349ms

  "_scroll_id" : "DXF1ZXJ5QW5kRmV0Y2gBAAAAAAAFnssWeUdmZzFOcHBUeFdzVTVwMTVPVTZNZw==",
  "took" : 15349,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
取上一步的_scroll_id
GET /_search/scroll
{
  "scroll":"5m",
  "scroll_id": "DXF1ZXJ5QW5kRmV0Y2gBAAAAAAAFm2IWeUdmZzFOcHBUeFdzVTVwMTVPVTZNZw=="
}
結果

通過_scroll_id,滾動翻頁鎖消耗時間大致相同

觀察took,此處消耗 2742ms

 "_scroll_id" : "DXF1ZXJ5QW5kRmV0Y2gBAAAAAAAFm2IWeUdmZzFOcHBUeFdzVTVwMTVPVTZNZw==",
  "took" : 2742,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,

方式三: search_after

5.0以后版本提供的功能
search_after分頁方式,第一次搜索需要指定sort,並保證值是唯一的,用前一次查詢結果中最后一條記錄的 sort 結果值作為下一次的查詢條件

案例

GET /db10/_search?pretty=true
{
  "size": 1, 
  "query": {
    "match_all": {
    }
  }
  , "sort": [
    {
      "age": {
        "order": "desc"
      }
    }
  ]
}

結果截取

       },
        *"sort" : [
          22
        ]*

檢索下一頁

"search_after":基於上一頁排序sort結果值檢索下一頁實現動態分頁

GET /db10/_search?pretty=true
{
  "size": 1, 
  "query": {
    "match_all": {
    }
  },
  *"search_after": [ 22 ]*
  , "sort": [
    {
      "age": {
        "order": "desc"
      }
    }
  ]
}

性能

案例DSL
GET /filebeat-7.4.0-2019.10.17-000001/_search?pretty
{
  "size": 20, 
  "query": {
    "match_all": {
      
    } 
  }
  , "sort": [
    {
      "@timestamp": {
        "order": "desc"
      }
    }
  ]
}
結果

觀察"took" : 4825

  "took" : 4825,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
        "sort" : [
          1571522951167
        ]
      }
取下一頁

"search_after": [ 1571522951167 ]
取上一頁最后一個sort值

GET /filebeat-7.4.0-2019.10.17-000001/_search?pretty
{
  "size": 20, 
  "query": {
    "match_all": {
      
    } 
  },
  "search_after": [ 1571522951167 ]
  , "sort": [
    {
      "@timestamp": {
        "order": "desc"
      }
    }
  ]
}
結果

觀察"took" : 4318,

{
  "took" : 4318,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,

總結

  • from+size
    • 使用from+size方式進行分頁,受max_result_window默認參數10000條文檔的限制,不建議針對該參數進行修改
    • 默認分頁方式,適用小數據量場景,大數據量場景應避免使用
    • 通過性能測試,隨着分頁越來越深,執行時間和堆內存使用逐漸升高的趨勢,在並發情況下from+size容易 造成集群服務的OutOfMemory問題
  • Scroll
    • Scroll游標方式分頁查詢適用大數據量場景,只能向后增量查找,無法向前或者跳頁查詢,適用增量滾動抽取、數據遷移、重建索引等場景
    • 通過性能案例分析,滾動分頁查找性能消耗相差不大,不會像from+size方式隨着分頁的深入性能逐漸升高的問題,且不會存在OOM問題
    • 該分頁方式是查詢的歷史快照,對文檔的更改(索引的更新或者刪除)只會影響以后的搜索請求,不適用實時性查詢場景
  • search_after
    • 分頁方式彌補了 scroll 方式打開scroll 占用內存資源問題
    • search_after可並行的拉取大量數據
    • search_after分頁方式通過唯一排序值定位,將每次需要處理的數據控制在一定范圍,避免深度分頁帶來的開銷,適用深度分頁的場景


免責聲明!

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



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