草庐IT

mongodb - 聚合期间出现 Mongo 错误 16996 - 生成的文档太大

coder 2023-11-04 原文

我正在解析维基百科转储,以便使用面向链接的元数据。其中一个集合名为 articles,其形式如下:

{
    _id : "Tree",
    id: "18955875",
    linksFrom: " [
        {
        name: "Forest",
        count: 6
        },
        [...]
    ],
    categories: [
        "Trees",
        "Forest_ecology"
        [...]
    ]
}

linksFrom 字段存储这篇文章指向的所有文章,以及发生的次数。接下来,我想创建另一个字段 linksTo,其中包含指向这篇文章的所有文章。一开始,我浏览了整个合集并更新了每篇文章,但是由于它们很多,所以花费了太多时间。出于性能目的,我切换到聚合,并在较小的集合上进行了尝试——效果很好,与旧方法相比速度超快。聚合流水线如下:

db.runCommand(
    {
        aggregate: "articles",
        pipeline : [
            {
                $unwind: "$linksFrom"
            },
            {
                $sort: { "linksFrom.count": -1 }
            },
            {
                $project:
                {
                    name: "$_id",
                    linksFrom: "$linksFrom"
                }
            },
            {
                $group:
                {
                    _id: "$linksFrom.name",
                    linksTo: { $push: { name: "$name", count: { $sum : "$linksFrom.count" } } },
                }
            },
            {
                $out: "TEMPORARY"
            }
        ] ,
        allowDiskUse: true
    }
)

但是,在作为英文维基百科的大型数据集上,几分钟后我收到以下错误:

{
    "ok" : 0,
    "errmsg" : "insert for $out failed: { connectionId: 24, err: \"BSONObj size: 24535193 (0x1766099) is invalid. Size must be between 0 and 16793600(16MB) First element: _id: \"United_States\"\", code: 10334, n: 0, ok: 1.0 }",
    "code" : 16996
}

我了解到有太多文章,链接到 United_States 文章,相应的文档大小增长到 16MB 以上,目前接近 24MB。不幸的是,我什至无法检查是否是这种情况(错误消息有时往往会撒谎)...因此,我正在尝试更改模型,以便文章之间的关系使用 ID 而不是长名称存储,但我'恐怕这可能还不够 - 尤其是因为我的计划是稍后为每篇文章合并这两个集合...

问题是:有没有人有更好的主意?我不想尝试增加限制,而是在考虑将此数据存储在数据库中的不同方法。


更新 在 Markus 发表评论后

Markus 是正确的,我正在使用 SAX 解析器,事实上,我已经以类似的方式存储了所有链接。除了文章之外,我还有另外三个集合 - 一个带有链接,另外两个是标签词干标签。第一个以下列方式存储转储中出现的所有链接:

{
    _id : "tree",
    stemmedName: "tree",
    targetArticle: "Christmas_tree"
}

_id 存储用于表示给定链接的文本,stemmedName 表示词干化的 _id,targetArticle 标记该文本指向的文章.我正在为此添加 sourceArticle,因为这显然是个好主意。

第二个集合labels包含如下文档:

{
    _id : "tree",
    targetArticles: [
        {
            name: "Christmas_tree",
            count: 1
        },
        {
            name: "Tree",
            count: 166
        }
        [...]
    ]
}

第三个 stemmed-labels 类似于 labels,其 _id 是根标签的词干版本。

到目前为止,第一个集合 links 用作其他两个集合的基线。我按名称将标签分组在一起,这样我只对每个短语进行一次查找,然后我可以通过一次查询立即获取所有目标文章。然后我使用 articleslabels 集合来:

  1. 查找具有给定名称的标签。
  2. 获取所有可能的文章 指向。
  3. 比较这些的传入和传出链接 文章。

这就是主要问题的来源。我认为最好将给定短语的所有可能文章存储在一个文档中,而不是将它们分散在 links 集合中。直到现在我才想到,只要查找被索引,一个大文档或许多小文档的整体性能可能是相同的!这是一个正确的假设吗?

最佳答案

我认为你的数据模型是错误的。个别文章(让我们以维基百科为例)链接的频率可能比您存储在文档中的频率更高(尽管有点理论上)。 Embedding only works with One-To(-Very)-Few™ relationships.

所以基本上,我认为您应该更改模型。我会告诉你我会怎么做。

我将在这个例子中使用 mongoshell 和 JavaScript,因为它是通用语言。您可能需要进行相应的翻译。

问题

让我们从您想回答的问题开始:

  1. 对于给定的文章,还有哪些其他文章链接到该文章?
  2. 对于给定的文章,该文章链接到哪些其他文章?
  3. 对于给定的文章,有多少文章链接到它?
  4. 可选:对于给定的文章,它链接到多少篇文章?

爬行

我基本上要做的是对文章实现 SAX 解析器,为您遇到的每个文章链接创建一个新文档。文档本身应该相当简单:

{
  "_id": new ObjectId(),
  // optional, for recrawling or pointing out a given state
  "date": new ISODate(),
  "article": wikiUrl,
  "linksTo": otherWikiUrl
}

