Я тестирую производительность Node.js с помощью MongoDB. Я знаю, что каждый из них отлично зависит от другого, но я пробую несколько тестов, чтобы почувствовать их. Я столкнулся с этой проблемой, и у меня возникли проблемы с определением источника.
Проблема
Я пытаюсь вставить 1 000 000 записей в одну программу Node.js. Он абсолютно ползет. Мы говорим о 20-минутном времени исполнения. Это происходит, будь то мой Mac или CentOS, хотя поведение немного отличается между ними. В конце концов, это завершается.
Эффект похож на подкачку, хотя это не так (память никогда не превышает 2 ГБ). Для MongoDB открыты только 3 подключения, и в большинстве случаев нет данных. Похоже, что он выполняет много переключений контекста, а ядро ядра Node.js максимизировано.
Эффект подобен эффекту, указанному в этой теме .
Я стараюсь использовать PHP, и он заканчивается через 2-3 минуты. Без драмы.
Зачем?
Возможные причины
В настоящее время я считаю, что это либо проблема сокета Node.js, что-то происходит с libev за кулисами, либо какая-то другая проблема с node-mongodb. Возможно, я совершенно не прав, поэтому я ищу здесь небольшое руководство.
Что касается других адаптеров Node.js MongoDB, я пробовал монгольский язык и, похоже, размещает в очереди документы, чтобы их вставлять в пакет, и заканчивается нехватка памяти. Вот и все. (Боковое замечание: я понятия не имею, почему на этом тоже, так как он даже не приближается к моему пределу в ящике на 16 ГБ, – но я не потрудился расследовать это дальше).
Вероятно, я должен упомянуть, что я действительно тестировал кластер мастеров / рабочих с 4 рабочими (на четырехъядерной машине) и закончил через 2-3 минуты.
Код
Вот моя программа Node.js CoffeeScript:
mongodb = require "mongodb" microtime = require "microtime" crypto = require "crypto" times = 1000000 server = new mongodb.Server "127.0.0.1", 27017 db = mongodb.Db "test", server db.open (error, client) -> throw error if error? collection = mongodb.Collection client, "foo" for i in [0...times] console.log "Inserting #{i}..." if i % 100000 == 0 hash = crypto.createHash "sha1" hash.update "" + microtime.now() + (Math.random() * 255 | 0) key = hash.digest "hex" doc = key: key, foo1: 1000, foo2: 1000, foo3: 1000, bar1: 2000, bar2: 2000, bar3: 2000, baz1: 3000, baz2: 3000, baz3: 3000 collection.insert doc, safe: true, (error, response) -> console.log error.message if error
И вот примерно эквивалентная PHP-программа:
<?php $mongo = new Mongo(); $collection = $mongo->test->foo; $times = 1000000; for ($i = 0; $i < $times; $i++) { if ($i % 100000 == 0) { print "Inserting $i...\n"; } $doc = array( "key" => sha1(microtime(true) + rand(0, 255)), "foo1" => 1000, "foo2" => 1000, "foo3" => 1000, "bar1" => 2000, "bar2" => 2000, "bar3" => 2000, "baz1" => 3000, "baz2" => 3000, "baz3" => 3000 ); try { $collection->insert($doc, array("safe" => true)); } catch (MongoCursorException $e) { print $e->getMessage() . "\n"; } }
Похоже, что вы используете предел кучи по умолчанию в V8. Я написал сообщение в блоге об устранении этого ограничения.
Сборщик мусора, вероятно, сходит с ума и пережевывает процессор, так как он будет постоянно выполняться до тех пор, пока вы не достигли предела 1,4 ГБ.
Что произойдет, если вы явно вернете значение в конце функции обратного вызова db.open? Ваш сгенерированный код javascript подталкивает все ваши данные collection.insert к большому массиву «_results», который, как мне кажется, будет медленнее и медленнее.
db.open(function(error, client) { var collection, doc, hash, i, key, _i, _results; if (error != null) { throw error; } collection = mongodb.Collection(client, "foo"); _results = []; for (i = _i = 0; 0 <= times ? _i < times : _i > times; i = 0 <= times ? ++_i : --_i) { ... _results.push(collection.insert(doc, { safe: true }, function(error, response) { if (error) { return console.log(error.message); } })); } return _results; });
Попробуйте добавить это в конце вашего кофейни:
collection.insert doc, safe: true, (error, response) -> console.log error.message if error return
* Обновление: * Итак, я на самом деле пытался запустить вашу программу и заметил еще несколько проблем:
Самая большая проблема заключается в том, что вы пытаетесь создать миллион вложений синхронно, что действительно приведет к гибели вашей оперативной памяти и, в конечном итоге, перестанет вставляться (по крайней мере, для меня). Я убил его при 800 МБ ОЗУ или около того.
Вам нужно изменить способ, которым вы вызываете collection.insert (), чтобы он работал асинхронно.
Я переписал его так, вырвав несколько функций для ясности:
mongodb = require "mongodb" microtime = require "microtime" crypto = require "crypto" gen = () -> hash = crypto.createHash "sha1" hash.update "" + microtime.now() + (Math.random() * 255 | 0) key = hash.digest "hex" key: key, foo1: 1000, foo2: 1000, foo3: 1000, bar1: 2000, bar2: 2000, bar3: 2000, baz1: 3000, baz2: 3000, baz3: 3000 times = 1000000 i = times insertDocs = (collection) -> collection.insert gen(), {safe:true}, () -> console.log "Inserting #{times-i}..." if i % 100000 == 0 if --i > 0 insertDocs(collection) else process.exit 0 return server = new mongodb.Server "127.0.0.1", 27017 db = mongodb.Db "test", server db.open (error, db) -> throw error if error? db.collection "foo", (err, collection) -> insertDocs(collection) return return
Который закончил через ~ 3 минуты:
wfreeman$ time coffee mongotest.coffee Inserting 0... Inserting 100000... Inserting 200000... Inserting 300000... Inserting 400000... Inserting 500000... Inserting 600000... Inserting 700000... Inserting 800000... Inserting 900000... real 3m31.991s user 1m55.211s sys 0m23.420s
Кроме того, у него есть преимущество использования <100 МБ оперативной памяти, 70% центрального процессора на узле и 40% процессора на mongod (в двухъядерном ящике, поэтому похоже, что он не максимизирует процессор).