mirror of https://github.com/seald/nedb
parent
792eecd1a1
commit
f906b0082e
@ -1,287 +0,0 @@ |
||||
/** |
||||
* Functions that are used in several benchmark tests |
||||
*/ |
||||
const fs = require('fs') |
||||
const Datastore = require('../lib/datastore') |
||||
const Persistence = require('../lib/persistence') |
||||
const { callbackify } = require('util') |
||||
let executeAsap |
||||
|
||||
try { |
||||
executeAsap = setImmediate |
||||
} catch (e) { |
||||
executeAsap = process.nextTick |
||||
} |
||||
|
||||
/** |
||||
* Configure the benchmark |
||||
*/ |
||||
module.exports.getConfiguration = function (benchDb) { |
||||
const program = require('commander') |
||||
|
||||
program |
||||
.option('-n --number [number]', 'Size of the collection to test on', parseInt) |
||||
.option('-i --with-index', 'Use an index') |
||||
.option('-m --in-memory', 'Test with an in-memory only store') |
||||
.parse(process.argv) |
||||
|
||||
const n = program.number || 10000 |
||||
|
||||
console.log('----------------------------') |
||||
console.log('Test with ' + n + ' documents') |
||||
console.log(program.withIndex ? 'Use an index' : 'Don\'t use an index') |
||||
console.log(program.inMemory ? 'Use an in-memory datastore' : 'Use a persistent datastore') |
||||
console.log('----------------------------') |
||||
|
||||
const d = new Datastore({ |
||||
filename: benchDb, |
||||
inMemoryOnly: program.inMemory |
||||
}) |
||||
|
||||
return { n, d, program } |
||||
} |
||||
|
||||
/** |
||||
* Ensure the workspace stat and the db datafile is empty |
||||
*/ |
||||
module.exports.prepareDb = function (filename, cb) { |
||||
callbackify((filename) => Persistence.ensureParentDirectoryExistsAsync(filename))(filename, function () { |
||||
fs.access(filename, fs.constants.FS_OK, function (err) { |
||||
if (!err) { |
||||
fs.unlink(filename, cb) |
||||
} else { return cb() } |
||||
}) |
||||
}) |
||||
} |
||||
|
||||
/** |
||||
* Return an array with the numbers from 0 to n-1, in a random order |
||||
* Uses Fisher Yates algorithm |
||||
* Useful to get fair tests |
||||
*/ |
||||
function getRandomArray (n) { |
||||
const res = [] |
||||
let i |
||||
let j |
||||
let temp |
||||
|
||||
for (i = 0; i < n; i += 1) { res[i] = i } |
||||
|
||||
for (i = n - 1; i >= 1; i -= 1) { |
||||
j = Math.floor((i + 1) * Math.random()) |
||||
temp = res[i] |
||||
res[i] = res[j] |
||||
res[j] = temp |
||||
} |
||||
|
||||
return res |
||||
} |
||||
module.exports.getRandomArray = getRandomArray |
||||
|
||||
/** |
||||
* Insert a certain number of documents for testing |
||||
*/ |
||||
module.exports.insertDocs = function (d, n, profiler, cb) { |
||||
const order = getRandomArray(n) |
||||
|
||||
profiler.step('Begin inserting ' + n + ' docs') |
||||
|
||||
function runFrom (i) { |
||||
if (i === n) { // Finished
|
||||
const opsPerSecond = Math.floor(1000 * n / profiler.elapsedSinceLastStep()) |
||||
console.log('===== RESULT (insert) ===== ' + opsPerSecond + ' ops/s') |
||||
profiler.step('Finished inserting ' + n + ' docs') |
||||
profiler.insertOpsPerSecond = opsPerSecond |
||||
return cb() |
||||
} |
||||
|
||||
// eslint-disable-next-line n/handle-callback-err
|
||||
d.insert({ docNumber: order[i] }, function (err) { |
||||
executeAsap(function () { |
||||
runFrom(i + 1) |
||||
}) |
||||
}) |
||||
} |
||||
|
||||
runFrom(0) |
||||
} |
||||
|
||||
/** |
||||
* Find documents with find |
||||
*/ |
||||
module.exports.findDocs = function (d, n, profiler, cb) { |
||||
const order = getRandomArray(n) |
||||
|
||||
profiler.step('Finding ' + n + ' documents') |
||||
|
||||
function runFrom (i) { |
||||
if (i === n) { // Finished
|
||||
console.log('===== RESULT (find) ===== ' + Math.floor(1000 * n / profiler.elapsedSinceLastStep()) + ' ops/s') |
||||
profiler.step('Finished finding ' + n + ' docs') |
||||
return cb() |
||||
} |
||||
|
||||
// eslint-disable-next-line n/handle-callback-err
|
||||
d.find({ docNumber: order[i] }, function (err, docs) { |
||||
if (docs.length !== 1 || docs[0].docNumber !== order[i]) { return cb(new Error('One find didnt work')) } |
||||
executeAsap(function () { |
||||
runFrom(i + 1) |
||||
}) |
||||
}) |
||||
} |
||||
|
||||
runFrom(0) |
||||
} |
||||
|
||||
/** |
||||
* Find documents with find and the $in operator |
||||
*/ |
||||
module.exports.findDocsWithIn = function (d, n, profiler, cb) { |
||||
const ins = [] |
||||
const arraySize = Math.min(10, n) |
||||
|
||||
// Preparing all the $in arrays, will take some time
|
||||
for (let i = 0; i < n; i += 1) { |
||||
ins[i] = [] |
||||
|
||||
for (let j = 0; j < arraySize; j += 1) { |
||||
ins[i].push((i + j) % n) |
||||
} |
||||
} |
||||
|
||||
profiler.step('Finding ' + n + ' documents WITH $IN OPERATOR') |
||||
|
||||
function runFrom (i) { |
||||
if (i === n) { // Finished
|
||||
console.log('===== RESULT (find with in selector) ===== ' + Math.floor(1000 * n / profiler.elapsedSinceLastStep()) + ' ops/s') |
||||
profiler.step('Finished finding ' + n + ' docs') |
||||
return cb() |
||||
} |
||||
|
||||
// eslint-disable-next-line n/handle-callback-err
|
||||
d.find({ docNumber: { $in: ins[i] } }, function (err, docs) { |
||||
if (docs.length !== arraySize) { return cb(new Error('One find didnt work')) } |
||||
executeAsap(function () { |
||||
runFrom(i + 1) |
||||
}) |
||||
}) |
||||
} |
||||
|
||||
runFrom(0) |
||||
} |
||||
|
||||
/** |
||||
* Find documents with findOne |
||||
*/ |
||||
module.exports.findOneDocs = function (d, n, profiler, cb) { |
||||
const order = getRandomArray(n) |
||||
|
||||
profiler.step('FindingOne ' + n + ' documents') |
||||
|
||||
function runFrom (i) { |
||||
if (i === n) { // Finished
|
||||
console.log('===== RESULT (findOne) ===== ' + Math.floor(1000 * n / profiler.elapsedSinceLastStep()) + ' ops/s') |
||||
profiler.step('Finished finding ' + n + ' docs') |
||||
return cb() |
||||
} |
||||
|
||||
// eslint-disable-next-line n/handle-callback-err
|
||||
d.findOne({ docNumber: order[i] }, function (err, doc) { |
||||
if (!doc || doc.docNumber !== order[i]) { return cb(new Error('One find didnt work')) } |
||||
executeAsap(function () { |
||||
runFrom(i + 1) |
||||
}) |
||||
}) |
||||
} |
||||
|
||||
runFrom(0) |
||||
} |
||||
|
||||
/** |
||||
* Update documents |
||||
* options is the same as the options object for update |
||||
*/ |
||||
module.exports.updateDocs = function (options, d, n, profiler, cb) { |
||||
const order = getRandomArray(n) |
||||
|
||||
profiler.step('Updating ' + n + ' documents') |
||||
|
||||
function runFrom (i) { |
||||
if (i === n) { // Finished
|
||||
console.log('===== RESULT (update) ===== ' + Math.floor(1000 * n / profiler.elapsedSinceLastStep()) + ' ops/s') |
||||
profiler.step('Finished updating ' + n + ' docs') |
||||
return cb() |
||||
} |
||||
|
||||
// Will not actually modify the document but will take the same time
|
||||
d.update({ docNumber: order[i] }, { docNumber: order[i] }, options, function (err, nr) { |
||||
if (err) { return cb(err) } |
||||
if (nr !== 1) { return cb(new Error('One update didnt work')) } |
||||
executeAsap(function () { |
||||
runFrom(i + 1) |
||||
}) |
||||
}) |
||||
} |
||||
|
||||
runFrom(0) |
||||
} |
||||
|
||||
/** |
||||
* Remove documents |
||||
* options is the same as the options object for update |
||||
*/ |
||||
module.exports.removeDocs = function (options, d, n, profiler, cb) { |
||||
const order = getRandomArray(n) |
||||
|
||||
profiler.step('Removing ' + n + ' documents') |
||||
|
||||
function runFrom (i) { |
||||
if (i === n) { // Finished
|
||||
// opsPerSecond corresponds to 1 insert + 1 remove, needed to keep collection size at 10,000
|
||||
// We need to subtract the time taken by one insert to get the time actually taken by one remove
|
||||
const opsPerSecond = Math.floor(1000 * n / profiler.elapsedSinceLastStep()) |
||||
const removeOpsPerSecond = Math.floor(1 / ((1 / opsPerSecond) - (1 / profiler.insertOpsPerSecond))) |
||||
console.log('===== RESULT (remove) ===== ' + removeOpsPerSecond + ' ops/s') |
||||
profiler.step('Finished removing ' + n + ' docs') |
||||
return cb() |
||||
} |
||||
|
||||
d.remove({ docNumber: order[i] }, options, function (err, nr) { |
||||
if (err) { return cb(err) } |
||||
if (nr !== 1) { return cb(new Error('One remove didnt work')) } |
||||
// eslint-disable-next-line n/handle-callback-err
|
||||
d.insert({ docNumber: order[i] }, function (err) { // We need to reinsert the doc so that we keep the collection's size at n
|
||||
// So actually we're calculating the average time taken by one insert + one remove
|
||||
executeAsap(function () { |
||||
runFrom(i + 1) |
||||
}) |
||||
}) |
||||
}) |
||||
} |
||||
|
||||
runFrom(0) |
||||
} |
||||
|
||||
/** |
||||
* Load database |
||||
*/ |
||||
module.exports.loadDatabase = function (d, n, profiler, cb) { |
||||
profiler.step('Loading the database ' + n + ' times') |
||||
|
||||
function runFrom (i) { |
||||
if (i === n) { // Finished
|
||||
console.log('===== RESULT ===== ' + Math.floor(1000 * n / profiler.elapsedSinceLastStep()) + ' ops/s') |
||||
profiler.step('Finished loading a database' + n + ' times') |
||||
return cb() |
||||
} |
||||
|
||||
// eslint-disable-next-line n/handle-callback-err
|
||||
d.loadDatabase(function (err) { |
||||
executeAsap(function () { |
||||
runFrom(i + 1) |
||||
}) |
||||
}) |
||||
} |
||||
|
||||
runFrom(0) |
||||
} |
@ -1,50 +0,0 @@ |
||||
const program = require('commander') |
||||
const { apply, waterfall } = require('../test/utils.test.js') |
||||
const Datastore = require('../lib/datastore') |
||||
const commonUtilities = require('./commonUtilities') |
||||
const Profiler = require('./profiler') |
||||
|
||||
const profiler = new Profiler('INSERT BENCH') |
||||
const benchDb = 'workspace/insert.bench.db' |
||||
const d = new Datastore(benchDb) |
||||
|
||||
program |
||||
.option('-n --number [number]', 'Size of the collection to test on', parseInt) |
||||
.option('-i --with-index', 'Test with an index') |
||||
.parse(process.argv) |
||||
|
||||
const n = program.number || 10000 |
||||
|
||||
console.log('----------------------------') |
||||
console.log('Test with ' + n + ' documents') |
||||
console.log('----------------------------') |
||||
|
||||
waterfall([ |
||||
apply(commonUtilities.prepareDb, benchDb), |
||||
function (cb) { |
||||
d.loadDatabase(function (err) { |
||||
if (err) { return cb(err) } |
||||
cb() |
||||
}) |
||||
}, |
||||
function (cb) { profiler.beginProfiling(); return cb() }, |
||||
apply(commonUtilities.insertDocs, d, n, profiler), |
||||
function (cb) { |
||||
let i |
||||
|
||||
profiler.step('Begin calling ensureIndex ' + n + ' times') |
||||
|
||||
for (i = 0; i < n; i += 1) { |
||||
d.ensureIndex({ fieldName: 'docNumber' }) |
||||
delete d.indexes.docNumber |
||||
} |
||||
|
||||
console.log('Average time for one ensureIndex: ' + (profiler.elapsedSinceLastStep() / n) + 'ms') |
||||
profiler.step('Finished calling ensureIndex ' + n + ' times') |
||||
cb() |
||||
} |
||||
], function (err) { |
||||
profiler.step('Benchmark finished') |
||||
|
||||
if (err) { return console.log('An error was encountered: ', err) } |
||||
}) |
@ -1,27 +0,0 @@ |
||||
const { apply, waterfall } = require('../test/utils.test.js') |
||||
const commonUtilities = require('./commonUtilities') |
||||
const Profiler = require('./profiler') |
||||
|
||||
const profiler = new Profiler('FIND BENCH') |
||||
const benchDb = 'workspace/find.bench.db' |
||||
const config = commonUtilities.getConfiguration(benchDb) |
||||
const d = config.d |
||||
const n = config.n |
||||
|
||||
waterfall([ |
||||
apply(commonUtilities.prepareDb, benchDb), |
||||
function (cb) { |
||||
d.loadDatabase(function (err) { |
||||
if (err) { return cb(err) } |
||||
if (config.program.withIndex) { d.ensureIndex({ fieldName: 'docNumber' }) } |
||||
cb() |
||||
}) |
||||
}, |
||||
function (cb) { profiler.beginProfiling(); return cb() }, |
||||
apply(commonUtilities.insertDocs, d, n, profiler), |
||||
apply(commonUtilities.findDocs, d, n, profiler) |
||||
], function (err) { |
||||
profiler.step('Benchmark finished') |
||||
|
||||
if (err) { return console.log('An error was encountered: ', err) } |
||||
}) |
@ -1,28 +0,0 @@ |
||||
const { apply, waterfall } = require('../test/utils.test.js') |
||||
const commonUtilities = require('./commonUtilities') |
||||
const Profiler = require('./profiler') |
||||
|
||||
const benchDb = 'workspace/findOne.bench.db' |
||||
const profiler = new Profiler('FINDONE BENCH') |
||||
const config = commonUtilities.getConfiguration(benchDb) |
||||
const d = config.d |
||||
const n = config.n |
||||
|
||||
waterfall([ |
||||
apply(commonUtilities.prepareDb, benchDb), |
||||
function (cb) { |
||||
d.loadDatabase(function (err) { |
||||
if (err) { return cb(err) } |
||||
if (config.program.withIndex) { d.ensureIndex({ fieldName: 'docNumber' }) } |
||||
cb() |
||||
}) |
||||
}, |
||||
function (cb) { profiler.beginProfiling(); return cb() }, |
||||
apply(commonUtilities.insertDocs, d, n, profiler), |
||||
function (cb) { setTimeout(function () { cb() }, 500) }, |
||||
apply(commonUtilities.findOneDocs, d, n, profiler) |
||||
], function (err) { |
||||
profiler.step('Benchmark finished') |
||||
|
||||
if (err) { return console.log('An error was encountered: ', err) } |
||||
}) |
@ -1,27 +0,0 @@ |
||||
const { apply, waterfall } = require('../test/utils.test.js') |
||||
const commonUtilities = require('./commonUtilities') |
||||
const Profiler = require('./profiler') |
||||
|
||||
const benchDb = 'workspace/find.bench.db' |
||||
const profiler = new Profiler('FIND BENCH') |
||||
const config = commonUtilities.getConfiguration(benchDb) |
||||
const d = config.d |
||||
const n = config.n |
||||
|
||||
waterfall([ |
||||
apply(commonUtilities.prepareDb, benchDb), |
||||
function (cb) { |
||||
d.loadDatabase(function (err) { |
||||
if (err) { return cb(err) } |
||||
if (config.program.withIndex) { d.ensureIndex({ fieldName: 'docNumber' }) } |
||||
cb() |
||||
}) |
||||
}, |
||||
function (cb) { profiler.beginProfiling(); return cb() }, |
||||
apply(commonUtilities.insertDocs, d, n, profiler), |
||||
apply(commonUtilities.findDocsWithIn, d, n, profiler) |
||||
], function (err) { |
||||
profiler.step('Benchmark finished') |
||||
|
||||
if (err) { return console.log('An error was encountered: ', err) } |
||||
}) |
@ -1,32 +0,0 @@ |
||||
const { apply, waterfall } = require('../test/utils.test.js') |
||||
const commonUtilities = require('./commonUtilities') |
||||
const Profiler = require('./profiler') |
||||
|
||||
const benchDb = 'workspace/insert.bench.db' |
||||
const profiler = new Profiler('INSERT BENCH') |
||||
const config = commonUtilities.getConfiguration(benchDb) |
||||
const d = config.d |
||||
let n = config.n |
||||
|
||||
waterfall([ |
||||
apply(commonUtilities.prepareDb, benchDb), |
||||
function (cb) { |
||||
d.loadDatabase(function (err) { |
||||
if (err) { return cb(err) } |
||||
if (config.program.withIndex) { |
||||
d.ensureIndex({ fieldName: 'docNumber' }) |
||||
n = 2 * n // We will actually insert twice as many documents
|
||||
// because the index is slower when the collection is already
|
||||
// big. So the result given by the algorithm will be a bit worse than
|
||||
// actual performance
|
||||
} |
||||
cb() |
||||
}) |
||||
}, |
||||
function (cb) { profiler.beginProfiling(); return cb() }, |
||||
apply(commonUtilities.insertDocs, d, n, profiler) |
||||
], function (err) { |
||||
profiler.step('Benchmark finished') |
||||
|
||||
if (err) { return console.log('An error was encountered: ', err) } |
||||
}) |
@ -1,35 +0,0 @@ |
||||
const { apply, waterfall } = require('../test/utils.test.js') |
||||
const program = require('commander') |
||||
const Datastore = require('../lib/datastore') |
||||
const commonUtilities = require('./commonUtilities') |
||||
const Profiler = require('./profiler') |
||||
|
||||
const benchDb = 'workspace/loaddb.bench.db' |
||||
const profiler = new Profiler('LOADDB BENCH') |
||||
const d = new Datastore(benchDb) |
||||
|
||||
program |
||||
.option('-n --number [number]', 'Size of the collection to test on', parseInt) |
||||
.option('-i --with-index', 'Test with an index') |
||||
.parse(process.argv) |
||||
|
||||
const n = program.number || 10000 |
||||
|
||||
console.log('----------------------------') |
||||
console.log('Test with ' + n + ' documents') |
||||
console.log(program.withIndex ? 'Use an index' : "Don't use an index") |
||||
console.log('----------------------------') |
||||
|
||||
waterfall([ |
||||
apply(commonUtilities.prepareDb, benchDb), |
||||
function (cb) { |
||||
d.loadDatabase(cb) |
||||
}, |
||||
function (cb) { profiler.beginProfiling(); return cb() }, |
||||
apply(commonUtilities.insertDocs, d, n, profiler), |
||||
apply(commonUtilities.loadDatabase, d, n, profiler) |
||||
], function (err) { |
||||
profiler.step('Benchmark finished') |
||||
|
||||
if (err) { return console.log('An error was encountered: ', err) } |
||||
}) |
@ -1,92 +0,0 @@ |
||||
const util = require('util') |
||||
|
||||
function formatTime (time, precision) { |
||||
// If we're dealing with ms, round up to seconds when time is at least 1 second
|
||||
if (time > 1000 && precision === 'ms') { |
||||
return (Math.floor(time / 100) / 10) + ' s' |
||||
} else { |
||||
return time.toFixed(3) + ' ' + precision |
||||
} |
||||
} |
||||
|
||||
// get time in ns
|
||||
function getTime () { |
||||
const t = process.hrtime() |
||||
return (t[0] * 1e9 + t[1]) |
||||
} |
||||
|
||||
/** |
||||
* Create a profiler with name testName to monitor the execution time of a route |
||||
* The profiler has two arguments: a step msg and an optional reset for the internal timer |
||||
* It will display the execution time per step and total from latest rest |
||||
* |
||||
* Optional logToConsole flag, which defaults to true, causes steps to be printed to console. |
||||
* otherwise, they can be accessed from Profiler.steps array. |
||||
*/ |
||||
function Profiler (name, logToConsole, precision) { |
||||
this.name = name |
||||
this.steps = [] |
||||
this.sinceBeginning = null |
||||
this.lastStep = null |
||||
this.logToConsole = typeof (logToConsole) === 'undefined' ? true : logToConsole |
||||
this.precision = typeof (precision) === 'undefined' ? 'ms' : precision |
||||
this.divisor = 1 |
||||
|
||||
if (this.precision === 'ms') this.divisor = 1e6 |
||||
} |
||||
|
||||
Profiler.prototype.beginProfiling = function () { |
||||
if (this.logToConsole) { console.log(this.name + ' - Begin profiling') } |
||||
this.resetTimers() |
||||
} |
||||
|
||||
Profiler.prototype.resetTimers = function () { |
||||
this.sinceBeginning = getTime() |
||||
this.lastStep = getTime() |
||||
this.steps.push(['BEGIN_TIMER', this.lastStep]) |
||||
} |
||||
|
||||
Profiler.prototype.elapsedSinceBeginning = function () { |
||||
return (getTime() - this.sinceBeginning) / this.divisor |
||||
} |
||||
|
||||
Profiler.prototype.elapsedSinceLastStep = function () { |
||||
return (getTime() - this.lastStep) / this.divisor |
||||
} |
||||
|
||||
// Return the deltas between steps, in nanoseconds
|
||||
|
||||
Profiler.prototype.getSteps = function () { |
||||
const divisor = this.divisor |
||||
|
||||
return this.steps.map(function (curr, index, arr) { |
||||
if (index === 0) return undefined |
||||
const delta = (curr[1] - arr[index - 1][1]) |
||||
return [curr[0], (delta / divisor)] |
||||
}).slice(1) |
||||
} |
||||
|
||||
Profiler.prototype.step = function (msg) { |
||||
if (!this.sinceBeginning || !this.lastStep) { |
||||
console.log(util.format( |
||||
'%s - %s - You must call beginProfiling before registering steps', |
||||
this.name, |
||||
msg |
||||
)) |
||||
return |
||||
} |
||||
|
||||
if (this.logToConsole) { |
||||
console.log(util.format('%s - %s - %s (total: %s)', |
||||
this.name, |
||||
msg, |
||||
formatTime(this.elapsedSinceLastStep(), this.precision), |
||||
formatTime(this.elapsedSinceBeginning(), this.precision) |
||||
)) |
||||
} |
||||
|
||||
this.lastStep = getTime() |
||||
this.steps.push([msg, this.lastStep]) |
||||
} |
||||
|
||||
module.exports = Profiler |
@ -1,35 +0,0 @@ |
||||
const { apply, waterfall } = require('../test/utils.test.js') |
||||
const commonUtilities = require('./commonUtilities') |
||||
const Profiler = require('./profiler') |
||||
|
||||
const benchDb = 'workspace/remove.bench.db' |
||||
const profiler = new Profiler('REMOVE BENCH') |
||||
const config = commonUtilities.getConfiguration(benchDb) |
||||
const d = config.d |
||||
const n = config.n |
||||
|
||||
waterfall([ |
||||
apply(commonUtilities.prepareDb, benchDb), |
||||
function (cb) { |
||||
d.loadDatabase(function (err) { |
||||
if (err) { return cb(err) } |
||||
if (config.program.withIndex) { d.ensureIndex({ fieldName: 'docNumber' }) } |
||||
cb() |
||||
}) |
||||
}, |
||||
function (cb) { profiler.beginProfiling(); return cb() }, |
||||
apply(commonUtilities.insertDocs, d, n, profiler), |
||||
|
||||
// Test with remove only one document
|
||||
function (cb) { profiler.step('MULTI: FALSE'); return cb() }, |
||||
apply(commonUtilities.removeDocs, { multi: false }, d, n, profiler), |
||||
// Test with multiple documents
|
||||
function (cb) { d.remove({}, { multi: true }, function () { return cb() }) }, |
||||
apply(commonUtilities.insertDocs, d, n, profiler), |
||||
function (cb) { profiler.step('MULTI: TRUE'); return cb() }, |
||||
apply(commonUtilities.removeDocs, { multi: true }, d, n, profiler) |
||||
], function (err) { |
||||
profiler.step('Benchmark finished') |
||||
|
||||
if (err) { return console.log('An error was encountered: ', err) } |
||||
}) |
@ -1,37 +0,0 @@ |
||||
const { apply, waterfall } = require('../test/utils.test.js') |
||||
const commonUtilities = require('./commonUtilities') |
||||
const Profiler = require('./profiler') |
||||
|
||||
const benchDb = 'workspace/update.bench.db' |
||||
const profiler = new Profiler('UPDATE BENCH') |
||||
const config = commonUtilities.getConfiguration(benchDb) |
||||
const d = config.d |
||||
const n = config.n |
||||
|
||||
waterfall([ |
||||
apply(commonUtilities.prepareDb, benchDb), |
||||
function (cb) { |
||||
d.loadDatabase(function (err) { |
||||
if (err) { return cb(err) } |
||||
if (config.program.withIndex) { d.ensureIndex({ fieldName: 'docNumber' }) } |
||||
cb() |
||||
}) |
||||
}, |
||||
function (cb) { profiler.beginProfiling(); return cb() }, |
||||
apply(commonUtilities.insertDocs, d, n, profiler), |
||||
|
||||
// Test with update only one document
|
||||
function (cb) { profiler.step('MULTI: FALSE'); return cb() }, |
||||
apply(commonUtilities.updateDocs, { multi: false }, d, n, profiler), |
||||
|
||||
// Test with multiple documents
|
||||
// eslint-disable-next-line n/handle-callback-err
|
||||
function (cb) { d.remove({}, { multi: true }, function (err) { return cb() }) }, |
||||
apply(commonUtilities.insertDocs, d, n, profiler), |
||||
function (cb) { profiler.step('MULTI: TRUE'); return cb() }, |
||||
apply(commonUtilities.updateDocs, { multi: true }, d, n, profiler) |
||||
], function (err) { |
||||
profiler.step('Benchmark finished') |
||||
|
||||
if (err) { return console.log('An error was encountered: ', err) } |
||||
}) |
@ -0,0 +1,292 @@ |
||||
import { promisify } from 'util' |
||||
import { performance } from 'node:perf_hooks' |
||||
import Persistence from '../src/persistence.js' |
||||
import { access, unlink, constants } from 'fs/promises' |
||||
import Datastore from '../src/datastore.js' |
||||
|
||||
const getRandomArray = n => { |
||||
const res = [] |
||||
let i |
||||
let j |
||||
let temp |
||||
|
||||
for (i = 0; i < n; i += 1) { res[i] = i } |
||||
|
||||
for (i = n - 1; i >= 1; i -= 1) { |
||||
j = Math.floor((i + 1) * Math.random()) |
||||
temp = res[i] |
||||
res[i] = res[j] |
||||
res[j] = temp |
||||
} |
||||
|
||||
return res |
||||
} |
||||
|
||||
const insertDocs = async (d, n) => { |
||||
const order = getRandomArray(n) |
||||
|
||||
for (let i = 0; i <= n; i++) { |
||||
await d.insertAsync({ docNumber: order[i] }) |
||||
await promisify(process.nextTick)() |
||||
} |
||||
} |
||||
|
||||
const measure = async (name, n, cb) => { |
||||
performance.mark(`${name} start`) |
||||
|
||||
for (let i = 0; i < n; i += 1) { |
||||
await cb(i) |
||||
} |
||||
|
||||
performance.mark(`${name} end`) |
||||
|
||||
const { duration } = performance.measure(`${name} average duration for ${n} operations`, `${name} start`, `${name} end`) |
||||
const opsPerSecond = Math.floor(1000 * n / duration) |
||||
|
||||
console.log(`===== RESULT (${name}) ===== ${opsPerSecond} ops/s ===== ${Math.floor(duration / n * 1000) / 1000}ms/execution`) |
||||
} |
||||
|
||||
describe('Performance tests without index', function () { |
||||
const n = 1000 |
||||
const benchDb = 'workspace/bench.db' |
||||
let d |
||||
|
||||
before(function () { |
||||
if (!process.argv.includes('--benchmark')) this.skip() |
||||
else this.timeout(60000) |
||||
}) |
||||
|
||||
beforeEach('Prepare database', async () => { |
||||
try { |
||||
await access(benchDb, constants.F_OK) |
||||
await unlink(benchDb) |
||||
} catch {} |
||||
await Persistence.ensureParentDirectoryExistsAsync(benchDb) |
||||
d = new Datastore({ filename: benchDb }) |
||||
await d.loadDatabaseAsync() |
||||
}) |
||||
|
||||
afterEach('Clean database', async () => { |
||||
try { |
||||
await d.this.executor.queue.guardian |
||||
d = null |
||||
await access(benchDb, constants.F_OK) |
||||
await unlink(benchDb) |
||||
} catch {} |
||||
}) |
||||
|
||||
it('ensureIndex', async () => { |
||||
await insertDocs(d, n) |
||||
await measure('ensureIndex', n, async (i) => { |
||||
d.ensureIndexAsync({ fieldName: 'docNumber' }) |
||||
delete d.indexes.docNumber |
||||
}) |
||||
}) |
||||
|
||||
it('find', async () => { |
||||
await insertDocs(d, n) |
||||
const order = getRandomArray(n) |
||||
await measure('find', n, async (i) => { |
||||
const docs = await d.findAsync({ docNumber: order[i] }) |
||||
if (docs.length !== 1 || docs[0].docNumber !== order[i]) throw new Error('One find didnt work') |
||||
await promisify(process.nextTick)() |
||||
}) |
||||
}) |
||||
|
||||
it('findOne', async () => { |
||||
await insertDocs(d, n) |
||||
const order = getRandomArray(n) |
||||
await measure('findOne', n, async (i) => { |
||||
const doc = await d.findOneAsync({ docNumber: order[i] }) |
||||
if (doc == null || doc.docNumber !== order[i]) throw new Error('One find didnt work') |
||||
await promisify(process.nextTick)() |
||||
}) |
||||
}) |
||||
|
||||
it('find with $in', async () => { |
||||
await insertDocs(d, n) |
||||
|
||||
const ins = [] |
||||
const arraySize = Math.min(10, n) |
||||
|
||||
// Preparing all the $in arrays, will take some time
|
||||
for (let i = 0; i < n; i += 1) { |
||||
ins[i] = [] |
||||
|
||||
for (let j = 0; j < arraySize; j += 1) { |
||||
ins[i].push((i + j) % n) |
||||
} |
||||
} |
||||
|
||||
await measure('find with $in', n, async (i) => { |
||||
const docs = await d.findAsync({ docNumber: { $in: ins[i] } }) |
||||
if (docs.length !== arraySize) throw new Error('One find didnt work') |
||||
await promisify(process.nextTick)() |
||||
}) |
||||
}) |
||||
|
||||
it('insert docs', async () => { |
||||
const order = getRandomArray(n) |
||||
await measure('insert docs', n, async (i) => { |
||||
await d.insertAsync({ docNumber: order[i] }) |
||||
await promisify(process.nextTick)() |
||||
}) |
||||
}) |
||||
|
||||
it('load database', async () => { |
||||
await insertDocs(d, n) |
||||
await measure('load database', n, async (i) => { |
||||
await d.loadDatabaseAsync() |
||||
}) |
||||
}) |
||||
|
||||
it('Insert and remove one', async () => { |
||||
// TODO measure only remove
|
||||
await insertDocs(d, n) |
||||
const order = getRandomArray(n) |
||||
await measure('insert and remove one', n, async (i) => { |
||||
const nr = await d.removeAsync({ docNumber: order[i] }, { multi: false }) |
||||
if (nr !== 1) throw new Error('One remove didnt work') |
||||
await d.insertAsync({ docNumber: order[i] }) |
||||
}) |
||||
}) |
||||
|
||||
it('Insert and remove one with multi option', async () => { |
||||
// TODO measure only remove
|
||||
await insertDocs(d, n) |
||||
const order = getRandomArray(n) |
||||
await measure('insert and remove one with multi option', n, async (i) => { |
||||
const nr = await d.removeAsync({ docNumber: order[i] }, { multi: true }) |
||||
if (nr !== 1) throw new Error('One remove didnt work') |
||||
await d.insertAsync({ docNumber: order[i] }) |
||||
}) |
||||
}) |
||||
|
||||
it('Update', async () => { |
||||
await insertDocs(d, n) |
||||
const order = getRandomArray(n) |
||||
await measure('insert and remove one with multi option', n, async (i) => { |
||||
const { numAffected } = await d.updateAsync({ docNumber: order[i] }, { docNumber: order[i] }, { multi: false }) |
||||
if (numAffected !== 1) throw new Error('One update didnt work') |
||||
await d.insertAsync({ docNumber: order[i] }) |
||||
}) |
||||
}) |
||||
|
||||
it('Update with multi option', async () => { |
||||
await insertDocs(d, n) |
||||
const order = getRandomArray(n) |
||||
await measure('insert and remove one with multi option', n, async (i) => { |
||||
const { numAffected } = await d.updateAsync({ docNumber: order[i] }, { docNumber: order[i] }, { multi: true }) |
||||
if (numAffected !== 1) throw new Error('One update didnt work') |
||||
await d.insertAsync({ docNumber: order[i] }) |
||||
}) |
||||
}) |
||||
|
||||
it('find with index', async () => { |
||||
await d.ensureIndexAsync({ fieldName: 'docNumber' }) |
||||
await insertDocs(d, n) |
||||
const order = getRandomArray(n) |
||||
await measure('find with index', n, async (i) => { |
||||
const docs = await d.findAsync({ docNumber: order[i] }) |
||||
if (docs.length !== 1 || docs[0].docNumber !== order[i]) throw new Error('One find didnt work') |
||||
await promisify(process.nextTick)() |
||||
}) |
||||
}) |
||||
|
||||
it('findOne with index', async () => { |
||||
await d.ensureIndexAsync({ fieldName: 'docNumber' }) |
||||
await insertDocs(d, n) |
||||
const order = getRandomArray(n) |
||||
await measure('findOne with index', n, async (i) => { |
||||
const doc = await d.findOneAsync({ docNumber: order[i] }) |
||||
if (doc == null || doc.docNumber !== order[i]) throw new Error('One find didnt work') |
||||
await promisify(process.nextTick)() |
||||
}) |
||||
}) |
||||
|
||||
it('find with $in with index', async () => { |
||||
await d.ensureIndexAsync({ fieldName: 'docNumber' }) |
||||
await insertDocs(d, n) |
||||
|
||||
const ins = [] |
||||
const arraySize = Math.min(10, n) |
||||
|
||||
// Preparing all the $in arrays, will take some time
|
||||
for (let i = 0; i < n; i += 1) { |
||||
ins[i] = [] |
||||
|
||||
for (let j = 0; j < arraySize; j += 1) { |
||||
ins[i].push((i + j) % n) |
||||
} |
||||
} |
||||
|
||||
await measure('find with $in with index', n, async (i) => { |
||||
const docs = await d.findAsync({ docNumber: { $in: ins[i] } }) |
||||
if (docs.length !== arraySize) throw new Error('One find didnt work') |
||||
await promisify(process.nextTick)() |
||||
}) |
||||
}) |
||||
|
||||
it('insert docs with index', async () => { |
||||
await d.ensureIndexAsync({ fieldName: 'docNumber' }) |
||||
const order = getRandomArray(n) |
||||
await measure('insert docs with index', n, async (i) => { |
||||
await d.insertAsync({ docNumber: order[i] }) |
||||
await promisify(process.nextTick)() |
||||
}) |
||||
}) |
||||
|
||||
it('load database with index', async () => { |
||||
await d.ensureIndexAsync({ fieldName: 'docNumber' }) |
||||
await insertDocs(d, n) |
||||
await measure('load database with index', n, async (i) => { |
||||
await d.loadDatabaseAsync() |
||||
}) |
||||
}) |
||||
|
||||
it('Insert and remove one with index', async () => { |
||||
// TODO measure only remove
|
||||
await d.ensureIndexAsync({ fieldName: 'docNumber' }) |
||||
await insertDocs(d, n) |
||||
const order = getRandomArray(n) |
||||
await measure('insert and remove one with index', n, async (i) => { |
||||
const nr = await d.removeAsync({ docNumber: order[i] }, { multi: false }) |
||||
if (nr !== 1) throw new Error('One remove didnt work') |
||||
await d.insertAsync({ docNumber: order[i] }) |
||||
}) |
||||
}) |
||||
|
||||
it('Insert and remove one with multi option with index', async () => { |
||||
// TODO measure only remove
|
||||
await d.ensureIndexAsync({ fieldName: 'docNumber' }) |
||||
await insertDocs(d, n) |
||||
const order = getRandomArray(n) |
||||
await measure('insert and remove one with multi option with index', n, async (i) => { |
||||
const nr = await d.removeAsync({ docNumber: order[i] }, { multi: true }) |
||||
if (nr !== 1) throw new Error('One remove didnt work') |
||||
await d.insertAsync({ docNumber: order[i] }) |
||||
}) |
||||
}) |
||||
|
||||
it('Update with index', async () => { |
||||
await d.ensureIndexAsync({ fieldName: 'docNumber' }) |
||||
await insertDocs(d, n) |
||||
const order = getRandomArray(n) |
||||
await measure('insert and remove one with multi option with index', n, async (i) => { |
||||
const { numAffected } = await d.updateAsync({ docNumber: order[i] }, { docNumber: order[i] }, { multi: false }) |
||||
if (numAffected !== 1) throw new Error('One update didnt work') |
||||
await d.insertAsync({ docNumber: order[i] }) |
||||
}) |
||||
}) |
||||
|
||||
it('Update with multi option with index', async () => { |
||||
await d.ensureIndexAsync({ fieldName: 'docNumber' }) |
||||
await insertDocs(d, n) |
||||
const order = getRandomArray(n) |
||||
await measure('insert and remove one with multi option with index', n, async (i) => { |
||||
const { numAffected } = await d.updateAsync({ docNumber: order[i] }, { docNumber: order[i] }, { multi: true }) |
||||
if (numAffected !== 1) throw new Error('One update didnt work') |
||||
await d.insertAsync({ docNumber: order[i] }) |
||||
}) |
||||
}) |
||||
}) |
Loading…
Reference in new issue