@peerbit/shared-log 9.0.10 → 9.1.0-57b8640
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/index.js +2 -2
- package/dist/benchmark/index.js.map +1 -1
- package/dist/benchmark/replication.js +3 -3
- package/dist/benchmark/replication.js.map +1 -1
- package/dist/src/index.d.ts +51 -31
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +435 -230
- package/dist/src/index.js.map +1 -1
- package/dist/src/pid.d.ts.map +1 -1
- package/dist/src/pid.js +20 -19
- package/dist/src/pid.js.map +1 -1
- package/dist/src/ranges.d.ts +22 -3
- package/dist/src/ranges.d.ts.map +1 -1
- package/dist/src/ranges.js +231 -336
- package/dist/src/ranges.js.map +1 -1
- package/dist/src/replication-domain-hash.d.ts +5 -0
- package/dist/src/replication-domain-hash.d.ts.map +1 -0
- package/dist/src/replication-domain-hash.js +30 -0
- package/dist/src/replication-domain-hash.js.map +1 -0
- package/dist/src/replication-domain-time.d.ts +14 -0
- package/dist/src/replication-domain-time.d.ts.map +1 -0
- package/dist/src/replication-domain-time.js +59 -0
- package/dist/src/replication-domain-time.js.map +1 -0
- package/dist/src/replication-domain.d.ts +33 -0
- package/dist/src/replication-domain.d.ts.map +1 -0
- package/dist/src/replication-domain.js +6 -0
- package/dist/src/replication-domain.js.map +1 -0
- package/dist/src/replication.d.ts +10 -8
- package/dist/src/replication.d.ts.map +1 -1
- package/dist/src/replication.js +64 -46
- package/dist/src/replication.js.map +1 -1
- package/dist/src/role.d.ts +2 -1
- package/dist/src/role.d.ts.map +1 -1
- package/dist/src/role.js +6 -5
- package/dist/src/role.js.map +1 -1
- package/package.json +70 -70
- package/src/index.ts +621 -312
- package/src/pid.ts +20 -19
- package/src/ranges.ts +328 -375
- package/src/replication-domain-hash.ts +43 -0
- package/src/replication-domain-time.ts +85 -0
- package/src/replication-domain.ts +50 -0
- package/src/replication.ts +50 -46
- package/src/role.ts +6 -5
package/src/index.ts
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { BorshError, field, variant } from "@dao-xyz/borsh";
|
|
2
2
|
import { CustomEvent } from "@libp2p/interface";
|
|
3
3
|
import { AnyBlockStore, RemoteBlocks } from "@peerbit/blocks";
|
|
4
4
|
import { Cache } from "@peerbit/cache";
|
|
5
5
|
import {
|
|
6
6
|
AccessError,
|
|
7
7
|
PublicSignKey,
|
|
8
|
-
sha256,
|
|
9
8
|
sha256Base64Sync,
|
|
10
9
|
sha256Sync,
|
|
11
10
|
} from "@peerbit/crypto";
|
|
@@ -15,13 +14,11 @@ import {
|
|
|
15
14
|
CountRequest,
|
|
16
15
|
DeleteRequest,
|
|
17
16
|
type Index,
|
|
18
|
-
IntegerCompare,
|
|
19
17
|
Or,
|
|
20
18
|
SearchRequest,
|
|
21
19
|
Sort,
|
|
22
20
|
StringMatch,
|
|
23
21
|
SumRequest,
|
|
24
|
-
toId,
|
|
25
22
|
} from "@peerbit/indexer-interface";
|
|
26
23
|
import {
|
|
27
24
|
type AppendOptions,
|
|
@@ -50,6 +47,7 @@ import { AbortError, delay, waitFor } from "@peerbit/time";
|
|
|
50
47
|
import debounce from "p-debounce";
|
|
51
48
|
import pDefer, { type DeferredPromise } from "p-defer";
|
|
52
49
|
import PQueue from "p-queue";
|
|
50
|
+
import { concat } from "uint8arrays";
|
|
53
51
|
import { BlocksMessage } from "./blocks.js";
|
|
54
52
|
import { type CPUUsage, CPUUsageIntervalLag } from "./cpu.js";
|
|
55
53
|
import {
|
|
@@ -63,32 +61,73 @@ import {
|
|
|
63
61
|
} from "./exchange-heads.js";
|
|
64
62
|
import { TransportMessage } from "./message.js";
|
|
65
63
|
import { PIDReplicationController } from "./pid.js";
|
|
66
|
-
import {
|
|
64
|
+
import {
|
|
65
|
+
getCoverSet,
|
|
66
|
+
getSamples,
|
|
67
|
+
hasCoveringRange,
|
|
68
|
+
isMatured,
|
|
69
|
+
minimumWidthToCover,
|
|
70
|
+
} from "./ranges.js";
|
|
71
|
+
import {
|
|
72
|
+
type ReplicationDomainHash,
|
|
73
|
+
createReplicationDomainHash,
|
|
74
|
+
hashToU32,
|
|
75
|
+
} from "./replication-domain-hash.js";
|
|
76
|
+
import {
|
|
77
|
+
type ReplicationDomainTime,
|
|
78
|
+
createReplicationDomainTime,
|
|
79
|
+
} from "./replication-domain-time.js";
|
|
80
|
+
import {
|
|
81
|
+
type ExtractDomainArgs,
|
|
82
|
+
type ReplicationDomain,
|
|
83
|
+
type u32,
|
|
84
|
+
} from "./replication-domain.js";
|
|
67
85
|
import {
|
|
68
86
|
AbsoluteReplicas,
|
|
87
|
+
AddedReplicationSegmentMessage,
|
|
88
|
+
AllReplicatingSegmentsMessage,
|
|
69
89
|
ReplicationError,
|
|
70
90
|
ReplicationIntent,
|
|
71
91
|
type ReplicationLimits,
|
|
72
92
|
ReplicationRange,
|
|
73
93
|
ReplicationRangeIndexable,
|
|
74
94
|
RequestReplicationInfoMessage,
|
|
75
|
-
ResponseReplicationInfoMessage,
|
|
76
95
|
ResponseRoleMessage,
|
|
77
|
-
StartedReplicating,
|
|
78
96
|
StoppedReplicating,
|
|
79
97
|
decodeReplicas,
|
|
80
98
|
encodeReplicas,
|
|
81
|
-
hashToUniformNumber,
|
|
82
99
|
maxReplicas,
|
|
83
100
|
} from "./replication.js";
|
|
84
|
-
import { Observer, Replicator,
|
|
85
|
-
|
|
86
|
-
export
|
|
87
|
-
|
|
101
|
+
import { MAX_U32, Observer, Replicator, scaleToU32 } from "./role.js";
|
|
102
|
+
|
|
103
|
+
export {
|
|
104
|
+
type ReplicationDomain,
|
|
105
|
+
type ReplicationDomainHash,
|
|
106
|
+
type ReplicationDomainTime,
|
|
107
|
+
createReplicationDomainHash,
|
|
108
|
+
createReplicationDomainTime,
|
|
109
|
+
};
|
|
88
110
|
export { type CPUUsage, CPUUsageIntervalLag };
|
|
111
|
+
export * from "./replication.js";
|
|
89
112
|
|
|
90
113
|
export const logger = loggerFn({ module: "shared-log" });
|
|
91
114
|
|
|
115
|
+
const getLatestEntry = (
|
|
116
|
+
entries: (ShallowOrFullEntry<any> | EntryWithRefs<any>)[],
|
|
117
|
+
) => {
|
|
118
|
+
let latest: ShallowOrFullEntry<any> | undefined = undefined;
|
|
119
|
+
for (const element of entries) {
|
|
120
|
+
let entry = element instanceof EntryWithRefs ? element.entry : element;
|
|
121
|
+
if (
|
|
122
|
+
!latest ||
|
|
123
|
+
entry.meta.clock.timestamp.compare(latest.meta.clock.timestamp) > 0
|
|
124
|
+
) {
|
|
125
|
+
latest = entry;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
return latest;
|
|
129
|
+
};
|
|
130
|
+
|
|
92
131
|
const groupByGid = async <
|
|
93
132
|
T extends ShallowEntry | Entry<any> | EntryWithRefs<any>,
|
|
94
133
|
>(
|
|
@@ -123,13 +162,16 @@ export type DynamicReplicationOptions = {
|
|
|
123
162
|
};
|
|
124
163
|
|
|
125
164
|
export type FixedReplicationOptions = {
|
|
126
|
-
|
|
165
|
+
normalized?: boolean;
|
|
166
|
+
factor: number | "all" | "right";
|
|
167
|
+
strict?: boolean; // if true, only this range will be replicated
|
|
127
168
|
offset?: number;
|
|
128
169
|
};
|
|
129
170
|
|
|
130
171
|
export type ReplicationOptions =
|
|
131
172
|
| DynamicReplicationOptions
|
|
132
173
|
| FixedReplicationOptions
|
|
174
|
+
| FixedReplicationOptions[]
|
|
133
175
|
| number
|
|
134
176
|
| boolean;
|
|
135
177
|
|
|
@@ -145,10 +187,19 @@ const isAdaptiveReplicatorOption = (
|
|
|
145
187
|
if ((options as FixedReplicationOptions).factor != null) {
|
|
146
188
|
return false;
|
|
147
189
|
}
|
|
190
|
+
if (Array.isArray(options)) {
|
|
191
|
+
return false;
|
|
192
|
+
}
|
|
148
193
|
return true;
|
|
149
194
|
};
|
|
150
195
|
|
|
151
|
-
|
|
196
|
+
const isUnreplicationOptions = (options?: ReplicationOptions): boolean =>
|
|
197
|
+
options === false ||
|
|
198
|
+
options === 0 ||
|
|
199
|
+
((options as FixedReplicationOptions)?.offset === undefined &&
|
|
200
|
+
(options as FixedReplicationOptions)?.factor === 0);
|
|
201
|
+
|
|
202
|
+
export type SharedLogOptions<T, D extends ReplicationDomain<any, T>> = {
|
|
152
203
|
replicate?: ReplicationOptions;
|
|
153
204
|
replicas?: ReplicationLimitsOptions;
|
|
154
205
|
respondToIHaveTimeout?: number;
|
|
@@ -158,6 +209,7 @@ export type SharedLogOptions<T> = {
|
|
|
158
209
|
waitForReplicatorTimeout?: number;
|
|
159
210
|
distributionDebounceTime?: number;
|
|
160
211
|
compatibility?: number;
|
|
212
|
+
domain?: D;
|
|
161
213
|
};
|
|
162
214
|
|
|
163
215
|
export const DEFAULT_MIN_REPLICAS = 2;
|
|
@@ -166,10 +218,14 @@ export const WAIT_FOR_ROLE_MATURITY = 5000;
|
|
|
166
218
|
const REBALANCE_DEBOUNCE_INTERVAL = 100;
|
|
167
219
|
const DEFAULT_DISTRIBUTION_DEBOUNCE_TIME = 500;
|
|
168
220
|
|
|
169
|
-
export type Args<
|
|
221
|
+
export type Args<
|
|
222
|
+
T,
|
|
223
|
+
D extends ReplicationDomain<any, T> = ReplicationDomainHash,
|
|
224
|
+
> = LogProperties<T> & LogEvents<T> & SharedLogOptions<T, D>;
|
|
170
225
|
|
|
171
226
|
export type SharedAppendOptions<T> = AppendOptions<T> & {
|
|
172
227
|
replicas?: AbsoluteReplicas | number;
|
|
228
|
+
replicate?: boolean;
|
|
173
229
|
target?: "all" | "replicators";
|
|
174
230
|
};
|
|
175
231
|
|
|
@@ -184,10 +240,10 @@ export interface SharedLogEvents extends ProgramEvents {
|
|
|
184
240
|
}
|
|
185
241
|
|
|
186
242
|
@variant("shared_log")
|
|
187
|
-
export class SharedLog<
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
> {
|
|
243
|
+
export class SharedLog<
|
|
244
|
+
T = Uint8Array,
|
|
245
|
+
D extends ReplicationDomain<any, T> = ReplicationDomainHash,
|
|
246
|
+
> extends Program<Args<T, D>, SharedLogEvents> {
|
|
191
247
|
@field({ type: Log })
|
|
192
248
|
log: Log<T>;
|
|
193
249
|
|
|
@@ -195,7 +251,9 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
195
251
|
rpc: RPC<TransportMessage, TransportMessage>;
|
|
196
252
|
|
|
197
253
|
// options
|
|
198
|
-
private
|
|
254
|
+
private _isReplicating: boolean;
|
|
255
|
+
private _isAdaptiveReplicating: boolean;
|
|
256
|
+
|
|
199
257
|
private _replicationRangeIndex!: Index<ReplicationRangeIndexable>;
|
|
200
258
|
/* private _totalParticipation!: number; */
|
|
201
259
|
private _gidPeersHistory!: Map<string, Set<string>>;
|
|
@@ -209,7 +267,7 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
209
267
|
|
|
210
268
|
private _logProperties?: LogProperties<T> &
|
|
211
269
|
LogEvents<T> &
|
|
212
|
-
SharedLogOptions<T>;
|
|
270
|
+
SharedLogOptions<T, D>;
|
|
213
271
|
private _closeController!: AbortController;
|
|
214
272
|
private _gidParentCache!: Cache<Entry<any>[]>;
|
|
215
273
|
private _respondToIHaveTimeout!: any;
|
|
@@ -228,7 +286,7 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
228
286
|
{ clear: () => void; callback: (entry: Entry<T>) => void }
|
|
229
287
|
>;
|
|
230
288
|
|
|
231
|
-
private
|
|
289
|
+
private latestReplicationInfoMessage!: Map<string, bigint>;
|
|
232
290
|
|
|
233
291
|
private remoteBlocks!: RemoteBlocks;
|
|
234
292
|
|
|
@@ -254,7 +312,7 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
254
312
|
private syncInFlightQueueInverted!: Map<string, Set<string>>;
|
|
255
313
|
|
|
256
314
|
// map of hash to public keys that we have asked for entries
|
|
257
|
-
|
|
315
|
+
syncInFlight!: Map<string, Map<string, { timestamp: number }>>;
|
|
258
316
|
|
|
259
317
|
replicas!: ReplicationLimits;
|
|
260
318
|
|
|
@@ -266,6 +324,7 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
266
324
|
|
|
267
325
|
replicationController!: PIDReplicationController;
|
|
268
326
|
history!: { usedMemory: number; factor: number }[];
|
|
327
|
+
domain: D;
|
|
269
328
|
|
|
270
329
|
private pq: PQueue<any>;
|
|
271
330
|
|
|
@@ -275,13 +334,6 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
275
334
|
this.rpc = new RPC();
|
|
276
335
|
}
|
|
277
336
|
|
|
278
|
-
/**
|
|
279
|
-
* Return the
|
|
280
|
-
*/
|
|
281
|
-
get replicationSettings(): ReplicationOptions | undefined {
|
|
282
|
-
return this._replicationSettings;
|
|
283
|
-
}
|
|
284
|
-
|
|
285
337
|
get compatibility(): number | undefined {
|
|
286
338
|
return this._logProperties?.compatibility;
|
|
287
339
|
}
|
|
@@ -291,16 +343,19 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
291
343
|
}
|
|
292
344
|
|
|
293
345
|
// @deprecated
|
|
294
|
-
private getRole() {
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
346
|
+
private async getRole() {
|
|
347
|
+
const segments = await this.getMyReplicationSegments();
|
|
348
|
+
if (segments.length > 1) {
|
|
349
|
+
throw new Error(
|
|
350
|
+
"More than one replication segment found. Can only use one segment for compatbility with v8",
|
|
351
|
+
);
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
if (segments.length > 0) {
|
|
355
|
+
const segment = segments[0].toReplicationRange();
|
|
301
356
|
return new Replicator({
|
|
302
|
-
factor:
|
|
303
|
-
offset:
|
|
357
|
+
factor: segment.factor / MAX_U32,
|
|
358
|
+
offset: segment.offset / MAX_U32,
|
|
304
359
|
});
|
|
305
360
|
}
|
|
306
361
|
|
|
@@ -309,15 +364,17 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
309
364
|
}
|
|
310
365
|
|
|
311
366
|
async isReplicating() {
|
|
312
|
-
if (!this.
|
|
367
|
+
if (!this._isReplicating) {
|
|
313
368
|
return false;
|
|
314
369
|
}
|
|
370
|
+
/*
|
|
315
371
|
if (isAdaptiveReplicatorOption(this._replicationSettings)) {
|
|
316
372
|
return true;
|
|
317
373
|
}
|
|
318
|
-
|
|
374
|
+
|
|
375
|
+
if ((this.replicationSettings as FixedReplicationOptions).factor !== 0) {
|
|
319
376
|
return true;
|
|
320
|
-
}
|
|
377
|
+
} */
|
|
321
378
|
|
|
322
379
|
return (await this.countReplicationSegments()) > 0;
|
|
323
380
|
}
|
|
@@ -330,7 +387,7 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
330
387
|
const sum = await this.replicationIndex.sum(
|
|
331
388
|
new SumRequest({ key: "width" }),
|
|
332
389
|
);
|
|
333
|
-
return Number(sum) /
|
|
390
|
+
return Number(sum) / MAX_U32;
|
|
334
391
|
}
|
|
335
392
|
|
|
336
393
|
async countReplicationSegments() {
|
|
@@ -346,6 +403,7 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
346
403
|
}
|
|
347
404
|
|
|
348
405
|
private setupRebalanceDebounceFunction() {
|
|
406
|
+
this.rebalanceParticipationDebounced = undefined;
|
|
349
407
|
this.rebalanceParticipationDebounced = debounce(
|
|
350
408
|
() => this.rebalanceParticipation(),
|
|
351
409
|
/* Math.max(
|
|
@@ -358,135 +416,256 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
358
416
|
REBALANCE_DEBOUNCE_INTERVAL, // TODO make this dynamic on the number of replicators
|
|
359
417
|
);
|
|
360
418
|
}
|
|
361
|
-
private async
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
: undefined,
|
|
381
|
-
},
|
|
382
|
-
);
|
|
419
|
+
private async _replicate(
|
|
420
|
+
options?: ReplicationOptions,
|
|
421
|
+
{
|
|
422
|
+
reset,
|
|
423
|
+
checkDuplicates,
|
|
424
|
+
announce,
|
|
425
|
+
}: {
|
|
426
|
+
reset?: boolean;
|
|
427
|
+
checkDuplicates?: boolean;
|
|
428
|
+
announce?: (
|
|
429
|
+
msg: AddedReplicationSegmentMessage | AllReplicatingSegmentsMessage,
|
|
430
|
+
) => void;
|
|
431
|
+
} = {},
|
|
432
|
+
) {
|
|
433
|
+
let offsetWasProvided = false;
|
|
434
|
+
if (isUnreplicationOptions(options)) {
|
|
435
|
+
await this.unreplicate();
|
|
436
|
+
} else {
|
|
437
|
+
let ranges: ReplicationRangeIndexable[] = [];
|
|
383
438
|
|
|
384
|
-
|
|
385
|
-
options
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
439
|
+
if (options == null) {
|
|
440
|
+
options = {};
|
|
441
|
+
} else if (options === true) {
|
|
442
|
+
options = {};
|
|
443
|
+
}
|
|
389
444
|
|
|
390
|
-
this.
|
|
391
|
-
|
|
445
|
+
this._isReplicating = true;
|
|
446
|
+
this._isAdaptiveReplicating = false;
|
|
392
447
|
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
this.
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
448
|
+
if (isAdaptiveReplicatorOption(options!)) {
|
|
449
|
+
this._isAdaptiveReplicating = true;
|
|
450
|
+
this.setupDebouncedRebalancing(options);
|
|
451
|
+
|
|
452
|
+
// initial role in a dynamic setup
|
|
453
|
+
const maybeRange = await this.getDynamicRange();
|
|
454
|
+
if (!maybeRange) {
|
|
455
|
+
// not allowed
|
|
456
|
+
return;
|
|
457
|
+
}
|
|
458
|
+
ranges = [maybeRange];
|
|
459
|
+
|
|
460
|
+
offsetWasProvided = true;
|
|
461
|
+
} else if (options instanceof ReplicationRange) {
|
|
462
|
+
ranges = [
|
|
463
|
+
options.toReplicationRangeIndexable(this.node.identity.publicKey),
|
|
464
|
+
];
|
|
465
|
+
|
|
466
|
+
offsetWasProvided = true;
|
|
403
467
|
} else {
|
|
468
|
+
let rangeArgs: FixedReplicationOptions[];
|
|
404
469
|
if (typeof options === "number") {
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
470
|
+
rangeArgs = [
|
|
471
|
+
{
|
|
472
|
+
factor: options,
|
|
473
|
+
} as FixedReplicationOptions,
|
|
474
|
+
];
|
|
408
475
|
} else {
|
|
409
|
-
|
|
476
|
+
rangeArgs = (
|
|
477
|
+
Array.isArray(options) ? options : [{ ...options }]
|
|
478
|
+
) as FixedReplicationOptions[];
|
|
410
479
|
}
|
|
411
|
-
}
|
|
412
|
-
} else {
|
|
413
|
-
return;
|
|
414
|
-
}
|
|
415
480
|
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
// fixed
|
|
421
|
-
const range = new ReplicationRangeIndexable({
|
|
422
|
-
offset:
|
|
423
|
-
(this._replicationSettings as FixedReplicationOptions).offset ??
|
|
424
|
-
Math.random(),
|
|
425
|
-
length: (this._replicationSettings as FixedReplicationOptions).factor,
|
|
426
|
-
publicKeyHash: this.node.identity.publicKey.hashcode(),
|
|
427
|
-
replicationIntent: ReplicationIntent.Explicit, // automatic means that this range might be reused later for dynamic replication behaviour
|
|
428
|
-
timestamp: BigInt(+new Date()),
|
|
429
|
-
id: sha256Sync(this.node.identity.publicKey.bytes),
|
|
430
|
-
});
|
|
431
|
-
await this.startAnnounceReplicating(range);
|
|
432
|
-
}
|
|
433
|
-
}
|
|
481
|
+
if (rangeArgs.length === 0) {
|
|
482
|
+
// nothing to do
|
|
483
|
+
return;
|
|
484
|
+
}
|
|
434
485
|
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
486
|
+
for (const rangeArg of rangeArgs) {
|
|
487
|
+
const normalized = rangeArg.normalized ?? true;
|
|
488
|
+
offsetWasProvided = rangeArg.offset != null;
|
|
489
|
+
const offset =
|
|
490
|
+
rangeArg.offset ??
|
|
491
|
+
(normalized ? Math.random() : scaleToU32(Math.random()));
|
|
492
|
+
let factor = rangeArg.factor;
|
|
493
|
+
let width = normalized ? 1 : scaleToU32(1);
|
|
494
|
+
ranges.push(
|
|
495
|
+
new ReplicationRangeIndexable({
|
|
496
|
+
normalized,
|
|
497
|
+
offset: offset,
|
|
498
|
+
length:
|
|
499
|
+
typeof factor === "number"
|
|
500
|
+
? factor
|
|
501
|
+
: factor === "all"
|
|
502
|
+
? width
|
|
503
|
+
: width - offset,
|
|
504
|
+
publicKeyHash: this.node.identity.publicKey.hashcode(),
|
|
505
|
+
mode: rangeArg.strict
|
|
506
|
+
? ReplicationIntent.Strict
|
|
507
|
+
: ReplicationIntent.NonStrict, // automatic means that this range might be reused later for dynamic replication behaviour
|
|
508
|
+
timestamp: BigInt(+new Date()),
|
|
509
|
+
}),
|
|
510
|
+
);
|
|
511
|
+
}
|
|
512
|
+
}
|
|
441
513
|
|
|
442
|
-
|
|
514
|
+
for (const range of ranges) {
|
|
443
515
|
this.oldestOpenTime = Math.min(
|
|
444
516
|
Number(range.timestamp),
|
|
445
517
|
this.oldestOpenTime,
|
|
446
518
|
);
|
|
519
|
+
}
|
|
447
520
|
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
521
|
+
let resetRanges = reset;
|
|
522
|
+
if (!resetRanges && !offsetWasProvided) {
|
|
523
|
+
resetRanges = true;
|
|
524
|
+
// because if we do something like replicate ({ factor: 0.5 }) it means that we want to replicate 50%
|
|
525
|
+
// but ({ replicate: 0.5, offset: 0.5 }) means that we want to add a range
|
|
526
|
+
// TODO make behaviour more clear
|
|
453
527
|
}
|
|
528
|
+
await this.startAnnounceReplicating(ranges, {
|
|
529
|
+
reset: resetRanges ?? false,
|
|
530
|
+
checkDuplicates,
|
|
531
|
+
announce,
|
|
532
|
+
});
|
|
454
533
|
}
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
setupDebouncedRebalancing(options?: DynamicReplicationOptions) {
|
|
537
|
+
this.cpuUsage?.stop?.();
|
|
538
|
+
this.replicationController = new PIDReplicationController(
|
|
539
|
+
this.node.identity.publicKey.hashcode(),
|
|
540
|
+
{
|
|
541
|
+
storage:
|
|
542
|
+
options?.limits?.storage != null
|
|
543
|
+
? { max: options?.limits?.storage }
|
|
544
|
+
: undefined,
|
|
545
|
+
cpu:
|
|
546
|
+
options?.limits?.cpu != null
|
|
547
|
+
? {
|
|
548
|
+
max:
|
|
549
|
+
typeof options?.limits?.cpu === "object"
|
|
550
|
+
? options.limits.cpu.max
|
|
551
|
+
: options?.limits?.cpu,
|
|
552
|
+
}
|
|
553
|
+
: undefined,
|
|
554
|
+
},
|
|
555
|
+
);
|
|
556
|
+
|
|
557
|
+
this.cpuUsage =
|
|
558
|
+
options?.limits?.cpu && typeof options?.limits?.cpu === "object"
|
|
559
|
+
? options?.limits?.cpu?.monitor || new CPUUsageIntervalLag()
|
|
560
|
+
: new CPUUsageIntervalLag();
|
|
561
|
+
this.cpuUsage?.start?.();
|
|
562
|
+
this.setupRebalanceDebounceFunction();
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
async replicate(
|
|
566
|
+
rangeOrEntry?:
|
|
567
|
+
| ReplicationRange
|
|
568
|
+
| ReplicationOptions
|
|
569
|
+
| Entry<T>
|
|
570
|
+
| Entry<T>[],
|
|
571
|
+
options?: {
|
|
572
|
+
reset?: boolean;
|
|
573
|
+
checkDuplicates?: boolean;
|
|
574
|
+
announce?: (
|
|
575
|
+
msg: AllReplicatingSegmentsMessage | AddedReplicationSegmentMessage,
|
|
576
|
+
) => void;
|
|
577
|
+
},
|
|
578
|
+
) {
|
|
579
|
+
let range: ReplicationRange[] | ReplicationOptions | undefined = undefined;
|
|
580
|
+
|
|
581
|
+
if (rangeOrEntry instanceof ReplicationRange) {
|
|
582
|
+
range = rangeOrEntry;
|
|
583
|
+
} else if (rangeOrEntry instanceof Entry) {
|
|
584
|
+
range = {
|
|
585
|
+
factor: 1,
|
|
586
|
+
offset: await this.domain.fromEntry(rangeOrEntry),
|
|
587
|
+
normalized: false,
|
|
588
|
+
};
|
|
589
|
+
} else if (Array.isArray(rangeOrEntry)) {
|
|
590
|
+
let ranges: (ReplicationRange | FixedReplicationOptions)[] = [];
|
|
591
|
+
for (const entry of rangeOrEntry) {
|
|
592
|
+
if (entry instanceof Entry) {
|
|
593
|
+
ranges.push({
|
|
594
|
+
factor: 1,
|
|
595
|
+
offset: await this.domain.fromEntry(entry),
|
|
596
|
+
normalized: false,
|
|
597
|
+
});
|
|
598
|
+
} else {
|
|
599
|
+
ranges.push(entry);
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
range = ranges;
|
|
603
|
+
} else {
|
|
604
|
+
range = rangeOrEntry ?? true;
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
const newRanges = await this._replicate(range, options);
|
|
455
608
|
|
|
456
609
|
// assume new role
|
|
457
610
|
await this.distribute();
|
|
611
|
+
|
|
612
|
+
return newRanges;
|
|
458
613
|
}
|
|
459
614
|
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
615
|
+
async unreplicate(rangeOrEntry?: Entry<T> | ReplicationRange) {
|
|
616
|
+
let range: FixedReplicationOptions;
|
|
617
|
+
if (rangeOrEntry instanceof Entry) {
|
|
618
|
+
range = {
|
|
619
|
+
factor: 1,
|
|
620
|
+
offset: await this.domain.fromEntry(rangeOrEntry),
|
|
621
|
+
};
|
|
622
|
+
} else if (rangeOrEntry instanceof ReplicationRange) {
|
|
623
|
+
range = rangeOrEntry;
|
|
624
|
+
} else {
|
|
625
|
+
this._isReplicating = false;
|
|
626
|
+
this._isAdaptiveReplicating = false;
|
|
627
|
+
await this.removeReplicator(this.node.identity.publicKey);
|
|
628
|
+
return;
|
|
629
|
+
}
|
|
469
630
|
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
631
|
+
if (this._isAdaptiveReplicating) {
|
|
632
|
+
// we can not unreplicate individual ranges when dynamically replicating (yet)
|
|
633
|
+
// TODO support this by never deleting the range with the segment id that is generated by the dynamic replication method
|
|
634
|
+
throw new Error("Unsupported when adaptive replicating");
|
|
635
|
+
}
|
|
473
636
|
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
(x) => new ByteMatchQuery({ key: "id", value: x.value.id }),
|
|
483
|
-
),
|
|
484
|
-
);
|
|
637
|
+
const indexed = await this.replicationIndex.query(
|
|
638
|
+
new SearchRequest({
|
|
639
|
+
query: {
|
|
640
|
+
width: 1,
|
|
641
|
+
start1: range.offset,
|
|
642
|
+
},
|
|
643
|
+
}),
|
|
644
|
+
);
|
|
485
645
|
|
|
486
|
-
|
|
646
|
+
const segmentIds = indexed.results.map((x) => x.id.key as Uint8Array);
|
|
647
|
+
await this.removeReplicationRange(segmentIds, this.node.identity.publicKey);
|
|
648
|
+
await this.rpc.send(new StoppedReplicating({ segmentIds }), {
|
|
649
|
+
priority: 1,
|
|
650
|
+
});
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
private async removeReplicator(key: PublicSignKey) {
|
|
654
|
+
const fn = async () => {
|
|
655
|
+
await this.replicationIndex.del(
|
|
656
|
+
new DeleteRequest({ query: { hash: key.hashcode() } }),
|
|
657
|
+
);
|
|
487
658
|
|
|
488
659
|
await this.updateOldestTimestampFromIndex();
|
|
489
660
|
|
|
661
|
+
if (this.node.identity.publicKey.equals(key)) {
|
|
662
|
+
// announce that we are no longer replicating
|
|
663
|
+
await this.rpc.send(
|
|
664
|
+
new AllReplicatingSegmentsMessage({ segments: [] }),
|
|
665
|
+
{ priority: 1 },
|
|
666
|
+
);
|
|
667
|
+
}
|
|
668
|
+
|
|
490
669
|
this.events.dispatchEvent(
|
|
491
670
|
new CustomEvent<ReplicationChange>("replication:change", {
|
|
492
671
|
detail: { publicKey: key },
|
|
@@ -531,11 +710,6 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
531
710
|
|
|
532
711
|
let query = new And([idMatcher, identityMatcher]);
|
|
533
712
|
|
|
534
|
-
/* const prevSum = await this.replicationIndex.sum(
|
|
535
|
-
new SumRequest({ query, key: "width" }),
|
|
536
|
-
);
|
|
537
|
-
const prevSumNormalized = Number(prevSum) / SEGMENT_COORDINATE_SCALE;
|
|
538
|
-
this._totalParticipation -= prevSumNormalized; */
|
|
539
713
|
await this.replicationIndex.del(new DeleteRequest({ query }));
|
|
540
714
|
|
|
541
715
|
await this.updateOldestTimestampFromIndex();
|
|
@@ -555,8 +729,12 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
555
729
|
}
|
|
556
730
|
|
|
557
731
|
private async addReplicationRange(
|
|
558
|
-
|
|
732
|
+
ranges: ReplicationRangeIndexable[],
|
|
559
733
|
from: PublicSignKey,
|
|
734
|
+
{
|
|
735
|
+
reset,
|
|
736
|
+
checkDuplicates,
|
|
737
|
+
}: { reset?: boolean; checkDuplicates?: boolean } = {},
|
|
560
738
|
) {
|
|
561
739
|
const fn = async () => {
|
|
562
740
|
if (
|
|
@@ -566,7 +744,6 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
566
744
|
return false;
|
|
567
745
|
}
|
|
568
746
|
|
|
569
|
-
range.id = new Uint8Array(range.id);
|
|
570
747
|
let prevCount = await this.replicationIndex.count(
|
|
571
748
|
new CountRequest({
|
|
572
749
|
query: new StringMatch({ key: "hash", value: from.hashcode() }),
|
|
@@ -574,22 +751,35 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
574
751
|
);
|
|
575
752
|
const isNewReplicator = prevCount === 0;
|
|
576
753
|
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
754
|
+
if (reset) {
|
|
755
|
+
await this.replicationIndex.del(
|
|
756
|
+
new DeleteRequest({ query: { hash: from.hashcode() } }),
|
|
757
|
+
);
|
|
758
|
+
} else if (checkDuplicates) {
|
|
759
|
+
let deduplicated: any[] = [];
|
|
760
|
+
|
|
761
|
+
// TODO also deduplicate/de-overlap among the ranges that ought to be inserted?
|
|
762
|
+
for (const range of ranges) {
|
|
763
|
+
if (!(await hasCoveringRange(this.replicationIndex, range))) {
|
|
764
|
+
deduplicated.push(range);
|
|
765
|
+
}
|
|
581
766
|
}
|
|
582
|
-
|
|
767
|
+
ranges = deduplicated;
|
|
583
768
|
}
|
|
584
769
|
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
770
|
+
for (const range of ranges) {
|
|
771
|
+
await this.replicationIndex.put(range);
|
|
772
|
+
if (!reset) {
|
|
773
|
+
this.oldestOpenTime = Math.min(
|
|
774
|
+
Number(range.timestamp),
|
|
775
|
+
this.oldestOpenTime,
|
|
776
|
+
);
|
|
777
|
+
}
|
|
778
|
+
}
|
|
588
779
|
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
);
|
|
780
|
+
if (reset) {
|
|
781
|
+
await this.updateOldestTimestampFromIndex();
|
|
782
|
+
}
|
|
593
783
|
|
|
594
784
|
this.events.dispatchEvent(
|
|
595
785
|
new CustomEvent<ReplicationChange>("replication:change", {
|
|
@@ -610,25 +800,50 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
610
800
|
}
|
|
611
801
|
return true;
|
|
612
802
|
};
|
|
803
|
+
|
|
804
|
+
// we sequialize this because we are going to queries to check wether to add or not
|
|
805
|
+
// if two processes do the same this both process might add a range while only one in practice should
|
|
613
806
|
return this.pq.add(fn);
|
|
614
807
|
}
|
|
615
808
|
|
|
616
|
-
async startAnnounceReplicating(
|
|
809
|
+
async startAnnounceReplicating(
|
|
810
|
+
range: ReplicationRangeIndexable[],
|
|
811
|
+
options: {
|
|
812
|
+
reset?: boolean;
|
|
813
|
+
checkDuplicates?: boolean;
|
|
814
|
+
announce?: (
|
|
815
|
+
msg: AllReplicatingSegmentsMessage | AddedReplicationSegmentMessage,
|
|
816
|
+
) => void;
|
|
817
|
+
} = {},
|
|
818
|
+
) {
|
|
617
819
|
const added = await this.addReplicationRange(
|
|
618
820
|
range,
|
|
619
821
|
this.node.identity.publicKey,
|
|
822
|
+
options,
|
|
620
823
|
);
|
|
621
824
|
if (!added) {
|
|
622
825
|
logger.warn("Not allowed to replicate by canReplicate");
|
|
623
826
|
}
|
|
624
827
|
|
|
828
|
+
let message: AllReplicatingSegmentsMessage | AddedReplicationSegmentMessage;
|
|
625
829
|
if (added) {
|
|
626
|
-
|
|
627
|
-
new
|
|
628
|
-
|
|
830
|
+
if (options.reset) {
|
|
831
|
+
message = new AllReplicatingSegmentsMessage({
|
|
832
|
+
segments: range.map((x) => x.toReplicationRange()),
|
|
833
|
+
});
|
|
834
|
+
} else {
|
|
835
|
+
message = new AddedReplicationSegmentMessage({
|
|
836
|
+
segments: range.map((x) => x.toReplicationRange()),
|
|
837
|
+
});
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
if (options.announce) {
|
|
841
|
+
return options.announce(message);
|
|
842
|
+
} else {
|
|
843
|
+
await this.rpc.send(message, {
|
|
629
844
|
priority: 1,
|
|
630
|
-
}
|
|
631
|
-
|
|
845
|
+
});
|
|
846
|
+
}
|
|
632
847
|
}
|
|
633
848
|
}
|
|
634
849
|
|
|
@@ -674,6 +889,10 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
674
889
|
const result = await this.log.append(data, appendOptions);
|
|
675
890
|
let mode: DeliveryMode | undefined = undefined;
|
|
676
891
|
|
|
892
|
+
if (options?.replicate) {
|
|
893
|
+
await this.replicate(result.entry, { checkDuplicates: true });
|
|
894
|
+
}
|
|
895
|
+
|
|
677
896
|
for (const message of await createExchangeHeadsMessages(
|
|
678
897
|
this.log,
|
|
679
898
|
[result.entry],
|
|
@@ -681,22 +900,29 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
681
900
|
)) {
|
|
682
901
|
if (options?.target === "replicators" || !options?.target) {
|
|
683
902
|
const minReplicas = decodeReplicas(result.entry).getValue(this);
|
|
903
|
+
|
|
684
904
|
let leaders: string[] | Set<string> = await this.findLeaders(
|
|
685
|
-
result.entry
|
|
905
|
+
result.entry,
|
|
686
906
|
minReplicas,
|
|
687
907
|
);
|
|
908
|
+
|
|
688
909
|
const isLeader = leaders.includes(
|
|
689
910
|
this.node.identity.publicKey.hashcode(),
|
|
690
911
|
);
|
|
912
|
+
|
|
691
913
|
if (message.heads[0].gidRefrences.length > 0) {
|
|
692
914
|
const newAndOldLeaders = new Set(leaders);
|
|
693
915
|
for (const ref of message.heads[0].gidRefrences) {
|
|
694
|
-
|
|
695
|
-
|
|
916
|
+
const entryFromGid = this.log.entryIndex.getHeads(ref, false);
|
|
917
|
+
for (const entry of await entryFromGid.all()) {
|
|
918
|
+
for (const hash of await this.findLeaders(entry, minReplicas)) {
|
|
919
|
+
newAndOldLeaders.add(hash);
|
|
920
|
+
}
|
|
696
921
|
}
|
|
697
922
|
}
|
|
698
923
|
leaders = newAndOldLeaders;
|
|
699
924
|
}
|
|
925
|
+
|
|
700
926
|
let set = this._gidPeersHistory.get(result.entry.meta.gid);
|
|
701
927
|
if (!set) {
|
|
702
928
|
set = new Set(leaders);
|
|
@@ -721,7 +947,7 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
721
947
|
return result;
|
|
722
948
|
}
|
|
723
949
|
|
|
724
|
-
async open(options?: Args<T>): Promise<void> {
|
|
950
|
+
async open(options?: Args<T, D>): Promise<void> {
|
|
725
951
|
this.replicas = {
|
|
726
952
|
min: options?.replicas?.min
|
|
727
953
|
? typeof options?.replicas?.min === "number"
|
|
@@ -734,11 +960,11 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
734
960
|
: options.replicas.max
|
|
735
961
|
: undefined,
|
|
736
962
|
};
|
|
737
|
-
|
|
963
|
+
this.domain = options?.domain ?? (createReplicationDomainHash() as D);
|
|
738
964
|
this._respondToIHaveTimeout = options?.respondToIHaveTimeout ?? 10 * 1000; // TODO make into arg
|
|
739
965
|
this._pendingDeletes = new Map();
|
|
740
966
|
this._pendingIHave = new Map();
|
|
741
|
-
this.
|
|
967
|
+
this.latestReplicationInfoMessage = new Map();
|
|
742
968
|
this.syncInFlightQueue = new Map();
|
|
743
969
|
this.syncInFlightQueueInverted = new Map();
|
|
744
970
|
this.syncInFlight = new Map();
|
|
@@ -782,9 +1008,14 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
782
1008
|
this._replicationRangeIndex = await replicationIndex.init({
|
|
783
1009
|
schema: ReplicationRangeIndexable,
|
|
784
1010
|
});
|
|
1011
|
+
|
|
785
1012
|
const logIndex = await logScope.scope("log");
|
|
1013
|
+
|
|
786
1014
|
await this.node.indexer.start(); // TODO why do we need to start the indexer here?
|
|
787
1015
|
|
|
1016
|
+
const hasIndexedReplicationInfo =
|
|
1017
|
+
(await this.replicationIndex.getSize()) > 0;
|
|
1018
|
+
|
|
788
1019
|
/* this._totalParticipation = await this.calculateTotalParticipation(); */
|
|
789
1020
|
|
|
790
1021
|
this._gidPeersHistory = new Map();
|
|
@@ -830,6 +1061,8 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
830
1061
|
this._onUnsubscriptionFn,
|
|
831
1062
|
);
|
|
832
1063
|
|
|
1064
|
+
await this.rpc.subscribe();
|
|
1065
|
+
|
|
833
1066
|
// await this.log.load();
|
|
834
1067
|
|
|
835
1068
|
// TODO (do better)
|
|
@@ -884,7 +1117,15 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
884
1117
|
});
|
|
885
1118
|
};
|
|
886
1119
|
|
|
887
|
-
|
|
1120
|
+
// if we had a previous session with replication info, and new replication info dictates that we unreplicate
|
|
1121
|
+
// we should do that. Otherwise if options is a unreplication we dont need to do anything because
|
|
1122
|
+
// we are already unreplicated (as we are just opening)
|
|
1123
|
+
if (
|
|
1124
|
+
hasIndexedReplicationInfo ||
|
|
1125
|
+
isUnreplicationOptions(options?.replicate) === false
|
|
1126
|
+
) {
|
|
1127
|
+
await this.replicate(options?.replicate, { checkDuplicates: true });
|
|
1128
|
+
}
|
|
888
1129
|
requestSync();
|
|
889
1130
|
}
|
|
890
1131
|
|
|
@@ -921,7 +1162,7 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
921
1162
|
|
|
922
1163
|
async onChange(change: Change<T>) {
|
|
923
1164
|
for (const added of change.added) {
|
|
924
|
-
this.onEntryAdded(added);
|
|
1165
|
+
this.onEntryAdded(added.entry);
|
|
925
1166
|
}
|
|
926
1167
|
for (const removed of change.removed) {
|
|
927
1168
|
this.onEntryRemoved(removed.hash);
|
|
@@ -953,6 +1194,40 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
953
1194
|
}
|
|
954
1195
|
}
|
|
955
1196
|
|
|
1197
|
+
async getCover(
|
|
1198
|
+
args: ExtractDomainArgs<D>,
|
|
1199
|
+
options?: {
|
|
1200
|
+
roleAge?: number;
|
|
1201
|
+
eager?:
|
|
1202
|
+
| {
|
|
1203
|
+
unmaturedFetchCoverSize?: number;
|
|
1204
|
+
}
|
|
1205
|
+
| boolean;
|
|
1206
|
+
},
|
|
1207
|
+
) {
|
|
1208
|
+
let roleAge = options?.roleAge ?? (await this.getDefaultMinRoleAge());
|
|
1209
|
+
let eager = options?.eager ?? false;
|
|
1210
|
+
const range = await this.domain.fromArgs(args, this);
|
|
1211
|
+
|
|
1212
|
+
const set = await getCoverSet({
|
|
1213
|
+
peers: this.replicationIndex,
|
|
1214
|
+
start: range.offset,
|
|
1215
|
+
widthToCoverScaled:
|
|
1216
|
+
range.length ??
|
|
1217
|
+
(await minimumWidthToCover(this.replicas.min.getValue(this))),
|
|
1218
|
+
roleAge,
|
|
1219
|
+
eager,
|
|
1220
|
+
intervalWidth: MAX_U32,
|
|
1221
|
+
});
|
|
1222
|
+
|
|
1223
|
+
// add all in flight
|
|
1224
|
+
for (const [key, _] of this.syncInFlight) {
|
|
1225
|
+
set.add(key);
|
|
1226
|
+
}
|
|
1227
|
+
|
|
1228
|
+
return [...set];
|
|
1229
|
+
}
|
|
1230
|
+
|
|
956
1231
|
private async _close() {
|
|
957
1232
|
clearTimeout(this.syncMoreInterval);
|
|
958
1233
|
clearInterval(this.distributeInterval);
|
|
@@ -985,7 +1260,7 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
985
1260
|
this.syncInFlightQueue.clear();
|
|
986
1261
|
this.syncInFlightQueueInverted.clear();
|
|
987
1262
|
this.syncInFlight.clear();
|
|
988
|
-
this.
|
|
1263
|
+
this.latestReplicationInfoMessage.clear();
|
|
989
1264
|
this._gidPeersHistory.clear();
|
|
990
1265
|
|
|
991
1266
|
this._replicationRangeIndex = undefined as any;
|
|
@@ -1074,6 +1349,7 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
1074
1349
|
const headsWithGid = await this.log.entryIndex
|
|
1075
1350
|
.getHeads(gid)
|
|
1076
1351
|
.all();
|
|
1352
|
+
const latestEntry = getLatestEntry(entries)!;
|
|
1077
1353
|
|
|
1078
1354
|
const maxReplicasFromHead =
|
|
1079
1355
|
headsWithGid && headsWithGid.length > 0
|
|
@@ -1091,12 +1367,12 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
1091
1367
|
|
|
1092
1368
|
if (isReplicating) {
|
|
1093
1369
|
isLeader = await this.waitForIsLeader(
|
|
1094
|
-
|
|
1370
|
+
latestEntry,
|
|
1095
1371
|
Math.max(maxReplicasFromHead, maxReplicasFromNewEntries),
|
|
1096
1372
|
);
|
|
1097
1373
|
} else {
|
|
1098
1374
|
isLeader = await this.findLeaders(
|
|
1099
|
-
|
|
1375
|
+
latestEntry,
|
|
1100
1376
|
Math.max(maxReplicasFromHead, maxReplicasFromNewEntries),
|
|
1101
1377
|
);
|
|
1102
1378
|
|
|
@@ -1180,7 +1456,7 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
1180
1456
|
const minReplicas = maxReplicas(this, headsWithGid.values());
|
|
1181
1457
|
|
|
1182
1458
|
const isLeader = await this.isLeader(
|
|
1183
|
-
entries[0].entry
|
|
1459
|
+
entries[0].entry,
|
|
1184
1460
|
minReplicas,
|
|
1185
1461
|
);
|
|
1186
1462
|
|
|
@@ -1202,7 +1478,7 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
1202
1478
|
if (
|
|
1203
1479
|
indexedEntry &&
|
|
1204
1480
|
(await this.isLeader(
|
|
1205
|
-
indexedEntry.value
|
|
1481
|
+
indexedEntry.value,
|
|
1206
1482
|
decodeReplicas(indexedEntry.value).getValue(this),
|
|
1207
1483
|
))
|
|
1208
1484
|
) {
|
|
@@ -1220,7 +1496,7 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
1220
1496
|
callback: async (entry: any) => {
|
|
1221
1497
|
if (
|
|
1222
1498
|
await this.isLeader(
|
|
1223
|
-
entry
|
|
1499
|
+
entry,
|
|
1224
1500
|
decodeReplicas(entry).getValue(this),
|
|
1225
1501
|
)
|
|
1226
1502
|
) {
|
|
@@ -1312,7 +1588,7 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
1312
1588
|
return;
|
|
1313
1589
|
}
|
|
1314
1590
|
await this.rpc.send(
|
|
1315
|
-
new
|
|
1591
|
+
new AllReplicatingSegmentsMessage({
|
|
1316
1592
|
segments: (await this.getMyReplicationSegments()).map((x) =>
|
|
1317
1593
|
x.toReplicationRange(),
|
|
1318
1594
|
),
|
|
@@ -1326,9 +1602,8 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
1326
1602
|
if (this.v8Behaviour) {
|
|
1327
1603
|
const role = this.getRole();
|
|
1328
1604
|
if (role instanceof Replicator) {
|
|
1329
|
-
const fixedSettings = this
|
|
1330
|
-
|
|
1331
|
-
if (fixedSettings.factor === 1) {
|
|
1605
|
+
const fixedSettings = !this._isAdaptiveReplicating;
|
|
1606
|
+
if (fixedSettings) {
|
|
1332
1607
|
await this.rpc.send(
|
|
1333
1608
|
new ResponseRoleMessage({
|
|
1334
1609
|
role,
|
|
@@ -1344,16 +1619,16 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
1344
1619
|
}
|
|
1345
1620
|
}
|
|
1346
1621
|
} else if (
|
|
1347
|
-
msg instanceof
|
|
1348
|
-
msg instanceof
|
|
1622
|
+
msg instanceof AllReplicatingSegmentsMessage ||
|
|
1623
|
+
msg instanceof AddedReplicationSegmentMessage
|
|
1349
1624
|
) {
|
|
1350
1625
|
if (context.from.equals(this.node.identity.publicKey)) {
|
|
1351
1626
|
return;
|
|
1352
1627
|
}
|
|
1353
1628
|
|
|
1354
1629
|
let replicationInfoMessage = msg as
|
|
1355
|
-
|
|
|
1356
|
-
|
|
|
1630
|
+
| AllReplicatingSegmentsMessage
|
|
1631
|
+
| AddedReplicationSegmentMessage;
|
|
1357
1632
|
|
|
1358
1633
|
// we have this statement because peers might have changed/announced their role,
|
|
1359
1634
|
// but we don't know them as "subscribers" yet. i.e. they are not online
|
|
@@ -1363,30 +1638,30 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
1363
1638
|
timeout: this.waitForReplicatorTimeout,
|
|
1364
1639
|
})
|
|
1365
1640
|
.then(async () => {
|
|
1366
|
-
//
|
|
1367
|
-
|
|
1641
|
+
// do use an operation log here, because we want to make sure that we don't miss any updates
|
|
1642
|
+
// and do them in the right order
|
|
1643
|
+
const prev = this.latestReplicationInfoMessage.get(
|
|
1644
|
+
context.from!.hashcode(),
|
|
1645
|
+
);
|
|
1646
|
+
|
|
1368
1647
|
if (prev && prev > context.timestamp) {
|
|
1369
1648
|
return;
|
|
1370
1649
|
}
|
|
1371
|
-
this.
|
|
1650
|
+
this.latestReplicationInfoMessage.set(
|
|
1372
1651
|
context.from!.hashcode(),
|
|
1373
1652
|
context.timestamp,
|
|
1374
1653
|
);
|
|
1375
1654
|
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
addedOnce = addedOnce || added;
|
|
1387
|
-
}
|
|
1388
|
-
}
|
|
1389
|
-
addedOnce && (await this.distribute());
|
|
1655
|
+
let reset = msg instanceof AllReplicatingSegmentsMessage;
|
|
1656
|
+
const added = await this.addReplicationRange(
|
|
1657
|
+
replicationInfoMessage.segments.map((x) =>
|
|
1658
|
+
x.toReplicationRangeIndexable(context.from!),
|
|
1659
|
+
),
|
|
1660
|
+
context.from!,
|
|
1661
|
+
{ reset, checkDuplicates: true },
|
|
1662
|
+
);
|
|
1663
|
+
|
|
1664
|
+
added && (await this.distribute());
|
|
1390
1665
|
|
|
1391
1666
|
/* await this._modifyReplicators(msg.role, context.from!); */
|
|
1392
1667
|
})
|
|
@@ -1452,7 +1727,7 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
1452
1727
|
return ranges.results.map((x) => x.value);
|
|
1453
1728
|
}
|
|
1454
1729
|
|
|
1455
|
-
async
|
|
1730
|
+
async getMyTotalParticipation() {
|
|
1456
1731
|
// sum all of my replicator rects
|
|
1457
1732
|
return (await this.getMyReplicationSegments()).reduce(
|
|
1458
1733
|
(acc, { widthNormalized }) => acc + widthNormalized,
|
|
@@ -1514,8 +1789,96 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
1514
1789
|
});
|
|
1515
1790
|
}
|
|
1516
1791
|
|
|
1792
|
+
async join(
|
|
1793
|
+
entries: (string | Entry<T> | ShallowEntry)[],
|
|
1794
|
+
options?: {
|
|
1795
|
+
verifySignatures?: boolean;
|
|
1796
|
+
timeout?: number;
|
|
1797
|
+
replicate?: boolean;
|
|
1798
|
+
},
|
|
1799
|
+
): Promise<void> {
|
|
1800
|
+
let messageToSend: AddedReplicationSegmentMessage | undefined = undefined;
|
|
1801
|
+
|
|
1802
|
+
if (options?.replicate) {
|
|
1803
|
+
// 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"
|
|
1804
|
+
|
|
1805
|
+
// check which entrise we already have but not are replicating, and replicate them
|
|
1806
|
+
let alreadyJoined: Entry<T>[] = [];
|
|
1807
|
+
for (const element of entries) {
|
|
1808
|
+
if (typeof element === "string") {
|
|
1809
|
+
const entry = await this.log.get(element);
|
|
1810
|
+
if (entry) {
|
|
1811
|
+
alreadyJoined.push(entry);
|
|
1812
|
+
}
|
|
1813
|
+
} else if (element instanceof Entry) {
|
|
1814
|
+
if (await this.log.has(element.hash)) {
|
|
1815
|
+
alreadyJoined.push(element);
|
|
1816
|
+
}
|
|
1817
|
+
} else {
|
|
1818
|
+
const entry = await this.log.get(element.hash);
|
|
1819
|
+
if (entry) {
|
|
1820
|
+
alreadyJoined.push(entry);
|
|
1821
|
+
}
|
|
1822
|
+
}
|
|
1823
|
+
}
|
|
1824
|
+
|
|
1825
|
+
// assume is heads
|
|
1826
|
+
await this.replicate(alreadyJoined, {
|
|
1827
|
+
checkDuplicates: true,
|
|
1828
|
+
announce: (msg) => {
|
|
1829
|
+
if (msg instanceof AllReplicatingSegmentsMessage) {
|
|
1830
|
+
throw new Error("Unexpected");
|
|
1831
|
+
}
|
|
1832
|
+
messageToSend = msg;
|
|
1833
|
+
},
|
|
1834
|
+
});
|
|
1835
|
+
}
|
|
1836
|
+
|
|
1837
|
+
let joinOptions = options?.replicate
|
|
1838
|
+
? {
|
|
1839
|
+
...options,
|
|
1840
|
+
onChange: async (change: Change<T>) => {
|
|
1841
|
+
if (change.added) {
|
|
1842
|
+
for (const entry of change.added) {
|
|
1843
|
+
if (entry.head) {
|
|
1844
|
+
await this.replicate(entry.entry, {
|
|
1845
|
+
checkDuplicates: true,
|
|
1846
|
+
|
|
1847
|
+
// we override the announce step here to make sure we announce all new replication info
|
|
1848
|
+
// in one large message instead
|
|
1849
|
+
announce: (msg) => {
|
|
1850
|
+
if (msg instanceof AllReplicatingSegmentsMessage) {
|
|
1851
|
+
throw new Error("Unexpected");
|
|
1852
|
+
}
|
|
1853
|
+
|
|
1854
|
+
if (messageToSend) {
|
|
1855
|
+
// merge segments to make it into one messages
|
|
1856
|
+
for (const segment of msg.segments) {
|
|
1857
|
+
messageToSend.segments.push(segment);
|
|
1858
|
+
}
|
|
1859
|
+
} else {
|
|
1860
|
+
messageToSend = msg;
|
|
1861
|
+
}
|
|
1862
|
+
},
|
|
1863
|
+
});
|
|
1864
|
+
}
|
|
1865
|
+
}
|
|
1866
|
+
}
|
|
1867
|
+
},
|
|
1868
|
+
}
|
|
1869
|
+
: options;
|
|
1870
|
+
|
|
1871
|
+
await this.log.join(entries, joinOptions);
|
|
1872
|
+
|
|
1873
|
+
if (messageToSend) {
|
|
1874
|
+
await this.rpc.send(messageToSend, {
|
|
1875
|
+
priority: 1,
|
|
1876
|
+
});
|
|
1877
|
+
}
|
|
1878
|
+
}
|
|
1879
|
+
|
|
1517
1880
|
async isLeader(
|
|
1518
|
-
|
|
1881
|
+
entry: ShallowOrFullEntry<any>,
|
|
1519
1882
|
numberOfLeaders: number,
|
|
1520
1883
|
options?: {
|
|
1521
1884
|
candidates?: string[];
|
|
@@ -1523,13 +1886,13 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
1523
1886
|
},
|
|
1524
1887
|
): Promise<boolean> {
|
|
1525
1888
|
const isLeader = (
|
|
1526
|
-
await this.findLeaders(
|
|
1889
|
+
await this.findLeaders(entry, numberOfLeaders, options)
|
|
1527
1890
|
).find((l) => l === this.node.identity.publicKey.hashcode());
|
|
1528
1891
|
return !!isLeader;
|
|
1529
1892
|
}
|
|
1530
1893
|
|
|
1531
1894
|
private async waitForIsLeader(
|
|
1532
|
-
|
|
1895
|
+
entry: ShallowOrFullEntry<T>,
|
|
1533
1896
|
numberOfLeaders: number,
|
|
1534
1897
|
timeout = this.waitForReplicatorTimeout,
|
|
1535
1898
|
): Promise<string[] | false> {
|
|
@@ -1550,7 +1913,7 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
1550
1913
|
}, timeout);
|
|
1551
1914
|
|
|
1552
1915
|
const check = () =>
|
|
1553
|
-
this.findLeaders(
|
|
1916
|
+
this.findLeaders(entry, numberOfLeaders).then((leaders) => {
|
|
1554
1917
|
const isLeader = leaders.find(
|
|
1555
1918
|
(l) => l === this.node.identity.publicKey.hashcode(),
|
|
1556
1919
|
);
|
|
@@ -1573,7 +1936,7 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
1573
1936
|
}
|
|
1574
1937
|
|
|
1575
1938
|
async findLeaders(
|
|
1576
|
-
|
|
1939
|
+
entry: ShallowOrFullEntry<any>,
|
|
1577
1940
|
numberOfLeaders: number,
|
|
1578
1941
|
options?: {
|
|
1579
1942
|
roleAge?: number;
|
|
@@ -1583,18 +1946,8 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
1583
1946
|
return [this.node.identity.publicKey.hashcode()]; // Assumption: if the store is closed, always assume we have responsibility over the data
|
|
1584
1947
|
}
|
|
1585
1948
|
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
// Convert this thing we wan't to distribute to 8 bytes so we get can convert it into a u64
|
|
1590
|
-
// modulus into an index
|
|
1591
|
-
const utf8writer = new BinaryWriter();
|
|
1592
|
-
utf8writer.string(subject.toString());
|
|
1593
|
-
const seed = await sha256(utf8writer.finalize());
|
|
1594
|
-
|
|
1595
|
-
// convert hash of slot to a number
|
|
1596
|
-
const cursor = hashToUniformNumber(seed); // bounded between 0 and 1
|
|
1597
|
-
return this.findLeadersFromUniformNumber(cursor, numberOfLeaders, options);
|
|
1949
|
+
const cursor = await this.domain.fromEntry(entry);
|
|
1950
|
+
return this.findLeadersFromU32(cursor, numberOfLeaders, options);
|
|
1598
1951
|
}
|
|
1599
1952
|
|
|
1600
1953
|
async getDefaultMinRoleAge(): Promise<number> {
|
|
@@ -1608,68 +1961,23 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
1608
1961
|
replLength > 1 ? now - this.oldestOpenTime - 1 : Number.MAX_SAFE_INTEGER;
|
|
1609
1962
|
return Math.min(
|
|
1610
1963
|
this.timeUntilRoleMaturity,
|
|
1611
|
-
diffToOldest,
|
|
1612
|
-
Math.
|
|
1964
|
+
Math.max(diffToOldest, this.timeUntilRoleMaturity),
|
|
1965
|
+
Math.max(
|
|
1966
|
+
Math.round((this.timeUntilRoleMaturity * Math.log(replLength + 1)) / 3),
|
|
1967
|
+
this.timeUntilRoleMaturity,
|
|
1968
|
+
),
|
|
1613
1969
|
); // / 3 so that if 2 replicators and timeUntilRoleMaturity = 1e4 the result will be 1
|
|
1614
1970
|
}
|
|
1615
1971
|
|
|
1616
|
-
private async
|
|
1617
|
-
cursor:
|
|
1972
|
+
private async findLeadersFromU32(
|
|
1973
|
+
cursor: u32,
|
|
1618
1974
|
numberOfLeaders: number,
|
|
1619
1975
|
options?: {
|
|
1620
1976
|
roleAge?: number;
|
|
1621
1977
|
},
|
|
1622
1978
|
) {
|
|
1623
1979
|
const roleAge = options?.roleAge ?? (await this.getDefaultMinRoleAge()); // TODO -500 as is added so that i f someone else is just as new as us, then we treat them as mature as us. without -500 we might be slower syncing if two nodes starts almost at the same time
|
|
1624
|
-
|
|
1625
|
-
const samples = await getSamples(
|
|
1626
|
-
cursor,
|
|
1627
|
-
this.replicationIndex,
|
|
1628
|
-
numberOfLeaders,
|
|
1629
|
-
roleAge,
|
|
1630
|
-
);
|
|
1631
|
-
|
|
1632
|
-
return samples;
|
|
1633
|
-
}
|
|
1634
|
-
|
|
1635
|
-
/**
|
|
1636
|
-
*
|
|
1637
|
-
* @returns groups where at least one in any group will have the entry you are looking for
|
|
1638
|
-
*/
|
|
1639
|
-
async getReplicatorUnion(roleAge?: number) {
|
|
1640
|
-
roleAge = roleAge ?? (await this.getDefaultMinRoleAge());
|
|
1641
|
-
if (this.closed === true) {
|
|
1642
|
-
throw new ClosedError();
|
|
1643
|
-
}
|
|
1644
|
-
|
|
1645
|
-
// Total replication "width"
|
|
1646
|
-
const width = 1;
|
|
1647
|
-
|
|
1648
|
-
// How much width you need to "query" to
|
|
1649
|
-
const peers = this.replicationIndex; // TODO types
|
|
1650
|
-
const minReplicas = Math.min(
|
|
1651
|
-
await peers.getSize(),
|
|
1652
|
-
this.replicas.min.getValue(this),
|
|
1653
|
-
);
|
|
1654
|
-
|
|
1655
|
-
// If min replicas = 2
|
|
1656
|
-
// then we need to make sure we cover 0.5 of the total 'width' of the replication space
|
|
1657
|
-
// to make sure we reach sufficient amount of nodes such that at least one one has
|
|
1658
|
-
// the entry we are looking for
|
|
1659
|
-
const coveringWidth = width / minReplicas;
|
|
1660
|
-
|
|
1661
|
-
const set = await getCoverSet(
|
|
1662
|
-
coveringWidth,
|
|
1663
|
-
peers,
|
|
1664
|
-
roleAge,
|
|
1665
|
-
this.node.identity.publicKey,
|
|
1666
|
-
);
|
|
1667
|
-
|
|
1668
|
-
// add all in flight
|
|
1669
|
-
for (const [key, _] of this.syncInFlight) {
|
|
1670
|
-
set.add(key);
|
|
1671
|
-
}
|
|
1672
|
-
return [...set];
|
|
1980
|
+
return getSamples(cursor, this.replicationIndex, numberOfLeaders, roleAge);
|
|
1673
1981
|
}
|
|
1674
1982
|
|
|
1675
1983
|
async isReplicator(
|
|
@@ -1679,11 +1987,7 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
1679
1987
|
roleAge?: number;
|
|
1680
1988
|
},
|
|
1681
1989
|
) {
|
|
1682
|
-
return this.isLeader(
|
|
1683
|
-
entry.meta.gid,
|
|
1684
|
-
decodeReplicas(entry).getValue(this),
|
|
1685
|
-
options,
|
|
1686
|
-
);
|
|
1990
|
+
return this.isLeader(entry, decodeReplicas(entry).getValue(this), options);
|
|
1687
1991
|
}
|
|
1688
1992
|
|
|
1689
1993
|
async handleSubscriptionChange(
|
|
@@ -1724,7 +2028,7 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
1724
2028
|
if (replicationSegments.length > 0) {
|
|
1725
2029
|
this.rpc
|
|
1726
2030
|
.send(
|
|
1727
|
-
new
|
|
2031
|
+
new AllReplicatingSegmentsMessage({
|
|
1728
2032
|
segments: replicationSegments.map((x) => x.toReplicationRange()),
|
|
1729
2033
|
}),
|
|
1730
2034
|
{
|
|
@@ -1736,7 +2040,7 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
1736
2040
|
if (this.v8Behaviour) {
|
|
1737
2041
|
// for backwards compatibility
|
|
1738
2042
|
this.rpc
|
|
1739
|
-
.send(new ResponseRoleMessage({ role: this.getRole() }), {
|
|
2043
|
+
.send(new ResponseRoleMessage({ role: await this.getRole() }), {
|
|
1740
2044
|
mode: new SilentDelivery({ redundancy: 1, to: [publicKey] }),
|
|
1741
2045
|
})
|
|
1742
2046
|
.catch((e) => logger.error(e.toString()));
|
|
@@ -1821,10 +2125,7 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
1821
2125
|
? Math.min(minReplicasValue, this.replicas.max.getValue(this))
|
|
1822
2126
|
: minReplicasValue;
|
|
1823
2127
|
|
|
1824
|
-
const leaders = await this.findLeaders(
|
|
1825
|
-
entry.meta.gid,
|
|
1826
|
-
minMinReplicasValue,
|
|
1827
|
-
);
|
|
2128
|
+
const leaders = await this.findLeaders(entry, minMinReplicasValue);
|
|
1828
2129
|
|
|
1829
2130
|
if (
|
|
1830
2131
|
leaders.find((x) => x === this.node.identity.publicKey.hashcode())
|
|
@@ -1937,7 +2238,7 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
1937
2238
|
|
|
1938
2239
|
const oldPeersSet = this._gidPeersHistory.get(gid);
|
|
1939
2240
|
const currentPeers = await this.findLeaders(
|
|
1940
|
-
|
|
2241
|
+
getLatestEntry(entries)!,
|
|
1941
2242
|
maxReplicas(this, entries), // pick max replication policy of all entries, so all information is treated equally important as the most important
|
|
1942
2243
|
);
|
|
1943
2244
|
|
|
@@ -2037,8 +2338,9 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
2037
2338
|
evt.detail.unsubscriptions.map((x) => x),
|
|
2038
2339
|
)}'`,
|
|
2039
2340
|
);
|
|
2040
|
-
this.
|
|
2341
|
+
this.latestReplicationInfoMessage.delete(evt.detail.from.hashcode());
|
|
2041
2342
|
|
|
2343
|
+
// TODO only emit this if the peer is actually replicating anything
|
|
2042
2344
|
this.events.dispatchEvent(
|
|
2043
2345
|
new CustomEvent<ReplicatorLeaveEvent>("replicator:leave", {
|
|
2044
2346
|
detail: { publicKey: evt.detail.from },
|
|
@@ -2107,11 +2409,11 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
2107
2409
|
}
|
|
2108
2410
|
|
|
2109
2411
|
// The role is fixed (no changes depending on memory usage or peer count etc)
|
|
2110
|
-
if (!this.
|
|
2412
|
+
if (!this._isReplicating) {
|
|
2111
2413
|
return false;
|
|
2112
2414
|
}
|
|
2113
2415
|
|
|
2114
|
-
if (
|
|
2416
|
+
if (this._isAdaptiveReplicating) {
|
|
2115
2417
|
const peers = this.replicationIndex;
|
|
2116
2418
|
const usedMemory = await this.getMemoryUsage();
|
|
2117
2419
|
let dynamicRange = await this.getDynamicRange();
|
|
@@ -2121,10 +2423,12 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
2121
2423
|
}
|
|
2122
2424
|
|
|
2123
2425
|
const peersSize = (await peers.getSize()) || 1;
|
|
2426
|
+
const totalParticipation = await this.calculateTotalParticipation();
|
|
2427
|
+
|
|
2124
2428
|
const newFactor = this.replicationController.step({
|
|
2125
2429
|
memoryUsage: usedMemory,
|
|
2126
2430
|
currentFactor: dynamicRange.widthNormalized,
|
|
2127
|
-
totalFactor:
|
|
2431
|
+
totalFactor: totalParticipation, // TODO use this._totalParticipation when flakiness is fixed
|
|
2128
2432
|
peerCount: peersSize,
|
|
2129
2433
|
cpuUsage: this.cpuUsage?.value(),
|
|
2130
2434
|
});
|
|
@@ -2136,11 +2440,11 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
2136
2440
|
if (relativeDifference > 0.0001) {
|
|
2137
2441
|
// TODO can not reuse old range, since it will (potentially) affect the index because of sideeffects
|
|
2138
2442
|
dynamicRange = new ReplicationRangeIndexable({
|
|
2139
|
-
offset:
|
|
2140
|
-
length: newFactor,
|
|
2443
|
+
offset: hashToU32(this.node.identity.publicKey.bytes),
|
|
2444
|
+
length: scaleToU32(newFactor),
|
|
2141
2445
|
publicKeyHash: dynamicRange.hash,
|
|
2142
2446
|
id: dynamicRange.id,
|
|
2143
|
-
|
|
2447
|
+
mode: dynamicRange.mode,
|
|
2144
2448
|
timestamp: dynamicRange.timestamp,
|
|
2145
2449
|
});
|
|
2146
2450
|
|
|
@@ -2151,7 +2455,10 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
2151
2455
|
return false;
|
|
2152
2456
|
}
|
|
2153
2457
|
|
|
2154
|
-
await this.startAnnounceReplicating(dynamicRange
|
|
2458
|
+
await this.startAnnounceReplicating([dynamicRange], {
|
|
2459
|
+
checkDuplicates: false,
|
|
2460
|
+
reset: false,
|
|
2461
|
+
});
|
|
2155
2462
|
|
|
2156
2463
|
/* await this._updateRole(newRole, onRoleChange); */
|
|
2157
2464
|
this.rebalanceParticipationDebounced?.();
|
|
@@ -2165,18 +2472,19 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
2165
2472
|
return false;
|
|
2166
2473
|
}
|
|
2167
2474
|
async getDynamicRange() {
|
|
2475
|
+
let dynamicRangeId = sha256Sync(
|
|
2476
|
+
concat([
|
|
2477
|
+
this.node.identity.publicKey.bytes,
|
|
2478
|
+
new TextEncoder().encode("dynamic"),
|
|
2479
|
+
]),
|
|
2480
|
+
);
|
|
2168
2481
|
let range = (
|
|
2169
2482
|
await this.replicationIndex.query(
|
|
2170
2483
|
new SearchRequest({
|
|
2171
2484
|
query: [
|
|
2172
|
-
new
|
|
2173
|
-
key: "
|
|
2174
|
-
value:
|
|
2175
|
-
}),
|
|
2176
|
-
new IntegerCompare({
|
|
2177
|
-
key: "replicationIntent",
|
|
2178
|
-
value: ReplicationIntent.Automatic,
|
|
2179
|
-
compare: "eq",
|
|
2485
|
+
new ByteMatchQuery({
|
|
2486
|
+
key: "id",
|
|
2487
|
+
value: dynamicRangeId,
|
|
2180
2488
|
}),
|
|
2181
2489
|
],
|
|
2182
2490
|
fetch: 1,
|
|
@@ -2184,18 +2492,19 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
2184
2492
|
)
|
|
2185
2493
|
)?.results[0]?.value;
|
|
2186
2494
|
if (!range) {
|
|
2187
|
-
let seed = Math.random();
|
|
2188
2495
|
range = new ReplicationRangeIndexable({
|
|
2189
|
-
|
|
2496
|
+
normalized: true,
|
|
2497
|
+
offset: Math.random(),
|
|
2190
2498
|
length: 0,
|
|
2191
2499
|
publicKeyHash: this.node.identity.publicKey.hashcode(),
|
|
2192
|
-
|
|
2500
|
+
mode: ReplicationIntent.NonStrict,
|
|
2193
2501
|
timestamp: BigInt(+new Date()),
|
|
2194
|
-
id:
|
|
2502
|
+
id: dynamicRangeId,
|
|
2195
2503
|
});
|
|
2196
2504
|
const added = await this.addReplicationRange(
|
|
2197
|
-
range,
|
|
2505
|
+
[range],
|
|
2198
2506
|
this.node.identity.publicKey,
|
|
2507
|
+
{ reset: false, checkDuplicates: false },
|
|
2199
2508
|
);
|
|
2200
2509
|
if (!added) {
|
|
2201
2510
|
logger.warn("Not allowed to replicate by canReplicate");
|