hypercore-storage 1.5.1 → 1.6.1

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