Skip to content

Commit

Permalink
feat: use new datastore-idb (#225)
Browse files Browse the repository at this point in the history
A new fast and smaller browser datastore is used `datastore-idb`  https://github.com/ipfs/js-datastore-idb

Also removes node globals and reduces bundle size. size: 77.09KB -> 45.07KB

```
datastore-idb up to 44% faster and 69% smaller

batch idb x 5.26 ops/sec ±4.57% (29 runs sampled)
batch level x 2.92 ops/sec ±3.76% (19 runs sampled)
Fastest is batch idb
    
size 37.32KB to 11.41KB
```

Co-authored-by: Alex Potsides <[email protected]>
  • Loading branch information
hugomrdias and achingbrain authored Apr 20, 2020
1 parent ac32e3c commit 99df42b
Show file tree
Hide file tree
Showing 22 changed files with 115 additions and 99 deletions.
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ stages:
- cov

node_js:
- '10'
- '12'
- '10'

os:
- linux
Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -181,21 +181,21 @@ Get a value at the root of the repo.

* `key` can be a buffer, a string or a [Key](https://github.com/ipfs/interface-datastore#keys).

[Blocks](https://github.com/ipfs/js-ipfs-block#readme):
[Blocks](https://github.com/ipfs/js-ipld-block#readme):

#### `Promise<Boolean> repo.isInitialized ()`

The returned promise resolves to `false` if the repo has not been initialized and `true` if it has.

#### `Promise repo.blocks.put (block:Block)`

* `block` should be of type [Block](https://github.com/ipfs/js-ipfs-block#readme).
* `block` should be of type [Block](https://github.com/ipfs/js-ipld-block#readme).

#### `Promise repo.blocks.putMany (blocks)`

Put many blocks.

* `block` should be an Iterable or AsyncIterable that yields entries of type [Block](https://github.com/ipfs/js-ipfs-block#readme).
* `block` should be an Iterable or AsyncIterable that yields entries of type [Block](https://github.com/ipfs/js-ipld-block#readme).

#### `Promise<Buffer> repo.blocks.get (cid)`

Expand Down
22 changes: 12 additions & 10 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
],
"browser": {
"rimraf": false,
"datastore-fs": "datastore-level",
"datastore-fs": "datastore-idb",
"./src/lock.js": "./src/lock-memory.js",
"./src/default-options.js": "./src/default-options-browser.js"
},
Expand Down Expand Up @@ -43,33 +43,35 @@
"npm": ">=3.0.0"
},
"devDependencies": {
"aegir": "^21.4.5",
"aegir": "^21.8.1",
"chai": "^4.2.0",
"dirty-chai": "^2.0.1",
"lodash": "^4.17.11",
"just-range": "^2.1.0",
"memdown": "^5.1.0",
"multihashes": "~0.4.15",
"multihashing-async": "~0.8.0",
"ncp": "^2.0.0",
"rimraf": "^3.0.0",
"sinon": "^9.0.1"
"sinon": "^9.0.2"
},
"dependencies": {
"base32.js": "~0.1.0",
"bignumber.js": "^9.0.0",
"buffer": "^5.5.0",
"bytes": "^3.1.0",
"cids": "^0.8.0",
"datastore-core": "~0.7.0",
"datastore-core": "^1.0.0",
"datastore-fs": "~0.9.0",
"datastore-idb": "^1.0.0",
"datastore-level": "~0.14.0",
"debug": "^4.1.0",
"err-code": "^2.0.0",
"interface-datastore": "^0.8.0",
"ipfs-block": "~0.8.1",
"ipfs-repo-migrations": "~0.1.0",
"interface-datastore": "^0.8.3",
"ipfs-repo-migrations": "^0.2.0",
"ipfs-utils": "^2.2.0",
"ipld-block": "^0.9.1",
"just-safe-get": "^2.0.0",
"just-safe-set": "^2.1.0",
"lodash.has": "^4.5.2",
"multibase": "^0.7.0",
"p-queue": "^6.0.0",
"proper-lockfile": "^4.0.0",
"sort-keys": "^4.0.0"
Expand Down
1 change: 1 addition & 0 deletions src/api-addr.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
'use strict'

const { Buffer } = require('buffer')
const Key = require('interface-datastore').Key

const apiFile = new Key('api')
Expand Down
10 changes: 3 additions & 7 deletions src/blockstore-utils.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
'use strict'

const base32 = require('base32.js')
const { Key } = require('interface-datastore')
const CID = require('cids')
const multibase = require('multibase')

/**
* Transform a cid to the appropriate datastore key.
Expand All @@ -11,8 +11,7 @@ const CID = require('cids')
* @returns {Key}
*/
exports.cidToKey = cid => {
const enc = new base32.Encoder()
return new Key('/' + enc.write(cid.buffer).finalize(), false)
return new Key('/' + multibase.encode('base32', cid.buffer).toString().slice(1).toUpperCase(), false)
}

/**
Expand All @@ -22,8 +21,5 @@ exports.cidToKey = cid => {
* @returns {CID}
*/
exports.keyToCid = key => {
// Block key is of the form /<base32 encoded string>
const decoder = new base32.Decoder()
const buff = decoder.write(key.toString().slice(1)).finalize()
return new CID(Buffer.from(buff))
return new CID(multibase.decode('b' + key.toString().slice(1).toLowerCase()))
}
2 changes: 1 addition & 1 deletion src/blockstore.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

const core = require('datastore-core')
const ShardingStore = core.ShardingDatastore
const Block = require('ipfs-block')
const Block = require('ipld-block')
const CID = require('cids')
const errcode = require('err-code')
const { cidToKey } = require('./blockstore-utils')
Expand Down
4 changes: 2 additions & 2 deletions src/config.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
'use strict'

const { Buffer } = require('buffer')
const Key = require('interface-datastore').Key
const { default: Queue } = require('p-queue')
const _get = require('just-safe-get')
const _set = require('just-safe-set')
const _has = require('lodash.has')
const errcode = require('err-code')
const errors = require('./errors')

Expand All @@ -27,7 +27,7 @@ module.exports = (store) => {

const encodedValue = await store.get(configKey)
const config = JSON.parse(encodedValue.toString())
if (key !== undefined && !_has(config, key)) {
if (key !== undefined && _get(config, key) === undefined) {
throw new errors.NotFoundError(`Key ${key} does not exist in config`)
}

Expand Down
8 changes: 4 additions & 4 deletions src/default-options-browser.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@
module.exports = {
lock: 'memory',
storageBackends: {
root: require('datastore-level'),
blocks: require('datastore-level'),
keys: require('datastore-level'),
datastore: require('datastore-level')
root: require('datastore-idb'),
blocks: require('datastore-idb'),
keys: require('datastore-idb'),
datastore: require('datastore-idb')
},
storageBackendOptions: {
root: {
Expand Down
33 changes: 23 additions & 10 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
'use strict'

const _get = require('just-safe-get')
const assert = require('assert')
const path = require('path')
const debug = require('debug')
const Big = require('bignumber.js')
const errcode = require('err-code')
const migrator = require('ipfs-repo-migrations')
const bytes = require('bytes')
const pathJoin = require('ipfs-utils/src/path-join')

const constants = require('./constants')
const backends = require('./backends')
Expand Down Expand Up @@ -40,7 +39,9 @@ class IpfsRepo {
* @param {object} options - Configuration
*/
constructor (repoPath, options) {
assert.strictEqual(typeof repoPath, 'string', 'missing repoPath')
if (typeof repoPath !== 'string') {
throw new Error('missing repoPath')
}

this.options = buildOptions(options)
this.closed = true
Expand Down Expand Up @@ -112,13 +113,15 @@ class IpfsRepo {
this.lockfile = await this._openLock(this.path)
log('acquired repo.lock')
log('creating datastore')
this.datastore = backends.create('datastore', path.join(this.path, 'datastore'), this.options)
this.datastore = backends.create('datastore', pathJoin(this.path, 'datastore'), this.options)
await this.datastore.open()
log('creating blocks')
const blocksBaseStore = backends.create('blocks', path.join(this.path, 'blocks'), this.options)
const blocksBaseStore = backends.create('blocks', pathJoin(this.path, 'blocks'), this.options)
await blocksBaseStore.open()
this.blocks = await blockstore(blocksBaseStore, this.options.storageBackendOptions.blocks)
log('creating keystore')
this.keys = backends.create('keys', path.join(this.path, 'keys'), this.options)

this.keys = backends.create('keys', pathJoin(this.path, 'keys'), this.options)
await this.keys.open()
const isCompatible = await this.version.check(constants.repoVersion)
if (!isCompatible) {
if (await this._isAutoMigrationEnabled()) {
Expand Down Expand Up @@ -152,11 +155,15 @@ class IpfsRepo {
*/
_getLocker () {
if (typeof this.options.lock === 'string') {
assert(lockers[this.options.lock], 'Unknown lock type: ' + this.options.lock)
if (!lockers[this.options.lock]) {
throw new Error('Unknown lock type: ' + this.options.lock)
}
return lockers[this.options.lock]
}

assert(this.options.lock, 'No lock provided')
if (!this.options.lock) {
throw new Error('No lock provided')
}
return this.options.lock
}

Expand Down Expand Up @@ -251,7 +258,13 @@ class IpfsRepo {
}
}

await Promise.all([this.root, this.blocks, this.keys, this.datastore].map((store) => store.close()))
await Promise.all([
this.root,
this.blocks,
this.keys,
this.datastore
].map((store) => store.close()))

log('unlocking')
this.closed = true
await this._closeLock()
Expand Down
12 changes: 11 additions & 1 deletion src/lock.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
'use strict'

const { LockExistsError } = require('./errors')
const path = require('path')
const debug = require('debug')
const { lock } = require('proper-lockfile')
Expand Down Expand Up @@ -27,7 +28,16 @@ const STALE_TIME = 20000
exports.lock = async (dir) => {
const file = path.join(dir, lockFile)
log('locking %s', file)
const release = await lock(dir, { lockfilePath: file, stale: STALE_TIME })
let release
try {
release = await lock(dir, { lockfilePath: file, stale: STALE_TIME })
} catch (err) {
if (err.code === 'ELOCKED') {
throw new LockExistsError(`Lock already being held for file: ${file}`)
} else {
throw err
}
}
return {
close: async () => { // eslint-disable-line require-await
release()
Expand Down
1 change: 1 addition & 0 deletions src/spec.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
'use strict'

const { Buffer } = require('buffer')
const Key = require('interface-datastore').Key
const sortKeys = require('sort-keys')

Expand Down
1 change: 1 addition & 0 deletions src/version.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
'use strict'

const { Buffer } = require('buffer')
const Key = require('interface-datastore').Key
const debug = require('debug')
const log = debug('repo:version')
Expand Down
1 change: 1 addition & 0 deletions test/api-addr-test.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/* eslint-env mocha */
'use strict'

const { Buffer } = require('buffer')
const chai = require('chai')
chai.use(require('dirty-chai'))
const expect = chai.expect
Expand Down
31 changes: 17 additions & 14 deletions test/blockstore-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,22 @@
/* eslint-env mocha */
'use strict'

const { Buffer } = require('buffer')
const chai = require('chai')
chai.use(require('dirty-chai'))
const expect = chai.expect
const assert = chai.assert
const Block = require('ipfs-block')
const Block = require('ipld-block')
const CID = require('cids')
const _ = require('lodash')
const range = require('just-range')
const multihashing = require('multihashing-async')
const path = require('path')
const Key = require('interface-datastore').Key
const base32 = require('base32.js')
const tempDir = require('ipfs-utils/src/temp-dir')
const { cidToKey } = require('../src/blockstore-utils')
const IPFSRepo = require('../')

module.exports = (repo) => {
describe('blockstore', () => {
const blockData = _.range(100).map((i) => Buffer.from(`hello-${i}-${Math.random()}`))
const blockData = range(100).map((i) => Buffer.from(`hello-${i}-${Math.random()}`))
const bData = Buffer.from('hello world')
let b

Expand Down Expand Up @@ -52,16 +52,16 @@ module.exports = (repo) => {

it('massive multiwrite', async function () {
this.timeout(15000) // add time for ci
const hashes = await Promise.all(_.range(100).map((i) => multihashing(blockData[i], 'sha2-256')))
await Promise.all(_.range(100).map((i) => {
const hashes = await Promise.all(range(100).map((i) => multihashing(blockData[i], 'sha2-256')))
await Promise.all(range(100).map((i) => {
const block = new Block(blockData[i], new CID(hashes[i]))
return repo.blocks.put(block)
}))
})

it('.putMany', async function () {
this.timeout(15000) // add time for ci
const blocks = await Promise.all(_.range(50).map(async (i) => {
const blocks = await Promise.all(range(50).map(async (i) => {
const d = Buffer.from('many' + Math.random())
const hash = await multihashing(d, 'sha2-256')
return new Block(d, new CID(hash))
Expand All @@ -79,9 +79,12 @@ module.exports = (repo) => {
const cid = new CID(hash)
let putInvoked = false
let commitInvoked = false
otherRepo = new IPFSRepo(path.join(path.basename(repo.path), '/repo-' + Date.now()), {
otherRepo = new IPFSRepo(tempDir(), {
storageBackends: {
blocks: class ExplodingBlockStore {
open () {
}

close () {

}
Expand Down Expand Up @@ -147,7 +150,7 @@ module.exports = (repo) => {

it('massive read', async function () {
this.timeout(15000) // add time for ci
await Promise.all(_.range(20 * 100).map(async (i) => {
await Promise.all(range(20 * 100).map(async (i) => {
const j = i % blockData.length
const hash = await multihashing(blockData[j], 'sha2-256')
const block = await repo.blocks.get(new CID(hash))
Expand Down Expand Up @@ -210,12 +213,12 @@ module.exports = (repo) => {
const data = Buffer.from(`TEST${Date.now()}`)
const hash = await multihashing(data, 'sha2-256')
const cid = new CID(hash)
const enc = new base32.Encoder()
const key = new Key('/' + enc.write(cid.buffer).finalize(), false)
const key = cidToKey(cid)

otherRepo = new IPFSRepo(path.join(path.basename(repo.path), '/repo-' + Date.now()), {
otherRepo = new IPFSRepo(tempDir(), {
storageBackends: {
blocks: class ExplodingBlockStore {
open () {}
close () {

}
Expand Down
1 change: 1 addition & 0 deletions test/config-test.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/* eslint-env mocha */
'use strict'

const { Buffer } = require('buffer')
const chai = require('chai')
chai.use(require('dirty-chai'))
const expect = chai.expect
Expand Down
Loading

0 comments on commit 99df42b

Please sign in to comment.