elasticsearch深度分页问题

elasticsearch深度分页问题

一、深度分页方式from + size

es 默认采用的分页方式是 from+ size 的形式,在深度分页的情况下,这种使用方式效率是非常低的,比如我们执行如下查询

curl -XGET '127.0.0.1:9200/shop/shop/_search?pretty' -H 'Content-Type: application/json' -d'
{
  "query":{
    "match_all": {}
  },
  "from":5000,
  "size":10
}
'

意味着 es 需要在各个分片上匹配排序并得到5010条数据,协调节点拿到这些数据再进行排序等处理,然后结果集中取最后10条数据返回。

我们会发现这样的深度分页将会使得效率非常低,因为我只需要查询10条数据,而es则需要执行from+size条数据然后处理后返回。

其次:es为了性能,限制了我们分页的深度,es目前支持的最大的 max_result_window = 10000;也就是说我们不能分页到10000条数据以上。

例如:

curl -XGET '127.0.0.1:9200/shop/shop/_search?pretty' -H 'Content-Type: application/json' -d'
{
  "query":{
    "match_all": {}
  },
  "from":10000,
  "size":10
}
'

当size + from > 10000;es查询失败,并且提示

Result window is too large, from + size must be less than or equal to: [10000] but was [10010]. See the scroll api for a more efficient way to request large data sets. This limit can be set by changing the [index.max_result_window] index level setting.

二、深度分页之scroll

在es中如果我们分页要请求大数据集或者一次请求要获取较大的数据集,scroll都是一个非常好的解决方案。

使用scroll滚动搜索,可以先搜索一批数据,然后下次再搜索一批数据,以此类推,直到搜索出全部的数据来scroll搜索会在第一次搜索的时候,保存一个当时的视图快照,之后只会基于该旧的视图快照提供数据搜索,如果这个期间数据变更,是不会让用户看到的。每次发送scroll请求,我们还需要指定一个scroll参数,指定一个时间窗口,每次搜索请求只要在这个时间窗口内能完成就可以了。

一个滚屏搜索允许我们做一个初始阶段搜索并且持续批量从Elasticsearch里拉取结果直到没有结果剩下。这有点像传统数据库里的cursors(游标)。

滚屏搜索会及时制作快照。这个快照不会包含任何在初始阶段搜索请求后对index做的修改。它通过将旧的数据文件保存在手边,所以可以保护index的样子看起来像搜索开始时的样子。这样将使得我们无法得到用户最近的更新行为。

scroll的使用很简单

执行如下curl,每次请求两条。可以定制 scroll = 5m意味着该窗口过期时间为5分钟。

