Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 17 additions & 2 deletions bench/bench-eventstore.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import benchmarks from 'beautify-benchmark';
import fs from 'fs-extra';
import Stable from 'event-storage';
import { EventStore as Latest } from '../index.js';
import MmapWritableIndex, { MmapReadOnlyIndex, loadMmapModule } from '../src/Index/MmapWritableIndex.js';

const Suite = new Benchmark.Suite('eventstore');
Suite.on('start', () => fs.emptyDirSync('data'));
Expand All @@ -11,6 +12,15 @@ Suite.on('complete', () => benchmarks.log());
Suite.on('error', (e) => console.log(e.target.error));

const WRITES = 1000;
const latestIndexOptions = {};

try {
loadMmapModule();
latestIndexOptions.IndexClass = MmapWritableIndex;
latestIndexOptions.ReadOnlyIndexClass = MmapReadOnlyIndex;
} catch (e) {
console.log('Mmap index unavailable, using latest default index implementation:', e.message);
}

/**
* @param {Stable|Latest} store
Expand All @@ -34,7 +44,12 @@ Suite.add('eventstore [stable]', function() {
});

Suite.add('eventstore [latest]', function() {
bench(new Latest('eventstore', { storageDirectory: 'data/latest' }), this.cycles);
bench(new Latest('eventstore', {
storageDirectory: 'data/latest',
storageConfig: {
indexOptions: latestIndexOptions
}
}), this.cycles);
});

Suite.run();
Suite.run();
203 changes: 166 additions & 37 deletions bench/bench-index.js
Original file line number Diff line number Diff line change
@@ -1,41 +1,170 @@
import Benchmark from 'benchmark';
import benchmarks from 'beautify-benchmark';
import fs from 'fs-extra';
import Stable from 'event-storage';
import { Index as LatestIndex } from '../index.js';
import MmapWritableIndex, { getMmapPackageName } from '../src/Index/MmapWritableIndex.js';

const Suite = new Benchmark.Suite('index');
Suite.on('start', () => fs.emptyDirSync('data'));
Suite.on('cycle', (event) => benchmarks.add(event.target));
Suite.on('complete', () => benchmarks.log());
Suite.on('error', (e) => console.log(e.target.error));

const WRITES = 1000;
let stableCallCount = 0;
let latestCallCount = 0;

function bench(index) {
index.open();
for (let i = 1; i<=WRITES; i++) {
index.add(new Stable.Index.Entry(i,2,4,8));
}
index.close();

let number;
index.open();
for (let entry of index.range(-WRITES + 1)) {
number = entry.number;
}
index.close();
if (number < WRITES) throw new Error('Not all entries were written! Last entry was '+number);
}

Suite.add('index [stable]', function() {
bench(new Stable.Index((stableCallCount++) + '.index', { dataDirectory: 'data/stable' }));
});

Suite.add('index [latest]', function() {
bench(new LatestIndex((latestCallCount++) + '.index', { dataDirectory: 'data/latest' }));
});

Suite.run();
const WRITE_DIR = 'data/write';
const READ_DIR = 'data/read';
const READ_SIZE = 1000;
const WRITE_WARMUP = 5000;
const WRITE_ITERATIONS = 50000;
const READ_WARMUP = 200;
const READ_ITERATIONS = 2000;

function createEntry(number) {
return new Stable.Index.Entry(number, 2, 4, 8);
}

function median(values) {
const sorted = values.slice().sort((a, b) => a - b);
return sorted[Math.floor(sorted.length / 2)];
}

function measure(label, iterations, warmup, fn) {
for (let i = 0; i < warmup; i++) fn();

const samples = [];
const chunkSize = Math.max(1, Math.floor(iterations / 20));
let done = 0;

while (done < iterations) {
const count = Math.min(chunkSize, iterations - done);
const start = process.hrtime.bigint();
for (let i = 0; i < count; i++) fn();
const elapsedNs = Number(process.hrtime.bigint() - start);
samples.push(elapsedNs / count);
done += count;
}

const nsPerOp = median(samples);
const opsPerSec = 1e9 / nsPerOp;
console.log(`${label.padEnd(32)} ${opsPerSec.toFixed(0).padStart(10)} ops/sec ${nsPerOp.toFixed(0).padStart(10)} ns/op`);

return { nsPerOp, opsPerSec };
}

function createIndexes(baseDir, fileName) {
const indexes = [
{
label: 'stable',
index: new Stable.Index(fileName, { dataDirectory: `${baseDir}/stable` }),
},
{
label: 'latest',
index: new LatestIndex(fileName, { dataDirectory: `${baseDir}/latest` }),
},
];

try {
const packageName = getMmapPackageName();
indexes.push({
label: packageName,
index: new MmapWritableIndex(fileName, { dataDirectory: `${baseDir}/mmap` }),
});
} catch (e) {
console.log('Skipping mmap benchmark:', e.message);
}

for (const item of indexes) {
item.index.open();
}

return indexes;
}

function closeIndexes(indexes) {
for (const item of indexes) {
try {
item.index.close();
} catch (e) {
}
}
}

function runWriteBenchmarks() {
fs.emptyDirSync(WRITE_DIR);
const indexes = createIndexes(WRITE_DIR, 'write.index');

console.log('\nindex write benchmark (add only, open/close excluded)');
const results = {};

try {
for (const item of indexes) {
let count = 0;
results[item.label] = measure(
`write:add [${item.label}]`,
WRITE_ITERATIONS,
WRITE_WARMUP,
() => {
count += 1;
item.index.add(createEntry(count));
}
);
}
} finally {
closeIndexes(indexes);
}

return results;
}

function runReadBenchmarks() {
fs.emptyDirSync(READ_DIR);
const indexes = createIndexes(READ_DIR, 'read.index');

for (const item of indexes) {
for (let i = 1; i <= READ_SIZE; i++) {
item.index.add(createEntry(i));
}
}

console.log('\nindex read benchmark (range iteration only, open/close excluded)');
const results = {};

try {
for (const item of indexes) {
results[item.label] = measure(
`read:range [${item.label}]`,
READ_ITERATIONS,
READ_WARMUP,
() => {
let number = 0;
for (const entry of item.index.range(-READ_SIZE + 1)) {
number = entry.number;
}
if (number !== READ_SIZE) {
throw new Error(`Read validation failed for ${item.label}: ${number}`);
}
}
);
}
} finally {
closeIndexes(indexes);
}

return results;
}

function printRelativeComparison(title, results) {
const stable = results.stable?.nsPerOp;
if (!stable) {
return;
}

console.log(`\n${title} (relative to stable)`);
for (const [label, result] of Object.entries(results)) {
const delta = ((result.nsPerOp / stable - 1) * 100).toFixed(1);
const sign = delta.startsWith('-') ? '' : '+';
console.log(` ${label.padEnd(20)} ${sign}${delta}%`);
}
}

function main() {
const writeResults = runWriteBenchmarks();
const readResults = runReadBenchmarks();

printRelativeComparison('write performance', writeResults);
printRelativeComparison('read performance', readResults);
}

main();
20 changes: 18 additions & 2 deletions bench/bench-read-scenarios.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import benchmarks from 'beautify-benchmark';
import fs from 'fs-extra';
import Stable from 'event-storage';
import { EventStore as Latest } from '../index.js';
import MmapWritableIndex, { MmapReadOnlyIndex, loadMmapModule } from '../src/Index/MmapWritableIndex.js';

const Suite = new Benchmark.Suite('read-scenarios');
Suite.on('cycle', (event) => benchmarks.add(event.target));
Expand All @@ -14,6 +15,15 @@ const STREAM_1 = 'stream-1';
const STREAM_2 = 'stream-2';
// Keep each event document close to 512 bytes
const EVENT_DOC = { type: 'SomeEvent', payload: 'x'.repeat(460) };
const latestIndexOptions = {};

try {
loadMmapModule();
latestIndexOptions.IndexClass = MmapWritableIndex;
latestIndexOptions.ReadOnlyIndexClass = MmapReadOnlyIndex;
} catch (e) {
console.log('Mmap index unavailable, using latest default index implementation:', e.message);
}

function countAll(iter) {
let n = 0;
Expand All @@ -24,7 +34,10 @@ function countAll(iter) {
function populateStore(EventStore, directory) {
return new Promise((resolve, reject) => {
fs.emptyDirSync(directory);
const store = new EventStore('bench', { storageDirectory: directory });
const config = EventStore === Latest
? { storageDirectory: directory, storageConfig: { indexOptions: latestIndexOptions } }
: { storageDirectory: directory };
const store = new EventStore('bench', config);
store.once('ready', () => {
for (let i = 0; i < EVENTS; i++) {
store.commit(i % 2 === 0 ? STREAM_1 : STREAM_2, Object.assign({ seq: i }, EVENT_DOC));
Expand All @@ -38,7 +51,10 @@ function populateStore(EventStore, directory) {

function openReadOnly(EventStore, directory) {
return new Promise((resolve, reject) => {
const store = new EventStore('bench', { storageDirectory: directory, readOnly: true });
const config = EventStore === Latest
? { storageDirectory: directory, readOnly: true, storageConfig: { indexOptions: latestIndexOptions } }
: { storageDirectory: directory, readOnly: true };
const store = new EventStore('bench', config);
store.once('ready', () => resolve(store));
store.once('error', reject);
});
Expand Down
17 changes: 15 additions & 2 deletions bench/bench-storage.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import benchmarks from 'beautify-benchmark';
import fs from 'fs-extra';
import Stable from 'event-storage';
import { Storage as LatestStorage } from '../index.js';
import MmapWritableIndex, { MmapReadOnlyIndex, loadMmapModule } from '../src/Index/MmapWritableIndex.js';

const Suite = new Benchmark.Suite('storage');
Suite.on('start', () => fs.emptyDirSync('data'));
Expand All @@ -11,6 +12,15 @@ Suite.on('complete', () => benchmarks.log());
Suite.on('error', (e) => console.log(e.target.error));

const WRITES = 1000;
const latestIndexOptions = {};

try {
loadMmapModule();
latestIndexOptions.IndexClass = MmapWritableIndex;
latestIndexOptions.ReadOnlyIndexClass = MmapReadOnlyIndex;
} catch (e) {
console.log('Mmap index unavailable, using latest default index implementation:', e.message);
}

function bench(storage) {
storage.open();
Expand All @@ -35,7 +45,10 @@ Suite.add('storage [stable]', function() {
});

Suite.add('storage [latest]', function() {
bench(new LatestStorage('storage-' + this.cycles, { dataDirectory: 'data/latest' }));
bench(new LatestStorage('storage-' + this.cycles, {
dataDirectory: 'data/latest',
indexOptions: latestIndexOptions
}));
});

Suite.run();
Suite.run();
Loading