@peerbit/shared-log 13.2.22 → 13.2.24
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/src/index.d.ts +12 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +107 -12
- package/dist/src/index.js.map +1 -1
- package/dist/src/leader-plan-cache.d.ts +33 -0
- package/dist/src/leader-plan-cache.d.ts.map +1 -0
- package/dist/src/leader-plan-cache.js +49 -0
- package/dist/src/leader-plan-cache.js.map +1 -0
- package/dist/src/ranges.d.ts +2 -2
- package/dist/src/ranges.d.ts.map +1 -1
- package/dist/src/ranges.js +10 -8
- package/dist/src/ranges.js.map +1 -1
- package/package.json +21 -20
- package/src/index.ts +171 -12
- package/src/leader-plan-cache.ts +61 -0
- package/src/ranges.ts +22 -16
- package/dist/benchmark/adaptive-ingest.d.ts +0 -2
- package/dist/benchmark/adaptive-ingest.d.ts.map +0 -1
- package/dist/benchmark/adaptive-ingest.js +0 -275
- package/dist/benchmark/adaptive-ingest.js.map +0 -1
- package/dist/benchmark/get-samples.d.ts +0 -2
- package/dist/benchmark/get-samples.d.ts.map +0 -1
- package/dist/benchmark/get-samples.js +0 -194
- package/dist/benchmark/get-samples.js.map +0 -1
- package/dist/benchmark/index.d.ts +0 -2
- package/dist/benchmark/index.d.ts.map +0 -1
- package/dist/benchmark/index.js +0 -224
- package/dist/benchmark/index.js.map +0 -1
- package/dist/benchmark/join-backfill-repair.d.ts +0 -2
- package/dist/benchmark/join-backfill-repair.d.ts.map +0 -1
- package/dist/benchmark/join-backfill-repair.js +0 -288
- package/dist/benchmark/join-backfill-repair.js.map +0 -1
- package/dist/benchmark/memory/child.d.ts +0 -2
- package/dist/benchmark/memory/child.d.ts.map +0 -1
- package/dist/benchmark/memory/child.js +0 -204
- package/dist/benchmark/memory/child.js.map +0 -1
- package/dist/benchmark/memory/index.d.ts +0 -2
- package/dist/benchmark/memory/index.d.ts.map +0 -1
- package/dist/benchmark/memory/index.js +0 -81
- package/dist/benchmark/memory/index.js.map +0 -1
- package/dist/benchmark/memory/utils.d.ts +0 -13
- package/dist/benchmark/memory/utils.d.ts.map +0 -1
- package/dist/benchmark/memory/utils.js +0 -2
- package/dist/benchmark/memory/utils.js.map +0 -1
- package/dist/benchmark/native-graph.d.ts +0 -2
- package/dist/benchmark/native-graph.d.ts.map +0 -1
- package/dist/benchmark/native-graph.js +0 -359
- package/dist/benchmark/native-graph.js.map +0 -1
- package/dist/benchmark/network-preset-e2e.d.ts +0 -2
- package/dist/benchmark/network-preset-e2e.d.ts.map +0 -1
- package/dist/benchmark/network-preset-e2e.js +0 -497
- package/dist/benchmark/network-preset-e2e.js.map +0 -1
- package/dist/benchmark/partial-sync.d.ts +0 -3
- package/dist/benchmark/partial-sync.d.ts.map +0 -1
- package/dist/benchmark/partial-sync.js +0 -121
- package/dist/benchmark/partial-sync.js.map +0 -1
- package/dist/benchmark/pid-convergence.d.ts +0 -2
- package/dist/benchmark/pid-convergence.d.ts.map +0 -1
- package/dist/benchmark/pid-convergence.js +0 -138
- package/dist/benchmark/pid-convergence.js.map +0 -1
- package/dist/benchmark/rateless-iblt-sender-startsync.d.ts +0 -2
- package/dist/benchmark/rateless-iblt-sender-startsync.d.ts.map +0 -1
- package/dist/benchmark/rateless-iblt-sender-startsync.js +0 -104
- package/dist/benchmark/rateless-iblt-sender-startsync.js.map +0 -1
- package/dist/benchmark/rateless-iblt-startsync-cache.d.ts +0 -2
- package/dist/benchmark/rateless-iblt-startsync-cache.d.ts.map +0 -1
- package/dist/benchmark/rateless-iblt-startsync-cache.js +0 -112
- package/dist/benchmark/rateless-iblt-startsync-cache.js.map +0 -1
- package/dist/benchmark/receive-prune.d.ts +0 -2
- package/dist/benchmark/receive-prune.d.ts.map +0 -1
- package/dist/benchmark/receive-prune.js +0 -818
- package/dist/benchmark/receive-prune.js.map +0 -1
- package/dist/benchmark/replication-prune.d.ts +0 -2
- package/dist/benchmark/replication-prune.d.ts.map +0 -1
- package/dist/benchmark/replication-prune.js +0 -105
- package/dist/benchmark/replication-prune.js.map +0 -1
- package/dist/benchmark/replication.d.ts +0 -2
- package/dist/benchmark/replication.d.ts.map +0 -1
- package/dist/benchmark/replication.js +0 -88
- package/dist/benchmark/replication.js.map +0 -1
- package/dist/benchmark/sync-batch-sweep.d.ts +0 -2
- package/dist/benchmark/sync-batch-sweep.d.ts.map +0 -1
- package/dist/benchmark/sync-batch-sweep.js +0 -353
- package/dist/benchmark/sync-batch-sweep.js.map +0 -1
- package/dist/benchmark/sync-catchup.d.ts +0 -2
- package/dist/benchmark/sync-catchup.d.ts.map +0 -1
- package/dist/benchmark/sync-catchup.js +0 -277
- package/dist/benchmark/sync-catchup.js.map +0 -1
- package/dist/benchmark/sync-phase-profile.d.ts +0 -2
- package/dist/benchmark/sync-phase-profile.d.ts.map +0 -1
- package/dist/benchmark/sync-phase-profile.js +0 -303
- package/dist/benchmark/sync-phase-profile.js.map +0 -1
- package/dist/benchmark/to-rebalance.d.ts +0 -2
- package/dist/benchmark/to-rebalance.d.ts.map +0 -1
- package/dist/benchmark/to-rebalance.js +0 -117
- package/dist/benchmark/to-rebalance.js.map +0 -1
- package/dist/benchmark/utils.d.ts +0 -24
- package/dist/benchmark/utils.d.ts.map +0 -1
- package/dist/benchmark/utils.js +0 -47
- package/dist/benchmark/utils.js.map +0 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@peerbit/shared-log",
|
|
3
|
-
"version": "13.2.
|
|
3
|
+
"version": "13.2.24",
|
|
4
4
|
"description": "Shared log",
|
|
5
5
|
"sideEffects": false,
|
|
6
6
|
"type": "module",
|
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
"files": [
|
|
25
25
|
"src",
|
|
26
26
|
"dist",
|
|
27
|
+
"!dist/benchmark",
|
|
27
28
|
"!dist/e2e",
|
|
28
29
|
"!dist/test",
|
|
29
30
|
"!**/*.tsbuildinfo"
|
|
@@ -61,36 +62,36 @@
|
|
|
61
62
|
"pidusage": "^4.0.1",
|
|
62
63
|
"pino": "^9.4.0",
|
|
63
64
|
"uint8arrays": "^5.1.0",
|
|
64
|
-
"@peerbit/
|
|
65
|
-
"@peerbit/blocks-interface": "2.1.
|
|
65
|
+
"@peerbit/any-store": "2.2.14",
|
|
66
|
+
"@peerbit/blocks-interface": "2.1.5",
|
|
66
67
|
"@peerbit/cache": "3.1.1",
|
|
67
|
-
"@peerbit/
|
|
68
|
-
"@peerbit/
|
|
69
|
-
"@peerbit/indexer-interface": "3.0.8",
|
|
68
|
+
"@peerbit/blocks": "4.2.10",
|
|
69
|
+
"@peerbit/crypto": "3.1.5",
|
|
70
70
|
"@peerbit/diagnostics": "0.0.1",
|
|
71
|
-
"@peerbit/indexer-
|
|
71
|
+
"@peerbit/indexer-interface": "3.0.9",
|
|
72
|
+
"@peerbit/indexer-sqlite3": "3.0.12",
|
|
73
|
+
"@peerbit/log": "6.2.14",
|
|
72
74
|
"@peerbit/logger": "2.0.1",
|
|
73
|
-
"@peerbit/
|
|
74
|
-
"@peerbit/
|
|
75
|
-
"@peerbit/
|
|
76
|
-
"@peerbit/
|
|
77
|
-
"@peerbit/rpc": "6.1.14",
|
|
78
|
-
"@peerbit/stream-interface": "6.0.14",
|
|
75
|
+
"@peerbit/program": "6.0.48",
|
|
76
|
+
"@peerbit/pubsub-interface": "5.2.0",
|
|
77
|
+
"@peerbit/riblt": "1.2.0",
|
|
78
|
+
"@peerbit/stream-interface": "6.0.15",
|
|
79
79
|
"@peerbit/time": "3.0.1",
|
|
80
|
-
"@peerbit/
|
|
80
|
+
"@peerbit/pubsub": "5.4.0",
|
|
81
|
+
"@peerbit/rpc": "6.1.16"
|
|
81
82
|
},
|
|
82
83
|
"optionalDependencies": {
|
|
83
|
-
"@peerbit/native-backbone": "0.2.
|
|
84
|
-
"@peerbit/shared-log-rust": "0.1.
|
|
84
|
+
"@peerbit/native-backbone": "0.2.5",
|
|
85
|
+
"@peerbit/shared-log-rust": "0.1.4"
|
|
85
86
|
},
|
|
86
87
|
"devDependencies": {
|
|
87
88
|
"@types/libsodium-wrappers": "^0.7.14",
|
|
88
89
|
"@types/pidusage": "^2.0.5",
|
|
89
90
|
"uuid": "^11.1.1",
|
|
90
|
-
"@peerbit/indexer-rust": "1.0.
|
|
91
|
-
"@peerbit/test-utils": "3.1.
|
|
92
|
-
"peerbit": "
|
|
93
|
-
"
|
|
91
|
+
"@peerbit/indexer-rust": "1.0.7",
|
|
92
|
+
"@peerbit/test-utils": "3.1.20",
|
|
93
|
+
"@peerbit/any-store-rust": "0.1.4",
|
|
94
|
+
"peerbit": "5.3.20"
|
|
94
95
|
},
|
|
95
96
|
"repository": {
|
|
96
97
|
"type": "git",
|
package/src/index.ts
CHANGED
|
@@ -186,6 +186,7 @@ import {
|
|
|
186
186
|
type Numbers,
|
|
187
187
|
createNumbers,
|
|
188
188
|
} from "./integers.js";
|
|
189
|
+
import { LeaderPlanCache } from "./leader-plan-cache.js";
|
|
189
190
|
import { TransportMessage } from "./message.js";
|
|
190
191
|
import { PIDReplicationController } from "./pid.js";
|
|
191
192
|
import {
|
|
@@ -1631,6 +1632,20 @@ type WaitForReplicatorsOptions<R extends "u32" | "u64"> =
|
|
|
1631
1632
|
|
|
1632
1633
|
type WaitForReplicator = { key: string; replicator: boolean };
|
|
1633
1634
|
|
|
1635
|
+
type ReceiveLeaderObservation = {
|
|
1636
|
+
isLeader: boolean;
|
|
1637
|
+
fromIsLeader: boolean;
|
|
1638
|
+
};
|
|
1639
|
+
|
|
1640
|
+
// Keep long-lived role callbacks outside the receive activation so event-target
|
|
1641
|
+
// wrappers cannot retain decoded entries through the callback's closure context.
|
|
1642
|
+
const createReceiveLeaderObserver =
|
|
1643
|
+
(observation: ReceiveLeaderObservation, localKey: string, fromKey: string) =>
|
|
1644
|
+
(key: string) => {
|
|
1645
|
+
observation.isLeader ||= localKey === key;
|
|
1646
|
+
observation.fromIsLeader ||= fromKey === key;
|
|
1647
|
+
};
|
|
1648
|
+
|
|
1634
1649
|
type PendingMaturityRecord<R extends "u32" | "u64"> = {
|
|
1635
1650
|
range: ReplicationChange<ReplicationRangeIndexable<R>>;
|
|
1636
1651
|
timeout: ReturnType<typeof setTimeout>;
|
|
@@ -2638,6 +2653,14 @@ const RECALCULATE_PARTICIPATION_MIN_RELATIVE_CHANGE_WITH_MEMORY_LIMIT = 0.001;
|
|
|
2638
2653
|
const RECALCULATE_PARTICIPATION_RELATIVE_DENOMINATOR_FLOOR = 1e-3;
|
|
2639
2654
|
const TOPIC_SUBSCRIBERS_CACHE_TTL_MS = 250;
|
|
2640
2655
|
const LEADER_SELECTION_CONTEXT_CACHE_TTL_MS = 50;
|
|
2656
|
+
// Leader-plan results are invalidated structurally (replication:change,
|
|
2657
|
+
// replicator:mature, subscriber changes). The TTL bounds wall-clock maturity
|
|
2658
|
+
// flips that have no timer at all — a range re-crossing maturity after the
|
|
2659
|
+
// dynamic default roleAge regrew is only visible via expiry — so it is
|
|
2660
|
+
// load-bearing for that edge and must stay at the same order as the
|
|
2661
|
+
// leader-selection context TTL.
|
|
2662
|
+
const LEADER_PLAN_CACHE_TTL_MS = 50;
|
|
2663
|
+
const LEADER_PLAN_CACHE_MAX = 10_000;
|
|
2641
2664
|
const ADAPTIVE_REBALANCE_IDLE_INTERVAL_MULTIPLIER = 5;
|
|
2642
2665
|
const ADAPTIVE_REBALANCE_MIN_IDLE_AFTER_LOCAL_APPEND_MS = 10_000;
|
|
2643
2666
|
|
|
@@ -4536,6 +4559,7 @@ export class SharedLog<
|
|
|
4536
4559
|
expiresAt: number;
|
|
4537
4560
|
context: LeaderSelectionContext;
|
|
4538
4561
|
};
|
|
4562
|
+
private _leaderPlanCache!: LeaderPlanCache;
|
|
4539
4563
|
// Sync capability bits advertised by peers (SyncCapabilitiesMessage), keyed
|
|
4540
4564
|
// by public key hash. Entries are dropped on unsubscribe/disconnect.
|
|
4541
4565
|
private _peerSyncCapabilities!: Map<string, number>;
|
|
@@ -5931,6 +5955,34 @@ export class SharedLog<
|
|
|
5931
5955
|
private invalidateLeaderSelectionContextCache() {
|
|
5932
5956
|
this._receiveOwnershipRevision++;
|
|
5933
5957
|
this._leaderSelectionContextCache = undefined;
|
|
5958
|
+
this._leaderPlanCache?.invalidate();
|
|
5959
|
+
}
|
|
5960
|
+
|
|
5961
|
+
/**
|
|
5962
|
+
* Cache key for a leader-plan lookup, or undefined when the call is
|
|
5963
|
+
* uncacheable: explicit freshness demands, per-call candidate filters,
|
|
5964
|
+
* roleAge outside the two hot buckets (dynamic default and 0), or an
|
|
5965
|
+
* in-flight replication-range mutation window (a plan observed
|
|
5966
|
+
* mid-transition must not outlive the transition).
|
|
5967
|
+
*/
|
|
5968
|
+
private leaderPlanCacheBucket(options?: {
|
|
5969
|
+
roleAge?: number;
|
|
5970
|
+
candidates?: Iterable<string>;
|
|
5971
|
+
freshLeaderPlan?: boolean;
|
|
5972
|
+
}): string | undefined {
|
|
5973
|
+
if (options?.freshLeaderPlan || options?.candidates != null) {
|
|
5974
|
+
return undefined;
|
|
5975
|
+
}
|
|
5976
|
+
if (this._receiveOwnershipMutationAdmissions !== 0) {
|
|
5977
|
+
return undefined;
|
|
5978
|
+
}
|
|
5979
|
+
if (options?.roleAge == null) {
|
|
5980
|
+
return "d";
|
|
5981
|
+
}
|
|
5982
|
+
if (options.roleAge === 0) {
|
|
5983
|
+
return "0";
|
|
5984
|
+
}
|
|
5985
|
+
return undefined;
|
|
5934
5986
|
}
|
|
5935
5987
|
|
|
5936
5988
|
private isReceiveOwnershipSnapshotStable(revision: number): boolean {
|
|
@@ -7027,6 +7079,9 @@ export class SharedLog<
|
|
|
7027
7079
|
}
|
|
7028
7080
|
|
|
7029
7081
|
this._isReplicating = true;
|
|
7082
|
+
// Flipping replication mode changes getDefaultMinRoleAge and the
|
|
7083
|
+
// self-replicating context input even when no range is admitted.
|
|
7084
|
+
this.invalidateLeaderSelectionContextCache();
|
|
7030
7085
|
|
|
7031
7086
|
if (isAdaptiveReplicatorOption(options!)) {
|
|
7032
7087
|
this._isAdaptiveReplicating = true;
|
|
@@ -10967,9 +11022,12 @@ export class SharedLog<
|
|
|
10967
11022
|
|
|
10968
11023
|
try {
|
|
10969
11024
|
throwIfInactive();
|
|
11025
|
+
// Ownership decisions here feed prune/delete outcomes; never serve
|
|
11026
|
+
// them from the leader-plan cache.
|
|
10970
11027
|
const currentLeaders = await this.findLeadersFromEntry(
|
|
10971
11028
|
args.entry,
|
|
10972
11029
|
decodeReplicas(args.entry).getValue(this),
|
|
11030
|
+
{ freshLeaderPlan: true },
|
|
10973
11031
|
);
|
|
10974
11032
|
throwIfInactive();
|
|
10975
11033
|
if (currentLeaders.size > 0) {
|
|
@@ -15817,6 +15875,10 @@ export class SharedLog<
|
|
|
15817
15875
|
this._repairMetrics = createRepairMetrics();
|
|
15818
15876
|
this._topicSubscribersCache = new Map();
|
|
15819
15877
|
this._leaderSelectionContextCache = undefined;
|
|
15878
|
+
this._leaderPlanCache = new LeaderPlanCache({
|
|
15879
|
+
max: LEADER_PLAN_CACHE_MAX,
|
|
15880
|
+
ttl: LEADER_PLAN_CACHE_TTL_MS,
|
|
15881
|
+
});
|
|
15820
15882
|
this._peerSyncCapabilities = new Map();
|
|
15821
15883
|
this._liveRawGossipBatches = new Map();
|
|
15822
15884
|
this._liveRawGossipFlushScheduled = false;
|
|
@@ -17300,7 +17362,10 @@ export class SharedLog<
|
|
|
17300
17362
|
) {
|
|
17301
17363
|
return;
|
|
17302
17364
|
}
|
|
17303
|
-
this.uniqueReplicators.
|
|
17365
|
+
if (!this.uniqueReplicators.has(keyHash)) {
|
|
17366
|
+
this.uniqueReplicators.add(keyHash);
|
|
17367
|
+
this.invalidateLeaderSelectionContextCache();
|
|
17368
|
+
}
|
|
17304
17369
|
|
|
17305
17370
|
if (!this._replicatorJoinEmitted.has(keyHash)) {
|
|
17306
17371
|
this._replicatorJoinEmitted.add(keyHash);
|
|
@@ -20422,6 +20487,10 @@ export class SharedLog<
|
|
|
20422
20487
|
isLeader = true;
|
|
20423
20488
|
fromIsLeader = leaders.has(contextFromHash);
|
|
20424
20489
|
} else {
|
|
20490
|
+
const leaderObservation: ReceiveLeaderObservation = {
|
|
20491
|
+
isLeader: false,
|
|
20492
|
+
fromIsLeader: false,
|
|
20493
|
+
};
|
|
20425
20494
|
leaders = await this._waitForEntryReplicators(
|
|
20426
20495
|
latestEntry,
|
|
20427
20496
|
maxMaxReplicas,
|
|
@@ -20435,15 +20504,15 @@ export class SharedLog<
|
|
|
20435
20504
|
// Let raw receive confirm immediate leadership against the current replicator set.
|
|
20436
20505
|
roleAge: 0,
|
|
20437
20506
|
timeout: 2e4,
|
|
20438
|
-
onLeader: (
|
|
20439
|
-
|
|
20440
|
-
|
|
20441
|
-
|
|
20442
|
-
|
|
20443
|
-
fromIsLeader || contextFromHash === key;
|
|
20444
|
-
},
|
|
20507
|
+
onLeader: createReceiveLeaderObserver(
|
|
20508
|
+
leaderObservation,
|
|
20509
|
+
this.node.identity.publicKey.hashcode(),
|
|
20510
|
+
contextFromHash,
|
|
20511
|
+
),
|
|
20445
20512
|
},
|
|
20446
20513
|
);
|
|
20514
|
+
isLeader = leaderObservation.isLeader;
|
|
20515
|
+
fromIsLeader = leaderObservation.fromIsLeader;
|
|
20447
20516
|
}
|
|
20448
20517
|
} else {
|
|
20449
20518
|
if (plannedLeaders) {
|
|
@@ -22229,8 +22298,20 @@ export class SharedLog<
|
|
|
22229
22298
|
timeout: this.waitForReplicatorTimeout,
|
|
22230
22299
|
},
|
|
22231
22300
|
): Promise<LeaderMap | false> {
|
|
22301
|
+
// Leader rechecks can outlive the receive call while replication roles
|
|
22302
|
+
// settle. Keep the metadata they need, not the decoded entry's serialized
|
|
22303
|
+
// backing buffer (which can be as large as the transferred block).
|
|
22304
|
+
const leaderSelectionEntry =
|
|
22305
|
+
entry instanceof Entry
|
|
22306
|
+
? deserialize(
|
|
22307
|
+
Uint8Array.from(serialize(entry.toShallow(true))),
|
|
22308
|
+
ShallowEntry,
|
|
22309
|
+
)
|
|
22310
|
+
: entry instanceof ShallowEntry
|
|
22311
|
+
? deserialize(Uint8Array.from(serialize(entry)), ShallowEntry)
|
|
22312
|
+
: entry;
|
|
22232
22313
|
if (
|
|
22233
|
-
this.canPlanNativeHashGid(
|
|
22314
|
+
this.canPlanNativeHashGid(leaderSelectionEntry) &&
|
|
22234
22315
|
(this._nativeBackbone ??
|
|
22235
22316
|
this._nativeSharedLogState ??
|
|
22236
22317
|
this._nativeRangePlanner)
|
|
@@ -22240,7 +22321,7 @@ export class SharedLog<
|
|
|
22240
22321
|
options,
|
|
22241
22322
|
async (checkOptions) => {
|
|
22242
22323
|
const plan = await this.planEntryLeaders(
|
|
22243
|
-
|
|
22324
|
+
leaderSelectionEntry,
|
|
22244
22325
|
replicas,
|
|
22245
22326
|
checkOptions,
|
|
22246
22327
|
);
|
|
@@ -22250,8 +22331,8 @@ export class SharedLog<
|
|
|
22250
22331
|
}
|
|
22251
22332
|
|
|
22252
22333
|
return this._waitForReplicators(
|
|
22253
|
-
await this.createCoordinates(
|
|
22254
|
-
|
|
22334
|
+
await this.createCoordinates(leaderSelectionEntry, replicas),
|
|
22335
|
+
leaderSelectionEntry,
|
|
22255
22336
|
waitFor,
|
|
22256
22337
|
options,
|
|
22257
22338
|
);
|
|
@@ -24903,6 +24984,43 @@ export class SharedLog<
|
|
|
24903
24984
|
}
|
|
24904
24985
|
|
|
24905
24986
|
private async _findLeaders(
|
|
24987
|
+
cursors: NumberFromType<R>[],
|
|
24988
|
+
options?: {
|
|
24989
|
+
roleAge?: number;
|
|
24990
|
+
candidates?: Iterable<string>;
|
|
24991
|
+
freshLeaderPlan?: boolean;
|
|
24992
|
+
},
|
|
24993
|
+
ownershipLifecycleController = this.captureReplicationOwnershipLifecycle(),
|
|
24994
|
+
): Promise<Map<string, { intersecting: boolean }>> {
|
|
24995
|
+
const cacheBucket = this.leaderPlanCacheBucket(options);
|
|
24996
|
+
if (cacheBucket == null) {
|
|
24997
|
+
return this._findLeadersUncached(
|
|
24998
|
+
cursors,
|
|
24999
|
+
options,
|
|
25000
|
+
ownershipLifecycleController,
|
|
25001
|
+
);
|
|
25002
|
+
}
|
|
25003
|
+
const cacheKey = `c:${cursors.join(",")}|${cacheBucket}`;
|
|
25004
|
+
const cached = this._leaderPlanCache.get(cacheKey);
|
|
25005
|
+
if (cached) {
|
|
25006
|
+
this.throwIfReplicationOwnershipLifecycleInactive(
|
|
25007
|
+
ownershipLifecycleController,
|
|
25008
|
+
);
|
|
25009
|
+
return cached;
|
|
25010
|
+
}
|
|
25011
|
+
const capturedVersion = this._leaderPlanCache.capture();
|
|
25012
|
+
const leaders = await this._findLeadersUncached(
|
|
25013
|
+
cursors,
|
|
25014
|
+
options,
|
|
25015
|
+
ownershipLifecycleController,
|
|
25016
|
+
);
|
|
25017
|
+
if (this._receiveOwnershipMutationAdmissions === 0) {
|
|
25018
|
+
this._leaderPlanCache.put(cacheKey, leaders, capturedVersion);
|
|
25019
|
+
}
|
|
25020
|
+
return leaders;
|
|
25021
|
+
}
|
|
25022
|
+
|
|
25023
|
+
private async _findLeadersUncached(
|
|
24906
25024
|
cursors: NumberFromType<R>[],
|
|
24907
25025
|
options?: {
|
|
24908
25026
|
roleAge?: number;
|
|
@@ -25353,6 +25471,46 @@ export class SharedLog<
|
|
|
25353
25471
|
}
|
|
25354
25472
|
|
|
25355
25473
|
private async _findLeadersFromHashGid(
|
|
25474
|
+
gid: string,
|
|
25475
|
+
replicas: number,
|
|
25476
|
+
options?: {
|
|
25477
|
+
roleAge?: number;
|
|
25478
|
+
candidates?: Iterable<string>;
|
|
25479
|
+
freshLeaderPlan?: boolean;
|
|
25480
|
+
},
|
|
25481
|
+
ownershipLifecycleController = this.captureReplicationOwnershipLifecycle(),
|
|
25482
|
+
): Promise<Map<string, { intersecting: boolean }> | undefined> {
|
|
25483
|
+
const cacheBucket = this.leaderPlanCacheBucket(options);
|
|
25484
|
+
if (cacheBucket == null) {
|
|
25485
|
+
return this._findLeadersFromHashGidUncached(
|
|
25486
|
+
gid,
|
|
25487
|
+
replicas,
|
|
25488
|
+
options,
|
|
25489
|
+
ownershipLifecycleController,
|
|
25490
|
+
);
|
|
25491
|
+
}
|
|
25492
|
+
const cacheKey = `g:${gid}:${replicas}|${cacheBucket}`;
|
|
25493
|
+
const cached = this._leaderPlanCache.get(cacheKey);
|
|
25494
|
+
if (cached) {
|
|
25495
|
+
this.throwIfReplicationOwnershipLifecycleInactive(
|
|
25496
|
+
ownershipLifecycleController,
|
|
25497
|
+
);
|
|
25498
|
+
return cached;
|
|
25499
|
+
}
|
|
25500
|
+
const capturedVersion = this._leaderPlanCache.capture();
|
|
25501
|
+
const leaders = await this._findLeadersFromHashGidUncached(
|
|
25502
|
+
gid,
|
|
25503
|
+
replicas,
|
|
25504
|
+
options,
|
|
25505
|
+
ownershipLifecycleController,
|
|
25506
|
+
);
|
|
25507
|
+
if (leaders && this._receiveOwnershipMutationAdmissions === 0) {
|
|
25508
|
+
this._leaderPlanCache.put(cacheKey, leaders, capturedVersion);
|
|
25509
|
+
}
|
|
25510
|
+
return leaders;
|
|
25511
|
+
}
|
|
25512
|
+
|
|
25513
|
+
private async _findLeadersFromHashGidUncached(
|
|
25356
25514
|
gid: string,
|
|
25357
25515
|
replicas: number,
|
|
25358
25516
|
options?: {
|
|
@@ -25480,6 +25638,7 @@ export class SharedLog<
|
|
|
25480
25638
|
replicas: number,
|
|
25481
25639
|
options?: {
|
|
25482
25640
|
roleAge?: number;
|
|
25641
|
+
freshLeaderPlan?: boolean;
|
|
25483
25642
|
},
|
|
25484
25643
|
ownershipLifecycleController = this.captureReplicationOwnershipLifecycle(),
|
|
25485
25644
|
): Promise<Map<string, { intersecting: boolean }>> {
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { Cache } from "@peerbit/cache";
|
|
2
|
+
|
|
3
|
+
type LeaderMap = Map<string, { intersecting: boolean }>;
|
|
4
|
+
|
|
5
|
+
const cloneLeaders = (leaders: LeaderMap): LeaderMap => {
|
|
6
|
+
const cloned: LeaderMap = new Map();
|
|
7
|
+
for (const [hash, meta] of leaders) {
|
|
8
|
+
cloned.set(hash, { intersecting: meta.intersecting });
|
|
9
|
+
}
|
|
10
|
+
return cloned;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Memoizes leader-election plans between replication-topology changes.
|
|
15
|
+
*
|
|
16
|
+
* Coherence is structural, not temporal: a private monotonic version is
|
|
17
|
+
* bumped (and the store wiped) on every invalidation, and an async fill is
|
|
18
|
+
* only stored when the version captured before the computation still holds —
|
|
19
|
+
* a plan computed across a topology change is returned to its caller but
|
|
20
|
+
* never cached. The TTL bounds wall-clock maturity flips that fire no event
|
|
21
|
+
* (a range re-crossing maturity after the dynamic roleAge regrew has no
|
|
22
|
+
* pending timer), so it is load-bearing for that edge.
|
|
23
|
+
*
|
|
24
|
+
* Callers mutate returned maps and their value objects, so every hit and
|
|
25
|
+
* every store clones.
|
|
26
|
+
*/
|
|
27
|
+
export class LeaderPlanCache {
|
|
28
|
+
private cache: Cache<{ leaders: LeaderMap }>;
|
|
29
|
+
private version = 0;
|
|
30
|
+
|
|
31
|
+
constructor(options: { max: number; ttl: number }) {
|
|
32
|
+
this.cache = new Cache(options);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
invalidate(): void {
|
|
36
|
+
this.version++;
|
|
37
|
+
this.cache.clear();
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
get(key: string): LeaderMap | undefined {
|
|
41
|
+
const entry = this.cache.get(key);
|
|
42
|
+
return entry ? cloneLeaders(entry.leaders) : undefined;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Capture the version before an awaited plan computation. */
|
|
46
|
+
capture(): number {
|
|
47
|
+
return this.version;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** Store only if no invalidation happened since `capturedVersion`. */
|
|
51
|
+
put(key: string, leaders: LeaderMap, capturedVersion: number): void {
|
|
52
|
+
if (capturedVersion !== this.version) {
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
this.cache.add(
|
|
56
|
+
key,
|
|
57
|
+
{ leaders: cloneLeaders(leaders) },
|
|
58
|
+
Math.max(1, leaders.size),
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
}
|
package/src/ranges.ts
CHANGED
|
@@ -126,7 +126,7 @@ export class EntryReplicatedU32 implements EntryReplicated<"u32"> {
|
|
|
126
126
|
@field({ type: Uint8Array })
|
|
127
127
|
private _meta: Uint8Array;
|
|
128
128
|
|
|
129
|
-
private _metaResolved
|
|
129
|
+
private _metaResolved?: ShallowMeta;
|
|
130
130
|
|
|
131
131
|
constructor(properties: {
|
|
132
132
|
coordinates: number[];
|
|
@@ -154,13 +154,16 @@ export class EntryReplicatedU32 implements EntryReplicated<"u32"> {
|
|
|
154
154
|
properties.meta?.clock.timestamp.wallTime ?? properties.wallTime!;
|
|
155
155
|
this.hashNumber = properties.hashNumber;
|
|
156
156
|
this._meta =
|
|
157
|
-
properties.metaBytes
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
157
|
+
properties.metaBytes != null
|
|
158
|
+
? Uint8Array.from(properties.metaBytes)
|
|
159
|
+
: Uint8Array.from(
|
|
160
|
+
serialize(
|
|
161
|
+
properties.meta instanceof Meta
|
|
162
|
+
? new ShallowMeta(properties.meta)
|
|
163
|
+
: properties.meta!,
|
|
164
|
+
),
|
|
165
|
+
);
|
|
166
|
+
this._metaResolved = undefined;
|
|
164
167
|
this.assignedToRangeBoundary = properties.assignedToRangeBoundary;
|
|
165
168
|
}
|
|
166
169
|
|
|
@@ -199,7 +202,7 @@ export class EntryReplicatedU64 implements EntryReplicated<"u64"> {
|
|
|
199
202
|
@field({ type: Uint8Array })
|
|
200
203
|
private _meta: Uint8Array;
|
|
201
204
|
|
|
202
|
-
private _metaResolved
|
|
205
|
+
private _metaResolved?: ShallowMeta;
|
|
203
206
|
|
|
204
207
|
constructor(properties: {
|
|
205
208
|
coordinates: bigint[];
|
|
@@ -227,13 +230,16 @@ export class EntryReplicatedU64 implements EntryReplicated<"u64"> {
|
|
|
227
230
|
this.wallTime =
|
|
228
231
|
properties.meta?.clock.timestamp.wallTime ?? properties.wallTime!;
|
|
229
232
|
this._meta =
|
|
230
|
-
properties.metaBytes
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
233
|
+
properties.metaBytes != null
|
|
234
|
+
? Uint8Array.from(properties.metaBytes)
|
|
235
|
+
: Uint8Array.from(
|
|
236
|
+
serialize(
|
|
237
|
+
properties.meta instanceof Meta
|
|
238
|
+
? new ShallowMeta(properties.meta)
|
|
239
|
+
: properties.meta!,
|
|
240
|
+
),
|
|
241
|
+
);
|
|
242
|
+
this._metaResolved = undefined;
|
|
237
243
|
this.assignedToRangeBoundary = properties.assignedToRangeBoundary;
|
|
238
244
|
}
|
|
239
245
|
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"adaptive-ingest.d.ts","sourceRoot":"","sources":["../../benchmark/adaptive-ingest.ts"],"names":[],"mappings":""}
|