Skip to content

Commit b35b56b

Browse files
authored
Add batch-put benchmark (#4)
1 parent 24b5031 commit b35b56b

File tree

5 files changed

+154
-0
lines changed

5 files changed

+154
-0
lines changed

README.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,16 @@ Perform concurrent `put()` operations on sorted string keys. Options:
141141
- `--concurrency`: default 10
142142
- `--valueSize`: size of value, as a number in bytes or string with unit (e.g. `--valueSize 1kb`)
143143

144+
### `batch-put`
145+
146+
Same as `write`, but in batches rather than singular puts. Perform concurrent `batch()` operations on random string keys and values. Options:
147+
148+
- `-n`: amount of operations, default 1e6
149+
- `--batchSize`: default 1000
150+
- `--chained`: boolean flag, default false, use chained batch
151+
- `--concurrency`: default 1
152+
- `--valueSize`: size of value, as a number in bytes or string with unit (e.g. `--valueSize 1kb`)
153+
144154
<!-- ### Other ideas
145155
146156
- Write batches in different sizes (feature: define a matrix)

benchmarks/batch-put.js

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
'use strict'
2+
3+
const crypto = require('crypto')
4+
const ldu = require('../lib/level-du')
5+
const keyTmpl = '0000000000000000'
6+
7+
exports.defaults = {
8+
benchmark: {
9+
n: 1e6,
10+
batchSize: 1e3,
11+
concurrency: 1,
12+
valueSize: 100,
13+
chained: false
14+
}
15+
}
16+
17+
exports.plot = require('./write.plot')
18+
19+
exports.run = function (factory, stream, options) {
20+
stream.write('Elapsed (ms), Entries, Bytes, Last 1000 Avg Time, MB/s\n')
21+
22+
function make16CharPaddedKey () {
23+
const r = Math.floor(Math.random() * options.n)
24+
const k = keyTmpl + r
25+
26+
return k.substr(k.length - 16)
27+
}
28+
29+
function start (db) {
30+
const startTime = Date.now()
31+
const batchSize = options.batchSize
32+
33+
let inProgress = 0
34+
let totalWrites = 0
35+
let totalBytes = 0
36+
let timesAccum = 0
37+
let elapsed
38+
39+
function report () {
40+
console.log(
41+
'Wrote', options.n, 'entries in',
42+
Math.floor((Date.now() - startTime) / 1000) + 's,',
43+
(Math.floor((totalBytes / 1048576) * 100) / 100) + 'MB'
44+
)
45+
46+
stream.end()
47+
48+
db.close(function (err) {
49+
if (err) throw err
50+
51+
ldu(db, function (err, size) {
52+
if (err) throw err
53+
if (size) console.log('Database size:', Math.floor(size / 1024 / 1024) + 'M')
54+
})
55+
})
56+
}
57+
58+
function write () {
59+
if (totalWrites >= options.n) return report(Date.now() - startTime)
60+
if (inProgress >= options.concurrency) return
61+
62+
inProgress++
63+
64+
if (totalWrites % 100000 === 0) {
65+
console.log('' + inProgress, totalWrites,
66+
Math.round(totalWrites / options.n * 100) + '%')
67+
}
68+
69+
// TODO: batchSize should be a multiple of 10
70+
if (totalWrites % 1000 === 0) {
71+
elapsed = Date.now() - startTime
72+
stream.write(
73+
elapsed +
74+
',' + totalWrites +
75+
',' + totalBytes +
76+
',' + Math.floor(timesAccum / 1000) +
77+
',' + (Math.floor(((totalBytes / 1048576) / (elapsed / 1000)) * 100) / 100) +
78+
'\n')
79+
timesAccum = 0
80+
}
81+
82+
let start
83+
84+
if (options.chained) {
85+
const batch = db.batch()
86+
87+
for (let i = 0; i < batchSize; i++) {
88+
// TODO: see comment in write.js
89+
const key = make16CharPaddedKey()
90+
const value = crypto.randomBytes(options.valueSize).toString('hex')
91+
92+
batch.put(key, value)
93+
}
94+
95+
start = process.hrtime()
96+
batch.write(onWrite)
97+
} else {
98+
const ops = new Array(batchSize)
99+
100+
for (let i = 0; i < batchSize; i++) {
101+
// TODO: see comment in write.js
102+
const key = make16CharPaddedKey()
103+
const value = crypto.randomBytes(options.valueSize).toString('hex')
104+
105+
ops[i] = { type: 'put', key, value }
106+
}
107+
108+
start = process.hrtime()
109+
db.batch(ops, onWrite)
110+
}
111+
112+
function onWrite (err) {
113+
if (err) throw err
114+
115+
const duration = process.hrtime(start)
116+
const nano = (duration[0] * 1e9) + duration[1]
117+
118+
totalBytes += (keyTmpl.length + options.valueSize) * batchSize
119+
totalWrites += batchSize
120+
timesAccum += nano
121+
inProgress--
122+
123+
process.nextTick(write)
124+
}
125+
}
126+
127+
for (let i = 0; i < options.concurrency; i++) write()
128+
}
129+
130+
// TODO (once stream is sync): skip setTimeout
131+
setTimeout(function () {
132+
factory(function (err, db) {
133+
if (err) throw err
134+
start(db)
135+
})
136+
}, 500)
137+
}

benchmarks/index.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,5 @@
33
exports.write = require('./write')
44
exports['write-random'] = require('./write-random')
55
exports['write-sorted'] = require('./write-sorted')
6+
7+
exports['batch-put'] = require('./batch-put')

benchmarks/write.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,10 @@ exports.run = function (factory, stream, options) {
7575
timesAccum = 0
7676
}
7777

78+
// TODO: though we don't start the clock until after crypto.randomBytes(),
79+
// due to concurrency there might be put() callbacks waiting in libuv
80+
// while the main thread is blocked? hmz. Maybe use async randomBytes(),
81+
// or pregenerated values (bonus: make them deterministic).
7882
const key = make16CharPaddedKey()
7983
const value = crypto.randomBytes(options.valueSize).toString('hex')
8084
const start = process.hrtime()

benchmarks/write.plot.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
const e = require('../lib/escape-gnuplot-string')
44

5+
// Note: also used by batch-put.js
56
module.exports = function (title, description, results) {
67
const durations = results.map(function (res, i) {
78
const file = res.csvFile

0 commit comments

Comments
 (0)