curl -XGET '127.0.0.1:9200/shop/shop/_search?scroll=5m&pretty' -H 'Content-Type: application/json' -d'
{
   "query": {
        "term" : {
            "branchStoreId" : "10004868"
        }
    },
  "size":2
}
'
{
  "_scroll_id" : "DnF1ZXJ5VGhlbkZldGNoBQAAAAAAABdiFlVVcW1sUmxmVFYtQk1ZbWR0dXNGamcAAAAAAAAXYxZVVXFtbFJsZlRWLUJNWW1kdHVzRmpnAAAAAAAAF2UWVVVxbWxSbGZUVi1CTVltZHR1c0ZqZwAAAAAAABdmFlVVcW1sUmxmVFYtQk1ZbWR0dXNGamcAAAAAAAAXZBZVVXFtbFJsZlRWLUJNWW1kdHVzRmpn",
  "took" : 1,
  "timed_out" : false,
  "_shards" : {
    "total" : 5,
    "successful" : 5,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : 6,
    "max_score" : 1.0,
    "hits" : [
      {
        "_index" : "shop",
        "_type" : "shop",
        "_id" : "33995",
        "_score" : 1.0,
        "_source" : {
          "appId" : 1,
          "id" : "33995",
          "shopName" : "海底捞火锅 ",
          "area" : "荃湾",
          "address" : "荃湾大河道100号海之恋商场地下G07-G08号舖",
          "branchStoreId" : 10004868,
          "longitude" : "114.11575732308106",
          "latitude" : "22.365408313009407",
          "collects" : "1867",
          "shopImg" : "/spider/food/op/de66a56637c611eb9bf500163e019e34.jpg",
          "shopType" : 0,
          "categoryName" : "川菜 (四川)/火锅",
          "shopGrade" : 4.5,
          "price" : "201-400",
          "likesReview" : 5,
          "okReview" : 2,
          "badReview" : 0,
          "attitude" : -1,
          "location" : "22.365408313009407,114.11575732308106",
          "status" : "",
          "score" : 0.5405438429598104,
          "esId" : "33995"
        }
      },
      {
        "_index" : "shop",
        "_type" : "shop",
        "_id" : "64058",
        "_score" : 1.0,
        "_source" : {
          "appId" : 1,
          "id" : "64058",
          "shopName" : "海底捞火锅",
          "area" : "尖沙咀",
          "address" : "尖沙咀金巴利道26号2楼",
          "branchStoreId" : 10004868,
          "longitude" : "114.1784066676926",
          "latitude" : "22.29771142024199",
          "collects" : "2882",
          "shopImg" : "/spider/food/op/c827568c37c211eb8dda00163e019e34.jpg",
          "shopType" : 0,
          "categoryName" : "川菜 (四川)/火锅",
          "shopGrade" : 4.5,
          "price" : "201-400",
          "likesReview" : 112,
          "okReview" : 2,
          "badReview" : 0,
          "attitude" : -1,
          "location" : "22.29771142024199,114.1784066676926",
          "status" : "",
          "score" : 0.5705653294987613,
          "esId" : "64058"
        }
      }
    ]
  }
}

在返回结果中,有一个很重要的 _scroll_id字段。在后面的请求中我们都要带着这个 _scroll_id 去请求。

curl -XGET '127.0.0.1:9200/_search/scroll?pretty&scroll=1m&scroll_id=DnF1ZXJ5VGhlbkZldGNoBQAAAAAAABdiFlVVcW1sUmxmVFYtQk1ZbWR0dXNGamcAAAAAAAAXYxZVVXFtbFJsZlRWLUJNWW1kdHVzRmpnAAAAAAAAF2UWVVVxbWxSbGZUVi1CTVltZHR1c0ZqZwAAAAAAABdmFlVVcW1sUmxmVFYtQk1ZbWR0dXNGamcAAAAAAAAXZBZVVXFtbFJsZlRWLUJNWW1kdHVzRmpn'

每次的查询,都把最新的scroll_id带上,直到数据查询完成为止。

所有文档获取完毕之后,需要手动清理掉 scroll_id 。虽然es 会有自动清理机制,但是 srcoll_id 的存在会耗费大量的资源来保存一份当前查询结果集快照,并且会占用文件描述符。所以用完之后要及时清理。使用 es 提供的 CLEAR_API 来删除指定的 scroll_id

  • 删掉指定的多个 srcoll_id
curl -XDELETE 127.0.0.1:9200/_search/scroll -H 'Content-Type: application/json' -d '{"scroll_id" : ["DnF1ZXJ5VGhlbkZldGNoBQAAAAAAABa4FlVVcW1sUmxmVFYtQk1ZbWR0dXNGamcAAAAAAAAWuhZVVXFtbFJsZlRWLUJNWW1kdHVzRmpnAAAAAAAAFrwWVVVxbWxSbGZUVi1CTVltZHR1c0ZqZwAAAAAAABa7FlVVcW1sUmxmVFYtQk1ZbWR0dXNGamcAAAAAAAAWuRZVVXFtbFJsZlRWLUJNWW1kdHVzRmpn"]}'

  • 删除掉所有索引上的 scroll_id
curl -XDELETE 127.0.0.1:9200/_search/scroll/_all

三、search_after 的方式

