@peerbit/shared-log 9.0.9 → 9.0.10-ccaf4f4
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 +46 -32
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +432 -231
- 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 +13 -3
- package/dist/src/ranges.d.ts.map +1 -1
- package/dist/src/ranges.js +207 -335
- 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 +609 -317
- package/src/pid.ts +20 -19
- package/src/ranges.ts +291 -371
- 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,9 +251,11 @@ 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
|
-
private _totalParticipation!: number;
|
|
258
|
+
/* private _totalParticipation!: number; */
|
|
201
259
|
private _gidPeersHistory!: Map<string, Set<string>>;
|
|
202
260
|
|
|
203
261
|
private _onSubscriptionFn!: (arg: any) => any;
|
|
@@ -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
|
-
|
|
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[] = [];
|
|
438
|
+
|
|
439
|
+
if (options == null) {
|
|
440
|
+
options = {};
|
|
441
|
+
} else if (options === true) {
|
|
442
|
+
options = {};
|
|
443
|
+
}
|
|
383
444
|
|
|
384
|
-
this.
|
|
385
|
-
|
|
386
|
-
? options?.limits?.cpu?.monitor || new CPUUsageIntervalLag()
|
|
387
|
-
: new CPUUsageIntervalLag();
|
|
388
|
-
this.cpuUsage?.start?.();
|
|
445
|
+
this._isReplicating = true;
|
|
446
|
+
this._isAdaptiveReplicating = false;
|
|
389
447
|
|
|
390
|
-
|
|
391
|
-
|
|
448
|
+
if (isAdaptiveReplicatorOption(options!)) {
|
|
449
|
+
this._isAdaptiveReplicating = true;
|
|
450
|
+
this.setupDebouncedRebalancing(options);
|
|
392
451
|
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
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
|
|
527
|
+
}
|
|
528
|
+
await this.startAnnounceReplicating(ranges, {
|
|
529
|
+
reset: resetRanges ?? false,
|
|
530
|
+
checkDuplicates,
|
|
531
|
+
announce,
|
|
532
|
+
});
|
|
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
|
+
}
|
|
453
601
|
}
|
|
602
|
+
range = ranges;
|
|
603
|
+
} else {
|
|
604
|
+
range = rangeOrEntry ?? true;
|
|
454
605
|
}
|
|
455
606
|
|
|
607
|
+
const newRanges = await this._replicate(range, options);
|
|
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
|
-
|
|
637
|
+
const indexed = await this.replicationIndex.query(
|
|
638
|
+
new SearchRequest({
|
|
639
|
+
query: {
|
|
640
|
+
width: 1,
|
|
641
|
+
start1: range.offset,
|
|
642
|
+
},
|
|
643
|
+
}),
|
|
644
|
+
);
|
|
479
645
|
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
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
|
+
}
|
|
485
652
|
|
|
486
|
-
|
|
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,26 +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
|
-
|
|
588
|
-
|
|
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
|
+
}
|
|
589
778
|
}
|
|
590
779
|
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
Number(range.timestamp),
|
|
595
|
-
this.oldestOpenTime,
|
|
596
|
-
);
|
|
780
|
+
if (reset) {
|
|
781
|
+
await this.updateOldestTimestampFromIndex();
|
|
782
|
+
}
|
|
597
783
|
|
|
598
784
|
this.events.dispatchEvent(
|
|
599
785
|
new CustomEvent<ReplicationChange>("replication:change", {
|
|
@@ -614,25 +800,50 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
614
800
|
}
|
|
615
801
|
return true;
|
|
616
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
|
|
617
806
|
return this.pq.add(fn);
|
|
618
807
|
}
|
|
619
808
|
|
|
620
|
-
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
|
+
) {
|
|
621
819
|
const added = await this.addReplicationRange(
|
|
622
820
|
range,
|
|
623
821
|
this.node.identity.publicKey,
|
|
822
|
+
options,
|
|
624
823
|
);
|
|
625
824
|
if (!added) {
|
|
626
825
|
logger.warn("Not allowed to replicate by canReplicate");
|
|
627
826
|
}
|
|
628
827
|
|
|
828
|
+
let message: AllReplicatingSegmentsMessage | AddedReplicationSegmentMessage;
|
|
629
829
|
if (added) {
|
|
630
|
-
|
|
631
|
-
new
|
|
632
|
-
|
|
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, {
|
|
633
844
|
priority: 1,
|
|
634
|
-
}
|
|
635
|
-
|
|
845
|
+
});
|
|
846
|
+
}
|
|
636
847
|
}
|
|
637
848
|
}
|
|
638
849
|
|
|
@@ -661,7 +872,9 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
661
872
|
}
|
|
662
873
|
if (options?.canAppend) {
|
|
663
874
|
appendOptions.canAppend = async (entry) => {
|
|
664
|
-
await this.canAppend(entry)
|
|
875
|
+
if (!(await this.canAppend(entry))) {
|
|
876
|
+
return false;
|
|
877
|
+
}
|
|
665
878
|
return options.canAppend!(entry);
|
|
666
879
|
};
|
|
667
880
|
}
|
|
@@ -676,6 +889,10 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
676
889
|
const result = await this.log.append(data, appendOptions);
|
|
677
890
|
let mode: DeliveryMode | undefined = undefined;
|
|
678
891
|
|
|
892
|
+
if (options?.replicate) {
|
|
893
|
+
await this.replicate(result.entry, { checkDuplicates: true });
|
|
894
|
+
}
|
|
895
|
+
|
|
679
896
|
for (const message of await createExchangeHeadsMessages(
|
|
680
897
|
this.log,
|
|
681
898
|
[result.entry],
|
|
@@ -683,22 +900,29 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
683
900
|
)) {
|
|
684
901
|
if (options?.target === "replicators" || !options?.target) {
|
|
685
902
|
const minReplicas = decodeReplicas(result.entry).getValue(this);
|
|
903
|
+
|
|
686
904
|
let leaders: string[] | Set<string> = await this.findLeaders(
|
|
687
|
-
result.entry
|
|
905
|
+
result.entry,
|
|
688
906
|
minReplicas,
|
|
689
907
|
);
|
|
908
|
+
|
|
690
909
|
const isLeader = leaders.includes(
|
|
691
910
|
this.node.identity.publicKey.hashcode(),
|
|
692
911
|
);
|
|
912
|
+
|
|
693
913
|
if (message.heads[0].gidRefrences.length > 0) {
|
|
694
914
|
const newAndOldLeaders = new Set(leaders);
|
|
695
915
|
for (const ref of message.heads[0].gidRefrences) {
|
|
696
|
-
|
|
697
|
-
|
|
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
|
+
}
|
|
698
921
|
}
|
|
699
922
|
}
|
|
700
923
|
leaders = newAndOldLeaders;
|
|
701
924
|
}
|
|
925
|
+
|
|
702
926
|
let set = this._gidPeersHistory.get(result.entry.meta.gid);
|
|
703
927
|
if (!set) {
|
|
704
928
|
set = new Set(leaders);
|
|
@@ -723,7 +947,7 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
723
947
|
return result;
|
|
724
948
|
}
|
|
725
949
|
|
|
726
|
-
async open(options?: Args<T>): Promise<void> {
|
|
950
|
+
async open(options?: Args<T, D>): Promise<void> {
|
|
727
951
|
this.replicas = {
|
|
728
952
|
min: options?.replicas?.min
|
|
729
953
|
? typeof options?.replicas?.min === "number"
|
|
@@ -736,11 +960,11 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
736
960
|
: options.replicas.max
|
|
737
961
|
: undefined,
|
|
738
962
|
};
|
|
739
|
-
|
|
963
|
+
this.domain = options?.domain ?? (createReplicationDomainHash() as D);
|
|
740
964
|
this._respondToIHaveTimeout = options?.respondToIHaveTimeout ?? 10 * 1000; // TODO make into arg
|
|
741
965
|
this._pendingDeletes = new Map();
|
|
742
966
|
this._pendingIHave = new Map();
|
|
743
|
-
this.
|
|
967
|
+
this.latestReplicationInfoMessage = new Map();
|
|
744
968
|
this.syncInFlightQueue = new Map();
|
|
745
969
|
this.syncInFlightQueueInverted = new Map();
|
|
746
970
|
this.syncInFlight = new Map();
|
|
@@ -778,16 +1002,21 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
778
1002
|
|
|
779
1003
|
await this.remoteBlocks.start();
|
|
780
1004
|
|
|
781
|
-
this._totalParticipation = 0;
|
|
1005
|
+
/* this._totalParticipation = 0; */
|
|
782
1006
|
const logScope = await this.node.indexer.scope(id);
|
|
783
1007
|
const replicationIndex = await logScope.scope("replication");
|
|
784
1008
|
this._replicationRangeIndex = await replicationIndex.init({
|
|
785
1009
|
schema: ReplicationRangeIndexable,
|
|
786
1010
|
});
|
|
1011
|
+
|
|
787
1012
|
const logIndex = await logScope.scope("log");
|
|
1013
|
+
|
|
788
1014
|
await this.node.indexer.start(); // TODO why do we need to start the indexer here?
|
|
789
1015
|
|
|
790
|
-
|
|
1016
|
+
const hasIndexedReplicationInfo =
|
|
1017
|
+
(await this.replicationIndex.getSize()) > 0;
|
|
1018
|
+
|
|
1019
|
+
/* this._totalParticipation = await this.calculateTotalParticipation(); */
|
|
791
1020
|
|
|
792
1021
|
this._gidPeersHistory = new Map();
|
|
793
1022
|
|
|
@@ -832,6 +1061,8 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
832
1061
|
this._onUnsubscriptionFn,
|
|
833
1062
|
);
|
|
834
1063
|
|
|
1064
|
+
await this.rpc.subscribe();
|
|
1065
|
+
|
|
835
1066
|
// await this.log.load();
|
|
836
1067
|
|
|
837
1068
|
// TODO (do better)
|
|
@@ -886,7 +1117,15 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
886
1117
|
});
|
|
887
1118
|
};
|
|
888
1119
|
|
|
889
|
-
|
|
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
|
+
}
|
|
890
1129
|
requestSync();
|
|
891
1130
|
}
|
|
892
1131
|
|
|
@@ -923,7 +1162,7 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
923
1162
|
|
|
924
1163
|
async onChange(change: Change<T>) {
|
|
925
1164
|
for (const added of change.added) {
|
|
926
|
-
this.onEntryAdded(added);
|
|
1165
|
+
this.onEntryAdded(added.entry);
|
|
927
1166
|
}
|
|
928
1167
|
for (const removed of change.removed) {
|
|
929
1168
|
this.onEntryRemoved(removed.hash);
|
|
@@ -955,6 +1194,28 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
955
1194
|
}
|
|
956
1195
|
}
|
|
957
1196
|
|
|
1197
|
+
async getCover(args: ExtractDomainArgs<D>, roleAge?: number) {
|
|
1198
|
+
roleAge = roleAge ?? (await this.getDefaultMinRoleAge());
|
|
1199
|
+
|
|
1200
|
+
const range = await this.domain.fromArgs(args, this);
|
|
1201
|
+
|
|
1202
|
+
const set = await getCoverSet(
|
|
1203
|
+
this.replicationIndex,
|
|
1204
|
+
roleAge,
|
|
1205
|
+
range.offset,
|
|
1206
|
+
range.length ??
|
|
1207
|
+
(await minimumWidthToCover(this.replicas.min.getValue(this))),
|
|
1208
|
+
MAX_U32,
|
|
1209
|
+
);
|
|
1210
|
+
|
|
1211
|
+
// add all in flight
|
|
1212
|
+
for (const [key, _] of this.syncInFlight) {
|
|
1213
|
+
set.add(key);
|
|
1214
|
+
}
|
|
1215
|
+
|
|
1216
|
+
return [...set];
|
|
1217
|
+
}
|
|
1218
|
+
|
|
958
1219
|
private async _close() {
|
|
959
1220
|
clearTimeout(this.syncMoreInterval);
|
|
960
1221
|
clearInterval(this.distributeInterval);
|
|
@@ -987,12 +1248,12 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
987
1248
|
this.syncInFlightQueue.clear();
|
|
988
1249
|
this.syncInFlightQueueInverted.clear();
|
|
989
1250
|
this.syncInFlight.clear();
|
|
990
|
-
this.
|
|
1251
|
+
this.latestReplicationInfoMessage.clear();
|
|
991
1252
|
this._gidPeersHistory.clear();
|
|
992
1253
|
|
|
993
1254
|
this._replicationRangeIndex = undefined as any;
|
|
994
1255
|
this.cpuUsage?.stop?.();
|
|
995
|
-
this._totalParticipation = 0;
|
|
1256
|
+
/* this._totalParticipation = 0; */
|
|
996
1257
|
this.pq.clear();
|
|
997
1258
|
}
|
|
998
1259
|
async close(from?: Program): Promise<boolean> {
|
|
@@ -1076,6 +1337,7 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
1076
1337
|
const headsWithGid = await this.log.entryIndex
|
|
1077
1338
|
.getHeads(gid)
|
|
1078
1339
|
.all();
|
|
1340
|
+
const latestEntry = getLatestEntry(entries)!;
|
|
1079
1341
|
|
|
1080
1342
|
const maxReplicasFromHead =
|
|
1081
1343
|
headsWithGid && headsWithGid.length > 0
|
|
@@ -1093,12 +1355,12 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
1093
1355
|
|
|
1094
1356
|
if (isReplicating) {
|
|
1095
1357
|
isLeader = await this.waitForIsLeader(
|
|
1096
|
-
|
|
1358
|
+
latestEntry,
|
|
1097
1359
|
Math.max(maxReplicasFromHead, maxReplicasFromNewEntries),
|
|
1098
1360
|
);
|
|
1099
1361
|
} else {
|
|
1100
1362
|
isLeader = await this.findLeaders(
|
|
1101
|
-
|
|
1363
|
+
latestEntry,
|
|
1102
1364
|
Math.max(maxReplicasFromHead, maxReplicasFromNewEntries),
|
|
1103
1365
|
);
|
|
1104
1366
|
|
|
@@ -1182,7 +1444,7 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
1182
1444
|
const minReplicas = maxReplicas(this, headsWithGid.values());
|
|
1183
1445
|
|
|
1184
1446
|
const isLeader = await this.isLeader(
|
|
1185
|
-
entries[0].entry
|
|
1447
|
+
entries[0].entry,
|
|
1186
1448
|
minReplicas,
|
|
1187
1449
|
);
|
|
1188
1450
|
|
|
@@ -1204,7 +1466,7 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
1204
1466
|
if (
|
|
1205
1467
|
indexedEntry &&
|
|
1206
1468
|
(await this.isLeader(
|
|
1207
|
-
indexedEntry.value
|
|
1469
|
+
indexedEntry.value,
|
|
1208
1470
|
decodeReplicas(indexedEntry.value).getValue(this),
|
|
1209
1471
|
))
|
|
1210
1472
|
) {
|
|
@@ -1222,7 +1484,7 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
1222
1484
|
callback: async (entry: any) => {
|
|
1223
1485
|
if (
|
|
1224
1486
|
await this.isLeader(
|
|
1225
|
-
entry
|
|
1487
|
+
entry,
|
|
1226
1488
|
decodeReplicas(entry).getValue(this),
|
|
1227
1489
|
)
|
|
1228
1490
|
) {
|
|
@@ -1314,7 +1576,7 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
1314
1576
|
return;
|
|
1315
1577
|
}
|
|
1316
1578
|
await this.rpc.send(
|
|
1317
|
-
new
|
|
1579
|
+
new AllReplicatingSegmentsMessage({
|
|
1318
1580
|
segments: (await this.getMyReplicationSegments()).map((x) =>
|
|
1319
1581
|
x.toReplicationRange(),
|
|
1320
1582
|
),
|
|
@@ -1328,9 +1590,8 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
1328
1590
|
if (this.v8Behaviour) {
|
|
1329
1591
|
const role = this.getRole();
|
|
1330
1592
|
if (role instanceof Replicator) {
|
|
1331
|
-
const fixedSettings = this
|
|
1332
|
-
|
|
1333
|
-
if (fixedSettings.factor === 1) {
|
|
1593
|
+
const fixedSettings = !this._isAdaptiveReplicating;
|
|
1594
|
+
if (fixedSettings) {
|
|
1334
1595
|
await this.rpc.send(
|
|
1335
1596
|
new ResponseRoleMessage({
|
|
1336
1597
|
role,
|
|
@@ -1346,16 +1607,16 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
1346
1607
|
}
|
|
1347
1608
|
}
|
|
1348
1609
|
} else if (
|
|
1349
|
-
msg instanceof
|
|
1350
|
-
msg instanceof
|
|
1610
|
+
msg instanceof AllReplicatingSegmentsMessage ||
|
|
1611
|
+
msg instanceof AddedReplicationSegmentMessage
|
|
1351
1612
|
) {
|
|
1352
1613
|
if (context.from.equals(this.node.identity.publicKey)) {
|
|
1353
1614
|
return;
|
|
1354
1615
|
}
|
|
1355
1616
|
|
|
1356
1617
|
let replicationInfoMessage = msg as
|
|
1357
|
-
|
|
|
1358
|
-
|
|
|
1618
|
+
| AllReplicatingSegmentsMessage
|
|
1619
|
+
| AddedReplicationSegmentMessage;
|
|
1359
1620
|
|
|
1360
1621
|
// we have this statement because peers might have changed/announced their role,
|
|
1361
1622
|
// but we don't know them as "subscribers" yet. i.e. they are not online
|
|
@@ -1365,30 +1626,30 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
1365
1626
|
timeout: this.waitForReplicatorTimeout,
|
|
1366
1627
|
})
|
|
1367
1628
|
.then(async () => {
|
|
1368
|
-
//
|
|
1369
|
-
|
|
1629
|
+
// do use an operation log here, because we want to make sure that we don't miss any updates
|
|
1630
|
+
// and do them in the right order
|
|
1631
|
+
const prev = this.latestReplicationInfoMessage.get(
|
|
1632
|
+
context.from!.hashcode(),
|
|
1633
|
+
);
|
|
1634
|
+
|
|
1370
1635
|
if (prev && prev > context.timestamp) {
|
|
1371
1636
|
return;
|
|
1372
1637
|
}
|
|
1373
|
-
this.
|
|
1638
|
+
this.latestReplicationInfoMessage.set(
|
|
1374
1639
|
context.from!.hashcode(),
|
|
1375
1640
|
context.timestamp,
|
|
1376
1641
|
);
|
|
1377
1642
|
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
addedOnce = addedOnce || added;
|
|
1389
|
-
}
|
|
1390
|
-
}
|
|
1391
|
-
addedOnce && (await this.distribute());
|
|
1643
|
+
let reset = msg instanceof AllReplicatingSegmentsMessage;
|
|
1644
|
+
const added = await this.addReplicationRange(
|
|
1645
|
+
replicationInfoMessage.segments.map((x) =>
|
|
1646
|
+
x.toReplicationRangeIndexable(context.from!),
|
|
1647
|
+
),
|
|
1648
|
+
context.from!,
|
|
1649
|
+
{ reset, checkDuplicates: true },
|
|
1650
|
+
);
|
|
1651
|
+
|
|
1652
|
+
added && (await this.distribute());
|
|
1392
1653
|
|
|
1393
1654
|
/* await this._modifyReplicators(msg.role, context.from!); */
|
|
1394
1655
|
})
|
|
@@ -1454,7 +1715,7 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
1454
1715
|
return ranges.results.map((x) => x.value);
|
|
1455
1716
|
}
|
|
1456
1717
|
|
|
1457
|
-
async
|
|
1718
|
+
async getMyTotalParticipation() {
|
|
1458
1719
|
// sum all of my replicator rects
|
|
1459
1720
|
return (await this.getMyReplicationSegments()).reduce(
|
|
1460
1721
|
(acc, { widthNormalized }) => acc + widthNormalized,
|
|
@@ -1516,8 +1777,96 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
1516
1777
|
});
|
|
1517
1778
|
}
|
|
1518
1779
|
|
|
1780
|
+
async join(
|
|
1781
|
+
entries: (string | Entry<T> | ShallowEntry)[],
|
|
1782
|
+
options?: {
|
|
1783
|
+
verifySignatures?: boolean;
|
|
1784
|
+
timeout?: number;
|
|
1785
|
+
replicate?: boolean;
|
|
1786
|
+
},
|
|
1787
|
+
): Promise<void> {
|
|
1788
|
+
let messageToSend: AddedReplicationSegmentMessage | undefined = undefined;
|
|
1789
|
+
|
|
1790
|
+
if (options?.replicate) {
|
|
1791
|
+
// 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"
|
|
1792
|
+
|
|
1793
|
+
// check which entrise we already have but not are replicating, and replicate them
|
|
1794
|
+
let alreadyJoined: Entry<T>[] = [];
|
|
1795
|
+
for (const element of entries) {
|
|
1796
|
+
if (typeof element === "string") {
|
|
1797
|
+
const entry = await this.log.get(element);
|
|
1798
|
+
if (entry) {
|
|
1799
|
+
alreadyJoined.push(entry);
|
|
1800
|
+
}
|
|
1801
|
+
} else if (element instanceof Entry) {
|
|
1802
|
+
if (await this.log.has(element.hash)) {
|
|
1803
|
+
alreadyJoined.push(element);
|
|
1804
|
+
}
|
|
1805
|
+
} else {
|
|
1806
|
+
const entry = await this.log.get(element.hash);
|
|
1807
|
+
if (entry) {
|
|
1808
|
+
alreadyJoined.push(entry);
|
|
1809
|
+
}
|
|
1810
|
+
}
|
|
1811
|
+
}
|
|
1812
|
+
|
|
1813
|
+
// assume is heads
|
|
1814
|
+
await this.replicate(alreadyJoined, {
|
|
1815
|
+
checkDuplicates: true,
|
|
1816
|
+
announce: (msg) => {
|
|
1817
|
+
if (msg instanceof AllReplicatingSegmentsMessage) {
|
|
1818
|
+
throw new Error("Unexpected");
|
|
1819
|
+
}
|
|
1820
|
+
messageToSend = msg;
|
|
1821
|
+
},
|
|
1822
|
+
});
|
|
1823
|
+
}
|
|
1824
|
+
|
|
1825
|
+
let joinOptions = options?.replicate
|
|
1826
|
+
? {
|
|
1827
|
+
...options,
|
|
1828
|
+
onChange: async (change: Change<T>) => {
|
|
1829
|
+
if (change.added) {
|
|
1830
|
+
for (const entry of change.added) {
|
|
1831
|
+
if (entry.head) {
|
|
1832
|
+
await this.replicate(entry.entry, {
|
|
1833
|
+
checkDuplicates: true,
|
|
1834
|
+
|
|
1835
|
+
// we override the announce step here to make sure we announce all new replication info
|
|
1836
|
+
// in one large message instead
|
|
1837
|
+
announce: (msg) => {
|
|
1838
|
+
if (msg instanceof AllReplicatingSegmentsMessage) {
|
|
1839
|
+
throw new Error("Unexpected");
|
|
1840
|
+
}
|
|
1841
|
+
|
|
1842
|
+
if (messageToSend) {
|
|
1843
|
+
// merge segments to make it into one messages
|
|
1844
|
+
for (const segment of msg.segments) {
|
|
1845
|
+
messageToSend.segments.push(segment);
|
|
1846
|
+
}
|
|
1847
|
+
} else {
|
|
1848
|
+
messageToSend = msg;
|
|
1849
|
+
}
|
|
1850
|
+
},
|
|
1851
|
+
});
|
|
1852
|
+
}
|
|
1853
|
+
}
|
|
1854
|
+
}
|
|
1855
|
+
},
|
|
1856
|
+
}
|
|
1857
|
+
: options;
|
|
1858
|
+
|
|
1859
|
+
await this.log.join(entries, joinOptions);
|
|
1860
|
+
|
|
1861
|
+
if (messageToSend) {
|
|
1862
|
+
await this.rpc.send(messageToSend, {
|
|
1863
|
+
priority: 1,
|
|
1864
|
+
});
|
|
1865
|
+
}
|
|
1866
|
+
}
|
|
1867
|
+
|
|
1519
1868
|
async isLeader(
|
|
1520
|
-
|
|
1869
|
+
entry: ShallowOrFullEntry<any>,
|
|
1521
1870
|
numberOfLeaders: number,
|
|
1522
1871
|
options?: {
|
|
1523
1872
|
candidates?: string[];
|
|
@@ -1525,13 +1874,13 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
1525
1874
|
},
|
|
1526
1875
|
): Promise<boolean> {
|
|
1527
1876
|
const isLeader = (
|
|
1528
|
-
await this.findLeaders(
|
|
1877
|
+
await this.findLeaders(entry, numberOfLeaders, options)
|
|
1529
1878
|
).find((l) => l === this.node.identity.publicKey.hashcode());
|
|
1530
1879
|
return !!isLeader;
|
|
1531
1880
|
}
|
|
1532
1881
|
|
|
1533
1882
|
private async waitForIsLeader(
|
|
1534
|
-
|
|
1883
|
+
entry: ShallowOrFullEntry<T>,
|
|
1535
1884
|
numberOfLeaders: number,
|
|
1536
1885
|
timeout = this.waitForReplicatorTimeout,
|
|
1537
1886
|
): Promise<string[] | false> {
|
|
@@ -1552,7 +1901,7 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
1552
1901
|
}, timeout);
|
|
1553
1902
|
|
|
1554
1903
|
const check = () =>
|
|
1555
|
-
this.findLeaders(
|
|
1904
|
+
this.findLeaders(entry, numberOfLeaders).then((leaders) => {
|
|
1556
1905
|
const isLeader = leaders.find(
|
|
1557
1906
|
(l) => l === this.node.identity.publicKey.hashcode(),
|
|
1558
1907
|
);
|
|
@@ -1575,7 +1924,7 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
1575
1924
|
}
|
|
1576
1925
|
|
|
1577
1926
|
async findLeaders(
|
|
1578
|
-
|
|
1927
|
+
entry: ShallowOrFullEntry<any>,
|
|
1579
1928
|
numberOfLeaders: number,
|
|
1580
1929
|
options?: {
|
|
1581
1930
|
roleAge?: number;
|
|
@@ -1585,18 +1934,8 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
1585
1934
|
return [this.node.identity.publicKey.hashcode()]; // Assumption: if the store is closed, always assume we have responsibility over the data
|
|
1586
1935
|
}
|
|
1587
1936
|
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
// Convert this thing we wan't to distribute to 8 bytes so we get can convert it into a u64
|
|
1592
|
-
// modulus into an index
|
|
1593
|
-
const utf8writer = new BinaryWriter();
|
|
1594
|
-
utf8writer.string(subject.toString());
|
|
1595
|
-
const seed = await sha256(utf8writer.finalize());
|
|
1596
|
-
|
|
1597
|
-
// convert hash of slot to a number
|
|
1598
|
-
const cursor = hashToUniformNumber(seed); // bounded between 0 and 1
|
|
1599
|
-
return this.findLeadersFromUniformNumber(cursor, numberOfLeaders, options);
|
|
1937
|
+
const cursor = await this.domain.fromEntry(entry);
|
|
1938
|
+
return this.findLeadersFromU32(cursor, numberOfLeaders, options);
|
|
1600
1939
|
}
|
|
1601
1940
|
|
|
1602
1941
|
async getDefaultMinRoleAge(): Promise<number> {
|
|
@@ -1615,63 +1954,15 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
1615
1954
|
); // / 3 so that if 2 replicators and timeUntilRoleMaturity = 1e4 the result will be 1
|
|
1616
1955
|
}
|
|
1617
1956
|
|
|
1618
|
-
private async
|
|
1619
|
-
cursor:
|
|
1957
|
+
private async findLeadersFromU32(
|
|
1958
|
+
cursor: u32,
|
|
1620
1959
|
numberOfLeaders: number,
|
|
1621
1960
|
options?: {
|
|
1622
1961
|
roleAge?: number;
|
|
1623
1962
|
},
|
|
1624
1963
|
) {
|
|
1625
1964
|
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
|
|
1626
|
-
|
|
1627
|
-
const samples = await getSamples(
|
|
1628
|
-
cursor,
|
|
1629
|
-
this.replicationIndex,
|
|
1630
|
-
numberOfLeaders,
|
|
1631
|
-
roleAge,
|
|
1632
|
-
);
|
|
1633
|
-
|
|
1634
|
-
return samples;
|
|
1635
|
-
}
|
|
1636
|
-
|
|
1637
|
-
/**
|
|
1638
|
-
*
|
|
1639
|
-
* @returns groups where at least one in any group will have the entry you are looking for
|
|
1640
|
-
*/
|
|
1641
|
-
async getReplicatorUnion(roleAge?: number) {
|
|
1642
|
-
roleAge = roleAge ?? (await this.getDefaultMinRoleAge());
|
|
1643
|
-
if (this.closed === true) {
|
|
1644
|
-
throw new ClosedError();
|
|
1645
|
-
}
|
|
1646
|
-
|
|
1647
|
-
// Total replication "width"
|
|
1648
|
-
const width = 1;
|
|
1649
|
-
|
|
1650
|
-
// How much width you need to "query" to
|
|
1651
|
-
const peers = this.replicationIndex; // TODO types
|
|
1652
|
-
const minReplicas = Math.min(
|
|
1653
|
-
await peers.getSize(),
|
|
1654
|
-
this.replicas.min.getValue(this),
|
|
1655
|
-
);
|
|
1656
|
-
|
|
1657
|
-
// If min replicas = 2
|
|
1658
|
-
// then we need to make sure we cover 0.5 of the total 'width' of the replication space
|
|
1659
|
-
// to make sure we reach sufficient amount of nodes such that at least one one has
|
|
1660
|
-
// the entry we are looking for
|
|
1661
|
-
const coveringWidth = width / minReplicas;
|
|
1662
|
-
|
|
1663
|
-
const set = await getCoverSet(
|
|
1664
|
-
coveringWidth,
|
|
1665
|
-
peers,
|
|
1666
|
-
roleAge,
|
|
1667
|
-
this.node.identity.publicKey,
|
|
1668
|
-
);
|
|
1669
|
-
|
|
1670
|
-
// add all in flight
|
|
1671
|
-
for (const [key, _] of this.syncInFlight) {
|
|
1672
|
-
set.add(key);
|
|
1673
|
-
}
|
|
1674
|
-
return [...set];
|
|
1965
|
+
return getSamples(cursor, this.replicationIndex, numberOfLeaders, roleAge);
|
|
1675
1966
|
}
|
|
1676
1967
|
|
|
1677
1968
|
async isReplicator(
|
|
@@ -1681,11 +1972,7 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
1681
1972
|
roleAge?: number;
|
|
1682
1973
|
},
|
|
1683
1974
|
) {
|
|
1684
|
-
return this.isLeader(
|
|
1685
|
-
entry.meta.gid,
|
|
1686
|
-
decodeReplicas(entry).getValue(this),
|
|
1687
|
-
options,
|
|
1688
|
-
);
|
|
1975
|
+
return this.isLeader(entry, decodeReplicas(entry).getValue(this), options);
|
|
1689
1976
|
}
|
|
1690
1977
|
|
|
1691
1978
|
async handleSubscriptionChange(
|
|
@@ -1726,7 +2013,7 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
1726
2013
|
if (replicationSegments.length > 0) {
|
|
1727
2014
|
this.rpc
|
|
1728
2015
|
.send(
|
|
1729
|
-
new
|
|
2016
|
+
new AllReplicatingSegmentsMessage({
|
|
1730
2017
|
segments: replicationSegments.map((x) => x.toReplicationRange()),
|
|
1731
2018
|
}),
|
|
1732
2019
|
{
|
|
@@ -1738,7 +2025,7 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
1738
2025
|
if (this.v8Behaviour) {
|
|
1739
2026
|
// for backwards compatibility
|
|
1740
2027
|
this.rpc
|
|
1741
|
-
.send(new ResponseRoleMessage({ role: this.getRole() }), {
|
|
2028
|
+
.send(new ResponseRoleMessage({ role: await this.getRole() }), {
|
|
1742
2029
|
mode: new SilentDelivery({ redundancy: 1, to: [publicKey] }),
|
|
1743
2030
|
})
|
|
1744
2031
|
.catch((e) => logger.error(e.toString()));
|
|
@@ -1823,10 +2110,7 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
1823
2110
|
? Math.min(minReplicasValue, this.replicas.max.getValue(this))
|
|
1824
2111
|
: minReplicasValue;
|
|
1825
2112
|
|
|
1826
|
-
const leaders = await this.findLeaders(
|
|
1827
|
-
entry.meta.gid,
|
|
1828
|
-
minMinReplicasValue,
|
|
1829
|
-
);
|
|
2113
|
+
const leaders = await this.findLeaders(entry, minMinReplicasValue);
|
|
1830
2114
|
|
|
1831
2115
|
if (
|
|
1832
2116
|
leaders.find((x) => x === this.node.identity.publicKey.hashcode())
|
|
@@ -1939,7 +2223,7 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
1939
2223
|
|
|
1940
2224
|
const oldPeersSet = this._gidPeersHistory.get(gid);
|
|
1941
2225
|
const currentPeers = await this.findLeaders(
|
|
1942
|
-
|
|
2226
|
+
getLatestEntry(entries)!,
|
|
1943
2227
|
maxReplicas(this, entries), // pick max replication policy of all entries, so all information is treated equally important as the most important
|
|
1944
2228
|
);
|
|
1945
2229
|
|
|
@@ -2039,8 +2323,9 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
2039
2323
|
evt.detail.unsubscriptions.map((x) => x),
|
|
2040
2324
|
)}'`,
|
|
2041
2325
|
);
|
|
2042
|
-
this.
|
|
2326
|
+
this.latestReplicationInfoMessage.delete(evt.detail.from.hashcode());
|
|
2043
2327
|
|
|
2328
|
+
// TODO only emit this if the peer is actually replicating anything
|
|
2044
2329
|
this.events.dispatchEvent(
|
|
2045
2330
|
new CustomEvent<ReplicatorLeaveEvent>("replicator:leave", {
|
|
2046
2331
|
detail: { publicKey: evt.detail.from },
|
|
@@ -2109,11 +2394,11 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
2109
2394
|
}
|
|
2110
2395
|
|
|
2111
2396
|
// The role is fixed (no changes depending on memory usage or peer count etc)
|
|
2112
|
-
if (!this.
|
|
2397
|
+
if (!this._isReplicating) {
|
|
2113
2398
|
return false;
|
|
2114
2399
|
}
|
|
2115
2400
|
|
|
2116
|
-
if (
|
|
2401
|
+
if (this._isAdaptiveReplicating) {
|
|
2117
2402
|
const peers = this.replicationIndex;
|
|
2118
2403
|
const usedMemory = await this.getMemoryUsage();
|
|
2119
2404
|
let dynamicRange = await this.getDynamicRange();
|
|
@@ -2123,10 +2408,12 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
2123
2408
|
}
|
|
2124
2409
|
|
|
2125
2410
|
const peersSize = (await peers.getSize()) || 1;
|
|
2411
|
+
const totalParticipation = await this.calculateTotalParticipation();
|
|
2412
|
+
|
|
2126
2413
|
const newFactor = this.replicationController.step({
|
|
2127
2414
|
memoryUsage: usedMemory,
|
|
2128
2415
|
currentFactor: dynamicRange.widthNormalized,
|
|
2129
|
-
totalFactor:
|
|
2416
|
+
totalFactor: totalParticipation, // TODO use this._totalParticipation when flakiness is fixed
|
|
2130
2417
|
peerCount: peersSize,
|
|
2131
2418
|
cpuUsage: this.cpuUsage?.value(),
|
|
2132
2419
|
});
|
|
@@ -2138,11 +2425,11 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
2138
2425
|
if (relativeDifference > 0.0001) {
|
|
2139
2426
|
// TODO can not reuse old range, since it will (potentially) affect the index because of sideeffects
|
|
2140
2427
|
dynamicRange = new ReplicationRangeIndexable({
|
|
2141
|
-
offset:
|
|
2142
|
-
length: newFactor,
|
|
2428
|
+
offset: hashToU32(this.node.identity.publicKey.bytes),
|
|
2429
|
+
length: scaleToU32(newFactor),
|
|
2143
2430
|
publicKeyHash: dynamicRange.hash,
|
|
2144
2431
|
id: dynamicRange.id,
|
|
2145
|
-
|
|
2432
|
+
mode: dynamicRange.mode,
|
|
2146
2433
|
timestamp: dynamicRange.timestamp,
|
|
2147
2434
|
});
|
|
2148
2435
|
|
|
@@ -2153,7 +2440,10 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
2153
2440
|
return false;
|
|
2154
2441
|
}
|
|
2155
2442
|
|
|
2156
|
-
await this.startAnnounceReplicating(dynamicRange
|
|
2443
|
+
await this.startAnnounceReplicating([dynamicRange], {
|
|
2444
|
+
checkDuplicates: false,
|
|
2445
|
+
reset: false,
|
|
2446
|
+
});
|
|
2157
2447
|
|
|
2158
2448
|
/* await this._updateRole(newRole, onRoleChange); */
|
|
2159
2449
|
this.rebalanceParticipationDebounced?.();
|
|
@@ -2167,18 +2457,19 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
2167
2457
|
return false;
|
|
2168
2458
|
}
|
|
2169
2459
|
async getDynamicRange() {
|
|
2460
|
+
let dynamicRangeId = sha256Sync(
|
|
2461
|
+
concat([
|
|
2462
|
+
this.node.identity.publicKey.bytes,
|
|
2463
|
+
new TextEncoder().encode("dynamic"),
|
|
2464
|
+
]),
|
|
2465
|
+
);
|
|
2170
2466
|
let range = (
|
|
2171
2467
|
await this.replicationIndex.query(
|
|
2172
2468
|
new SearchRequest({
|
|
2173
2469
|
query: [
|
|
2174
|
-
new
|
|
2175
|
-
key: "
|
|
2176
|
-
value:
|
|
2177
|
-
}),
|
|
2178
|
-
new IntegerCompare({
|
|
2179
|
-
key: "replicationIntent",
|
|
2180
|
-
value: ReplicationIntent.Automatic,
|
|
2181
|
-
compare: "eq",
|
|
2470
|
+
new ByteMatchQuery({
|
|
2471
|
+
key: "id",
|
|
2472
|
+
value: dynamicRangeId,
|
|
2182
2473
|
}),
|
|
2183
2474
|
],
|
|
2184
2475
|
fetch: 1,
|
|
@@ -2186,18 +2477,19 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
2186
2477
|
)
|
|
2187
2478
|
)?.results[0]?.value;
|
|
2188
2479
|
if (!range) {
|
|
2189
|
-
let seed = Math.random();
|
|
2190
2480
|
range = new ReplicationRangeIndexable({
|
|
2191
|
-
|
|
2481
|
+
normalized: true,
|
|
2482
|
+
offset: Math.random(),
|
|
2192
2483
|
length: 0,
|
|
2193
2484
|
publicKeyHash: this.node.identity.publicKey.hashcode(),
|
|
2194
|
-
|
|
2485
|
+
mode: ReplicationIntent.NonStrict,
|
|
2195
2486
|
timestamp: BigInt(+new Date()),
|
|
2196
|
-
id:
|
|
2487
|
+
id: dynamicRangeId,
|
|
2197
2488
|
});
|
|
2198
2489
|
const added = await this.addReplicationRange(
|
|
2199
|
-
range,
|
|
2490
|
+
[range],
|
|
2200
2491
|
this.node.identity.publicKey,
|
|
2492
|
+
{ reset: false, checkDuplicates: false },
|
|
2201
2493
|
);
|
|
2202
2494
|
if (!added) {
|
|
2203
2495
|
logger.warn("Not allowed to replicate by canReplicate");
|