hypercore-storage 3.1.2 → 3.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (4) hide show
  1. package/README.md +11 -1
  2. package/index.js +54 -12
  3. package/lib/tx.js +67 -13
  4. package/package.json +3 -2
package/README.md CHANGED
@@ -14,10 +14,20 @@ The following API is what Hypercore 11 binds to to do I/O.
14
14
  const Storage = require('hypercore-storage')
15
15
  ```
16
16
 
17
- #### `store = new Storage(dbOrPath)`
17
+ #### `store = new Storage(dbOrPath, opts = {})`
18
18
 
19
19
  Make a new storage engine.
20
20
 
21
+ `opts` includes:
22
+
23
+ ```
24
+ {
25
+ treeCache: { // Same options as `xache`
26
+ maxSize: 8192 // Max number of tree nodes to cache
27
+ }
28
+ }
29
+ ```
30
+
21
31
  #### `core = await store.createCore({ key, discoveyKey, manifest?, keyPair?, encryptionKey?, userData? })`
22
32
 
23
33
  Create a new core, returns a storage instance for that core.
package/index.js CHANGED
@@ -4,6 +4,7 @@ const ScopeLock = require('scope-lock')
4
4
  const DeviceFile = require('device-file')
5
5
  const path = require('path')
6
6
  const fs = require('fs')
7
+ const Xache = require('xache')
7
8
 
8
9
  const View = require('./lib/view.js')
9
10
 
@@ -84,7 +85,7 @@ class Atom {
84
85
  }
85
86
 
86
87
  class HypercoreStorage {
87
- constructor(store, db, core, view, atom) {
88
+ constructor(store, db, treeCache, core, view, atom) {
88
89
  this.store = store
89
90
  this.db = db
90
91
  this.core = core
@@ -92,6 +93,7 @@ class HypercoreStorage {
92
93
  this.atom = atom
93
94
 
94
95
  this.view.readStart()
96
+ this.treeCache = treeCache
95
97
  }
96
98
 
97
99
  get readOnly() {
@@ -201,6 +203,7 @@ class HypercoreStorage {
201
203
  return new HypercoreStorage(
202
204
  this.store,
203
205
  this.db.snapshot(),
206
+ this.treeCache,
204
207
  this.core,
205
208
  this.view.snapshot(),
206
209
  this.atom
@@ -218,7 +221,15 @@ class HypercoreStorage {
218
221
  if (this.atom && this.atom !== atom) {
219
222
  throw new Error('Cannot atomize and atomized session with a new atom')
220
223
  }
221
- return new HypercoreStorage(this.store, this.db.session(), this.core, atom.view, atom)
224
+
225
+ return new HypercoreStorage(
226
+ this.store,
227
+ this.db.session(),
228
+ this.treeCache,
229
+ this.core,
230
+ atom.view,
231
+ atom
232
+ )
222
233
  }
223
234
 
224
235
  createAtom() {
@@ -267,7 +278,7 @@ class HypercoreStorage {
267
278
  dependencies: []
268
279
  }
269
280
 
270
- const coreRx = new CoreRX(core, this.db, this.view)
281
+ const coreRx = new CoreRX(core, this.db, this.view, this.treeCache, this.store.stats, -1)
271
282
 
272
283
  const dependencyPromise = coreRx.getDependency()
273
284
  coreRx.tryFlush()
@@ -278,6 +289,7 @@ class HypercoreStorage {
278
289
  return new HypercoreStorage(
279
290
  this.store,
280
291
  this.db.session(),
292
+ this.treeCache,
281
293
  core,
282
294
  this.atom ? this.view : new View(),
283
295
  this.atom
@@ -341,6 +353,7 @@ class HypercoreStorage {
341
353
  return new HypercoreStorage(
342
354
  this.store,
343
355
  this.db.session(),
356
+ this.treeCache,
344
357
  core,
345
358
  this.atom ? this.view : new View(),
346
359
  this.atom
@@ -412,8 +425,11 @@ class HypercoreStorage {
412
425
  await tx.flush()
413
426
  }
414
427
 
415
- read() {
416
- return new CoreRX(this.core, this.db, this.view)
428
+ read(fork = -1) {
429
+ let treeCache = null
430
+ if (!this.atom) treeCache = this.treeCache
431
+
432
+ return new CoreRX(this.core, this.db, this.view, treeCache, this.store.stats, fork)
417
433
  }
418
434
 
419
435
  write() {
@@ -430,7 +446,7 @@ class HypercoreStorage {
430
446
  }
431
447
 
432
448
  static async export(ptr, db, { batches = false } = {}) {
433
- const rx = new CoreRX(ptr, db, EMPTY)
449
+ const rx = new CoreRX(ptr, db, EMPTY, null, null, -1)
434
450
 
435
451
  const core = {
436
452
  head: null,
@@ -507,8 +523,19 @@ class CorestoreStorage {
507
523
 
508
524
  const dbPath = path.join(this.path, 'db')
509
525
 
526
+ this.stats = {
527
+ treeCache: {
528
+ hits: 0,
529
+ misses: 0,
530
+ parallel: 0,
531
+ skips: 0
532
+ }
533
+ }
510
534
  this.rocks = storage === null ? db : new RocksDB(dbPath, { ...opts, lock: this.deviceFile })
511
535
  this.db = createColumnFamily(this.rocks, opts)
536
+
537
+ const { treeCache = { maxSize: 8192 } } = opts
538
+ this.treeCache = new Xache(treeCache)
512
539
  }
513
540
 
514
541
  get opened() {
@@ -530,7 +557,7 @@ class CorestoreStorage {
530
557
 
531
558
  async audit() {
532
559
  for await (const { core } of this.createCoreStream()) {
533
- const coreRx = new CoreRX(core, this.db, EMPTY)
560
+ const coreRx = new CoreRX(core, this.db, EMPTY, this.treeCache, this.stats, -1)
534
561
  const authPromise = coreRx.getAuth()
535
562
 
536
563
  coreRx.tryFlush()
@@ -548,7 +575,7 @@ class CorestoreStorage {
548
575
  }
549
576
 
550
577
  async deleteCore(ptr) {
551
- const rx = new CoreRX(ptr, this.db, EMPTY)
578
+ const rx = new CoreRX(ptr, this.db, EMPTY, this.treeCache, this.stats, -1)
552
579
 
553
580
  const authPromise = rx.getAuth()
554
581
  const sessionsPromise = rx.getSessions()
@@ -782,6 +809,7 @@ class CorestoreStorage {
782
809
  await this._flush()
783
810
  await this.db.close()
784
811
  await this.rocks.close()
812
+ this.treeCache.destroy()
785
813
  if (this.deviceFile) await this.deviceFile.close()
786
814
  }
787
815
 
@@ -1053,7 +1081,14 @@ class CorestoreStorage {
1053
1081
  const ptr = { corePointer, dataPointer, dependencies: [] }
1054
1082
 
1055
1083
  while (true) {
1056
- const rx = new CoreRX({ dataPointer, corePointer: 0, dependencies: [] }, this.db, EMPTY)
1084
+ const rx = new CoreRX(
1085
+ { dataPointer, corePointer: 0, dependencies: [] },
1086
+ this.db,
1087
+ EMPTY,
1088
+ this.treeCache,
1089
+ this.stats,
1090
+ -1
1091
+ )
1057
1092
  const dependencyPromise = rx.getDependency()
1058
1093
  rx.tryFlush()
1059
1094
  const dependency = await dependencyPromise
@@ -1074,7 +1109,14 @@ class CorestoreStorage {
1074
1109
  const core = { corePointer, dataPointer, dependencies: [] }
1075
1110
 
1076
1111
  while (true) {
1077
- const rx = new CoreRX({ dataPointer, corePointer: 0, dependencies: [] }, this.db, view)
1112
+ const rx = new CoreRX(
1113
+ { dataPointer, corePointer: 0, dependencies: [] },
1114
+ this.db,
1115
+ view,
1116
+ this.treeCache,
1117
+ this.stats,
1118
+ -1
1119
+ )
1078
1120
  const dependencyPromise = rx.getDependency()
1079
1121
  rx.tryFlush()
1080
1122
  const dependency = await dependencyPromise
@@ -1083,7 +1125,7 @@ class CorestoreStorage {
1083
1125
  dataPointer = dependency.dataPointer
1084
1126
  }
1085
1127
 
1086
- const result = new HypercoreStorage(this, this.db.session(), core, EMPTY, null)
1128
+ const result = new HypercoreStorage(this, this.db.session(), this.treeCache, core, EMPTY, null)
1087
1129
 
1088
1130
  if (version < VERSION) await this._migrateCore(result, discoveryKey, version, create)
1089
1131
  return result
@@ -1136,7 +1178,7 @@ class CorestoreStorage {
1136
1178
 
1137
1179
  tx.apply()
1138
1180
 
1139
- return new HypercoreStorage(this, this.db.session(), ptr, EMPTY, null)
1181
+ return new HypercoreStorage(this, this.db.session(), this.treeCache, ptr, EMPTY, null)
1140
1182
  }
1141
1183
 
1142
1184
  async createCore(data) {
package/lib/tx.js CHANGED
@@ -176,39 +176,42 @@ class CoreTX {
176
176
  }
177
177
 
178
178
  class CoreRX {
179
- constructor(core, db, view) {
179
+ constructor(core, db, view, treeCache, stats, fork) {
180
180
  this.core = core
181
181
  this.read = db.read({ autoDestroy: true })
182
182
  this.view = view
183
+ this.treeCache = treeCache
184
+ this.fork = fork
185
+ this.stats = stats
183
186
 
184
187
  view.readStart()
185
188
  }
186
189
 
187
190
  static async getAuth(db, c) {
188
- return await decode(CORE_AUTH, await db.get(core.auth(c.corePointer)))
191
+ return decode(CORE_AUTH, await db.get(core.auth(c.corePointer)))
189
192
  }
190
193
 
191
194
  async getAuth() {
192
- return await decode(CORE_AUTH, await this.view.get(this.read, core.auth(this.core.corePointer)))
195
+ return decode(CORE_AUTH, await this.view.get(this.read, core.auth(this.core.corePointer)))
193
196
  }
194
197
 
195
198
  async getSessions() {
196
- return await decode(
199
+ return decode(
197
200
  CORE_SESSIONS,
198
201
  await this.view.get(this.read, core.sessions(this.core.corePointer))
199
202
  )
200
203
  }
201
204
 
202
205
  static async getHead(db, c) {
203
- return await decode(CORE_HEAD, await db.get(core.head(c.dataPointer)))
206
+ return decode(CORE_HEAD, await db.get(core.head(c.dataPointer)))
204
207
  }
205
208
 
206
209
  async getHead() {
207
- return await decode(CORE_HEAD, await this.view.get(this.read, core.head(this.core.dataPointer)))
210
+ return decode(CORE_HEAD, await this.view.get(this.read, core.head(this.core.dataPointer)))
208
211
  }
209
212
 
210
213
  async getDependency() {
211
- return await decode(
214
+ return decode(
212
215
  CORE_DEPENDENCY,
213
216
  await this.view.get(this.read, core.dependency(this.core.dataPointer))
214
217
  )
@@ -222,14 +225,11 @@ class CoreRX {
222
225
  }
223
226
 
224
227
  static async getHints(db, c) {
225
- return await decode(CORE_HINTS, await db.get(core.hints(c.dataPointer)))
228
+ return decode(CORE_HINTS, await db.get(core.hints(c.dataPointer)))
226
229
  }
227
230
 
228
231
  async getHints() {
229
- return await decode(
230
- CORE_HINTS,
231
- await this.view.get(this.read, core.hints(this.core.dataPointer))
232
- )
232
+ return decode(CORE_HINTS, await this.view.get(this.read, core.hints(this.core.dataPointer)))
233
233
  }
234
234
 
235
235
  getBlock(index) {
@@ -242,10 +242,64 @@ class CoreRX {
242
242
  return this.view.get(this.read, core.bitfield(this.core.dataPointer, index, 0))
243
243
  }
244
244
 
245
+ async _resolveTreeCache(key, promise, entry) {
246
+ try {
247
+ const value = decode(CORE_TREE_NODE, await promise)
248
+ const cached = this.treeCache.get(key)
249
+
250
+ // preflight is gone; no-op
251
+ if (cached !== entry) return value
252
+
253
+ if (value !== null) {
254
+ // replace cached entry with value
255
+ this.treeCache.set(key, { pending: null, value })
256
+ } else {
257
+ // discard non-existing values
258
+ this.treeCache.delete(key)
259
+ }
260
+
261
+ return value
262
+ } catch (err) {
263
+ // cleanup
264
+ const cached = this.treeCache.get(key)
265
+ if (cached === entry) {
266
+ this.treeCache.delete(key)
267
+ }
268
+
269
+ throw err
270
+ }
271
+ }
272
+
245
273
  async getTreeNode(index) {
246
274
  const dep = findTreeDependency(this.core.dependencies, index)
247
275
  const data = dep === null ? this.core.dataPointer : dep.dataPointer
248
- return decode(CORE_TREE_NODE, await this.view.get(this.read, core.tree(data, index)))
276
+
277
+ if (!this.treeCache || this.fork < 0) {
278
+ if (this.stats !== null) this.stats.treeCache.skips++
279
+ // no-cache
280
+ return decode(CORE_TREE_NODE, await this.view.get(this.read, core.tree(data, index)))
281
+ }
282
+
283
+ const key = `${data}:${index}:${this.fork}`
284
+ let entry = this.treeCache.get(key)
285
+ if (entry) {
286
+ if (entry.pending) {
287
+ if (this.stats !== null) this.stats.treeCache.parallel++
288
+ return entry.pending
289
+ }
290
+
291
+ if (this.stats !== null) this.stats.treeCache.hits++
292
+ return entry.value
293
+ }
294
+
295
+ if (this.stats !== null) this.stats.treeCache.misses++
296
+
297
+ const promise = this.view.get(this.read, core.tree(data, index))
298
+ entry = { pending: null, value: null }
299
+ entry.pending = this._resolveTreeCache(key, promise, entry)
300
+ this.treeCache.set(key, entry)
301
+
302
+ return entry.pending
249
303
  }
250
304
 
251
305
  async hasTreeNode(index) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hypercore-storage",
3
- "version": "3.1.2",
3
+ "version": "3.2.0",
4
4
  "main": "index.js",
5
5
  "files": [
6
6
  "index.js",
@@ -58,7 +58,8 @@
58
58
  "resolve-reject-promise": "^1.0.0",
59
59
  "rocksdb-native": "^3.11.0",
60
60
  "scope-lock": "^1.2.4",
61
- "streamx": "^2.21.1"
61
+ "streamx": "^2.21.1",
62
+ "xache": "^1.2.1"
62
63
  },
63
64
  "devDependencies": {
64
65
  "bare-os": "^3.9.1",