在Elasticsearch中查找重复项

9

我正在尝试查找数据中在多个方面相等的条目。目前,我使用嵌套聚合的复杂查询来实现此目的:

{
  "size": 0, 
  "aggs": { 
    "duplicateFIELD1": { 
      "terms": { 
        "field": "FIELD1", 
        "min_doc_count": 2 },
      "aggs": { 
        "duplicateFIELD2": { 
          "terms": { 
            "field": "FIELD2", 
            "min_doc_count": 2 },
          "aggs": {
            "duplicateFIELD3": {
              "terms": {
                "field": "FIELD3",
                "min_doc_count": 2 },
              "aggs": {
                "duplicateFIELD4": {
                  "terms": {
                    "field": "FIELD4",
                    "min_doc_count": 2 },
                  "aggs": {
                    "duplicate_documents": { 
                      "top_hits": {} } } } } } } } } } } }

这在一定程度上起作用,当找不到重复项时,我得到的结果大致如下:

{
  "took" : 5,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "failed" : 0
  },
  "hits" : {
    "total" : 27524067,
    "max_score" : 0.0,
    "hits" : [ ]
  },
  "aggregations" : {
    "duplicateFIELD1" : {
      "doc_count_error_upper_bound" : 0,
      "sum_other_doc_count" : 27524027,
      "buckets" : [
        {
          "key" : <valueFromField1>,
          "doc_count" : 4,
          "duplicateFIELD2" : {
            "doc_count_error_upper_bound" : 0,
            "sum_other_doc_count" : 0,
            "buckets" : [
              {
                "key" : <valueFromField2>,
                "doc_count" : 2,
                "duplicateFIELD3" : {
                  "doc_count_error_upper_bound" : 0,
                  "sum_other_doc_count" : 0,
                  "buckets" : [
                    {
                      "key" : <valueFromField3>,
                      "doc_count" : 2,
                      "duplicateFIELD4" : {
                        "doc_count_error_upper_bound" : 0,
                        "sum_other_doc_count" : 0,
                        "buckets" : [ ]
                      }
                    }
                  ]
                }
              },
              {
                "key" : <valueFromField2>,
                "doc_count" : 2,
                "duplicateFIELD3" : {
                  "doc_count_error_upper_bound" : 0,
                  "sum_other_doc_count" : 0,
                  "buckets" : [
                    {
                      "key" : <valueFromField3>,
                      "doc_count" : 2,
                      "duplicateFIELD4" : {
                        "doc_count_error_upper_bound" : 0,
                        "sum_other_doc_count" : 0,
                        "buckets" : [ ]
                      }
                    }
                  ]
                }
              }
            ]
          }
        },
        {
          "key" : <valueFromField1>,
          "doc_count" : 4,
          "duplicateFIELD2" : {
            "doc_count_error_upper_bound" : 0,
            "sum_other_doc_count" : 0,
            "buckets" : [
              {
                "key" : <valueFromField2>,
                "doc_count" : 2,
                "duplicateFIELD3" : {
                  "doc_count_error_upper_bound" : 0,
                  "sum_other_doc_count" : 0,
                  "buckets" : [
                    {
                      "key" : <valueFromField3>,
                      "doc_count" : 2,
                      "duplicateFIELD4" : {
                        "doc_count_error_upper_bound" : 0,
                        "sum_other_doc_count" : 0,
                        "buckets" : [ ]
                      }
                    }
                  ]
                }
              },
              {
                "key" : <valueFromField2>,
                "doc_count" : 2,
                "duplicateFIELD3" : {
                  "doc_count_error_upper_bound" : 0,
                  "sum_other_doc_count" : 0,
                  "buckets" : [
                    {
                      "key" : <valueFromField3>,
                      "doc_count" : 2,
                      "duplicateFIELD4" : {
                        "doc_count_error_upper_bound" : 0,
                        "sum_other_doc_count" : 0,
                        "buckets" : [ ]
                      }
                    }
                  ]
                }
              }
            ]
          }
        },
        ...

我跳过了一些看起来相似的输出。

现在,我可以扫描这个复杂的深度嵌套数据结构,并发现所有这些嵌套桶中都没有存储文档。但这似乎相当麻烦。我想可能有更好(更直接)的方法来做到这一点。

此外,如果我想检查四个以上的字段,这个嵌套结构将会不断地变大。因此它不能很好地扩展,我想避免这种情况。

我能否改进我的解决方案,以便获得一个简单的列表,其中包含所有重复的文档?(也许是彼此重复的文档分组在一起的那些文档)。或者是否有完全不同的方法(例如不使用聚合),它没有我在这里描述的缺点?

编辑:我在ES中找到了一种使用脚本功能的方法here,但在我版本的ES中,这只返回一个错误消息。也许有人能告诉我如何在ES 5.0中实现它?到目前为止,我的尝试没有成功。

编辑:我发现了一种使用现代方式(语言“painless”)的脚本来实现我的方法:

{
  "size": 0,
  "aggs": {
    "duplicateFOO": {
      "terms": {
        "script": {
          "lang": "painless",
          "inline": "doc['FIELD1'].value + doc['FIELD2'].value + doc['FIELD3'].value + doc['FIELD4'].value"
        },                   
        "min_doc_count": 2
      }                        
    }                         
  }
}

这似乎适用于非常小的数据量,并且对于实际数据量会导致错误(circuit_breaking_exception[request] Data too large, data for [<reused_arrays>] would be larger than limit of [6348236390/5.9gb])。您有什么想法可以解决这个问题吗?也许调整ES的某些配置以使用更大的内部缓冲区或类似的方法?
在我的情况下,似乎没有一种避免嵌套的正确解决方案。
幸运的是,我的四个字段中有三个具有非常有限的值范围;第一个只能是1或2,第二个可以是1、2或3,第三个可以是1、2、3或4。由于这只是24个组合,所以我目前选择在应用聚合之前过滤掉完整数据集的1/24,然后只处理剩余的一个字段。然后我必须将所有操作应用24次(每个受限制的三个字段的每个组合都要应用一次),但这仍然比一次处理整个数据集更可行。
现在我发送的查询(即24个查询之一)看起来像这样:
{
  "size": 0,
  "query": {
    "bool": {
      "must": [
        { "match": { "FIELD1": 2 } },
        { "match": { "FIELD2": 3 } },
        { "match": { "FIELD3": 4 } } ] } },
  "aggs": {
    "duplicateFIELD4": {
      "terms": {
        "field": "FIELD4",
        "min_doc_count": 2 } } } }

当然,这样做的结果不再是嵌套的了。但是如果多个字段包含更大范围的任意值,则无法执行此操作。
我还发现,如果必须进行嵌套,则具有最小值范围(例如仅两个值,如“1或2”)的字段应该是最内层的,而具有最大值范围的字段应该是最外层的。这将极大地提高性能(但在我的情况下仍然不够)。如果做错了,你可能会遇到无法使用的查询(数小时内没有响应,最终服务器端出现内存溢出)。
我现在认为适当地聚合是解决像我这样的问题的关键。使用脚本来拥有一个平坦的桶列表(如我所描述的)的方法很容易使服务器过载,因为它无法以任何方式分配任务。如果根本找不到双倍,它必须在内存中保存每个文档的一个桶(其中只有一个文档)。即使只有几个双倍可以找到,对于较大的数据集也无法完成此操作。如果没有其他选择,就需要人为地将数据集分成组。例如,可以通过从相关字段构建哈希并使用最后4位将文档放入16个组之一来创建16个子数据集。然后可以单独处理每个组;使用此技术,双倍肯定会落入一个组中。
但是,与这些一般思考无关,ES API应该提供浏览聚合结果的任何手段。遗憾的是,目前还没有这样的选项。

在我看来,最好和正确的方法是在您的文档中创建一个新字段(当然,这意味着将数据重新索引到新索引中),该字段应包含您要查找的那些字段组合。然后,在搜索时,您可以对该单个字段进行聚合。 - Andrei Stefan
如果您正在连接不同的字段,最好在它们之间添加一些分隔符,这样您就可以更加确定多个字段的合并不同于其他字段组合的合并。例如:'test' + 'ing' = 'testing' => 'test' + '#' + 'ing' <> 'testing'。 - Roeland Van Heddegem
2个回答

1
你上次的方法似乎是最好的。你可以按照以下方式更新你的elasticsearch设置
indices.breaker.request.limit: "75%"
indices.breaker.total.limit: "85%"

我选择了 75% ,因为默认值是 60% ,而且你的elasticsearch中有 5.9gb 的数据,你的查询变得约为 ~6.3gb,这大约是基于日志的 71.1%

circuit_breaking_exception: [request] Data too large, data for [<reused_arrays>] would be larger than limit of [6348236390/5.9gb]

最后,根据 elasticsearch 文档indices.breaker.total.limit 必须大于 indices.breaker.fielddata.limit

请查看我的回答,了解为什么这种方法在处理真正大型数据集时不可行(我现在认为是这样),因此将某些技术限制向上推并不是一个解决方案。 - Alfe

0
一个在Logstash场景中可能有效的想法是使用复制字段:
将所有组合复制到单独的字段并将它们连接起来:
mutate {
  add_field => {
    "new_field" => "%{oldfield1} %{oldfield2}"
  }
}

对新字段进行聚合。

在这里看一下:https://www.elastic.co/guide/en/logstash/current/plugins-filters-mutate.html

我不知道add_field是否支持数组(如果您查看文档,则其他人确实支持)。如果不支持,您可以尝试添加多个新字段并使用merge仅有一个字段。

如果您能够在索引时执行此操作,那肯定会更好。

您只需要组合(A_B),而不是所有排列组合(A_B,B_A)。


当然,这可能是可行的,这取决于我们所讨论的大小。在我的情况下,我担心变异过程可能需要相当长的时间,如果我需要多个字段集相等,我将不得不创建几个版本的新字段。在我的情况下,这也可能成为一个内存问题 :-/ - Alfe
我认为你的回答只适用于Logstash的情况。我没有使用Logstash,但仍然想解决我的问题;-) - Alfe
那么使用组合重新索引呢?如果你有10个字段,那么每个字段的索引次数将是正常情况下的9倍,而该字段可能是一个非分析字段。如果你不想这样做,可以考虑使用脚本字段来存储这些值,但是通过冗余索引字段来提高性能肯定会更好。 - Dennis Ich

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