MongoDB 如何批量生成百万条数据?

metal · 2012年02月22日 · 最后由 hlxwell 回复于 2012年02月27日 · 8607 次阅读

经常看别人说 mongodb 生成几百万几千万条的数据测试。

我想知道怎么弄出来的,有没有现成的列子可以观摩的。

1_000_000.times { |t| Model.create(:id => t, ...) }

如果跟 Ruby 无关,则用相同的方式直接在 mongo shell 下用 javascript new 一个 obj,然后 db.collname.insert(obj);

#2 楼 @ashchan 搞定了。可以 js 的。爽呀。

十万数据无法读出来了。 = =。

18:00:25 [clientcursormon] mem (MB) res:1057 virt:2828 mapped:1344
18:00:58 [conn55] query kennx_blog_app_development.posts ntoreturn:20 nscanned:100000 scanAndOrder:1 nreturned:20 reslen:3470 218ms
18:00:58 [conn55] query kennx_blog_app_development.posts ntoreturn:20 nscanned:100000 scanAndOrder:1 nreturned:20 reslen:960 215ms
18:00:58 [conn55] query kennx_blog_app_development.posts ntoreturn:20 nscanned:100000 scanAndOrder:1 nreturned:20 reslen:960 234ms
18:01:32 [conn55] info DFM::findAll(): extent 0:4000 was empty, skipping ahead. ns:kennx_blog_app_development.posts
18:01:32 [conn55] query kennx_blog_app_development.posts ntoreturn:20 nscanned:100000 scanAndOrder:1 nreturned:20 reslen:3470 222ms
18:01:32 [conn55] query kennx_blog_app_development.posts ntoreturn:20 nscanned:100000 scanAndOrder:1 nreturned:20 reslen:960 215ms
18:01:32 [conn55] query kennx_blog_app_development.posts ntoreturn:20 nscanned:100000 scanAndOrder:1 nreturned:20 reslen:960 216ms

第一次访问速度非常慢。如果是一个列表的话,一般都会 find(:all) 吧,这样好像会预先读一次数据库记录,如果有一百万条记录的话,不是会卡死。一个论坛应该没多久就有 10W 条数据了吧。微博这样的东西更加不敢想象。

String.new("百万条数据")

#6 楼 @shawnyu 算你狠~ 哈哈

这个需要做个分页 或者筛选条件吧

mongodb 是不能这么用的。。。

不可以用 1 樓這種用法,你等於是 new 100 萬個 object 在 memory 裡,這樣會 memory bloat 搞到你的電腦 crash 的

http://stackoverflow.com/questions/5099460/mass-insert-mongoid # 一次塞 batch 而不是 new 100 萬個 object。

而且我另外的建議是拆十萬條塞一次這樣

#10 楼 @xdite 在得出这样的结论之前,你有做过实验吗?

我同意你的观点,放 100 万个对象不是最佳方式。但对于一个简单的如例子中的 mongo object 来说,在 loop 中生成 100 万个并依次插入真能把“你”的电脑弄崩?

我实际跑了一下,代码如下:(用的 ORM 是 mongoid,helper 可以无视,只是用来在每次运行前清理和创建相应的 db 和 collection 的)

require File.expand_path(File.dirname(__FILE__) + '/../helper')
require 'benchmark'

class Monkey
  include Mongoid::Document
  field :name
end

Benchmark.bm do |bm|
  p 'create million monkeys, crash me if you can'

  bm.report("1_000_000.times #create") do
    1_000_000.times do |i|
      Monkey.create(:id => i, :name => "##{i} monkey")
    end
  end
end

实际运行时间会跟机器环境有关。我的是 Mid 2010 Mac Mini,8G 内存,SSD,10.8 Mountain Lion,Ruby 1.9.3-p0,但测试用的 Mongodb 是存储在 mount 上来的一块 5400 转的普通笔记本硬盘上的。

看插入到 90 万条时的情况:

no crash

此时 MongoDB 内存使用到 300M 不到(可以跟 Safari 的使用情况比比,哈哈)。Ruby 进程没显示在上面,此时是 28M 左右且保持稳定。

