@peerbit/shared-log 13.0.8-dac5207 → 13.0.9

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@peerbit/shared-log",
3
- "version": "13.0.8-dac5207",
3
+ "version": "13.0.9",
4
4
  "description": "Shared log",
5
5
  "sideEffects": false,
6
6
  "type": "module",
@@ -54,39 +54,40 @@
54
54
  "dependencies": {
55
55
  "@dao-xyz/borsh": "^6.0.0",
56
56
  "@libp2p/crypto": "^5.1.10",
57
- "@peerbit/log": "6.0.7-dac5207",
58
- "@peerbit/logger": "2.0.0-dac5207",
59
- "@peerbit/program": "6.0.5-dac5207",
60
- "@peerbit/riblt": "1.2.0-dac5207",
61
- "@peerbit/rpc": "6.0.7-dac5207",
62
- "@peerbit/any-store": "2.2.5-dac5207",
63
- "@peerbit/blocks": "4.0.3-dac5207",
64
- "@peerbit/blocks-interface": "2.0.1-dac5207",
65
- "@peerbit/cache": "3.0.0-dac5207",
66
- "@peerbit/crypto": "3.0.0-dac5207",
67
- "@peerbit/indexer-interface": "3.0.0-dac5207",
68
- "@peerbit/indexer-sqlite3": "3.0.0-dac5207",
69
- "@peerbit/pubsub": "5.0.3-dac5207",
70
- "@peerbit/pubsub-interface": "5.0.2-dac5207",
71
- "@peerbit/stream-interface": "6.0.1-dac5207",
72
- "@peerbit/time": "3.0.0-dac5207",
73
57
  "json-stringify-deterministic": "^1.0.7",
74
58
  "p-each-series": "^3.0.0",
75
59
  "p-defer": "^4.0.0",
76
60
  "p-queue": "^8.0.1",
77
61
  "pidusage": "^4.0.1",
78
62
  "pino": "^9.4.0",
79
- "uint8arrays": "^5.1.0"
63
+ "uint8arrays": "^5.1.0",
64
+ "@peerbit/log": "6.0.8",
65
+ "@peerbit/program": "6.0.5",
66
+ "@peerbit/riblt": "1.2.0",
67
+ "@peerbit/logger": "2.0.0",
68
+ "@peerbit/rpc": "6.0.8",
69
+ "@peerbit/any-store": "2.2.5",
70
+ "@peerbit/blocks": "4.0.3",
71
+ "@peerbit/blocks-interface": "2.0.1",
72
+ "@peerbit/crypto": "3.0.0",
73
+ "@peerbit/cache": "3.0.0",
74
+ "@peerbit/indexer-sqlite3": "3.0.1",
75
+ "@peerbit/indexer-interface": "3.0.0",
76
+ "@peerbit/pubsub": "5.0.3",
77
+ "@peerbit/stream-interface": "6.0.1",
78
+ "@peerbit/time": "3.0.0",
79
+ "@peerbit/pubsub-interface": "5.0.2"
80
80
  },
81
81
  "devDependencies": {
82
- "@peerbit/test-utils": "3.0.7-dac5207",
83
82
  "@types/libsodium-wrappers": "^0.7.14",
84
83
  "@types/pidusage": "^2.0.5",
85
- "uuid": "^10.0.0"
84
+ "uuid": "^10.0.0",
85
+ "@peerbit/test-utils": "3.0.8"
86
86
  },
