@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/dist/benchmark/adaptive-ingest.d.ts +2 -0
- package/dist/benchmark/adaptive-ingest.d.ts.map +1 -0
- package/dist/benchmark/adaptive-ingest.js +275 -0
- package/dist/benchmark/adaptive-ingest.js.map +1 -0
- package/dist/src/index.d.ts +8 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +97 -7
- package/dist/src/index.js.map +1 -1
- package/package.json +21 -20
- package/src/index.ts +157 -28
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@peerbit/shared-log",
|
|
3
|
-
"version": "13.0.
|
|
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.
|
|
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
|
-
|
|
2937
|
-
|
|
2938
|
-
|
|
2939
|
-
|
|
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
|
-
|
|
3805
|
-
|
|
3806
|
-
|
|
3807
|
-
|
|
3808
|
-
|
|
3809
|
-
|
|
3810
|
-
|
|
3811
|
-
|
|
3812
|
-
|
|
3813
|
-
|
|
3814
|
-
|
|
3815
|
-
|
|
3816
|
-
|
|
3817
|
-
|
|
3818
|
-
|
|
3819
|
-
|
|
3820
|
-
|
|
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 (
|
|
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 (
|
|
4727
|
+
if (localHashes.has(element.hash)) {
|
|
4620
4728
|
entriesToReplicate.push(element);
|
|
4621
4729
|
}
|
|
4622
4730
|
} else {
|
|
4623
|
-
if (
|
|
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
|
}
|