@peerbit/shared-log 9.0.9-e1db01f → 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 +429 -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 +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 +7 -7
- package/src/index.ts +606 -316
- 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
|
|
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
|
-
|
|
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
|
|
|
@@ -678,6 +889,10 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
678
889
|
const result = await this.log.append(data, appendOptions);
|
|
679
890
|
let mode: DeliveryMode | undefined = undefined;
|
|
680
891
|
|
|
892
|
+
if (options?.replicate) {
|
|
893
|
+
await this.replicate(result.entry, { checkDuplicates: true });
|
|
894
|
+
}
|
|
895
|
+
|
|
681
896
|
for (const message of await createExchangeHeadsMessages(
|
|
682
897
|
this.log,
|
|
683
898
|
[result.entry],
|
|
@@ -685,22 +900,29 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
685
900
|
)) {
|
|
686
901
|
if (options?.target === "replicators" || !options?.target) {
|
|
687
902
|
const minReplicas = decodeReplicas(result.entry).getValue(this);
|
|
903
|
+
|
|
688
904
|
let leaders: string[] | Set<string> = await this.findLeaders(
|
|
689
|
-
result.entry
|
|
905
|
+
result.entry,
|
|
690
906
|
minReplicas,
|
|
691
907
|
);
|
|
908
|
+
|
|
692
909
|
const isLeader = leaders.includes(
|
|
693
910
|
this.node.identity.publicKey.hashcode(),
|
|
694
911
|
);
|
|
912
|
+
|
|
695
913
|
if (message.heads[0].gidRefrences.length > 0) {
|
|
696
914
|
const newAndOldLeaders = new Set(leaders);
|
|
697
915
|
for (const ref of message.heads[0].gidRefrences) {
|
|
698
|
-
|
|
699
|
-
|
|
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
|
+
}
|
|
700
921
|
}
|
|
701
922
|
}
|
|
702
923
|
leaders = newAndOldLeaders;
|
|
703
924
|
}
|
|
925
|
+
|
|
704
926
|
let set = this._gidPeersHistory.get(result.entry.meta.gid);
|
|
705
927
|
if (!set) {
|
|
706
928
|
set = new Set(leaders);
|
|
@@ -725,7 +947,7 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
725
947
|
return result;
|
|
726
948
|
}
|
|
727
949
|
|
|
728
|
-
async open(options?: Args<T>): Promise<void> {
|
|
950
|
+
async open(options?: Args<T, D>): Promise<void> {
|
|
729
951
|
this.replicas = {
|
|
730
952
|
min: options?.replicas?.min
|
|
731
953
|
? typeof options?.replicas?.min === "number"
|
|
@@ -738,11 +960,11 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
738
960
|
: options.replicas.max
|
|
739
961
|
: undefined,
|
|
740
962
|
};
|
|
741
|
-
|
|
963
|
+
this.domain = options?.domain ?? (createReplicationDomainHash() as D);
|
|
742
964
|
this._respondToIHaveTimeout = options?.respondToIHaveTimeout ?? 10 * 1000; // TODO make into arg
|
|
743
965
|
this._pendingDeletes = new Map();
|
|
744
966
|
this._pendingIHave = new Map();
|
|
745
|
-
this.
|
|
967
|
+
this.latestReplicationInfoMessage = new Map();
|
|
746
968
|
this.syncInFlightQueue = new Map();
|
|
747
969
|
this.syncInFlightQueueInverted = new Map();
|
|
748
970
|
this.syncInFlight = new Map();
|
|
@@ -780,16 +1002,21 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
780
1002
|
|
|
781
1003
|
await this.remoteBlocks.start();
|
|
782
1004
|
|
|
783
|
-
this._totalParticipation = 0;
|
|
1005
|
+
/* this._totalParticipation = 0; */
|
|
784
1006
|
const logScope = await this.node.indexer.scope(id);
|
|
785
1007
|
const replicationIndex = await logScope.scope("replication");
|
|
786
1008
|
this._replicationRangeIndex = await replicationIndex.init({
|
|
787
1009
|
schema: ReplicationRangeIndexable,
|
|
788
1010
|
});
|
|
1011
|
+
|
|
789
1012
|
const logIndex = await logScope.scope("log");
|
|
1013
|
+
|
|
790
1014
|
await this.node.indexer.start(); // TODO why do we need to start the indexer here?
|
|
791
1015
|
|
|
792
|
-
|
|
1016
|
+
const hasIndexedReplicationInfo =
|
|
1017
|
+
(await this.replicationIndex.getSize()) > 0;
|
|
1018
|
+
|
|
1019
|
+
/* this._totalParticipation = await this.calculateTotalParticipation(); */
|
|
793
1020
|
|
|
794
1021
|
this._gidPeersHistory = new Map();
|
|
795
1022
|
|
|
@@ -834,6 +1061,8 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
834
1061
|
this._onUnsubscriptionFn,
|
|
835
1062
|
);
|
|
836
1063
|
|
|
1064
|
+
await this.rpc.subscribe();
|
|
1065
|
+
|
|
837
1066
|
// await this.log.load();
|
|
838
1067
|
|
|
839
1068
|
// TODO (do better)
|
|
@@ -888,7 +1117,15 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
888
1117
|
});
|
|
889
1118
|
};
|
|
890
1119
|
|
|
891
|
-
|
|
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
|
+
}
|
|
892
1129
|
requestSync();
|
|
893
1130
|
}
|
|
894
1131
|
|
|
@@ -925,7 +1162,7 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
925
1162
|
|
|
926
1163
|
async onChange(change: Change<T>) {
|
|
927
1164
|
for (const added of change.added) {
|
|
928
|
-
this.onEntryAdded(added);
|
|
1165
|
+
this.onEntryAdded(added.entry);
|
|
929
1166
|
}
|
|
930
1167
|
for (const removed of change.removed) {
|
|
931
1168
|
this.onEntryRemoved(removed.hash);
|
|
@@ -957,6 +1194,28 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
957
1194
|
}
|
|
958
1195
|
}
|
|
959
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
|
+
|
|
960
1219
|
private async _close() {
|
|
961
1220
|
clearTimeout(this.syncMoreInterval);
|
|
962
1221
|
clearInterval(this.distributeInterval);
|
|
@@ -989,12 +1248,12 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
989
1248
|
this.syncInFlightQueue.clear();
|
|
990
1249
|
this.syncInFlightQueueInverted.clear();
|
|
991
1250
|
this.syncInFlight.clear();
|
|
992
|
-
this.
|
|
1251
|
+
this.latestReplicationInfoMessage.clear();
|
|
993
1252
|
this._gidPeersHistory.clear();
|
|
994
1253
|
|
|
995
1254
|
this._replicationRangeIndex = undefined as any;
|
|
996
1255
|
this.cpuUsage?.stop?.();
|
|
997
|
-
this._totalParticipation = 0;
|
|
1256
|
+
/* this._totalParticipation = 0; */
|
|
998
1257
|
this.pq.clear();
|
|
999
1258
|
}
|
|
1000
1259
|
async close(from?: Program): Promise<boolean> {
|
|
@@ -1078,6 +1337,7 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
1078
1337
|
const headsWithGid = await this.log.entryIndex
|
|
1079
1338
|
.getHeads(gid)
|
|
1080
1339
|
.all();
|
|
1340
|
+
const latestEntry = getLatestEntry(entries)!;
|
|
1081
1341
|
|
|
1082
1342
|
const maxReplicasFromHead =
|
|
1083
1343
|
headsWithGid && headsWithGid.length > 0
|
|
@@ -1095,12 +1355,12 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
1095
1355
|
|
|
1096
1356
|
if (isReplicating) {
|
|
1097
1357
|
isLeader = await this.waitForIsLeader(
|
|
1098
|
-
|
|
1358
|
+
latestEntry,
|
|
1099
1359
|
Math.max(maxReplicasFromHead, maxReplicasFromNewEntries),
|
|
1100
1360
|
);
|
|
1101
1361
|
} else {
|
|
1102
1362
|
isLeader = await this.findLeaders(
|
|
1103
|
-
|
|
1363
|
+
latestEntry,
|
|
1104
1364
|
Math.max(maxReplicasFromHead, maxReplicasFromNewEntries),
|
|
1105
1365
|
);
|
|
1106
1366
|
|
|
@@ -1184,7 +1444,7 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
1184
1444
|
const minReplicas = maxReplicas(this, headsWithGid.values());
|
|
1185
1445
|
|
|
1186
1446
|
const isLeader = await this.isLeader(
|
|
1187
|
-
entries[0].entry
|
|
1447
|
+
entries[0].entry,
|
|
1188
1448
|
minReplicas,
|
|
1189
1449
|
);
|
|
1190
1450
|
|
|
@@ -1206,7 +1466,7 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
1206
1466
|
if (
|
|
1207
1467
|
indexedEntry &&
|
|
1208
1468
|
(await this.isLeader(
|
|
1209
|
-
indexedEntry.value
|
|
1469
|
+
indexedEntry.value,
|
|
1210
1470
|
decodeReplicas(indexedEntry.value).getValue(this),
|
|
1211
1471
|
))
|
|
1212
1472
|
) {
|
|
@@ -1224,7 +1484,7 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
1224
1484
|
callback: async (entry: any) => {
|
|
1225
1485
|
if (
|
|
1226
1486
|
await this.isLeader(
|
|
1227
|
-
entry
|
|
1487
|
+
entry,
|
|
1228
1488
|
decodeReplicas(entry).getValue(this),
|
|
1229
1489
|
)
|
|
1230
1490
|
) {
|
|
@@ -1316,7 +1576,7 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
1316
1576
|
return;
|
|
1317
1577
|
}
|
|
1318
1578
|
await this.rpc.send(
|
|
1319
|
-
new
|
|
1579
|
+
new AllReplicatingSegmentsMessage({
|
|
1320
1580
|
segments: (await this.getMyReplicationSegments()).map((x) =>
|
|
1321
1581
|
x.toReplicationRange(),
|
|
1322
1582
|
),
|
|
@@ -1330,9 +1590,8 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
1330
1590
|
if (this.v8Behaviour) {
|
|
1331
1591
|
const role = this.getRole();
|
|
1332
1592
|
if (role instanceof Replicator) {
|
|
1333
|
-
const fixedSettings = this
|
|
1334
|
-
|
|
1335
|
-
if (fixedSettings.factor === 1) {
|
|
1593
|
+
const fixedSettings = !this._isAdaptiveReplicating;
|
|
1594
|
+
if (fixedSettings) {
|
|
1336
1595
|
await this.rpc.send(
|
|
1337
1596
|
new ResponseRoleMessage({
|
|
1338
1597
|
role,
|
|
@@ -1348,16 +1607,16 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
1348
1607
|
}
|
|
1349
1608
|
}
|
|
1350
1609
|
} else if (
|
|
1351
|
-
msg instanceof
|
|
1352
|
-
msg instanceof
|
|
1610
|
+
msg instanceof AllReplicatingSegmentsMessage ||
|
|
1611
|
+
msg instanceof AddedReplicationSegmentMessage
|
|
1353
1612
|
) {
|
|
1354
1613
|
if (context.from.equals(this.node.identity.publicKey)) {
|
|
1355
1614
|
return;
|
|
1356
1615
|
}
|
|
1357
1616
|
|
|
1358
1617
|
let replicationInfoMessage = msg as
|
|
1359
|
-
|
|
|
1360
|
-
|
|
|
1618
|
+
| AllReplicatingSegmentsMessage
|
|
1619
|
+
| AddedReplicationSegmentMessage;
|
|
1361
1620
|
|
|
1362
1621
|
// we have this statement because peers might have changed/announced their role,
|
|
1363
1622
|
// but we don't know them as "subscribers" yet. i.e. they are not online
|
|
@@ -1367,30 +1626,30 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
1367
1626
|
timeout: this.waitForReplicatorTimeout,
|
|
1368
1627
|
})
|
|
1369
1628
|
.then(async () => {
|
|
1370
|
-
//
|
|
1371
|
-
|
|
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
|
+
|
|
1372
1635
|
if (prev && prev > context.timestamp) {
|
|
1373
1636
|
return;
|
|
1374
1637
|
}
|
|
1375
|
-
this.
|
|
1638
|
+
this.latestReplicationInfoMessage.set(
|
|
1376
1639
|
context.from!.hashcode(),
|
|
1377
1640
|
context.timestamp,
|
|
1378
1641
|
);
|
|
1379
1642
|
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
addedOnce = addedOnce || added;
|
|
1391
|
-
}
|
|
1392
|
-
}
|
|
1393
|
-
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());
|
|
1394
1653
|
|
|
1395
1654
|
/* await this._modifyReplicators(msg.role, context.from!); */
|
|
1396
1655
|
})
|
|
@@ -1456,7 +1715,7 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
1456
1715
|
return ranges.results.map((x) => x.value);
|
|
1457
1716
|
}
|
|
1458
1717
|
|
|
1459
|
-
async
|
|
1718
|
+
async getMyTotalParticipation() {
|
|
1460
1719
|
// sum all of my replicator rects
|
|
1461
1720
|
return (await this.getMyReplicationSegments()).reduce(
|
|
1462
1721
|
(acc, { widthNormalized }) => acc + widthNormalized,
|
|
@@ -1518,8 +1777,96 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
1518
1777
|
});
|
|
1519
1778
|
}
|
|
1520
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
|
+
|
|
1521
1868
|
async isLeader(
|
|
1522
|
-
|
|
1869
|
+
entry: ShallowOrFullEntry<any>,
|
|
1523
1870
|
numberOfLeaders: number,
|
|
1524
1871
|
options?: {
|
|
1525
1872
|
candidates?: string[];
|
|
@@ -1527,13 +1874,13 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
1527
1874
|
},
|
|
1528
1875
|
): Promise<boolean> {
|
|
1529
1876
|
const isLeader = (
|
|
1530
|
-
await this.findLeaders(
|
|
1877
|
+
await this.findLeaders(entry, numberOfLeaders, options)
|
|
1531
1878
|
).find((l) => l === this.node.identity.publicKey.hashcode());
|
|
1532
1879
|
return !!isLeader;
|
|
1533
1880
|
}
|
|
1534
1881
|
|
|
1535
1882
|
private async waitForIsLeader(
|
|
1536
|
-
|
|
1883
|
+
entry: ShallowOrFullEntry<T>,
|
|
1537
1884
|
numberOfLeaders: number,
|
|
1538
1885
|
timeout = this.waitForReplicatorTimeout,
|
|
1539
1886
|
): Promise<string[] | false> {
|
|
@@ -1554,7 +1901,7 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
1554
1901
|
}, timeout);
|
|
1555
1902
|
|
|
1556
1903
|
const check = () =>
|
|
1557
|
-
this.findLeaders(
|
|
1904
|
+
this.findLeaders(entry, numberOfLeaders).then((leaders) => {
|
|
1558
1905
|
const isLeader = leaders.find(
|
|
1559
1906
|
(l) => l === this.node.identity.publicKey.hashcode(),
|
|
1560
1907
|
);
|
|
@@ -1577,7 +1924,7 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
1577
1924
|
}
|
|
1578
1925
|
|
|
1579
1926
|
async findLeaders(
|
|
1580
|
-
|
|
1927
|
+
entry: ShallowOrFullEntry<any>,
|
|
1581
1928
|
numberOfLeaders: number,
|
|
1582
1929
|
options?: {
|
|
1583
1930
|
roleAge?: number;
|
|
@@ -1587,18 +1934,8 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
1587
1934
|
return [this.node.identity.publicKey.hashcode()]; // Assumption: if the store is closed, always assume we have responsibility over the data
|
|
1588
1935
|
}
|
|
1589
1936
|
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
// Convert this thing we wan't to distribute to 8 bytes so we get can convert it into a u64
|
|
1594
|
-
// modulus into an index
|
|
1595
|
-
const utf8writer = new BinaryWriter();
|
|
1596
|
-
utf8writer.string(subject.toString());
|
|
1597
|
-
const seed = await sha256(utf8writer.finalize());
|
|
1598
|
-
|
|
1599
|
-
// convert hash of slot to a number
|
|
1600
|
-
const cursor = hashToUniformNumber(seed); // bounded between 0 and 1
|
|
1601
|
-
return this.findLeadersFromUniformNumber(cursor, numberOfLeaders, options);
|
|
1937
|
+
const cursor = await this.domain.fromEntry(entry);
|
|
1938
|
+
return this.findLeadersFromU32(cursor, numberOfLeaders, options);
|
|
1602
1939
|
}
|
|
1603
1940
|
|
|
1604
1941
|
async getDefaultMinRoleAge(): Promise<number> {
|
|
@@ -1617,63 +1954,15 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
1617
1954
|
); // / 3 so that if 2 replicators and timeUntilRoleMaturity = 1e4 the result will be 1
|
|
1618
1955
|
}
|
|
1619
1956
|
|
|
1620
|
-
private async
|
|
1621
|
-
cursor:
|
|
1957
|
+
private async findLeadersFromU32(
|
|
1958
|
+
cursor: u32,
|
|
1622
1959
|
numberOfLeaders: number,
|
|
1623
1960
|
options?: {
|
|
1624
1961
|
roleAge?: number;
|
|
1625
1962
|
},
|
|
1626
1963
|
) {
|
|
1627
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
|
|
1628
|
-
|
|
1629
|
-
const samples = await getSamples(
|
|
1630
|
-
cursor,
|
|
1631
|
-
this.replicationIndex,
|
|
1632
|
-
numberOfLeaders,
|
|
1633
|
-
roleAge,
|
|
1634
|
-
);
|
|
1635
|
-
|
|
1636
|
-
return samples;
|
|
1637
|
-
}
|
|
1638
|
-
|
|
1639
|
-
/**
|
|
1640
|
-
*
|
|
1641
|
-
* @returns groups where at least one in any group will have the entry you are looking for
|
|
1642
|
-
*/
|
|
1643
|
-
async getReplicatorUnion(roleAge?: number) {
|
|
1644
|
-
roleAge = roleAge ?? (await this.getDefaultMinRoleAge());
|
|
1645
|
-
if (this.closed === true) {
|
|
1646
|
-
throw new ClosedError();
|
|
1647
|
-
}
|
|
1648
|
-
|
|
1649
|
-
// Total replication "width"
|
|
1650
|
-
const width = 1;
|
|
1651
|
-
|
|
1652
|
-
// How much width you need to "query" to
|
|
1653
|
-
const peers = this.replicationIndex; // TODO types
|
|
1654
|
-
const minReplicas = Math.min(
|
|
1655
|
-
await peers.getSize(),
|
|
1656
|
-
this.replicas.min.getValue(this),
|
|
1657
|
-
);
|
|
1658
|
-
|
|
1659
|
-
// If min replicas = 2
|
|
1660
|
-
// then we need to make sure we cover 0.5 of the total 'width' of the replication space
|
|
1661
|
-
// to make sure we reach sufficient amount of nodes such that at least one one has
|
|
1662
|
-
// the entry we are looking for
|
|
1663
|
-
const coveringWidth = width / minReplicas;
|
|
1664
|
-
|
|
1665
|
-
const set = await getCoverSet(
|
|
1666
|
-
coveringWidth,
|
|
1667
|
-
peers,
|
|
1668
|
-
roleAge,
|
|
1669
|
-
this.node.identity.publicKey,
|
|
1670
|
-
);
|
|
1671
|
-
|
|
1672
|
-
// add all in flight
|
|
1673
|
-
for (const [key, _] of this.syncInFlight) {
|
|
1674
|
-
set.add(key);
|
|
1675
|
-
}
|
|
1676
|
-
return [...set];
|
|
1965
|
+
return getSamples(cursor, this.replicationIndex, numberOfLeaders, roleAge);
|
|
1677
1966
|
}
|
|
1678
1967
|
|
|
1679
1968
|
async isReplicator(
|
|
@@ -1683,11 +1972,7 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
1683
1972
|
roleAge?: number;
|
|
1684
1973
|
},
|
|
1685
1974
|
) {
|
|
1686
|
-
return this.isLeader(
|
|
1687
|
-
entry.meta.gid,
|
|
1688
|
-
decodeReplicas(entry).getValue(this),
|
|
1689
|
-
options,
|
|
1690
|
-
);
|
|
1975
|
+
return this.isLeader(entry, decodeReplicas(entry).getValue(this), options);
|
|
1691
1976
|
}
|
|
1692
1977
|
|
|
1693
1978
|
async handleSubscriptionChange(
|
|
@@ -1728,7 +2013,7 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
1728
2013
|
if (replicationSegments.length > 0) {
|
|
1729
2014
|
this.rpc
|
|
1730
2015
|
.send(
|
|
1731
|
-
new
|
|
2016
|
+
new AllReplicatingSegmentsMessage({
|
|
1732
2017
|
segments: replicationSegments.map((x) => x.toReplicationRange()),
|
|
1733
2018
|
}),
|
|
1734
2019
|
{
|
|
@@ -1740,7 +2025,7 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
1740
2025
|
if (this.v8Behaviour) {
|
|
1741
2026
|
// for backwards compatibility
|
|
1742
2027
|
this.rpc
|
|
1743
|
-
.send(new ResponseRoleMessage({ role: this.getRole() }), {
|
|
2028
|
+
.send(new ResponseRoleMessage({ role: await this.getRole() }), {
|
|
1744
2029
|
mode: new SilentDelivery({ redundancy: 1, to: [publicKey] }),
|
|
1745
2030
|
})
|
|
1746
2031
|
.catch((e) => logger.error(e.toString()));
|
|
@@ -1825,10 +2110,7 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
1825
2110
|
? Math.min(minReplicasValue, this.replicas.max.getValue(this))
|
|
1826
2111
|
: minReplicasValue;
|
|
1827
2112
|
|
|
1828
|
-
const leaders = await this.findLeaders(
|
|
1829
|
-
entry.meta.gid,
|
|
1830
|
-
minMinReplicasValue,
|
|
1831
|
-
);
|
|
2113
|
+
const leaders = await this.findLeaders(entry, minMinReplicasValue);
|
|
1832
2114
|
|
|
1833
2115
|
if (
|
|
1834
2116
|
leaders.find((x) => x === this.node.identity.publicKey.hashcode())
|
|
@@ -1941,7 +2223,7 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
1941
2223
|
|
|
1942
2224
|
const oldPeersSet = this._gidPeersHistory.get(gid);
|
|
1943
2225
|
const currentPeers = await this.findLeaders(
|
|
1944
|
-
|
|
2226
|
+
getLatestEntry(entries)!,
|
|
1945
2227
|
maxReplicas(this, entries), // pick max replication policy of all entries, so all information is treated equally important as the most important
|
|
1946
2228
|
);
|
|
1947
2229
|
|
|
@@ -2041,8 +2323,9 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
2041
2323
|
evt.detail.unsubscriptions.map((x) => x),
|
|
2042
2324
|
)}'`,
|
|
2043
2325
|
);
|
|
2044
|
-
this.
|
|
2326
|
+
this.latestReplicationInfoMessage.delete(evt.detail.from.hashcode());
|
|
2045
2327
|
|
|
2328
|
+
// TODO only emit this if the peer is actually replicating anything
|
|
2046
2329
|
this.events.dispatchEvent(
|
|
2047
2330
|
new CustomEvent<ReplicatorLeaveEvent>("replicator:leave", {
|
|
2048
2331
|
detail: { publicKey: evt.detail.from },
|
|
@@ -2111,11 +2394,11 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
2111
2394
|
}
|
|
2112
2395
|
|
|
2113
2396
|
// The role is fixed (no changes depending on memory usage or peer count etc)
|
|
2114
|
-
if (!this.
|
|
2397
|
+
if (!this._isReplicating) {
|
|
2115
2398
|
return false;
|
|
2116
2399
|
}
|
|
2117
2400
|
|
|
2118
|
-
if (
|
|
2401
|
+
if (this._isAdaptiveReplicating) {
|
|
2119
2402
|
const peers = this.replicationIndex;
|
|
2120
2403
|
const usedMemory = await this.getMemoryUsage();
|
|
2121
2404
|
let dynamicRange = await this.getDynamicRange();
|
|
@@ -2125,10 +2408,12 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
2125
2408
|
}
|
|
2126
2409
|
|
|
2127
2410
|
const peersSize = (await peers.getSize()) || 1;
|
|
2411
|
+
const totalParticipation = await this.calculateTotalParticipation();
|
|
2412
|
+
|
|
2128
2413
|
const newFactor = this.replicationController.step({
|
|
2129
2414
|
memoryUsage: usedMemory,
|
|
2130
2415
|
currentFactor: dynamicRange.widthNormalized,
|
|
2131
|
-
totalFactor:
|
|
2416
|
+
totalFactor: totalParticipation, // TODO use this._totalParticipation when flakiness is fixed
|
|
2132
2417
|
peerCount: peersSize,
|
|
2133
2418
|
cpuUsage: this.cpuUsage?.value(),
|
|
2134
2419
|
});
|
|
@@ -2140,11 +2425,11 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
2140
2425
|
if (relativeDifference > 0.0001) {
|
|
2141
2426
|
// TODO can not reuse old range, since it will (potentially) affect the index because of sideeffects
|
|
2142
2427
|
dynamicRange = new ReplicationRangeIndexable({
|
|
2143
|
-
offset:
|
|
2144
|
-
length: newFactor,
|
|
2428
|
+
offset: hashToU32(this.node.identity.publicKey.bytes),
|
|
2429
|
+
length: scaleToU32(newFactor),
|
|
2145
2430
|
publicKeyHash: dynamicRange.hash,
|
|
2146
2431
|
id: dynamicRange.id,
|
|
2147
|
-
|
|
2432
|
+
mode: dynamicRange.mode,
|
|
2148
2433
|
timestamp: dynamicRange.timestamp,
|
|
2149
2434
|
});
|
|
2150
2435
|
|
|
@@ -2155,7 +2440,10 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
2155
2440
|
return false;
|
|
2156
2441
|
}
|
|
2157
2442
|
|
|
2158
|
-
await this.startAnnounceReplicating(dynamicRange
|
|
2443
|
+
await this.startAnnounceReplicating([dynamicRange], {
|
|
2444
|
+
checkDuplicates: false,
|
|
2445
|
+
reset: false,
|
|
2446
|
+
});
|
|
2159
2447
|
|
|
2160
2448
|
/* await this._updateRole(newRole, onRoleChange); */
|
|
2161
2449
|
this.rebalanceParticipationDebounced?.();
|
|
@@ -2169,18 +2457,19 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
2169
2457
|
return false;
|
|
2170
2458
|
}
|
|
2171
2459
|
async getDynamicRange() {
|
|
2460
|
+
let dynamicRangeId = sha256Sync(
|
|
2461
|
+
concat([
|
|
2462
|
+
this.node.identity.publicKey.bytes,
|
|
2463
|
+
new TextEncoder().encode("dynamic"),
|
|
2464
|
+
]),
|
|
2465
|
+
);
|
|
2172
2466
|
let range = (
|
|
2173
2467
|
await this.replicationIndex.query(
|
|
2174
2468
|
new SearchRequest({
|
|
2175
2469
|
query: [
|
|
2176
|
-
new
|
|
2177
|
-
key: "
|
|
2178
|
-
value:
|
|
2179
|
-
}),
|
|
2180
|
-
new IntegerCompare({
|
|
2181
|
-
key: "replicationIntent",
|
|
2182
|
-
value: ReplicationIntent.Automatic,
|
|
2183
|
-
compare: "eq",
|
|
2470
|
+
new ByteMatchQuery({
|
|
2471
|
+
key: "id",
|
|
2472
|
+
value: dynamicRangeId,
|
|
2184
2473
|
}),
|
|
2185
2474
|
],
|
|
2186
2475
|
fetch: 1,
|
|
@@ -2188,18 +2477,19 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
2188
2477
|
)
|
|
2189
2478
|
)?.results[0]?.value;
|
|
2190
2479
|
if (!range) {
|
|
2191
|
-
let seed = Math.random();
|
|
2192
2480
|
range = new ReplicationRangeIndexable({
|
|
2193
|
-
|
|
2481
|
+
normalized: true,
|
|
2482
|
+
offset: Math.random(),
|
|
2194
2483
|
length: 0,
|
|
2195
2484
|
publicKeyHash: this.node.identity.publicKey.hashcode(),
|
|
2196
|
-
|
|
2485
|
+
mode: ReplicationIntent.NonStrict,
|
|
2197
2486
|
timestamp: BigInt(+new Date()),
|
|
2198
|
-
id:
|
|
2487
|
+
id: dynamicRangeId,
|
|
2199
2488
|
});
|
|
2200
2489
|
const added = await this.addReplicationRange(
|
|
2201
|
-
range,
|
|
2490
|
+
[range],
|
|
2202
2491
|
this.node.identity.publicKey,
|
|
2492
|
+
{ reset: false, checkDuplicates: false },
|
|
2203
2493
|
);
|
|
2204
2494
|
if (!added) {
|
|
2205
2495
|
logger.warn("Not allowed to replicate by canReplicate");
|