hypercore-storage 1.5.1 → 1.6.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 (2) hide show
  1. package/migrations/0/index.js +135 -24
  2. package/package.json +1 -1
@@ -226,6 +226,62 @@ async function store (storage, { version, dryRun = true, gc = true }) {
226
226
  if (gc) await rm(primaryKeyFile)
227
227
  }
228
228
 
229
+ class BlockSlicer {
230
+ constructor (filename) {
231
+ this.stream = fs.createReadStream(filename)
232
+ this.closed = new Promise(resolve => this.stream.once('close', resolve))
233
+ this.offset = 0
234
+ this.overflow = null
235
+ }
236
+
237
+ async take (offset, size) {
238
+ let buffer = null
239
+
240
+ while (true) {
241
+ let data = null
242
+
243
+ if (this.overflow) {
244
+ data = this.overflow
245
+ this.overflow = null
246
+ } else {
247
+ data = this.stream.read()
248
+
249
+ if (!data) {
250
+ await new Promise(resolve => this.stream.once('readable', resolve))
251
+ continue
252
+ }
253
+ }
254
+
255
+ let chunk = null
256
+
257
+ if (this.offset === offset) {
258
+ chunk = data
259
+ } else if (this.offset + data.byteLength > offset) {
260
+ chunk = data.subarray(offset - this.offset)
261
+ }
262
+
263
+ this.offset += data.byteLength
264
+ if (!chunk) continue
265
+
266
+ if (buffer) buffer = b4a.concat([buffer, chunk])
267
+ else buffer = chunk
268
+
269
+ if (chunk < size) continue
270
+
271
+ const result = chunk.subarray(0, size)
272
+ this.overflow = size === chunk.byteLength ? null : chunk.subarray(size)
273
+ this.offset -= this.overflow ? this.overflow.byteLength : 0
274
+ return result
275
+ }
276
+ }
277
+
278
+ close () {
279
+ this.stream.on('error', noop)
280
+ this.stream.destroy()
281
+ return this.closed
282
+ }
283
+ }
284
+
229
285
  class TreeSlicer {
230
286
  constructor () {
231
287
  this.buffer = null
@@ -359,7 +415,7 @@ async function core (core, { version, dryRun = true, gc = true }) {
359
415
  const pages = new Map()
360
416
  const headerBits = new Map()
361
417
 
362
- const roots = await getRoots(head.length, getTreeNode)
418
+ const roots = await getRootsFromStorage(core, head.length)
363
419
 
364
420
  for (const e of oplog.entries) {
365
421
  if (!e.bitfield) continue
@@ -369,28 +425,35 @@ async function core (core, { version, dryRun = true, gc = true }) {
369
425
  }
370
426
  }
371
427
 
372
- let w = core.write()
428
+ let batch = []
429
+
430
+ const cache = new Map()
431
+ const blocks = new BlockSlicer(files.data)
432
+
373
433
  for (const index of allBits(bitfield)) {
374
434
  if (headerBits.get(index) === false) continue
375
435
 
376
436
  setBitInPage(index)
377
437
 
378
- const blk = await getBlockFromFile(files.data, index, roots, getTreeNode)
379
-
380
- if (w.changes.length > 1024) {
381
- await w.flush()
382
- w = core.write()
383
- }
438
+ batch.push(index)
439
+ if (batch.length < 1024) continue
384
440
 
385
- w.putBlock(index, blk)
441
+ await writeBlocksBatch()
442
+ continue
386
443
  }
387
444
 
445
+ if (batch.length) await writeBlocksBatch()
446
+
447
+ await blocks.close()
448
+
449
+ const w = core.write()
450
+
388
451
  for (const [index, bit] of headerBits) {
389
452
  if (!bit) continue
390
453
 
391
454
  setBitInPage(index)
392
455
 
393
- const blk = await getBlockFromFile(files.data, index, roots, getTreeNode)
456
+ const blk = await getBlockFromFile(files.data, core, index, roots, cache)
394
457
  w.putBlock(index, blk)
395
458
  }
396
459
 
@@ -441,11 +504,28 @@ async function core (core, { version, dryRun = true, gc = true }) {
441
504
  page[b] |= v
442
505
  }
443
506
 
444
- function getTreeNode (index) {
507
+ async function writeBlocksBatch () {
445
508
  const read = core.read()
446
- const promise = read.getTreeNode(index)
509
+ const promises = []
510
+ for (const index of batch) promises.push(getByteRangeFromStorage(read, 2 * index, roots, cache))
447
511
  read.tryFlush()
448
- return promise
512
+
513
+ const r = await Promise.all(promises)
514
+ const tx = core.write()
515
+
516
+ for (let i = 0; i < r.length; i++) {
517
+ const index = batch[i]
518
+ const [offset, size] = r[i]
519
+
520
+ const block = await blocks.take(offset, size)
521
+
522
+ tx.putBlock(index, block)
523
+ }
524
+
525
+ batch = []
526
+ if (cache.size > 16384) cache.clear()
527
+
528
+ await tx.flush()
449
529
  }
450
530
  }
451
531
 
@@ -468,6 +548,20 @@ async function commitCoreMigration (auth, core, version) {
468
548
  await View.flush(view.changes, core.db)
469
549
  }
470
550
 
551
+ async function getBlockFromFile (file, core, index, roots, cache) {
552
+ const rx = core.read()
553
+ const promise = getByteRangeFromStorage(rx, index, roots, cache)
554
+ rx.tryFlush()
555
+ const [offset, size] = await promise
556
+
557
+ return new Promise(function (resolve) {
558
+ readAll(file, size, offset, function (err, buf) {
559
+ if (err) return resolve(null)
560
+ resolve(buf)
561
+ })
562
+ })
563
+ }
564
+
471
565
  function getFiles (dir) {
472
566
  return {
473
567
  path: dir,
@@ -478,6 +572,16 @@ function getFiles (dir) {
478
572
  }
479
573
  }
480
574
 
575
+ async function getRootsFromStorage (core, length) {
576
+ const all = []
577
+ const rx = core.read()
578
+ for (const index of flat.fullRoots(2 * length)) {
579
+ all.push(rx.getTreeNode(index))
580
+ }
581
+ rx.tryFlush()
582
+ return Promise.all(all)
583
+ }
584
+
481
585
  async function getRoots (length, getTreeNode) {
482
586
  const all = []
483
587
  for (const index of flat.fullRoots(2 * length)) {
@@ -486,19 +590,20 @@ async function getRoots (length, getTreeNode) {
486
590
  return all
487
591
  }
488
592
 
489
- async function getBlockFromFile (file, index, roots, getTreeNode) {
490
- const size = (await getTreeNode(2 * index)).size
491
- const offset = await getByteOffset(2 * index, roots, getTreeNode)
593
+ function getCached (read, cache, index) {
594
+ if (cache.has(index)) return cache.get(index)
595
+ const p = read.getTreeNode(index)
596
+ cache.set(index, p)
597
+ return p
598
+ }
492
599
 
493
- return new Promise(function (resolve) {
494
- readAll(file, size, offset, function (err, buf) {
495
- if (err) return resolve(null)
496
- resolve(buf)
497
- })
498
- })
600
+ async function getByteRangeFromStorage (read, index, roots, cache) {
601
+ const promises = [getCached(read, cache, index), getByteOffsetFromStorage(read, index, roots, cache)]
602
+ const [node, offset] = await Promise.all(promises)
603
+ return [offset, node.size]
499
604
  }
500
605
 
501
- async function getByteOffset (index, roots, getTreeNode) {
606
+ async function getByteOffsetFromStorage (rx, index, roots, cache) {
502
607
  if (index === 0) return 0
503
608
  if ((index & 1) === 1) index = flat.leftSpan(index)
504
609
 
@@ -514,16 +619,20 @@ async function getByteOffset (index, roots, getTreeNode) {
514
619
  }
515
620
 
516
621
  const ite = flat.iterator(node.index)
622
+ const promises = []
517
623
 
518
624
  while (ite.index !== index) {
519
625
  if (index < ite.index) {
520
626
  ite.leftChild()
521
627
  } else {
522
- offset += (await getTreeNode(ite.leftChild())).size
628
+ promises.push(getCached(rx, cache, ite.leftChild()))
523
629
  ite.sibling()
524
630
  }
525
631
  }
526
632
 
633
+ const nodes = await Promise.all(promises)
634
+ for (const node of nodes) offset += node.size
635
+
527
636
  return offset
528
637
  }
529
638
 
@@ -687,3 +796,5 @@ function readOplog (oplog) {
687
796
  })
688
797
  })
689
798
  }
799
+
800
+ function noop () {}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hypercore-storage",
3
- "version": "1.5.1",
3
+ "version": "1.6.0",
4
4
  "main": "index.js",
5
5
  "files": [
6
6
  "index.js",