87
87
  "scripts": {
88
88
  "clean": "aegir clean",
89
89
  "build": "aegir build --no-bundle",
90
+ "benchmark:adaptive-ingest": "node --loader ts-node/esm ./benchmark/adaptive-ingest.ts",
90
91
  "test": "aegir test --target node",
91
92
  "lint": "aegir lint",
92
93
  "test:cov": "aegir test -t node --cov"
package/src/index.ts CHANGED
@@ -23,6 +23,7 @@ import {
23
23
  type AppendOptions,
24
24
  type Change,
25
25
  Entry,
26
+ EntryType,
26
27
  Log,
27
28
  type LogEvents,
28
29
  type LogProperties,
@@ -399,6 +400,7 @@ export type SharedLogOptions<
399
400
  ? I
400
401
  : "u32",
401
402
  > = {
403
+ appendDurability?: LogProperties<T>["appendDurability"];
402
404
  replicate?: ReplicationOptions<R>;
403
405
  replicas?: ReplicationLimitsOptions;
404
406
  respondToIHaveTimeout?: number;
@@ -441,6 +443,8 @@ const RECALCULATE_PARTICIPATION_MIN_RELATIVE_CHANGE = 0.01;
441
443
  const RECALCULATE_PARTICIPATION_MIN_RELATIVE_CHANGE_WITH_CPU_LIMIT = 0.005;
442
444
  const RECALCULATE_PARTICIPATION_MIN_RELATIVE_CHANGE_WITH_MEMORY_LIMIT = 0.001;
443
445
  const RECALCULATE_PARTICIPATION_RELATIVE_DENOMINATOR_FLOOR = 1e-3;
446
+ const ADAPTIVE_REBALANCE_IDLE_INTERVAL_MULTIPLIER = 5;
447
+ const ADAPTIVE_REBALANCE_MIN_IDLE_AFTER_LOCAL_APPEND_MS = 10_000;
444
448
 
445
449
  const DEFAULT_DISTRIBUTION_DEBOUNCE_TIME = 500;
446
450
  const RECENT_REPAIR_DISPATCH_TTL_MS = 5_000;
@@ -705,6 +709,8 @@ export class SharedLog<
705
709
  replicas!: ReplicationLimits;
706
710
 
707
711
  private cpuUsage?: CPUUsage;
712
+ private _lastLocalAppendAt!: number;
713
+ private adaptiveRebalanceIdleMs!: number;
708
714
 
709
715
  timeUntilRoleMaturity!: number;
710
716
  waitForReplicatorTimeout!: number;
@@ -1457,6 +1463,71 @@ export class SharedLog<
1457
1463
  );
1458
1464
  }
1459
1465
 
1466
+ private markLocalAppendActivity(timestamp = Date.now()) {
1467
+ this._lastLocalAppendAt = Math.max(this._lastLocalAppendAt ?? 0, timestamp);
1468
+ }
1469
+
1470
+ private shouldDelayAdaptiveRebalance(now = Date.now()) {
1471
+ return (
1472
+ this._isAdaptiveReplicating &&
1473
+ this._lastLocalAppendAt > 0 &&
1474
+ now - this._lastLocalAppendAt < this.adaptiveRebalanceIdleMs
1475
+ );
1476
+ }
1477
+
1478
+ private shouldDeferHeadCoordinatePersistence(
1479
+ options?: SharedAppendOptions<T>,
1480
+ ) {
1481
+ return (
1482
+ !this._isReplicating &&
1483
+ options?.replicate === false &&
1484
+ options?.target === "none"
1485
+ );
1486
+ }
1487
+
1488
+ private async deleteCoordinatesForHashes(hashes: Iterable<string>) {
1489
+ const values = [...new Set([...hashes].filter(Boolean))];
1490
+ if (values.length === 0) {
1491
+ return;
1492
+ }
1493
+ await this.entryCoordinatesIndex.del({
1494
+ query:
1495
+ values.length === 1
1496
+ ? { hash: values[0] }
1497
+ : new Or(
1498
+ values.map((hash) => new StringMatch({ key: "hash", value: hash })),
1499
+ ),
1500
+ });
1501
+ }
1502
+
1503
+ private async ensureCurrentHeadCoordinatesIndexed() {
1504
+ const heads = await this.log.getHeads(true).all();
1505
+ const headsByHash = new Map(heads.map((head) => [head.hash, head]));
1506
+ const indexedHeads = await this.entryCoordinatesIndex
1507
+ .iterate({}, { shape: { hash: true } })
1508
+ .all();
1509
+ const indexedHashes = new Set(indexedHeads.map((entry) => entry.value.hash));
1510
+ const staleHashes = indexedHeads
1511
+ .map((entry) => entry.value.hash)
1512
+ .filter((hash) => !headsByHash.has(hash));
1513
+
1514
+ if (staleHashes.length > 0) {
1515
+ await this.deleteCoordinatesForHashes(staleHashes);
1516
+ }
1517
+
1518
+ for (const head of heads) {
1519
+ if (indexedHashes.has(head.hash)) {
1520
+ continue;
1521
+ }
1522
+ const minReplicas = decodeReplicas(head).getValue(this);
1523
+ await this.findLeaders(
1524
+ await this.createCoordinates(head, minReplicas),
1525
+ head,
1526
+ { persist: {} },
1527
+ );
1528
+ }
1529
+ }
1530
+
1460
1531
  private async _replicate(
1461
1532
  options?: ReplicationOptions<R>,
1462
1533
  {
@@ -2264,6 +2335,8 @@ export class SharedLog<
2264
2335
  ) => void;
2265
2336
  } = {},
2266
2337
  ) {
2338
+ await this.ensureCurrentHeadCoordinatesIndexed();
2339
+
2267
2340
  const change = await this.addReplicationRange(
2268
2341
  range,
2269
2342
  this.node.identity.publicKey,
@@ -2659,6 +2732,10 @@ export class SharedLog<
2659
2732
  entry: Entry<T>;
2660
2733
  removed: ShallowOrFullEntry<T>[];
2661
2734
  }> {
2735
+ if (this._isAdaptiveReplicating) {
2736
+ this.markLocalAppendActivity();
2737
+ }
2738
+
2662
2739
  const appendOptions: AppendOptions<T> = { ...options };
2663
2740
  const minReplicas = this.getClampedReplicas(
2664
2741
  options?.replicas
@@ -2693,13 +2770,23 @@ export class SharedLog<
2693
2770
  return options.onChange!(change);
2694
2771
  };
2695
2772
  }
2696
-
2697
2773
  const result = await this.log.append(data, appendOptions);
2774
+ const deferHeadCoordinatePersistence =
2775
+ result.entry.meta.type !== EntryType.CUT &&
2776
+ this.shouldDeferHeadCoordinatePersistence(options);
2698
2777
 
2699
2778
  if (options?.replicate) {
2700
2779
  await this.replicate(result.entry, { checkDuplicates: true });
2701
2780
  }
2702
2781
 
2782
+ if (deferHeadCoordinatePersistence) {
2783
+ await this.deleteCoordinatesForHashes([
2784
+ ...result.entry.meta.next,
2785
+ ...result.removed.map((entry) => entry.hash),
2786
+ ]);
2787
+ return result;
2788
+ }
2789
+
2703
2790
  const coordinates = await this.createCoordinates(
2704
2791
  result.entry,
2705
2792
  minReplicasValue,
@@ -2744,13 +2831,15 @@ export class SharedLog<
2744
2831
  }
2745
2832
  }