请注意,您不应该执行插入,而应该执行更新插入。这样做的原因是我们不想记录链接的数量,而是链接到的文章。如果我们进行插入,articlelinksTo 的相同组合可能会出现多次。

所以我们在遇到链接时的声明例如如下所示:

db.links.update(
  { "article":"HMS_Warrior_(1860)", "linksTo":"Royal_Navy" },
  { "date": new ISODate(), "article":"HMS_Warrior_(1860)", "linksTo":"Royal_Navy" },   
  { upsert:true }
)

回答问题

您可能已经猜到,现在回答问题变得非常简单。我使用以下语句创建了一些文档:

db.links.update(
  { "article":"HMS_Warrior_(1860)", "linksTo":"Royal_Navy" },
  { "date": new ISODate(), "article":"HMS_Warrior_(1860)", "linksTo":"Royal_Navy" },
  { upsert:true }
)
db.links.update(
  { "article":"Royal_Navy", "linksTo":"Mutiny_on_the_Bounty" },
  { "date":new ISODate(), "article":"Royal_Navy", "linksTo":"Mutiny_on_the_Bounty" },
  { upsert:true }
)
db.links.update(
  { "article":"Mutiny_on_the_Bounty", "linksTo":"Royal_Navy"},
  { "date":new ISODate(), "article":"Mutiny_on_the_Bounty", "linksTo":"Royal_Navy" },
  { upsert:true }
)

对于给定的文章,还有哪些其他文章链接到该文章?

我们发现我们不应该使用聚合,因为这可能会超过大小限制。但我们不必这样做。我们只需使用游标并收集结果:

var toLinks =[]

var cursor = db.links.find({"linksTo":"Royal_Navy"},{"_id":0,"article":1})
cursor.forEach(
  function(doc){
    toLinks.push(doc.article);
  }
)
printjson(toLinks)
// Output: [ "HMS_Warrior_(1860)", "Mutiny_on_the_Bounty" ]

对于给定的文章,该文章链接到哪些其他文章?

这与第一个问题非常相似——我们基本上只更改查询:

var fromLinks = []
var cursor = db.links.find({"article":"Royal_Navy"},{"_id":0,"linksTo":1})
cursor.forEach(
  function(doc){
    fromLinks.push(doc.linksTo)
  }
)
printjson(fromLinks)
// Output: [ "Mutiny_on_the_Bounty" ]

对于给定的文章,有多少文章链接到它?

很明显,如果您已经回答了问题 1,您可以简单地检查 toLinks.length。但让我们假设你没有。还有另外两种方法可以做到这一点

使用.count()

您可以在副本集上使用此方法。在分片集群上,这效果不佳。但这很容易:

db.links.find({ "linksTo":"Royal_Navy" }).count()
// Output: 2

使用聚合

这适用于任何环境并且并不复杂:

db.links.aggregate([
  { "$match":{ "linksTo":"Royal_Navy" }},
  { "$group":{ "_id":"$linksTo", "isLinkedFrom":{ "$sum":1 }}}
])
// Output: { "_id" : "Royal_Navy", "isLinkedFrom" : 2 }

可选:对于给定的文章,它链接到多少篇文章?

同样,您可以通过使用 .count() 方法读取问题 2 中的数组长度来回答这个问题。聚合再次很简单

db.links.aggregate([
  { "$match":{ "article":"Royal_Navy" }},
  { "$group":{ "_id":"$article", "linksTo":{ "$sum":1 }}}
])
// Output: { "_id" : "Royal_Navy", "linksTo" : 1 }

指数

至于索引,我还没有真正检查过它们,但是字段上的单个索引可能是您想要的:

db.links.createIndex({"article":1})
db.links.createIndex({"linksTo":1})

复合索引不会有太大帮助,因为顺序很重要,我们并不总是要求第一个字段。所以这可能是尽可能优化的。

结论

我们正在使用一个极其简单、可扩展的模型和相当简单的查询和聚合来回答您对数据提出的问题。

关于mongodb - 聚合期间出现 Mongo 错误 16996 - 生成的文档太大,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/34510047/

