Skip to content

Commit c8e3451

Browse files
committed
Major performance improvements, new API.
1 parent 548dd87 commit c8e3451

File tree

4 files changed

+71
-72
lines changed

4 files changed

+71
-72
lines changed

index.js

+58-60
Original file line numberDiff line numberDiff line change
@@ -10,95 +10,97 @@ const nest = (map, key, cid) => {
1010
map.set(key.shift(), cid)
1111
}
1212

13-
const mkcid = c => c instanceof CID ? c : new CID(c)
13+
const mkcid = c => c.toBaseEncodedString ? c : new CID(c)
1414

1515
class ComplexIPLDGraph {
16-
constructor (store, cbor) {
16+
constructor (store, root, cbor) {
1717
this.shardPaths = new Map()
1818
if (!cbor) {
1919
cbor = fancyCBOR
2020
}
2121
this.cbor = cbor((...args) => store.get(...args))
2222
this.store = store
23+
this.root = root
2324
this._clear()
25+
this._bulk = store.bulk()
26+
this._shardPaths = new Map()
2427
}
2528
_clear () {
29+
this._getCache = new Map()
2630
this._pending = new Map()
2731
this._patches = new Map()
28-
this._bulk = null
2932
}
3033
shardPath (path, handler) {
3134
path = path.split('/').filter(x => x)
3235
if (path[path.length - 1] !== '*') {
3336
throw new Error('All shard paths must end in "*".')
3437
}
35-
this.shardPaths.set(path, handler)
38+
nest(this._shardPaths, path, handler)
3639
}
37-
async _realKey (path) {
40+
_realKey (path) {
3841
path = path.split('/').filter(x => x)
3942
let _realpath = []
40-
let _shardKeys = new Set(this.shardPaths.keys())
41-
42-
let i = 0
43+
let maps = [this._shardPaths]
4344
while (path.length) {
4445
let key = path.shift()
46+
let nextMaps = []
4547
let changed = false
46-
for (let _path of Array.from(_shardKeys)) {
47-
let _key = _path[i]
48-
if (!_key) continue
49-
if (_key === '*') {
50-
_realpath.push(await this.shardPaths.get(_path)(key))
51-
changed = true
52-
break
53-
} else if (_key.startsWith(':')) {
54-
continue
55-
} else if (_key === key) {
56-
continue
57-
} else {
58-
_shardKeys.delete(_path)
48+
for (let map of maps) {
49+
for (let [_key, handler] of map.entries()) {
50+
if (_key === key || _key.startsWith(':')) {
51+
nextMaps.push(handler)
52+
}
53+
if (_key === '*' && !changed) {
54+
_realpath.push(handler(key))
55+
changed = true
56+
}
5957
}
58+
maps = nextMaps
6059
}
6160
if (!changed) _realpath.push(key)
62-
i++
6361
}
64-
// handlers can return '/' in keys
65-
_realpath = _realpath.join('/').split('/')
66-
return _realpath
62+
return _realpath.join('/').split('/')
6763
}
68-
async _kick () {
69-
if (!this._bulk) this._bulk = await this.store.bulk()
70-
if (!this._draining) {
71-
this._draining = (async () => {
72-
for (let [path, block] of this._pending.entries()) {
73-
path = await this._realKey(path)
74-
this._bulk.put(block.cid, block.data)
75-
nest(this._patches, path, block.cid)
76-
this._draining = null
64+
_prime (path) {
65+
/* pre-fetch intermediate node's we'll need to build the graph */
66+
path = Array.from(path)
67+
let run = (parent) => {
68+
if (path.length) {
69+
let key = path.shift()
70+
if (parent[key] && parent[key]['/']) {
71+
this.get(mkcid(parent[key]['/'])).then(run)
7772
}
78-
})()
73+
}
7974
}
80-
return this._draining
75+
this.get(this.root).then(run)
8176
}
8277
_queue (path, block) {
8378
this._pending.set(path, block)
84-
this._kick()
79+
80+
path = this._realKey(path)
81+
this._prime(path)
82+
nest(this._patches, path, block.cid)
83+
this._draining = null
8584
}
86-
add (path, block) {
85+
async add (path, block) {
86+
if (this._spent) {
87+
throw new Error('This graph instance has already been flushed.')
88+
}
8789
this._queue(path, block)
90+
return this._bulk.put(block.cid, block.data)
8891
}
89-
async flush (root, clobber = true) {
90-
if (!root) {
91-
root = this.root
92+
async flush () {
93+
if (this._spent) {
94+
throw new Error('This graph instance has already been flushed.')
9295
}
96+
let root = this.root
9397
if (!root) throw new Error('No root node.')
9498
root = mkcid(root)
95-
await this._kick()
96-
await this._kick()
9799

98100
let mkcbor = async obj => {
99101
let cid
100102
for await (let block of this.cbor.serialize(obj)) {
101-
this._bulk.put(block.cid, block.data)
103+
await this._bulk.put(block.cid, block.data)
102104
cid = block.cid
103105
}
104106
return cid
@@ -107,24 +109,23 @@ class ComplexIPLDGraph {
107109
return {'/': cid.toBaseEncodedString()}
108110
}
109111

112+
this.nodesWalked = 0
113+
110114
let _iter = async (map, node) => {
115+
this.nodesWalked++
111116
for (let [key, value] of map.entries()) {
112117
if (value instanceof Map) {
113118
let _node
114119
let cid
115-
if (node[key]) {
120+
if (node[key] && node[key]['/']) {
116121
cid = mkcid(node[key]['/'])
117122
_node = await this.get(cid)
118123
} else {
119124
_node = {}
120125
}
121126
node[key] = toLink(await _iter(value, _node))
122-
if (clobber && cid &&
123-
node[key]['/'] !== cid.toBaseEncodedString()) {
124-
this._bulk.del(cid)
125-
}
126127
} else {
127-
if (!(value instanceof CID)) throw new Error('Value not CID.')
128+
if (!(value.toBaseEncodedString)) throw new Error('Value not CID.')
128129
node[key] = toLink(value)
129130
}
130131
}
@@ -135,23 +136,20 @@ class ComplexIPLDGraph {
135136
let cid = await _iter(this._patches, await this.get(root))
136137
this._graphBuildTime = Date.now() - start
137138

138-
if (clobber &&
139-
root.toBaseEncodedString() !== cid.toBaseEncodedString()) {
140-
this._bulk.del(root)
141-
}
142-
143-
start = Date.now()
144139
await this._bulk.flush()
145-
this._flushTime = Date.now() - start
146140

147141
this._clear()
148-
142+
this._spent = true
149143
return cid
150144
}
151145

152146
async get (cid) {
153-
let buffer = await this.store.get(cid)
154-
return this.cbor.deserialize(buffer)
147+
if (cid.toBaseEncodedString) cid = cid.toBaseEncodedString()
148+
if (!this._getCache.has(cid)) {
149+
let p = this.store.get(cid).then(b => this.cbor.deserialize(b))
150+
this._getCache.set(cid, p)
151+
}
152+
return this._getCache.get(cid)
155153
}
156154

157155
async resolve (path, root) {

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
"eslint-plugin-node": "^6.0.1",
2020
"eslint-plugin-promise": "^3.8.0",
2121
"eslint-plugin-standard": "^3.1.0",
22-
"ipld-store": "^0.1.1",
22+
"ipld-store": "^0.2.0",
2323
"tap": "^12.0.1"
2424
},
2525
"eslintConfig": {

test/test-graph-building.js

+8-7
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ const graphTest = async (str, cb) => {
1919
let store = createStore(dir)
2020
let block = await empty
2121
await store.put(block.cid, block.data)
22-
let graph = complex(store)
22+
let graph = complex(store, block.cid)
2323
await test(str, async t => {
2424
t.tearDown(() => {
2525
rimraf.sync(dir)
@@ -32,13 +32,13 @@ graphTest('basic graph build', async (t, graph) => {
3232
let block = await serialize({test: 1234})
3333
graph.add('/one/two/three', block)
3434
graph.add('/one/three/four', block)
35-
let newroot = await graph.flush((await empty).cid)
35+
let newroot = await graph.flush()
3636
let i = 0
3737
for await (let cid of graph.store.cids()) {
3838
t.ok(cid)
3939
i++
4040
}
41-
t.same(i, 5)
41+
t.same(i, 6)
4242
let two = await graph.resolve('/one/two', newroot)
4343
t.ok(two.value.three)
4444
let leaf = await graph.resolve('/one/three/four', newroot)
@@ -48,17 +48,18 @@ graphTest('basic graph build', async (t, graph) => {
4848
graphTest('graph updates', async (t, graph) => {
4949
let block = await serialize({test: 1234})
5050
graph.add('/one/two/edge', block)
51-
let root = await graph.flush((await empty).cid)
52-
graph.add('/one/three/four', block)
51+
let root = await graph.flush()
5352

54-
let newroot = await graph.flush(root)
53+
graph = complex(graph.store, root)
54+
graph.add('/one/three/four', block)
55+
let newroot = await graph.flush()
5556

5657
let i = 0
5758
for await (let cid of graph.store.cids()) {
5859
t.ok(cid)
5960
i++
6061
}
61-
t.same(i, 5)
62+
t.same(i, 8)
6263
let two = await graph.resolve('/one/two', newroot)
6364
t.ok(two.value.edge)
6465
let three = await graph.resolve('/one/three', newroot)

test/test-path-sharding.js

+4-4
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ const {test} = require('tap')
22
const complex = require('../')
33

44
test('test path sharding', async t => {
5-
let graph = complex()
5+
let graph = complex({bulk: () => {}})
66
graph.shardPath('/one/:two/*', key => {
77
t.same(key, 'three')
88
return 'four'
@@ -12,7 +12,7 @@ test('test path sharding', async t => {
1212
})
1313

1414
test('test path sharding with slash', async t => {
15-
let graph = complex()
15+
let graph = complex({bulk: () => {}})
1616
graph.shardPath('/one/:two/*', key => {
1717
t.same(key, 'three')
1818
return 'four/five'
@@ -22,7 +22,7 @@ test('test path sharding with slash', async t => {
2222
})
2323

2424
test('test shard two paths', async t => {
25-
let graph = complex()
25+
let graph = complex({bulk: () => {}})
2626
graph.shardPath('/one/:two/*', key => {
2727
t.same(key, 'three')
2828
return 'four/five'
@@ -38,7 +38,7 @@ test('test shard two paths', async t => {
3838
})
3939

4040
test('test shard twice in same path', async t => {
41-
let graph = complex()
41+
let graph = complex({bulk: () => {}})
4242
graph.shardPath('/one/*', key => {
4343
t.same(key, 'two')
4444
return 2

0 commit comments

Comments
 (0)