2746
2833
 
2747
- if (!isLeader) {
2834
+ if (!isLeader && !this.shouldDelayAdaptiveRebalance()) {
2748
2835
  this.pruneDebouncedFnAddIfNotKeeping({
2749
2836
  key: result.entry.hash,
2750
2837
  value: { entry: result.entry, leaders },
2751
2838
  });
2752
2839
  }
2753
- this.rebalanceParticipationDebounced?.call();
2840
+ if (!this._isAdaptiveReplicating) {
2841
+ this.rebalanceParticipationDebounced?.call();
2842
+ }
2754
2843
 
2755
2844
  return result;
2756
2845
  }
@@ -2805,6 +2894,17 @@ export class SharedLog<
2805
2894
  this._replicatorLivenessCursor = 0;
2806
2895
  this._replicatorLivenessFailures = new Map();
2807
2896
  this._replicatorLastActivityAt = new Map();
2897
+ this._lastLocalAppendAt = 0;
2898
+ const adaptiveReplicateOptions =
2899
+ options?.replicate && isAdaptiveReplicatorOption(options.replicate)
2900
+ ? options.replicate
2901
+ : undefined;
2902
+ this.adaptiveRebalanceIdleMs = Math.max(
2903
+ ADAPTIVE_REBALANCE_MIN_IDLE_AFTER_LOCAL_APPEND_MS,
2904
+ (adaptiveReplicateOptions?.limits?.interval ??
2905
+ RECALCULATE_PARTICIPATION_DEBOUNCE_INTERVAL) *
2906
+ ADAPTIVE_REBALANCE_IDLE_INTERVAL_MULTIPLIER,
2907
+ );
2808
2908
 
