搜索后续如何在Elasticsearch中工作?

23

我一直在尝试为我们的应用程序使用Elasticsearch,但是每页限制10k的分页实际上对我们来说是一个问题,而滚动API由于存在超时问题也不是推荐的选择。

我发现Elasticsearch有一种称为search_after的东西,它是支持深度分页的理想解决方案。我一直在尝试从文档中理解它,但有些困惑,无法清楚地理解它的工作原理。

假设我的文档中有三列,id, first_name, last_name,其中ID是唯一主键。

{
    "size": 10,
    "query": {
        "match" : {
            "title" : "elasticsearch"
        }
    },
    "sort": [
        {"id": "asc"}      
    ]
}
我可以使用上述查询来使用search_after功能吗?我在他们的文档中读到,我们必须在排序中使用多个唯一值而不仅仅是一个(ID),但是您知道在我的数据集中,我只有唯一的ID。为了在我的数据集示例中使用search_after,我该怎么办?
如果我在排序中使用一个唯一的tie-breaker,我无法理解所述问题。有人可以帮忙以通俗易懂的方式解释一下吗?
https://www.elastic.co/guide/en/elasticsearch/reference/6.8/search-request-search-after.html
每个文档具有一个唯一值的字段应该作为排序规范的tiebreaker。否则,具有相同排序值的文档的排序顺序将是未定义的,并且可能会导致缺失或重复的结果。_id字段对于每个文档具有唯一值,但不建议直接将其用作tiebreaker。请注意,search_after查找第一个完全或部分匹配tiebreaker提供的值的文档。因此,如果文档具有“654323”的tiebreaker值,并且您搜索“654”,它仍将匹配该文档并返回在其之后找到的结果。这个字段上禁用了doc value,因此在它上面进行排序需要在内存中加载大量数据。因此,建议在另一个启用了doc value的字段中复制(客户端侧或使用集合摄取处理器)_id字段的内容,并将此新字段用作排序的tiebreaker。

据我理解,如果字段值是唯一的,您可以仅在一个字段上使用排序。当您想要根据某个不唯一的字段对文档进行排序时,您需要添加多个排序字段(其中一个具有唯一值作为次要排序)作为绑定器。 - Pramod
从文档中得知,只使用_ID字段并不理想,因为search_after执行的是部分匹配而不是完全匹配。我猜这就是他们在文档页面上解释的内容。我想知道如何解决这个问题? - user_12
我认为你提到的Id字段与_id字段不同。是的,在排序中不建议使用_id,因为它需要在内存中加载大量数据。您可以将_id字段复制为文档的id字段,并使用该字段进行排序。 - Pramod
@Pramod 抱歉,ID字段与_id字段不同。这是一个笔误。至于他们正在讨论的问题,“因此,如果一个文档具有“654323”的打破平局值,并且您搜索“654”进行search_after,则仍将匹配该文档并返回在其之后找到的结果。” - user_12
2个回答

35
在您的情况下,如果您的id字段包含唯一值并且具有keyword类型(或数字类型),那么您绝对可以使用它来使用search_after进行分页。因此,第一个调用将是您在问题中提到的那个调用:
{
    "size": 10,
    "query": {
        "match" : {
            "title" : "elasticsearch"
        }
    },
    "sort": [
        {"id": "asc"},
        {"score": "desc"}      
    ]
}

在您的响应中,您需要查看最后一个命中并从该最后一个命中中取出sort值:

{
    "_index" : "myindex",
    "_type" : "_doc",
    "_id" : "100000012",
    "_score" : null,
    "_source": { ... },
    "sort" : [
      "100000012",                                <--- take this
      "98"                                        <--- take this
    ]
}

接下来,在您的下一次搜索调用中,您将在search_after中指定该值

{
    "size": 10,
    "query": {
        "match" : {
            "title" : "elasticsearch"
        }
    },
    "search_after": [ "100000012", "98" ],        <--- add this
    "sort": [
        {"id": "asc"}      
    ]
}

下一个结果集的第一个命中将是id: 100000013,就这样。没有更多了。

如果您始终使用完整的id值排序,则您指出的问题与您无关。其工作方式是始终使用先前结果的最后一个id值。如果您添加了"search_after": ["1000"],则会遇到他们提到的问题,但您没有理由这样做。


如果我有一个名为“score”的额外列(字段),每个文档都将具有0-100的分数,那么search_after会起作用吗?问题在于可能存在具有相同分数的文档。我希望结果文档按降序排列得分。在这种情况下,我需要同时在排序中使用ID和分数吗?如果我这样做,哪个会更优先?我的结果是基于ID还是分数排序的?我能使用search_after吗? - user_12
1
如果您使用两个排序字段(首先是id,其次是score)进行搜索,则结果中的sort数组将具有两个值(["100000012", "98"]),您需要在下一个查询中同时使用这两个值作为search_after。但由于id具有唯一值,因此您不会错过任何数据。我已相应地更新了我的答案。 - Val
我希望可以交换 [{"score": "desc"} ,{"id": "asc"} ],因为如果我先使用 id,则结果将根据ID排序,如果我先使用 score,则结果将根据分数排序。我想要基于分数的降序结果。 - user_12
1
只要有充当“决胜者”的“id”字段,交换排序字段是可以的。 - Val
一切良好。关闭问题了。再次感谢您提供的所有澄清信息。 - user_12
显示剩余4条评论

1

我添加了一个简单的测试,以使其更易于理解。你可以看一下。

POST search_after/_bulk
{"index":{}}
{"id":1,"field_name":"field_value test 1"}
{"index":{}}
{"id":2,"field_name":"field_value test 2"}
{"index":{}}
{"id":3,"field_name":"field_value test 3"}
{"index":{}}
{"id":4,"field_name":"field_value test 4"}
{"index":{}}
{"id":5,"field_name":"field_value test 5"}
{"index":{}}
{"id":6,"field_name":"field_value test 6"}
{"index":{}}
{"id":7,"field_name":"field_value test 7"}

#第一个查询

GET search_after/_search
{
  "size": 3, 
  "query": {
    "match": {
      "field_name": "field_value"
    }
  },
  "search_after": ["0"],
  "sort": [
    {
      "id": {
        "order": "asc"
      }
    }
  ]
}

#第二个查询

GET search_after/_search
{
  "size": 3, 
  "query": {
    "match": {
      "field_name": "field_value"
    }
  },
  "search_after": ["3"],
  "sort": [
    {
      "id": {
        "order": "asc"
      }
    }
  ]
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接