有关mongodb - 聚合期间出现 Mongo 错误 16996 - 生成的文档太大的更多相关文章

  1. ruby - 使用 RubyZip 生成 ZIP 文件时设置压缩级别 - 2

    我有一个Ruby程序,它使用rubyzip压缩XML文件的目录树。gem。我的问题是文件开始变得很重,我想提高压缩级别,因为压缩时间不是问题。我在rubyzipdocumentation中找不到一种为创建的ZIP文件指定压缩级别的方法。有人知道如何更改此设置吗?是否有另一个允许指定压缩级别的Ruby库? 最佳答案 这是我通过查看ruby​​zip内部创建的代码。level=Zlib::BEST_COMPRESSIONZip::ZipOutputStream.open(zip_file)do|zip|Dir.glob("**/*")d

  2. ruby-on-rails - Rails 常用字符串(用于通知和错误信息等) - 2

    大约一年前,我决定确保每个包含非唯一文本的Flash通知都将从模块中的方法中获取文本。我这样做的最初原因是为了避免一遍又一遍地输入相同的字符串。如果我想更改措辞,我可以在一个地方轻松完成,而且一遍又一遍地重复同一件事而出现拼写错误的可能性也会降低。我最终得到的是这样的:moduleMessagesdefformat_error_messages(errors)errors.map{|attribute,message|"Error:#{attribute.to_s.titleize}#{message}."}enddeferror_message_could_not_find(obje

  3. ruby - 在 jRuby 中使用 'fork' 生成进程的替代方案? - 2

    在MRIRuby中我可以这样做:deftransferinternal_server=self.init_serverpid=forkdointernal_server.runend#Maketheserverprocessrunindependently.Process.detach(pid)internal_client=self.init_client#Dootherstuffwithconnectingtointernal_server...internal_client.post('somedata')ensure#KillserverProcess.kill('KILL',

  4. ruby - 如何使用 Ruby aws/s3 Gem 生成安全 URL 以从 s3 下载文件 - 2

    我正在编写一个小脚本来定位aws存储桶中的特定文件,并创建一个临时验证的url以发送给同事。(理想情况下,这将创建类似于在控制台上右键单击存储桶中的文件并复制链接地址的结果)。我研究过回形针,它似乎不符合这个标准,但我可能只是不知道它的全部功能。我尝试了以下方法:defauthenticated_url(file_name,bucket)AWS::S3::S3Object.url_for(file_name,bucket,:secure=>true,:expires=>20*60)end产生这种类型的结果:...-1.amazonaws.com/file_path/file.zip.A

  5. ruby-on-rails - 迷你测试错误 : "NameError: uninitialized constant" - 2

    我遵循MichaelHartl的“RubyonRails教程:学习Web开发”,并创建了检查用户名和电子邮件长度有效性的测试(名称最多50个字符,电子邮件最多255个字符)。test/helpers/application_helper_test.rb的内容是:require'test_helper'classApplicationHelperTest在运行bundleexecraketest时,所有测试都通过了,但我看到以下消息在最后被标记为错误:ERROR["test_full_title_helper",ApplicationHelperTest,1.820016791]test

  6. ruby-on-rails - 如何在 Rails View 上显示错误消息? - 2

    我是rails的新手,想在form字段上应用验证。myviewsnew.html.erb.....模拟.rbclassSimulation{:in=>1..25,:message=>'Therowmustbebetween1and25'}end模拟Controller.rbclassSimulationsController我想检查模型类中row字段的整数范围,如果不在范围内则返回错误信息。我可以检查上面代码的范围,但无法返回错误消息提前致谢 最佳答案 关键是您使用的是模型表单,一种显示ActiveRecord模型实例属性的表单。c

  7. 使用 ACL 调用 upload_file 时出现 Ruby S3 "Access Denied"错误 - 2

    我正在尝试编写一个将文件上传到AWS并公开该文件的Ruby脚本。我做了以下事情:s3=Aws::S3::Resource.new(credentials:Aws::Credentials.new(KEY,SECRET),region:'us-west-2')obj=s3.bucket('stg-db').object('key')obj.upload_file(filename)这似乎工作正常,除了该文件不是公开可用的,而且我无法获得它的公共(public)URL。但是当我登录到S3时,我可以正常查看我的文件。为了使其公开可用,我将最后一行更改为obj.upload_file(file

  8. ruby-on-rails - 错误 : Error installing pg: ERROR: Failed to build gem native extension - 2

    我克隆了一个rails仓库,我现在正尝试捆绑安装背景:OSXElCapitanruby2.2.3p173(2015-08-18修订版51636)[x86_64-darwin15]rails-v在您的Gemfile中列出的或native可用的任何gem源中找不到gem'pg(>=0)ruby​​'。运行bundleinstall以安装缺少的gem。bundleinstallFetchinggemmetadatafromhttps://rubygems.org/............Fetchingversionmetadatafromhttps://rubygems.org/...Fe

  9. ruby - #之间? Cooper 的 *Beginning Ruby* 中的错误或异常 - 2

    在Cooper的书BeginningRuby中,第166页有一个我无法重现的示例。classSongincludeComparableattr_accessor:lengthdef(other)@lengthother.lengthenddefinitialize(song_name,length)@song_name=song_name@length=lengthendenda=Song.new('Rockaroundtheclock',143)b=Song.new('BohemianRhapsody',544)c=Song.new('MinuteWaltz',60)a.betwee

  10. ruby-on-rails - 每次我尝试部署时,我都会得到 - (gcloud.preview.app.deploy) 错误响应 : [4] DEADLINE_EXCEEDED - 2

    我是Google云的新手,我正在尝试对其进行首次部署。我的第一个部署是RubyonRails项目。我基本上是在关注thisguideinthegoogleclouddocumentation.唯一的区别是我使用的是我自己的项目,而不是他们提供的“helloworld”项目。这是我的app.yaml文件runtime:customvm:trueentrypoint:bundleexecrackup-p8080-Eproductionconfig.ruresources:cpu:0.5memory_gb:1.3disk_size_gb:10当我转到我的项目目录并运行gcloudprevie

随机推荐