2809
2909
  this.openTime = +new Date();
2810
2910
  this.oldestOpenTime = this.openTime;
@@ -2933,10 +3033,10 @@ export class SharedLog<
2933
3033
  ],
2934
3034
  })) > 0;
2935
3035
 
2936
- this._gidPeersHistory = new Map();
2937
- this._requestIPruneSent = new Map();
2938
- this._requestIPruneResponseReplicatorSet = new Map();
2939
- this._checkedPruneRetries = new Map();
3036
+ this._gidPeersHistory = new Map();
3037
+ this._requestIPruneSent = new Map();
3038
+ this._requestIPruneResponseReplicatorSet = new Map();
3039
+ this._checkedPruneRetries = new Map();
2940
3040
 
2941
3041
  this.replicationChangeDebounceFn = debounceAggregationChanges<
2942
3042
  ReplicationRangeIndexable<R>
@@ -3801,23 +3901,23 @@ export class SharedLog<
3801
3901
  }
3802
3902
 
3803
3903
  await this.remoteBlocks.stop();
3804
- this._pendingDeletes.clear();
3805
- this._pendingIHave.clear();
3806
- this._checkedPruneRetries.clear();
3807
- this.latestReplicationInfoMessage.clear();
3808
- this._gidPeersHistory.clear();
3809
- this._requestIPruneSent.clear();
3810
- this._requestIPruneResponseReplicatorSet.clear();
3811
- // Cancel any pending debounced timers so they can't fire after we've torn down
3812
- // indexes/RPC state.
3813
- this.rebalanceParticipationDebounced?.close();
3814
- this.replicationChangeDebounceFn?.close?.();
3815
- this.pruneDebouncedFn?.close?.();
3816
- this.responseToPruneDebouncedFn?.close?.();
3817
- this.pruneDebouncedFn = undefined as any;
3818
- this.rebalanceParticipationDebounced = undefined;
3819
- this._replicationRangeIndex.stop();
3820
- this._entryCoordinatesIndex.stop();
3904
+ this._pendingDeletes.clear();
3905
+ this._pendingIHave.clear();
3906
+ this._checkedPruneRetries.clear();
3907
+ this.latestReplicationInfoMessage.clear();
3908
+ this._gidPeersHistory.clear();
3909
+ this._requestIPruneSent.clear();
3910
+ this._requestIPruneResponseReplicatorSet.clear();
3911
+ // Cancel any pending debounced timers so they can't fire after we've torn down
3912
+ // indexes/RPC state.
3913
+ this.rebalanceParticipationDebounced?.close();
3914
+ this.replicationChangeDebounceFn?.close?.();
3915
+ this.pruneDebouncedFn?.close?.();
3916
+ this.responseToPruneDebouncedFn?.close?.();
3917
+ this.pruneDebouncedFn = undefined as any;
3918
+ this.rebalanceParticipationDebounced = undefined;
3919
+ this._replicationRangeIndex.stop();
3920
+ this._entryCoordinatesIndex.stop();
3821
3921
  this._replicationRangeIndex = undefined as any;