之所以说同意你的观点,是因为 MongoDB 的内存是会一直增长的,这是 MongoDB 使用内存的方式引起的。Ruby 内存使用很稳定,我个人认为这说明至少从 Ruby 本身来说,在 loop 中创建百万级的对象在现在的计算机环境下根本是小 case!而且有理由相信有可能在 loop 中的对象也会被 GC 掉。

另外,MongoDB 和 Ruby 进程的 CPU 占用在这种情况下都会比较高。

反而,单纯从内存角度来讲,我倒是觉得 batch insert 的方式会比较有问题,一个 Array 中放 100 百万个 object 再去插入,这一百万个 object 在插入完成前是不能被释放掉的。你说的分 10 万一次插倒是很实际有效的方法。

花这么多力气来回你,只是想说,方法各有不同,能达到效果就能。但在说“不可以”前,先想想真的不可以吗?至少我可以!

OK,因为今天要来公司,所以到了后拿 Air 又跑了一遍,一图抵万言:

another example

Ruby 和 MongoDB 环境是一样的,机器是去年的,内存 4G。

这次没忘把结果也记下来了:

       user     system      total        real
"create a million monkeys, crash me if you can"
1_000_000.times #create275.970000  14.640000 290.610000 (290.673265)
匿名 #15 2012年02月23日

#14 楼 @ashchan 认真专业的分享精神,tks

#14 楼 @ashchan mongoid 怎么把 id 生成像 sql 那样的自插入的数字。mongodb 的 ID 都是字符串。

#16 楼 @metal 要用其他类型的话,用这个:

identity :type => Integer

要用自插入的数字的话,用华顺 (@huacnlee) 写的这个库:

https://github.com/huacnlee/mongoid_auto_increment_id

#18 楼 @iwinux 谢谢分享。batch insert 大多是以空间(内存)换速度(时间),1 楼的方法当然是最笨而且很慢的方法,但不会是最消耗内存的写法。

#10 楼 @xdite 是不是看成 1_000_000.collect 了?

#20 楼 @ashchan 嗯,我跑了你的测试,内存使用没有明显增加,但 CPU 保持 50% 占用率一直到测试运行完……我还另外写了一个 batch_insert.rb,作为对照。

BTW: 为什么 benchmark 不显示内存占用??

我的机器配置

CPU: Pentium(R) Dual-Core CPU T4200 @ 2.00GHz RAM: DDR2 2G Arch Linux + Ruby 1.9.3-p0

loop_insert.rb 测试结果

       user     system      total        real
"create a million monkeys, crash me if you can"
1_000_000.times #create554.660000  53.490000 608.150000 (641.897088)

batch_insert.rb 的测试结果

       user     system      total        real
1_000_000.times #create155.340000   0.480000 155.820000 (177.756519)

#22 楼 @iwinux cool!

关于 benchmark 为何不显示内存占用,请看它的说明:)

The Benchmark module provides methods to measure and report the time used to execute Ruby code.

雖然我覺得這是一個故意要給我難看的帖 (?)

不過很可惜我現在也找不到當時的環境去弄出來了。不知道是不是有人可以幫我找到這樣的環境再作類似的事?

我之前是在 Ruby 1.8.6 + Rails 2.3.1 + ActiveRecord(MySQL) Macbook 1.83 2G + SATA 硬碟上面跑這種 benchmark 的。不用跑到 100 萬。50 萬就爛掉了...

死因就是 memory bloat。不過我想我大概是帶著 ActiveRecord 的印象自動在回。沒看清這題是 MongoDB

https://rails.lighthouseapp.com/projects/8994/tickets/6129

#24 楼 @xdite 纯粹讨论技术细节而已,对事不对人。如有冒犯还请海涵!

mongostat 可以看到 mongo 大概支持 10000 条/s 记录的插入。就是不知道 dump 到磁盘里的数据是多快。

其实 100 万数据上次我做过一个试验。我发现你去分页 100 万数据,skip 掉 90 万,然后取 10 条。非常非常慢。加了 index 也没有用。而且在任何数据库都有这个问题。大数据量的时候分页不知道有没有其他解决方案

需要 登录 后方可回复, 如果你还没有账号请 注册新账号