【问题标题】:Applying "tag" to millions of documents, using bulk/update methods使用批量/更新方法将“标签”应用于数百万个文档
【发布时间】:2014-12-13 04:51:25
【问题描述】:

我们的 ElasticSearch 实例中有大约 55.000.000 个文档。我们有一个带有 user_ids 的 CSV 文件,最大的 CSV 有 9M 条目。我们的文档以 user_id 作为 key,所以这很方便。

我发布此问题是因为我想讨论并拥有完成此问题的最佳选择,因为有不同的方法可以解决此问题。如果用户文档还没有它,我们需要将新的“标签”添加到文档中,例如用“stackoverflow”或“github”标记用户。

  1. 有经典的partial update 端点。这听起来很慢,因为我们需要迭代超过 9M 的 user_id 并为每个用户发出 api 调用。
  2. bulk request,它提供了一些更好的性能,但一次调用中可以提及的文档数量有限,只有 1000-5000 个。知道批次何时过大有点知道我们需要如何在旅途中学习。
  3. 然后是official open issue for /update_by_query 端点,它有大量流量,但没有确认它在标准版本中实现。
  4. 在这个未解决的问题上,提到了update_by_query plugin,它应该可以提供更好的处理,但存在一些旧的和未解决的问题,用户抱怨性能问题和内存问题。
  5. 我不确定它在 EL 上是否可行,但我想我会将所有 CSV 条目加载到一个单独的索引中,并且会以某种方式加入两个索引并应用脚本,如果不存在则添加标签。

所以问题仍然是最好的方法是什么,如果你们中的一些人过去这样做过,请确保分享你的数字/表现以及这次你将如何做不同的事情。

【问题讨论】:

  • 有趣的问题;我会选择选项#2 与选项#5 混合;每个请求 1k 的文档是好的;在添加新标签之前,我还将通过 user_id 为空创建一个 55M 唯一文档,然后更新文档

标签: search elasticsearch


【解决方案1】:

在等待查询支持更新时,我选择了:

  1. 使用scan/scroll API 循环访问要标记的文档ID (related answer)。

  2. 使用bulk API 执行partial updates 为每个匹配的文档设置标签。

此外,我将标记数据(您的 CSV)存储在单独的文档类型中,并从中查询并在所有新文档创建时对其进行标记,即不必先索引然后更新。

Python sn-p 来说明方法:

def actiongen():
    docs = helpers.scan(es, query=myquery, index=myindex, fields=['_id'])
    for doc in docs:
        yield {
            '_op_type': 'update',
            '_index': doc['_index'],
            '_type': doc['_type'],
            '_id': doc['_id'],
            'doc': {'tags': tags},
        }

helpers.bulk(es, actiongen(), index=args.index, stats_only=True)

【讨论】:

  • 这基本上是 update-by-query 插件执行的操作,增加了许多网络往返。虽然插件还不支持部分文档,只支持脚本。
  • @Teka,很高兴知道。我猜你的意思是这个插件没有许多不必要的网络往返。
  • 这确实是我的意思。
【解决方案2】:

使用上述update-by-query plugin,您只需调用:

curl -XPOST localhost:9200/index/type/_update_by_query -d '{
    "query": {"filtered": {"filter":{
        "not": {"term": {"tag": "github"}}
    }}},
    "script": "ctx._source.label = \"github\""
}'

update-by-query 插件只接受脚本,不接受部分文档。

至于性能和内存问题,我想最好还是试试看吧。

【讨论】:

    【解决方案3】:

    我会使用批量 API,但需要注意的是,您应该尝试以最少的次数更新每个文档。更新只是原子删除和添加,并将已删除的文档作为墓碑留下,直到可以合并为止。

    发送一个 groovy 脚本来执行更新可能在这里最有意义,因此您不必先获取文档。

    【讨论】:

    • 提供部分文档比编写脚本更有效。 Update API 文档提供了此示例:curl -XPOST 'localhost:9200/test/type1/1/_update' -d '{"doc":{"label":"github"},"detect_noop": true}'。但是请注意,它不能附加到数组,它只能将字段设置为给定值或值数组。
    • 只是添加了 Nik 的 pull request 的链接 - github.com/elastic/elasticsearch/pull/15125
    【解决方案4】:

    您能否创建一个父/子关系,从而可以添加一个“标签”类型,该类型引用您的“帖子”类型作为其父级。这样您就不需要对数据执行完整的重新索引 - 只需根据相应的帖子 ID 为每个相应的标签编制索引。

    【讨论】:

    • 如果文档很大或经常更新,这种方法确实很有趣。但是,在选择此解决方案之前,应该知道父子查询在查询时会产生内存成本:所有父 ID 在内存中加载以有效地执行连接。根据性能需求,一劳永逸地更新原始文档可能更有效。
    【解决方案5】:

    一个非常古老的线程。通过 github 页面登陆实现“通过查询更新”,看看它是否在 2.0 中实现,但不幸的是没有。多亏了 Teka 的插件,如果更新很小,从意义上来说这是非常可行的,但我们的用例是每天根据某些复杂的查询更新数百万个文档。最后,我们转向了 es-hadoop 连接器。尽管基础设施在这里是一个很大的开销,但是通过 Spark 并行化获取/更新/插入文档的过程无论如何都对我们有帮助。如果有人在过去一年中发现了任何其他建议:),很想听听。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2018-03-30
      • 1970-01-01
      • 2019-05-18
      • 1970-01-01
      • 2019-08-15
      • 1970-01-01
      • 1970-01-01
      • 2018-03-31
      相关资源
      最近更新 更多