3822
3922
  this._entryCoordinatesIndex = undefined as any;
3823
3923
 
@@ -4602,6 +4702,14 @@ export class SharedLog<
4602
4702
  },
4603
4703
  ): Promise<void> {
4604
4704
  let entriesToReplicate: Entry<T>[] = [];
4705
+ const localHashes =
4706
+ options?.replicate && this.log.length > 0
4707
+ ? await this.log.entryIndex.hasMany(
4708
+ entries.map((element) =>
4709
+ typeof element === "string" ? element : element.hash,
4710
+ ),
4711
+ )
4712
+ : new Set<string>();
4605
4713
  if (options?.replicate && this.log.length > 0) {
4606
4714
  // TODO this block should perhaps be called from a callback on the this.log.join method on all the ignored element because already joined, like "onAlreadyJoined"
4607
4715
 
@@ -4609,18 +4717,18 @@ export class SharedLog<
4609
4717
  // we can not just do the 'join' call because it will ignore the already joined entries
4610
4718
  for (const element of entries) {
4611
4719
  if (typeof element === "string") {
4612
- if (await this.log.has(element)) {
4720
+ if (localHashes.has(element)) {
4613
4721
  const entry = await this.log.get(element);
4614
4722
  if (entry) {
4615
4723
  entriesToReplicate.push(entry);
4616
4724
  }
4617
4725
  }
4618
4726
  } else if (element instanceof Entry) {
4619
- if (await this.log.has(element.hash)) {
4727
+ if (localHashes.has(element.hash)) {
4620
4728
  entriesToReplicate.push(element);
4621
4729
  }
4622
4730
  } else {
4623
- if (await this.log.has(element.hash)) {
4731
+ if (localHashes.has(element.hash)) {
4624
4732
  const entry = await this.log.get(element.hash);
4625
4733
  if (entry) {
4626
4734
  entriesToReplicate.push(entry);
@@ -6168,6 +6276,17 @@ export class SharedLog<
6168
6276
  // update more participation rate to converge to the average expected rate or bounded by
6169
6277
  // resources such as memory and or cpu
6170
6278
 
6279
+ const isClosedStoreRace = (error: any) => {
6280
+ const message =
6281
+ typeof error?.message === "string" ? error.message : String(error);
6282
+ return (
6283
+ this.closed ||
6284
+ message.includes("Iterator is not open") ||
6285
+ message.includes("cannot read after close()") ||
6286
+ message.includes("Database is not open")
6287
+ );
6288
+ };
6289
+
6171
6290
  const fn = async () => {
6172
6291
  if (this.closed) {
6173
6292
  return false;
@@ -6179,6 +6298,11 @@ export class SharedLog<
6179
6298
  }
6180
6299
 
6181
6300
  if (this._isAdaptiveReplicating) {
6301
+ if (this.shouldDelayAdaptiveRebalance()) {
6302
+ this.rebalanceParticipationDebounced?.call();
6303
+ return false;
6304
+ }
6305
+
6182
6306
  const peers = this.replicationIndex;
6183
6307
  const usedMemory = await this.getMemoryUsage();
6184
6308
  let dynamicRange = await this.getDynamicRange();
@@ -6252,7 +6376,12 @@ export class SharedLog<
6252
6376
  return false;
6253
6377
  };
6254
6378
 
6255
- const resp = await fn();
6379
+ const resp = await fn().catch((error: any) => {
6380
+ if (isNotStartedError(error) || isClosedStoreRace(error)) {
6381
+ return false;
6382
+ }
6383
+ throw error;
6384
+ });
6256
6385
 
6257
6386
  return resp;
6258
6387
  }