参考上一篇博客,测试mongodb集群,插入4千万数据的报告:
https://cnodejs.org/topic/5518a873687c387d2f5b2953
起初打算用Node.js
去做这个性能 测试,碰了几次壁后,改用Python
顺利完成任务,现在回过头来再看这个问题,发现里面似乎有坑。
先放上一段Node.js
的插入10万条记录的代码:
var MongoClient = require('mongodb').MongoClient;
var url = 'mongodb://127.0.0.1:27017/testdb';
var dataList = []
var globalCount = 0
console.time('insert,10w');
MongoClient.connect(url, {
server: {
poolSize: 50
}
},function(err, db) {
if(err) throw(err)
for(var i=0; i<100000; i++){
db.collection('test1').insert({
count:i
}, function(err, r) {
if(err) throw(err)
globalCount++
if(globalCount>=100000){
console.timeEnd('insert,10w');
process.exit(1);
}
});
}
});
测试结果慢的令人发指,Node.js
版本是最新的0.12.2
E:\nodejs>node mongodb_error2.js
insert,10w: 9791ms
我用同样的Python
脚本去做:
import pymongo
import json
import datetime,time
import copy
import sys, os
def getTimestampFromDatetime(d=None):
if d is None:
d = datetime.datetime.now()
return time.mktime(d.timetuple())
if __name__ == '__main__':
start = getTimestampFromDatetime()
client = pymongo.MongoClient("localhost", 27017, max_pool_size=50)
db = client.testdb
saveData = []
for i in range(0, 100000):
saveData.append({
'count':i
})
db.test2.insert(saveData)
end = getTimestampFromDatetime()
print('time: {0}s'.format(end-start))
测试结果如下:
E:\python>python save_10w.py
time: 1.0
我在想是不是Node.js
代码写的太垃圾,于是我找了Mongodb
包的另外一个insertMany
方法,但是结果却报错了:
E:\nodejs\node_modules\mongodb\lib\utils.js:97
process.nextTick(function() { throw err; });
^
Error
at Object.<anonymous> (E:\nodejs\node_modules\mongodb\node_modules\mongodb-c
ore\lib\error.js:42:24)
at Module._compile (module.js:460:26)
at Object.Module._extensions..js (module.js:478:10)
at Module.load (module.js:355:32)
at Function.Module._load (module.js:310:12)
at Module.require (module.js:365:17)
at require (module.js:384:17)
at Object.<anonymous> (E:\nodejs\node_modules\mongodb\node_modules\mongodb-core\index.js:2:17)
at Module._compile (module.js:460:26)
at Object.Module._extensions..js (module.js:478:10)
下图是Node.js
插入10万条后,数据库的截图
下图是Python
插入10万条后,数据库的截图
可以看到数据库里都有10万条数据了,两者都成功的插入了,并且大小都一样,求大神指教,这个怎么破?
我也测了一下,我这平均在4400左右.
确实很吓人:
var MongoClient = require('mongodb').MongoClient;
var url = 'mongodb://127.0.0.1:27017/testdb';
MongoClient.connect(url, function(err, db) {
console.time('insert,10w');
if (err) throw (err)
var _collection = db.collection('test1');
for (var i = 0; i < 100000; i++) {
_collection.insert({
count: i
});
}
console.timeEnd('insert,10w');
process.exit(1);
});
我也用Meteor试了一下,结果不忍直视:
if (Meteor.isServer) {
Meteor.startup(function() {
// code to run on server at startup
});
Meteor.methods({
'insert': function() {
console.time('insert 10w');
for (var i = 0; i < 100000; i++) {
Test.insert({
count: i
});
}
console.timeEnd('insert 10w');
process.exit(1);
}
});
}
@wuxiaoqian88 不是的,都是循环插入,我之前也跟你一样的想法,后来我看了pymongo
的源码,发现也是把list
循环起来插入的,和Node.js
操作一样,另外Node.js
还支持一个insertMany
,是支持批量插入,几千条数据少没问题,一旦到几万就报错了。
pymongo 源码,其中docs就是用户插入的list
...
if manipulate:
def gen():
db = self.__database
for doc in docs:
# Apply user-configured SON manipulators. This order of
# operations is required for backwards compatibility,
# see PYTHON-709.
doc = db._apply_incoming_manipulators(doc, self)
if '_id' not in doc:
doc['_id'] = ObjectId()
doc = db._apply_incoming_copying_manipulators(doc, self)
ids.append(doc['_id'])
yield doc
...
不过刚才试了下,python每次循环操作插入和一次性插入,差距还是挺大的,估计是pymongo底层库做掉了这部分事情,要研究下源码了
@DoubleSpout 那就有可能是代码本身的效率问题,尤其在C与javascript交互的那个界面上,是效率热点。
我现在越来越怀疑node.js的设计哲学有问题了,callback被滥用,也用烂了。
@DoubleSpout 这个不清楚,我刚刚用go分别测了一下 单位(毫秒) 第一个就是直接的insert. 第二个采用批处理 测试结果:
第一种
第二种
package main
import "fmt"
import "time"
import "gopkg.in/mgo.v2"
type Count struct {
I int
}
func main() {
session, err := mgo.Dial("127.0.0.1:27017")
if err != nil {
fmt.Print(err)
}
defer session.Close()
b := session.DB("testdb").C("test1").Bulk() // 第二种
// c := session.DB("testdb").C("test1") //第一种
start := time.Now()
for i := 0; i < 100000; i++ {
b.Insert(&Count{i}) //第二种
//c.Insert(&Count{i}) //第一种
}
b.Run()// 第二种
duration := time.Since(start)
fmt.Printf("insert 10w: %v", duration.Seconds()*1e6/float64(1000))
}
差距还是很大的, 都不是一个级别的,对于数据量大的插入也应该有相应的处理,python那个mongo包估计就 做了处理,而node中可能也有。具体的API我没看,这里依据go中的API进行仅仅的猜测。
@DoubleSpout
刚刚稍微看了一下文档
现在我插入10W耗时1s
这个和上面我写的go的批处理一样,只不过比go逊色一点 :)
var MongoClient = require('mongodb').MongoClient;
var url = 'mongodb://127.0.0.1:27017/testdb';
MongoClient.connect(url, function(err, db) {
if (err) throw (err)
var col = db.collection("test1");
var batch = col.initializeUnorderedBulkOp({
useLegacyOps: true
});
console.time('insert,10w');
for (var i = 0; i < 100000; i++) {
batch.insert({
count: i
});
}
batch.execute(function(err) {
if (err) {
console.log(err)
}
console.timeEnd('insert,10w');
})
});
@DoubleSpout 其实你还可以把循环push这个操作踢出去,因为这个也是耗的。 总结就是对于mongodb的写入,8000条插一个和插入8000是两个完全不同的概念,而且常规情况下也是前者,不可能会一条条插8000次的。 by the way:mongodb的2.X驱动对于insertMany做了条数限制,只允许同时批量插入1000条。http://mongodb.github.io/node-mongodb-native/2.0/meta/changes-from-1.0/
var MongoClient = require('mongodb').MongoClient;
var url = 'mongodb://127.0.0.1:27017/testdb';
var dataList = []
var globalCount = 0
console.time('insert,10w');
MongoClient.connect(url, {
server: {
poolSize: 50
}
},function(err, db) {
if(err) throw(err)
var listData = []
for(var i=0; i<100000; i++){
listData.push({
count:i
})
}
db.collection('test1').insert(listData,function(err,results){
if(err){
console.log(err)
}else{
console.log(results)
}
});
console.timeEnd('insert,10w');
});
我这边还要快,但是
{ [MongoError: exceeded maximum write batch size of 1000]
name: 'MongoError',
message: 'exceeded maximum write batch size of 1000' }
insert,10w: 413ms
报错了 貌似插不进去吧
偶然在nodeclub里看到的通过循环查询评论的作者信息的for循环,这应该算是异步的?@alsotang,这样插入10W条记录,虽然不能保证顺序,但是会不会稍微快点?
for (var j = 0; j < 100000; j++) {
(function () {
//do someting
})();
}
insert,10w: 5746ms
(one by one) v.s. insert,10w: 961ms
(bulk insert)
var MongoClient = require('mongodb').MongoClient;
var url = 'mongodb://127.0.0.1:27017/testdb';
console.time('insert,10w');
MongoClient.connect(url, {
server: {
poolSize: 50
}
}, function(err, db) {
var col = db.collection('test2');
var bulk = col.initializeUnorderedBulkOp();
for(var i = 0; i < 100000; i++){
bulk.insert({
count: i
});
}
bulk.execute();
console.timeEnd('insert,10w');
process.exit(1);
});
bulk.execute();
console.timeEnd('insert,10w');
process.exit(1);
@district10 这三步都是异步的吧!估计数据没保存好,就打印时间和退出了吧!