ElasticSearch 7.5 中的多样化搜索结果

Diversifying search results in ElasticSearch 7.5

我有一个包含不同目录产品的搜索索引。现在,当我搜索给定的搜索词时,经常会返回如下结果:

Catalog 1 - Product 1
Catalog 1 - Product 2
Catalog 1 - Product 3
...
Catalog 1 - Product x
Catalog 2 - Product 1
...

这不是最佳选择,因为我想将用户指向其他目录,同时又不想让他浏览包含同一目录所有产品的多个搜索结果页面。所以我尝试使用diversified_sampler-aggregation which, in conjunction with a child top_hits-aggregation,似乎正是解决方案,我想要:

POST /myIndex/_search?typed_keys=true
{
  "query": {
    "query_string": {
      "fields": [
        "title^2",
        "description^2",
        "descriptionOriginal^0.01"
      ],
      "query": "*someSearchTerm*"
    }
  },
  "size": 0,
  "aggs": {
    "aggDiversifiedSampler": {
      "diversified_sampler": {
        "shard_size": 100000,
        "field": "catalogId",
        "max_docs_per_value": 3
      },
      "aggs": {
        "aggTopHits": {
          "top_hits": {
            "from": 0,
            "size": 50,
            "sort": [
              {
                "_score": {
                  "order": "desc"
                }
              }
            ]
          }
        }
      }
    }
  }
}

正在通过内部 top_hits-aggregation 的 "size" 和 "from" 属性进行分页。搜索结果可以从内部 top_hits-aggregation 的值集合中获取 - 因此我将查询本身的大小设置为 0.

这似乎可行 - 乍一看,但仔细查看结果后发现,并非所有搜索结果都被返回。结果现在如下所示:

Catalog 1 - Product 1
Catalog 1 - Product 2
Catalog 1 - Product 3
Catalog 2 - Product 1
Catalog 2 - Product 2
Catalog 2 - Product 3
...
Catalog x - Product 1
Catalog x - Product 2
Catalog x - Product 3

...然后就结束了。

似乎 diversified_sampler 在到达最后一个目录后没有绕行,因此不会出现单个目录的进一步结果。我想要的是这样的:

Catalog 1 - Product 1
Catalog 1 - Product 2
Catalog 1 - Product 3
Catalog 2 - Product 1
Catalog 2 - Product 2
Catalog 2 - Product 3
...
Catalog x - Product 1
Catalog x - Product 2
Catalog x - Product 3
Catalog 1 - Product 4
Catalog 1 - Product 5
Catalog 1 - Product 6
Catalog 2 - Product 4
Catalog 2 - Product 5
Catalog 2 - Product 6
...

有什么想法吗?我使用 diversified_sampler 的技术并不是一成不变的,但我想不出别的东西。一些花哨的基于脚本的查询排序可能吗?不知道。基于客户端的重新排序不是一种选择,因为我不希望 elasticsearch-wise 分页被破坏。我需要分页来保持性能 - 搜索索引大约 18GB,包含 900k 文档...

我想我找到了一个没有 diversified_sampler-aggregation 使用脚本排序的解决方案:

POST /myIndex/_search?typed_keys=true
{
  "query": {
    "query_string": {
      "fields": [
        "title^2",
        "description^2",
        "descriptionOriginal^0.01"
      ],
      "query": "*someSearchTerm*"
    }
  },
  "sort": [{
      "_script": {
        "script": {
          "source": "Math.round(_score / params.fuzziness) * params.fuzziness",
          "params": {
            "fuzziness": 2
          }
        },
        "type": "number",
        "order": "desc"
      }
    }, {
      "_script": {
        "script": {
          "source": "if(doc['catalogId'].value != params.cid) {params.cid=doc['catalogId'].value;params.sort=0;return params.count=0;} else {return (++params.count % params.grpSize == 0) ?++params.sort : params.sort;}",
          "params": {
            "cid": 0,
            "sort": 0,
            "count": 0,
            "grpSize": 3
          }
        },
        "type": "number",
        "order": "asc"
      }
    }, {
      "_score": {
        "order": "desc"
      }
    }
  ]
}

在第一个脚本排序中,我对我的文档进行了预排序,以便在某个 _score 范围内的结果落在一起。这是由模糊参数控制的。然后,我使用脚本排序在这些范围内进行排序,以便始终采用每个目录 ID 的下 3 个(由参数 grpSize 控制)文档,然后递增排序顺序。 (不知道将脚本参数用作 "global" 变量是否危险...我对此感到有点不舒服...)

以下是更易读的脚本:

if(doc['catalogId'].value != params.cid) {
  params.cid = doc['catalogId'].value;
  params.sort = 0;
  return params.count = 0;
} else {
  return (++params.count % params.grpSize == 0) ? ++params.sort : params.sort;
}

最后但同样重要的是,具有相同 _score-range 和排序顺序的文档正在按其真实 _score 排序。

该解决方案不涉及真正的性能影响(至少在我的索引上)并且提供了我想要的结果。

欢迎大家post提意见和优化!