上述的 scroll search 的方式,官方的建议并不是用于实时的请求,因为每一个 scroll_id 不仅会占用大量的资源(特别是排序的请求),而且是生成的历史快照,对于数据的变更不会反映到快照上。这种方式往往用于非实时处理大量数据的情况,比如要进行数据迁移或者索引变更之类的。那么在实时情况下如果处理深度分页的问题呢?es 给出了 search_after 的方式,这是在 >= 5.0 版本才提供的功能。

search_after 分页的方式和 scroll 有一些显著的区别,首先它是根据上一页的最后一条数据来确定下一页的位置,同时在分页请求的过程中,如果有索引数据的增删改查,这些变更也会实时的反映到游标上。

  1. 第一页的请求和正常的请求一样,
curl -XGET '127.0.0.1:9200/shop/shop/_search?pretty' -H 'Content-Type: application/json' -d'
{
    "size": 2,
    "query": {
        "term" : {
            "area" : "尖沙咀"
        }
    },
    "sort": [
        {"_id": "desc"}
    ]
}
'

返回结果

{
  "took" : 23,
  "timed_out" : false,
  "_shards" : {
    "total" : 5,
    "successful" : 5,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : 10264,
    "max_score" : null,
    "hits" : [
      {
        "_index" : "shop",
        "_type" : "shop",
        "_id" : "99998",
        "_score" : null,
        "_source" : {
          "appId" : 3,
          "id" : "99998",
          "shopName" : "茹絲葵牛排餐廳",
          "area" : "尖沙咀",
          "address" : "尖沙咀麼地道66號尖沙咀中心1樓108-110號舖",
          "branchStoreId" : 10001661,
          "longitude" : "114.182714701441",
          "latitude" : "22.294923943567408",
          "collects" : "11535",
          "shopImg" : "/prod/restaurant/ea25592c551af318ed45375e2176314f.jpg",
          "shopType" : 0,
          "categoryName" : "美國菜/扒房/酒",
          "shopGrade" : 4.5,
          "price" : "401-800",
          "likesReview" : 64,
          "okReview" : 6,
          "badReview" : 2,
          "attitude" : -1,
          "location" : "22.294923943567408,114.182714701441",
          "status" : "",
          "score" : 0.5910990762455836,
          "esId" : "99998"
        },
        "sort" : [
          "99998"
        ]
      },
      {
        "_index" : "shop",
        "_type" : "shop",
        "_id" : "99990",
        "_score" : null,
        "_source" : {
          "appId" : 3,
          "id" : "99990",
          "shopName" : "鴻福堂",
          "area" : "尖沙咀",
          "address" : "尖沙咀港鐵尖東站28號舖",
          "branchStoreId" : 10000614,
          "longitude" : "114.17838477184749",
          "latitude" : "22.29206829010525",
          "collects" : "93",
          "shopImg" : "/prod/restaurant/a110399137a18f2a629a808a3d121940.jpg",
          "shopType" : 0,
          "categoryName" : "粵菜 (廣東)/涼茶/龜苓膏/藥膳",
          "shopGrade" : 3.5,
          "price" : "50以下",
          "likesReview" : 0,
          "okReview" : 2,
          "badReview" : 1,
          "attitude" : -1,
          "location" : "22.29206829010525,114.17838477184749",
          "status" : "",
          "score" : 0.49259720096169446,
          "esId" : "99990"
        },
        "sort" : [
          "99990"
        ]
      }
    ]
  }
}

第二页的请求,使用第一页返回结果的最后一个数据的sort值,传给 search_after 字段来取下一页。注意,使用 search_after 的时候要将 from 置为 0 或 -1

curl -XGET '127.0.0.1:9200/shop/shop/_search?pretty' -H 'Content-Type: application/json' -d'
{
    "size": 2,
    "query": {
        "term" : {
            "area" : "尖沙咀"
        }
    },
    "search_after": [99905],
    "sort": [
        {"_id": "desc"}
    ]
}
'

总结:search_after 适用于深度分页 + 排序,因为每一页的数据依赖于上一页最后一条数据,所以无法跳页请求。

且返回的始终是最新的数据,在分页过程中数据的位置可能会有变更。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值