Skip to content

Commit

Permalink
Integrate LMDB as an alternative backend.
Browse files Browse the repository at this point in the history
  • Loading branch information
paberr committed Mar 23, 2018
1 parent 02caa05 commit 7928851
Show file tree
Hide file tree
Showing 49 changed files with 1,930 additions and 66 deletions.
6 changes: 4 additions & 2 deletions .travis.yml
Expand Up @@ -11,7 +11,8 @@ env:

matrix:
include:
- env: TO_TEST=NodeJS
- env: TO_TEST=leveldb
- env: TO_TEST=lmdb
- env: TO_TEST=ESLint
# - os: osx
# env: TO_TEST=Karma/Travis_CI/Safari
Expand Down Expand Up @@ -92,6 +93,7 @@ script:
- if [[ "$TO_TEST" == Karma/Travis_CI/Firefox_* ]] && [ "$KARMA_BROWSERS" = "" ]; then export KARMA_BROWSERS=Firefox TO_TEST=Karma USE_ISTANBUL=1; fi
- if [[ "$TO_TEST" == Karma/Travis_CI/* ]] && [ "$KARMA_BROWSERS" = "" ]; then export KARMA_BROWSERS=Safari TO_TEST=Karma; fi
- if [ "$TO_TEST" = "Karma" ]; then node_modules/.bin/karma start; fi
- if [ "$TO_TEST" = "NodeJS" ]; then node_modules/.bin/jasmine; fi
- if [ "$TO_TEST" = "leveldb" ]; then node_modules/.bin/jasmine; fi
- if [ "$TO_TEST" = "lmdb" ]; then node_modules/.bin/jasmine --config=spec/support/lmdb-jasmine.json; fi
- if [ "$TO_TEST" = "ESLint" ]; then node_modules/.bin/eslint src/main src/test *.js && node_modules/.bin/esdoc; fi
- if [ "$USE_ISTANBUL" = "1" ]; then node_modules/.bin/codecov; fi
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
Expand Up @@ -3,7 +3,7 @@
<head>
<meta charset="UTF-8">
<title>JungleDB Browser Benchmark</title>
<script src="../../dist/web.js"></script>
<script src="../../dist/indexeddb.js"></script>
<script src="../shared/Timer.js"></script>
<script src="../shared/Stats.js"></script>
<script src="../shared/RandomGenerator.js"></script>
Expand Down
3 changes: 2 additions & 1 deletion benchmark/nodejs/index.js → benchmark/leveldb/index.js
Expand Up @@ -3,6 +3,7 @@ process.on('uncaughtException', function(err){
process.exit();
});

const JDB = require('../../dist/leveldb.js');
const BenchmarkRunner = require('../shared/BenchmarkRunner.js');

BenchmarkRunner.runBenchmarks().then(() => console.log('\nBenchmarks finished.'));
BenchmarkRunner.runBenchmarks(undefined, JDB).then(() => console.log('\nBenchmarks finished.'));
9 changes: 9 additions & 0 deletions benchmark/lmdb/index.js
@@ -0,0 +1,9 @@
process.on('uncaughtException', function(err){
console.error(err.stack);
process.exit();
});

const JDB = require('../../dist/lmdb.js');
const BenchmarkRunner = require('../shared/BenchmarkRunner.js');

BenchmarkRunner.runBenchmarks(undefined, JDB, { maxDbSize: 1024*1024*1024 }).then(() => console.log('\nBenchmarks finished.'));
30 changes: 22 additions & 8 deletions benchmark/shared/Benchmark.js
@@ -1,5 +1,4 @@
if (typeof(require) !== 'undefined') {
JDB = require('../../dist/node.js');
Stats = require('./Stats.js');
BenchmarkUtils = require('./BenchmarkUtils.js');
}
Expand All @@ -10,19 +9,37 @@ class Benchmark {
* @param {number} benchmarkVersion
* @param {Array.<string>} tableNames
*/
constructor(name, benchmarkVersion = 1, tableNames = ['default']) {
constructor(name, benchmarkVersion = 1, tableNames = ['default'], indices = {}) {
this.name = name;
this._databaseName = `db-${name}`;
this._tableNames = tableNames;
this._objectStores = [];
this._indices = indices;
this._benchmarkVersion = benchmarkVersion;
this._db = null;

this._jdbOptions = {};
this._jdb = null;
}

setJDB(customJDB, options) {
this._jdb = customJDB;
this._jdbOptions = options || {};
}

get jdb() {
return this._jdb ? this._jdb : JDB;
}

async _init() {
this._db = new JDB.JungleDB(this._databaseName, this._benchmarkVersion);
this._db = new this.jdb.JungleDB(this._databaseName, this._benchmarkVersion, undefined, this._jdbOptions);
for (const tableName of this._tableNames) {
this._objectStores.push(this._db.createObjectStore(tableName));
const store = this._db.createObjectStore(tableName);
this._objectStores.push(store);
if (this._indices[tableName]) {
const { name, keyPath } = this._indices[tableName];
store.createIndex(name, keyPath);
}
}
await this._db.connect();
}
Expand Down Expand Up @@ -56,16 +73,13 @@ class Benchmark {
* @returns {Promise.<Stats>}
*/
async run(count = 2) {
BenchmarkUtils.logColored(`\n\nRun benchmark ${this.description || this.name}:`);
await this._init();
const stats = [];
for (let i=0; i<count; ++i) {
const runStats = new Stats();
const startTime = Date.now();
await this._benchmark(runStats); // eslint-disable-line no-await-in-loop
const elapsedTime = Date.now() - startTime;
console.log(`Run ${i+1} took ${elapsedTime/1000} seconds.`);
console.log(runStats.toString());
stats.push(runStats);
if (i<count-1) {
// if another run follows, reset the benchmark
Expand All @@ -74,7 +88,7 @@ class Benchmark {
}
await this._finalCleanup();
const averageStats = Stats.averageStats(stats);
BenchmarkUtils.logColored(averageStats.toString());
BenchmarkUtils.logColored(`${this.description || this.name},${averageStats.totalTime()}`);
return averageStats;
}
}
Expand Down
4 changes: 2 additions & 2 deletions benchmark/shared/BenchmarkDelete.js
Expand Up @@ -25,8 +25,8 @@ class BenchmarkDelete extends Benchmark {
this._batchSize = batchSize;
this._sync = sync;
this._entryKeys = BenchmarkUtils.createRandomKeys(databaseEntryCount);
this.description = `${BenchmarkDelete.NAME}, ${databaseEntryCount} entries in database, `
+ `delete ${deleteCount} entries, ${entrySize} Bytes per entry, batch size ${batchSize}, `
this.description = `${BenchmarkDelete.NAME},${databaseEntryCount} entries,`
+ `delete ${deleteCount} entries,${entrySize} B/entry,${batchSize} ops/tx,`
+ `${this._sync? '' : 'a'}sync`;
}

Expand Down
4 changes: 2 additions & 2 deletions benchmark/shared/BenchmarkFill.js
Expand Up @@ -23,8 +23,8 @@ class BenchmarkFill extends Benchmark {
this._batchSize = batchSize;
this._useRandomKeys = useRandomKeys;
this._sync = sync;
this.description = `${BenchmarkFill.NAME}, ${entryCount} entries, ${entrySize} Bytes per entry, `
+ `batch size ${batchSize}, use ${useRandomKeys? 'random' : 'sequential'} keys, `
this.description = `${BenchmarkFill.NAME},${entryCount} entries,${entrySize} B/entry,`
+ `${batchSize} ops/tx,${useRandomKeys? 'random' : 'sequential'} keys,`
+ `${this._sync? '' : 'a'}sync`;
}

Expand Down
4 changes: 2 additions & 2 deletions benchmark/shared/BenchmarkOverwrite.js
Expand Up @@ -24,8 +24,8 @@ class BenchmarkOverwrite extends Benchmark {
this._batchSize = batchSize;
this._sync = sync;
this._entryKeys = BenchmarkUtils.createRandomKeys(databaseEntryCount);
this.description = `${BenchmarkOverwrite.NAME}, ${databaseEntryCount} entries in database, `
+ `overwrite ${overwriteCount} entries, ${entrySize} Bytes per entry, batch size ${batchSize}, `
this.description = `${BenchmarkOverwrite.NAME},${databaseEntryCount} entries,`
+ `overwrite ${overwriteCount} entries,${entrySize} B/entry,${batchSize} ops/tx,`
+ `${this._sync? '' : 'a'}sync`;
}

Expand Down
4 changes: 2 additions & 2 deletions benchmark/shared/BenchmarkRead.js
Expand Up @@ -25,8 +25,8 @@ class BenchmarkRead extends Benchmark {
this._batchSize = batchSize;
this._sync = sync;
this._entryKeys = BenchmarkUtils.createRandomKeys(databaseEntryCount);
this.description = `${BenchmarkRead.NAME}, ${databaseEntryCount} entries in database, `
+ `read ${readCount} entries, ${entrySize} Bytes per entry, batch size ${batchSize}, `
this.description = `${BenchmarkRead.NAME},${databaseEntryCount} entries,`
+ `read ${readCount} entries,${entrySize} B/entry,${batchSize} ops/tx,`
+ `${this._sync? '' : 'a'}sync`;
}

Expand Down
4 changes: 2 additions & 2 deletions benchmark/shared/BenchmarkReadFromMultiple.js
Expand Up @@ -30,8 +30,8 @@ class BenchmarkReadFromMultiple extends Benchmark {
this._batchSize = batchSize;
this._sync = sync;
this._entryKeys = BenchmarkUtils.createRandomKeys(databaseEntryCount);
this.description = `${BenchmarkReadFromMultiple.NAME}, ${databaseEntryCount} entries in database, `
+ `read ${readCount} entries, ${totalEntrySize} Bytes per entry, ${numberTables} tables, batch size ${batchSize}, `
this.description = `${BenchmarkReadFromMultiple.NAME},${databaseEntryCount} entries,`
+ `read ${readCount} entries,${totalEntrySize} B/entry,${numberTables} tables,${batchSize} ops/tx,`
+ `${this._sync? '' : 'a'}sync`;
}

Expand Down
71 changes: 71 additions & 0 deletions benchmark/shared/BenchmarkReadIndex.js
@@ -0,0 +1,71 @@
if (typeof(require) !== 'undefined') {
Benchmark = require('./Benchmark.js');
BenchmarkUtils = require('./BenchmarkUtils.js');
RandomGenerator = require('./RandomGenerator.js');
}

class BenchmarkReadIndex extends Benchmark {
/**
* A Benchmark where random, already existing database entries get read.
* @param databaseEntryCount
* @param readCount
* @param entrySize
* @param batchSize
* @param sync
*/
constructor({ databaseEntryCount, readCount, entrySize, batchSize, sync }) {
if (readCount>databaseEntryCount || batchSize>databaseEntryCount
|| (batchSize===1 && !sync)) { // we ignore this setting as it is the same as batchSize 1 with sync
throw('Illegal parameter combination.');
}
const indices = {};
indices[BenchmarkReadIndex.TABLE_NAME] = { name: 'index', keyPath: 'index' };
super(BenchmarkReadIndex.NAME, BenchmarkReadIndex.VERSION, [BenchmarkReadIndex.TABLE_NAME], indices);
this._databaseEntryCount = databaseEntryCount;
this._readCount = readCount;
this._entrySize = entrySize;
this._batchSize = batchSize;
this._sync = sync;
this._entryKeys = BenchmarkUtils.createRandomKeys(databaseEntryCount);
this._indexKeys = BenchmarkUtils.createRandomKeys(databaseEntryCount);
this.description = `${BenchmarkReadIndex.NAME},${databaseEntryCount} entries,`
+ `read ${readCount} entries,${entrySize} B/entry,${batchSize} ops/tx,`
+ `${this._sync? '' : 'a'}sync`;
}


async _init() {
await super._init();
// fill up the database
const objectStore = this._db.getObjectStore(BenchmarkReadIndex.TABLE_NAME);
await BenchmarkUtils.fillObjectStoreWithIndex(objectStore, this._databaseEntryCount, this._entrySize, 1000, false,
this._entryKeys, this._indexKeys);
}


async _benchmark(stats) {
const readKeys = [];
for (let i=0; i<this._readCount; ++i) {
readKeys.push(RandomGenerator.getRandomEntry(this._indexKeys));
}
const readOperation = (transaction, index) => {
const key = readKeys[index];
const i = transaction.index('index');
return i.values(this.jdb.KeyRange.only(key));
};
const objectStore = this._db.getObjectStore(BenchmarkReadIndex.TABLE_NAME);
const result = await BenchmarkUtils.performBatchOperation(objectStore, this._readCount, this._batchSize,
this._sync, readOperation);
if (!result.results.every(entry => !!entry)) {
console.error('Error reading data from database.');
}
stats.addReads(this._readCount, this._readCount * this._entrySize, result.totalTime);
}
}
BenchmarkReadIndex.NAME = 'benchmark-read-index';
BenchmarkReadIndex.TABLE_NAME = 'default';
BenchmarkReadIndex.VERSION = 1;

if (typeof(module) !== 'undefined') {
module.exports = BenchmarkReadIndex;
}
34 changes: 26 additions & 8 deletions benchmark/shared/BenchmarkRunner.js
Expand Up @@ -4,10 +4,11 @@ if (typeof(require) !== 'undefined') {
BenchmarkRead = require('./BenchmarkRead.js');
BenchmarkDelete = require('./BenchmarkDelete.js');
BenchmarkReadFromMultiple = require('./BenchmarkReadFromMultiple.js');
BenchmarkReadIndex = require('./BenchmarkReadIndex.js');
}

class BenchmarkRunner {
static async runBenchmarks(benchmarkUi = null) {
static async runBenchmarks(benchmarkUi = null, customJDB = null, options = {}) {
if (benchmarkUi) {
for (const benchmarkDescription of BenchmarkRunner.BenchmarkDescriptions) {
const type = benchmarkDescription.benchmark.NAME;
Expand All @@ -25,9 +26,10 @@ class BenchmarkRunner {
let result;
try {
const benchmark = new benchmarkDescription.benchmark(params);
benchmark.setJDB(customJDB, options);
result = await benchmark.run(); // eslint-disable-line no-await-in-loop
} catch(e) {
console.log('\nSkipped invalid configuration', params, 'because of error', e);
} catch (e) {
// console.log('\nSkipped invalid configuration', params, 'because of error', e);
result = null;
}
if (benchmarkUi) {
Expand Down Expand Up @@ -103,31 +105,31 @@ BenchmarkRunner.BenchmarkDescriptions = [
entryCount: [100, 500, 1000, 10000],
entrySize: [100, 1000, 100000],
batchSize: [1, 100, 500],
sync: [true, false]
sync: [true]
},
{
benchmark: BenchmarkOverwrite,
databaseEntryCount: [10000, 10000],
overwriteCount: [1000, 10000],
entrySize: [10, 100, 1000],
batchSize: [1, 10, 100, 1000],
sync: [true, false]
sync: [true]
},
{
benchmark: BenchmarkRead,
databaseEntryCount: [100, 500, 1000],
readCount: [10, 100, 1000],
entrySize: [100, 1000, 1000000],
batchSize: [1, 10, 100],
sync: [true, false]
sync: [true]
},
{
benchmark: BenchmarkDelete,
databaseEntryCount: [100, 500, 1000],
deleteCount: [10, 100, 1000],
entrySize: [100, 1000],
batchSize: [1, 10, 100],
sync: [true, false]
sync: [true]
},
{
benchmark: BenchmarkReadFromMultiple,
Expand All @@ -136,7 +138,23 @@ BenchmarkRunner.BenchmarkDescriptions = [
totalEntrySize: [100, 1000],
numberTables: [1, 2, 3],
batchSize: [1, 10, 100],
sync: [true, false]
sync: [true]
},
{
benchmark: BenchmarkReadIndex,
databaseEntryCount: [100, 500, 1000],
readCount: [10, 100, 1000],
entrySize: [100, 1000, 1000000],
batchSize: [1, 10, 100],
sync: [true]
},
{
benchmark: BenchmarkReadIndex,
databaseEntryCount: [100000],
readCount: [10, 100, 1000],
entrySize: [100, 1000],
batchSize: [1, 10, 100],
sync: [true]
}
];

Expand Down
34 changes: 33 additions & 1 deletion benchmark/shared/BenchmarkUtils.js
Expand Up @@ -32,6 +32,37 @@ class BenchmarkUtils {
}


/**
* @param {ObjectStore} objectStore
* @param {number} entryCount
* @param {number} entrySize
* @param {number} [batchSize]
* @param {boolean} [sync]
* @param {Array.<string>} [keys]
* @param {Stats} [stats]
* @returns {Promise.<void>}
*/
static async fillObjectStoreWithIndex(objectStore, entryCount, entrySize, batchSize=1, sync=false, keys=null, indexKeys=null, stats=null) {
const randomData = [];
for (let i=0; i<entryCount; ++i) {
randomData.push({
index: indexKeys ? indexKeys[i] : String(i),
data: RandomGenerator.generateRandomData(entrySize)
});
}
const putEntry = async (transaction, index) => {
const key = keys ? keys[index] : String(index);
const data = randomData[index];
await transaction.put(key, data);
};
const totalTime =
(await BenchmarkUtils.performBatchOperation(objectStore, entryCount, batchSize, sync, putEntry)).totalTime;
if (stats) {
stats.addWrites(entryCount, entryCount*entrySize, totalTime);
}
}


/**
* @param {ObjectStore} objectStore
* @param {number} totalCount
Expand Down Expand Up @@ -163,7 +194,8 @@ class BenchmarkUtils {
console.log(`%c${str}`, 'color: teal; font-weight: bold;');
} else {
// node
console.log('\x1b[36m%s\x1b[0m', str);
// console.log('\x1b[36m%s\x1b[0m', str);
console.log(str);
}
}
}
Expand Down
4 changes: 4 additions & 0 deletions benchmark/shared/Stats.js
Expand Up @@ -196,6 +196,10 @@ class Stats {
}
}

totalTime() {
return `${this.writeTimePerRun + this.readTimePerRun},${Stats._getTimeString(this.writeTimePerRun + this.readTimePerRun)}`;
}

toString() {
let result = '';
if (this.writes > 0) {
Expand Down

0 comments on commit 7928851

Please sign in to comment.