@peerbit/shared-log 10.0.6 → 10.1.0-7d319f1
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/get-samples.js +1 -1
- package/dist/benchmark/get-samples.js.map +1 -1
- package/dist/benchmark/utils.js +1 -1
- package/dist/benchmark/utils.js.map +1 -1
- package/dist/src/index.d.ts +16 -11
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +146 -65
- package/dist/src/index.js.map +1 -1
- package/dist/src/ranges.d.ts +95 -11
- package/dist/src/ranges.d.ts.map +1 -1
- package/dist/src/ranges.js +440 -163
- package/dist/src/ranges.js.map +1 -1
- package/dist/src/replication-domain-hash.d.ts +2 -2
- package/dist/src/replication-domain-hash.d.ts.map +1 -1
- package/dist/src/replication-domain-hash.js +2 -17
- package/dist/src/replication-domain-hash.js.map +1 -1
- package/dist/src/replication-domain-time.d.ts +7 -2
- package/dist/src/replication-domain-time.d.ts.map +1 -1
- package/dist/src/replication-domain-time.js +7 -12
- package/dist/src/replication-domain-time.js.map +1 -1
- package/dist/src/replication-domain.d.ts +3 -20
- package/dist/src/replication-domain.d.ts.map +1 -1
- package/dist/src/replication-domain.js +0 -33
- package/dist/src/replication-domain.js.map +1 -1
- package/package.json +70 -70
- package/src/index.ts +215 -108
- package/src/ranges.ts +652 -202
- package/src/replication-domain-hash.ts +16 -29
- package/src/replication-domain-time.ts +46 -40
- package/src/replication-domain.ts +7 -59
package/src/index.ts
CHANGED
|
@@ -63,6 +63,7 @@ import {
|
|
|
63
63
|
} from "./exchange-heads.js";
|
|
64
64
|
import {
|
|
65
65
|
MAX_U32,
|
|
66
|
+
MAX_U64,
|
|
66
67
|
type NumberFromType,
|
|
67
68
|
type Numbers,
|
|
68
69
|
bytesToNumber,
|
|
@@ -75,6 +76,8 @@ import {
|
|
|
75
76
|
type EntryReplicated,
|
|
76
77
|
EntryReplicatedU32,
|
|
77
78
|
EntryReplicatedU64,
|
|
79
|
+
type ReplicationChange,
|
|
80
|
+
type ReplicationChanges,
|
|
78
81
|
ReplicationIntent,
|
|
79
82
|
type ReplicationRangeIndexable,
|
|
80
83
|
ReplicationRangeIndexableU32,
|
|
@@ -82,9 +85,11 @@ import {
|
|
|
82
85
|
ReplicationRangeMessage,
|
|
83
86
|
SyncStatus,
|
|
84
87
|
appromixateCoverage,
|
|
88
|
+
countCoveringRangesSameOwner,
|
|
89
|
+
debounceAggregationChanges,
|
|
90
|
+
getAllMergeCandiates,
|
|
85
91
|
getCoverSet,
|
|
86
92
|
getSamples,
|
|
87
|
-
iHaveCoveringRange,
|
|
88
93
|
isMatured,
|
|
89
94
|
isReplicationRangeMessage,
|
|
90
95
|
mergeRanges,
|
|
@@ -102,11 +107,8 @@ import {
|
|
|
102
107
|
} from "./replication-domain-time.js";
|
|
103
108
|
import {
|
|
104
109
|
type ExtractDomainArgs,
|
|
105
|
-
type ReplicationChange,
|
|
106
|
-
type ReplicationChanges,
|
|
107
110
|
type ReplicationDomain,
|
|
108
|
-
|
|
109
|
-
mergeReplicationChanges,
|
|
111
|
+
type ReplicationDomainConstructor,
|
|
110
112
|
} from "./replication-domain.js";
|
|
111
113
|
import {
|
|
112
114
|
AbsoluteReplicas,
|
|
@@ -143,6 +145,7 @@ export {
|
|
|
143
145
|
EntryReplicatedU32,
|
|
144
146
|
EntryReplicatedU64,
|
|
145
147
|
};
|
|
148
|
+
export { MAX_U32, MAX_U64, type NumberFromType };
|
|
146
149
|
export const logger = loggerFn({ module: "shared-log" });
|
|
147
150
|
|
|
148
151
|
const getLatestEntry = (
|
|
@@ -183,7 +186,6 @@ export type FixedReplicationOptions = {
|
|
|
183
186
|
factor: number | bigint | "all" | "right";
|
|
184
187
|
strict?: boolean; // if true, only this range will be replicated
|
|
185
188
|
offset?: number | bigint;
|
|
186
|
-
syncStatus?: SyncStatus;
|
|
187
189
|
};
|
|
188
190
|
|
|
189
191
|
export type ReplicationOptions<R extends "u32" | "u64" = any> =
|
|
@@ -191,7 +193,8 @@ export type ReplicationOptions<R extends "u32" | "u64" = any> =
|
|
|
191
193
|
| FixedReplicationOptions
|
|
192
194
|
| FixedReplicationOptions[]
|
|
193
195
|
| number
|
|
194
|
-
| boolean
|
|
196
|
+
| boolean
|
|
197
|
+
| "resume";
|
|
195
198
|
|
|
196
199
|
const isAdaptiveReplicatorOption = (
|
|
197
200
|
options: ReplicationOptions<any>,
|
|
@@ -224,6 +227,10 @@ const isReplicationOptionsDependentOnPreviousState = (
|
|
|
224
227
|
return true;
|
|
225
228
|
}
|
|
226
229
|
|
|
230
|
+
if (options === "resume") {
|
|
231
|
+
return true;
|
|
232
|
+
}
|
|
233
|
+
|
|
227
234
|
if (options == null) {
|
|
228
235
|
// when not providing options, we assume previous behaviour
|
|
229
236
|
return true;
|
|
@@ -249,7 +256,7 @@ interface IndexableDomain<R extends "u32" | "u64"> {
|
|
|
249
256
|
properties: {
|
|
250
257
|
id?: Uint8Array;
|
|
251
258
|
offset: NumberFromType<R>;
|
|
252
|
-
|
|
259
|
+
width: NumberFromType<R>;
|
|
253
260
|
mode?: ReplicationIntent;
|
|
254
261
|
timestamp?: bigint;
|
|
255
262
|
} & ({ publicKeyHash: string } | { publicKey: PublicSignKey }),
|
|
@@ -299,7 +306,7 @@ export type SharedLogOptions<
|
|
|
299
306
|
waitForPruneDelay?: number;
|
|
300
307
|
distributionDebounceTime?: number;
|
|
301
308
|
compatibility?: number;
|
|
302
|
-
domain?: D
|
|
309
|
+
domain?: ReplicationDomainConstructor<D>;
|
|
303
310
|
};
|
|
304
311
|
|
|
305
312
|
export const DEFAULT_MIN_REPLICAS = 2;
|
|
@@ -374,6 +381,8 @@ export class SharedLog<
|
|
|
374
381
|
private _replicationRangeIndex!: Index<ReplicationRangeIndexable<R>>;
|
|
375
382
|
private _entryCoordinatesIndex!: Index<EntryReplicated<R>>;
|
|
376
383
|
private coordinateToHash!: Cache<string>;
|
|
384
|
+
private recentlyRebalanced!: Cache<string>;
|
|
385
|
+
|
|
377
386
|
private uniqueReplicators!: Set<string>;
|
|
378
387
|
|
|
379
388
|
/* private _totalParticipation!: number; */
|
|
@@ -459,7 +468,7 @@ export class SharedLog<
|
|
|
459
468
|
private _requestIPruneResponseReplicatorSet!: Map<string, Set<string>>; // tracks entry hash to peer hash
|
|
460
469
|
|
|
461
470
|
private replicationChangeDebounceFn!: ReturnType<
|
|
462
|
-
typeof debounceAggregationChanges
|
|
471
|
+
typeof debounceAggregationChanges<ReplicationRangeIndexable<R>>
|
|
463
472
|
>;
|
|
464
473
|
|
|
465
474
|
// regular distribution checks
|
|
@@ -557,13 +566,11 @@ export class SharedLog<
|
|
|
557
566
|
{
|
|
558
567
|
reset,
|
|
559
568
|
checkDuplicates,
|
|
560
|
-
syncStatus,
|
|
561
569
|
announce,
|
|
562
570
|
mergeSegments,
|
|
563
571
|
}: {
|
|
564
572
|
reset?: boolean;
|
|
565
573
|
checkDuplicates?: boolean;
|
|
566
|
-
syncStatus?: SyncStatus;
|
|
567
574
|
mergeSegments?: boolean;
|
|
568
575
|
announce?: (
|
|
569
576
|
msg: AddedReplicationSegmentMessage | AllReplicatingSegmentsMessage,
|
|
@@ -573,8 +580,11 @@ export class SharedLog<
|
|
|
573
580
|
let offsetWasProvided = false;
|
|
574
581
|
if (isUnreplicationOptions(options)) {
|
|
575
582
|
await this.unreplicate();
|
|
583
|
+
} else if (options === "resume") {
|
|
584
|
+
// don't do anything
|
|
576
585
|
} else {
|
|
577
|
-
let
|
|
586
|
+
let rangesToReplicate: ReplicationRangeIndexable<R>[] = [];
|
|
587
|
+
let rangesToUnreplicate: ReplicationRangeIndexable<R>[] = [];
|
|
578
588
|
|
|
579
589
|
if (options == null) {
|
|
580
590
|
options = {};
|
|
@@ -595,11 +605,11 @@ export class SharedLog<
|
|
|
595
605
|
// not allowed
|
|
596
606
|
return;
|
|
597
607
|
}
|
|
598
|
-
|
|
608
|
+
rangesToReplicate = [maybeRange];
|
|
599
609
|
|
|
600
610
|
offsetWasProvided = true;
|
|
601
611
|
} else if (isReplicationRangeMessage(options)) {
|
|
602
|
-
|
|
612
|
+
rangesToReplicate = [
|
|
603
613
|
options.toReplicationRangeIndexable(this.node.identity.publicKey),
|
|
604
614
|
];
|
|
605
615
|
|
|
@@ -650,60 +660,62 @@ export class SharedLog<
|
|
|
650
660
|
let factorDenormalized = !normalized
|
|
651
661
|
? factor
|
|
652
662
|
: this.indexableDomain.numbers.denormalize(factor as number);
|
|
653
|
-
|
|
663
|
+
rangesToReplicate.push(
|
|
654
664
|
new this.indexableDomain.constructorRange({
|
|
655
665
|
id: rangeArg.id,
|
|
656
666
|
// @ts-ignore
|
|
657
667
|
offset: offset,
|
|
658
668
|
// @ts-ignore
|
|
659
|
-
|
|
669
|
+
width: (factor === "all"
|
|
660
670
|
? fullWidth
|
|
661
671
|
: factor === "right"
|
|
662
672
|
? // @ts-ignore
|
|
663
673
|
fullWidth - offset
|
|
664
674
|
: factorDenormalized) as NumberFromType<R>,
|
|
665
|
-
/* typeof factor === "number"
|
|
666
|
-
? factor
|
|
667
|
-
: factor === "all"
|
|
668
|
-
? width
|
|
669
|
-
// @ts-ignore
|
|
670
|
-
: width - offset, */
|
|
671
675
|
publicKeyHash: this.node.identity.publicKey.hashcode(),
|
|
672
676
|
mode: rangeArg.strict
|
|
673
677
|
? ReplicationIntent.Strict
|
|
674
678
|
: ReplicationIntent.NonStrict, // automatic means that this range might be reused later for dynamic replication behaviour
|
|
675
679
|
timestamp: timestamp ?? BigInt(+new Date()),
|
|
676
680
|
}),
|
|
677
|
-
/* new ReplicationRangeIndexable({
|
|
678
|
-
id: rangeArg.id,
|
|
679
|
-
normalized,
|
|
680
|
-
offset: offset,
|
|
681
|
-
length:
|
|
682
|
-
typeof factor === "number"
|
|
683
|
-
? factor
|
|
684
|
-
: factor === "all"
|
|
685
|
-
? width
|
|
686
|
-
// @ts-ignore
|
|
687
|
-
: width - offset,
|
|
688
|
-
publicKeyHash: this.node.identity.publicKey.hashcode(),
|
|
689
|
-
mode: rangeArg.strict
|
|
690
|
-
? ReplicationIntent.Strict
|
|
691
|
-
: ReplicationIntent.NonStrict, // automatic means that this range might be reused later for dynamic replication behaviour
|
|
692
|
-
timestamp: BigInt(+new Date()),
|
|
693
|
-
}), */
|
|
694
681
|
);
|
|
695
682
|
}
|
|
696
683
|
|
|
697
|
-
if (mergeSegments
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
684
|
+
if (mergeSegments) {
|
|
685
|
+
let range =
|
|
686
|
+
rangesToReplicate.length > 1
|
|
687
|
+
? mergeRanges(rangesToReplicate, this.indexableDomain.numbers)
|
|
688
|
+
: rangesToReplicate[0];
|
|
689
|
+
|
|
690
|
+
// also merge segments that are already in the index
|
|
691
|
+
if (this.domain.canMerge) {
|
|
692
|
+
const mergable = await getAllMergeCandiates(
|
|
693
|
+
this.replicationIndex,
|
|
694
|
+
range,
|
|
695
|
+
this.indexableDomain.numbers,
|
|
696
|
+
);
|
|
697
|
+
const mergeableFiltered: ReplicationRangeIndexable<R>[] = [range];
|
|
698
|
+
|
|
699
|
+
for (const mergeCandidate of mergable) {
|
|
700
|
+
if (this.domain.canMerge(mergeCandidate, range)) {
|
|
701
|
+
mergeableFiltered.push(mergeCandidate);
|
|
702
|
+
if (mergeCandidate.idString !== range.idString) {
|
|
703
|
+
rangesToUnreplicate.push(mergeCandidate);
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
if (mergeableFiltered.length > 1) {
|
|
708
|
+
range = mergeRanges(
|
|
709
|
+
mergeableFiltered,
|
|
710
|
+
this.indexableDomain.numbers,
|
|
711
|
+
);
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
rangesToReplicate = [range];
|
|
703
715
|
}
|
|
704
716
|
}
|
|
705
717
|
|
|
706
|
-
for (const range of
|
|
718
|
+
for (const range of rangesToReplicate) {
|
|
707
719
|
this.oldestOpenTime = Math.min(
|
|
708
720
|
Number(range.timestamp),
|
|
709
721
|
this.oldestOpenTime,
|
|
@@ -717,14 +729,31 @@ export class SharedLog<
|
|
|
717
729
|
// but ({ replicate: 0.5, offset: 0.5 }) means that we want to add a range
|
|
718
730
|
// TODO make behaviour more clear
|
|
719
731
|
}
|
|
720
|
-
|
|
732
|
+
if (rangesToUnreplicate.length > 0) {
|
|
733
|
+
await this.removeReplicationRanges(
|
|
734
|
+
rangesToUnreplicate,
|
|
735
|
+
this.node.identity.publicKey,
|
|
736
|
+
);
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
await this.startAnnounceReplicating(rangesToReplicate, {
|
|
721
740
|
reset: resetRanges ?? false,
|
|
722
741
|
checkDuplicates,
|
|
723
742
|
announce,
|
|
724
|
-
syncStatus,
|
|
725
743
|
});
|
|
726
744
|
|
|
727
|
-
|
|
745
|
+
if (rangesToUnreplicate.length > 0) {
|
|
746
|
+
await this.rpc.send(
|
|
747
|
+
new StoppedReplicating({
|
|
748
|
+
segmentIds: rangesToUnreplicate.map((x) => x.id),
|
|
749
|
+
}),
|
|
750
|
+
{
|
|
751
|
+
priority: 1,
|
|
752
|
+
},
|
|
753
|
+
);
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
return rangesToReplicate;
|
|
728
757
|
}
|
|
729
758
|
}
|
|
730
759
|
|
|
@@ -773,7 +802,6 @@ export class SharedLog<
|
|
|
773
802
|
| ReplicationRangeMessage<any>[]
|
|
774
803
|
| ReplicationOptions<R>
|
|
775
804
|
| undefined = undefined;
|
|
776
|
-
let syncStatus = SyncStatus.Unsynced;
|
|
777
805
|
|
|
778
806
|
if (rangeOrEntry instanceof ReplicationRangeMessage) {
|
|
779
807
|
range = rangeOrEntry;
|
|
@@ -783,7 +811,6 @@ export class SharedLog<
|
|
|
783
811
|
offset: await this.domain.fromEntry(rangeOrEntry),
|
|
784
812
|
normalized: false,
|
|
785
813
|
};
|
|
786
|
-
syncStatus = SyncStatus.Synced; /// we already have the entries
|
|
787
814
|
} else if (Array.isArray(rangeOrEntry)) {
|
|
788
815
|
let ranges: (ReplicationRangeMessage<any> | FixedReplicationOptions)[] =
|
|
789
816
|
[];
|
|
@@ -793,9 +820,8 @@ export class SharedLog<
|
|
|
793
820
|
factor: 1,
|
|
794
821
|
offset: await this.domain.fromEntry(entry),
|
|
795
822
|
normalized: false,
|
|
823
|
+
strict: true,
|
|
796
824
|
});
|
|
797
|
-
|
|
798
|
-
syncStatus = SyncStatus.Synced; /// we already have the entries
|
|
799
825
|
} else {
|
|
800
826
|
ranges.push(entry);
|
|
801
827
|
}
|
|
@@ -805,18 +831,34 @@ export class SharedLog<
|
|
|
805
831
|
range = rangeOrEntry ?? true;
|
|
806
832
|
}
|
|
807
833
|
|
|
808
|
-
return this._replicate(range,
|
|
834
|
+
return this._replicate(range, options);
|
|
809
835
|
}
|
|
810
836
|
|
|
811
|
-
async unreplicate(rangeOrEntry?: Entry<T> |
|
|
812
|
-
let
|
|
837
|
+
async unreplicate(rangeOrEntry?: Entry<T> | { id: Uint8Array }[]) {
|
|
838
|
+
let segmentIds: Uint8Array<ArrayBufferLike>[];
|
|
813
839
|
if (rangeOrEntry instanceof Entry) {
|
|
814
|
-
range = {
|
|
840
|
+
let range: FixedReplicationOptions = {
|
|
815
841
|
factor: 1,
|
|
816
842
|
offset: await this.domain.fromEntry(rangeOrEntry),
|
|
817
843
|
};
|
|
818
|
-
|
|
819
|
-
|
|
844
|
+
const indexed = this.replicationIndex.iterate({
|
|
845
|
+
query: {
|
|
846
|
+
width: 1,
|
|
847
|
+
start1: range.offset /* ,
|
|
848
|
+
hash: this.node.identity.publicKey.hashcode(), */,
|
|
849
|
+
},
|
|
850
|
+
});
|
|
851
|
+
segmentIds = (await indexed.all()).map((x) => x.id.key as Uint8Array);
|
|
852
|
+
if (segmentIds.length === 0) {
|
|
853
|
+
logger.warn("No segment found to unreplicate");
|
|
854
|
+
return;
|
|
855
|
+
}
|
|
856
|
+
} else if (Array.isArray(rangeOrEntry)) {
|
|
857
|
+
segmentIds = rangeOrEntry.map((x) => x.id);
|
|
858
|
+
if (segmentIds.length === 0) {
|
|
859
|
+
logger.warn("No segment found to unreplicate");
|
|
860
|
+
return;
|
|
861
|
+
}
|
|
820
862
|
} else {
|
|
821
863
|
this._isReplicating = false;
|
|
822
864
|
this._isAdaptiveReplicating = false;
|
|
@@ -830,15 +872,14 @@ export class SharedLog<
|
|
|
830
872
|
throw new Error("Unsupported when adaptive replicating");
|
|
831
873
|
}
|
|
832
874
|
|
|
833
|
-
const
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
await this.removeReplicationRange(segmentIds, this.node.identity.publicKey);
|
|
875
|
+
const rangesToRemove = await this.resolveReplicationRangesFromIdsAndKey(
|
|
876
|
+
segmentIds,
|
|
877
|
+
this.node.identity.publicKey,
|
|
878
|
+
);
|
|
879
|
+
await this.removeReplicationRanges(
|
|
880
|
+
rangesToRemove,
|
|
881
|
+
this.node.identity.publicKey,
|
|
882
|
+
);
|
|
842
883
|
await this.rpc.send(new StoppedReplicating({ segmentIds }), {
|
|
843
884
|
priority: 1,
|
|
844
885
|
});
|
|
@@ -881,10 +922,12 @@ export class SharedLog<
|
|
|
881
922
|
}
|
|
882
923
|
}
|
|
883
924
|
|
|
925
|
+
const timestamp = BigInt(+new Date());
|
|
884
926
|
for (const x of deleted) {
|
|
885
927
|
this.replicationChangeDebounceFn.add({
|
|
886
928
|
range: x.value,
|
|
887
929
|
type: "removed",
|
|
930
|
+
timestamp,
|
|
888
931
|
});
|
|
889
932
|
}
|
|
890
933
|
|
|
@@ -917,7 +960,10 @@ export class SharedLog<
|
|
|
917
960
|
: +new Date();
|
|
918
961
|
}
|
|
919
962
|
|
|
920
|
-
private async
|
|
963
|
+
private async resolveReplicationRangesFromIdsAndKey(
|
|
964
|
+
ids: Uint8Array[],
|
|
965
|
+
from: PublicSignKey,
|
|
966
|
+
) {
|
|
921
967
|
let idMatcher = new Or(
|
|
922
968
|
ids.map((x) => new ByteMatchQuery({ key: "id", value: x })),
|
|
923
969
|
);
|
|
@@ -929,10 +975,17 @@ export class SharedLog<
|
|
|
929
975
|
});
|
|
930
976
|
|
|
931
977
|
let query = new And([idMatcher, identityMatcher]);
|
|
932
|
-
|
|
978
|
+
return (await this.replicationIndex.iterate({ query }).all()).map(
|
|
979
|
+
(x) => x.value,
|
|
980
|
+
);
|
|
981
|
+
}
|
|
982
|
+
private async removeReplicationRanges(
|
|
983
|
+
ranges: ReplicationRangeIndexable<R>[],
|
|
984
|
+
from: PublicSignKey,
|
|
985
|
+
) {
|
|
933
986
|
const pendingMaturity = this.pendingMaturity.get(from.hashcode());
|
|
934
987
|
if (pendingMaturity) {
|
|
935
|
-
for (const id of
|
|
988
|
+
for (const id of ranges) {
|
|
936
989
|
const info = pendingMaturity.get(id.toString());
|
|
937
990
|
if (info) {
|
|
938
991
|
clearTimeout(info.timeout);
|
|
@@ -944,7 +997,11 @@ export class SharedLog<
|
|
|
944
997
|
}
|
|
945
998
|
}
|
|
946
999
|
|
|
947
|
-
await this.replicationIndex.del({
|
|
1000
|
+
await this.replicationIndex.del({
|
|
1001
|
+
query: new Or(
|
|
1002
|
+
ranges.map((x) => new ByteMatchQuery({ key: "id", value: x.id })),
|
|
1003
|
+
),
|
|
1004
|
+
});
|
|
948
1005
|
|
|
949
1006
|
const otherSegmentsIterator = this.replicationIndex.iterate(
|
|
950
1007
|
{ query: { hash: from.hashcode() } },
|
|
@@ -974,15 +1031,17 @@ export class SharedLog<
|
|
|
974
1031
|
{
|
|
975
1032
|
reset,
|
|
976
1033
|
checkDuplicates,
|
|
977
|
-
|
|
1034
|
+
timestamp: ts,
|
|
1035
|
+
}: { reset?: boolean; checkDuplicates?: boolean; timestamp?: number } = {},
|
|
978
1036
|
) {
|
|
979
1037
|
if (this._isTrustedReplicator && !(await this._isTrustedReplicator(from))) {
|
|
980
1038
|
return undefined;
|
|
981
1039
|
}
|
|
982
1040
|
let isNewReplicator = false;
|
|
1041
|
+
let timestamp = BigInt(ts ?? +new Date());
|
|
983
1042
|
|
|
984
|
-
let diffs: ReplicationChanges
|
|
985
|
-
let deleted: ReplicationRangeIndexable<
|
|
1043
|
+
let diffs: ReplicationChanges<ReplicationRangeIndexable<R>>;
|
|
1044
|
+
let deleted: ReplicationRangeIndexable<R>[] | undefined = undefined;
|
|
986
1045
|
if (reset) {
|
|
987
1046
|
deleted = (
|
|
988
1047
|
await this.replicationIndex
|
|
@@ -998,10 +1057,10 @@ export class SharedLog<
|
|
|
998
1057
|
|
|
999
1058
|
diffs = [
|
|
1000
1059
|
...deleted.map((x) => {
|
|
1001
|
-
return { range: x, type: "removed" as const };
|
|
1060
|
+
return { range: x, type: "removed" as const, timestamp };
|
|
1002
1061
|
}),
|
|
1003
1062
|
...ranges.map((x) => {
|
|
1004
|
-
return { range: x, type: "added" as const };
|
|
1063
|
+
return { range: x, type: "added" as const, timestamp };
|
|
1005
1064
|
}),
|
|
1006
1065
|
];
|
|
1007
1066
|
|
|
@@ -1031,7 +1090,9 @@ export class SharedLog<
|
|
|
1031
1090
|
|
|
1032
1091
|
// TODO also deduplicate/de-overlap among the ranges that ought to be inserted?
|
|
1033
1092
|
for (const range of ranges) {
|
|
1034
|
-
if (
|
|
1093
|
+
if (
|
|
1094
|
+
!(await countCoveringRangesSameOwner(this.replicationIndex, range))
|
|
1095
|
+
) {
|
|
1035
1096
|
deduplicated.push(range);
|
|
1036
1097
|
}
|
|
1037
1098
|
}
|
|
@@ -1042,19 +1103,31 @@ export class SharedLog<
|
|
|
1042
1103
|
existingMap.set(result.value.idString, result.value);
|
|
1043
1104
|
}
|
|
1044
1105
|
|
|
1045
|
-
let changes: ReplicationChanges = ranges
|
|
1106
|
+
let changes: ReplicationChanges<ReplicationRangeIndexable<R>> = ranges
|
|
1046
1107
|
.map((x) => {
|
|
1047
1108
|
const prev = existingMap.get(x.idString);
|
|
1048
1109
|
if (prev) {
|
|
1049
1110
|
if (prev.equalRange(x)) {
|
|
1050
|
-
return
|
|
1111
|
+
return [];
|
|
1051
1112
|
}
|
|
1052
|
-
return
|
|
1113
|
+
return [
|
|
1114
|
+
{
|
|
1115
|
+
range: prev,
|
|
1116
|
+
timestamp: x.timestamp - 1n,
|
|
1117
|
+
prev,
|
|
1118
|
+
type: "replaced" as const,
|
|
1119
|
+
},
|
|
1120
|
+
{
|
|
1121
|
+
range: x,
|
|
1122
|
+
timestamp: x.timestamp,
|
|
1123
|
+
type: "added" as const,
|
|
1124
|
+
},
|
|
1125
|
+
];
|
|
1053
1126
|
} else {
|
|
1054
|
-
return { range: x, type: "added" };
|
|
1127
|
+
return { range: x, timestamp: x.timestamp, type: "added" as const };
|
|
1055
1128
|
}
|
|
1056
1129
|
})
|
|
1057
|
-
.
|
|
1130
|
+
.flat() as ReplicationChanges<ReplicationRangeIndexable<R>>;
|
|
1058
1131
|
diffs = changes;
|
|
1059
1132
|
}
|
|
1060
1133
|
|
|
@@ -1065,7 +1138,7 @@ export class SharedLog<
|
|
|
1065
1138
|
let isAllMature = true;
|
|
1066
1139
|
|
|
1067
1140
|
for (const diff of diffs) {
|
|
1068
|
-
if (diff.type === "added"
|
|
1141
|
+
if (diff.type === "added") {
|
|
1069
1142
|
/* if (this.closed) {
|
|
1070
1143
|
return;
|
|
1071
1144
|
} */
|
|
@@ -1104,7 +1177,7 @@ export class SharedLog<
|
|
|
1104
1177
|
}),
|
|
1105
1178
|
);
|
|
1106
1179
|
|
|
1107
|
-
this.replicationChangeDebounceFn.add(diff); // we need to call this here because the outcom of findLeaders will be different when some ranges become mature, i.e. some of data we own might be prunable!
|
|
1180
|
+
this.replicationChangeDebounceFn.add({ ...diff, matured: true }); // we need to call this here because the outcom of findLeaders will be different when some ranges become mature, i.e. some of data we own might be prunable!
|
|
1108
1181
|
pendingRanges.delete(diff.range.idString);
|
|
1109
1182
|
if (pendingRanges.size === 0) {
|
|
1110
1183
|
this.pendingMaturity.delete(diff.range.hash);
|
|
@@ -1123,7 +1196,7 @@ export class SharedLog<
|
|
|
1123
1196
|
});
|
|
1124
1197
|
}
|
|
1125
1198
|
}
|
|
1126
|
-
} else {
|
|
1199
|
+
} else if (diff.type === "removed") {
|
|
1127
1200
|
const pendingFromPeer = this.pendingMaturity.get(diff.range.hash);
|
|
1128
1201
|
if (pendingFromPeer) {
|
|
1129
1202
|
const prev = pendingFromPeer.get(diff.range.idString);
|
|
@@ -1136,6 +1209,7 @@ export class SharedLog<
|
|
|
1136
1209
|
}
|
|
1137
1210
|
}
|
|
1138
1211
|
}
|
|
1212
|
+
// else replaced, do nothing
|
|
1139
1213
|
}
|
|
1140
1214
|
|
|
1141
1215
|
if (reset) {
|
|
@@ -1394,10 +1468,10 @@ export class SharedLog<
|
|
|
1394
1468
|
|
|
1395
1469
|
// TODO types
|
|
1396
1470
|
this.domain = options?.domain
|
|
1397
|
-
? (options.domain as
|
|
1471
|
+
? (options.domain(this) as D)
|
|
1398
1472
|
: (createReplicationDomainHash(
|
|
1399
1473
|
options?.compatibility && options?.compatibility < 10 ? "u32" : "u64",
|
|
1400
|
-
) as D);
|
|
1474
|
+
)(this) as D);
|
|
1401
1475
|
this.indexableDomain = createIndexableDomainFromResolution(
|
|
1402
1476
|
this.domain.resolution,
|
|
1403
1477
|
);
|
|
@@ -1406,6 +1480,7 @@ export class SharedLog<
|
|
|
1406
1480
|
this._pendingIHave = new Map();
|
|
1407
1481
|
this.latestReplicationInfoMessage = new Map();
|
|
1408
1482
|
this.coordinateToHash = new Cache<string>({ max: 1e6, ttl: 1e4 });
|
|
1483
|
+
this.recentlyRebalanced = new Cache<string>({ max: 1e4, ttl: 1e5 });
|
|
1409
1484
|
|
|
1410
1485
|
this.uniqueReplicators = new Set();
|
|
1411
1486
|
|
|
@@ -1476,7 +1551,9 @@ export class SharedLog<
|
|
|
1476
1551
|
this._requestIPruneSent = new Map();
|
|
1477
1552
|
this._requestIPruneResponseReplicatorSet = new Map();
|
|
1478
1553
|
|
|
1479
|
-
this.replicationChangeDebounceFn = debounceAggregationChanges
|
|
1554
|
+
this.replicationChangeDebounceFn = debounceAggregationChanges<
|
|
1555
|
+
ReplicationRangeIndexable<R>
|
|
1556
|
+
>(
|
|
1480
1557
|
(change) =>
|
|
1481
1558
|
this.onReplicationChange(change).then(() =>
|
|
1482
1559
|
this.rebalanceParticipationDebounced?.(),
|
|
@@ -1800,17 +1877,18 @@ export class SharedLog<
|
|
|
1800
1877
|
) {
|
|
1801
1878
|
let roleAge = options?.roleAge ?? (await this.getDefaultMinRoleAge());
|
|
1802
1879
|
let eager = options?.eager ?? false;
|
|
1803
|
-
const range = await this.domain.fromArgs(args
|
|
1804
|
-
|
|
1880
|
+
const range = await this.domain.fromArgs(args);
|
|
1881
|
+
|
|
1882
|
+
const width =
|
|
1883
|
+
range.length ??
|
|
1884
|
+
(await minimumWidthToCover<R>(
|
|
1885
|
+
this.replicas.min.getValue(this),
|
|
1886
|
+
this.indexableDomain.numbers,
|
|
1887
|
+
));
|
|
1805
1888
|
const set = await getCoverSet<R>({
|
|
1806
1889
|
peers: this.replicationIndex,
|
|
1807
1890
|
start: range.offset,
|
|
1808
|
-
widthToCoverScaled:
|
|
1809
|
-
range.length ??
|
|
1810
|
-
(await minimumWidthToCover<R>(
|
|
1811
|
-
this.replicas.min.getValue(this),
|
|
1812
|
-
this.indexableDomain.numbers,
|
|
1813
|
-
)),
|
|
1891
|
+
widthToCoverScaled: width,
|
|
1814
1892
|
roleAge,
|
|
1815
1893
|
eager,
|
|
1816
1894
|
numbers: this.indexableDomain.numbers,
|
|
@@ -1837,6 +1915,7 @@ export class SharedLog<
|
|
|
1837
1915
|
|
|
1838
1916
|
this.distributeQueue?.clear();
|
|
1839
1917
|
this.coordinateToHash.clear();
|
|
1918
|
+
this.recentlyRebalanced.clear();
|
|
1840
1919
|
this.uniqueReplicators.clear();
|
|
1841
1920
|
|
|
1842
1921
|
this._closeController.abort();
|
|
@@ -2378,7 +2457,11 @@ export class SharedLog<
|
|
|
2378
2457
|
x.toReplicationRangeIndexable(context.from!),
|
|
2379
2458
|
),
|
|
2380
2459
|
context.from!,
|
|
2381
|
-
{
|
|
2460
|
+
{
|
|
2461
|
+
reset,
|
|
2462
|
+
checkDuplicates: true,
|
|
2463
|
+
timestamp: Number(context.timestamp),
|
|
2464
|
+
},
|
|
2382
2465
|
);
|
|
2383
2466
|
|
|
2384
2467
|
/* await this._modifyReplicators(msg.role, context.from!); */
|
|
@@ -2403,7 +2486,19 @@ export class SharedLog<
|
|
|
2403
2486
|
return;
|
|
2404
2487
|
}
|
|
2405
2488
|
|
|
2406
|
-
await this.
|
|
2489
|
+
const rangesToRemove = await this.resolveReplicationRangesFromIdsAndKey(
|
|
2490
|
+
msg.segmentIds,
|
|
2491
|
+
context.from,
|
|
2492
|
+
);
|
|
2493
|
+
await this.removeReplicationRanges(rangesToRemove, context.from);
|
|
2494
|
+
const timestamp = BigInt(+new Date());
|
|
2495
|
+
for (const range of rangesToRemove) {
|
|
2496
|
+
this.replicationChangeDebounceFn.add({
|
|
2497
|
+
range,
|
|
2498
|
+
type: "removed",
|
|
2499
|
+
timestamp,
|
|
2500
|
+
});
|
|
2501
|
+
}
|
|
2407
2502
|
} else {
|
|
2408
2503
|
throw new Error("Unexpected message");
|
|
2409
2504
|
}
|
|
@@ -2563,6 +2658,7 @@ export class SharedLog<
|
|
|
2563
2658
|
| boolean
|
|
2564
2659
|
| {
|
|
2565
2660
|
mergeSegments?: boolean;
|
|
2661
|
+
assumeSynced?: boolean;
|
|
2566
2662
|
};
|
|
2567
2663
|
},
|
|
2568
2664
|
): Promise<void> {
|
|
@@ -2605,11 +2701,20 @@ export class SharedLog<
|
|
|
2605
2701
|
|
|
2606
2702
|
const persistCoordinate = async (entry: Entry<T>) => {
|
|
2607
2703
|
const minReplicas = decodeReplicas(entry).getValue(this);
|
|
2608
|
-
await this.findLeaders(
|
|
2704
|
+
const leaders = await this.findLeaders(
|
|
2609
2705
|
await this.createCoordinates(entry, minReplicas),
|
|
2610
2706
|
entry,
|
|
2611
2707
|
{ persist: {} },
|
|
2612
2708
|
);
|
|
2709
|
+
|
|
2710
|
+
if (
|
|
2711
|
+
options?.replicate &&
|
|
2712
|
+
typeof options.replicate !== "boolean" &&
|
|
2713
|
+
options.replicate.assumeSynced
|
|
2714
|
+
) {
|
|
2715
|
+
// make sure we dont start to initate syncing process outwards for this entry
|
|
2716
|
+
this.addPeersToGidPeerHistory(entry.meta.gid, leaders.keys());
|
|
2717
|
+
}
|
|
2613
2718
|
};
|
|
2614
2719
|
let entriesToPersist: Entry<T>[] = [];
|
|
2615
2720
|
let joinOptions = {
|
|
@@ -3468,9 +3573,10 @@ export class SharedLog<
|
|
|
3468
3573
|
this._gidPeersHistory.clear();
|
|
3469
3574
|
}
|
|
3470
3575
|
|
|
3576
|
+
const timestamp = BigInt(+new Date());
|
|
3471
3577
|
this.onReplicationChange(
|
|
3472
3578
|
(await this.getAllReplicationSegments()).map((x) => {
|
|
3473
|
-
return { range: x, type: "added" };
|
|
3579
|
+
return { range: x, type: "added", timestamp };
|
|
3474
3580
|
}),
|
|
3475
3581
|
);
|
|
3476
3582
|
}
|
|
@@ -3480,7 +3586,9 @@ export class SharedLog<
|
|
|
3480
3586
|
}
|
|
3481
3587
|
|
|
3482
3588
|
async onReplicationChange(
|
|
3483
|
-
changeOrChanges:
|
|
3589
|
+
changeOrChanges:
|
|
3590
|
+
| ReplicationChanges<ReplicationRangeIndexable<R>>
|
|
3591
|
+
| ReplicationChanges<ReplicationRangeIndexable<R>>[],
|
|
3484
3592
|
) {
|
|
3485
3593
|
/**
|
|
3486
3594
|
* TODO use information of new joined/leaving peer to create a subset of heads
|
|
@@ -3493,7 +3601,6 @@ export class SharedLog<
|
|
|
3493
3601
|
|
|
3494
3602
|
await this.log.trim();
|
|
3495
3603
|
|
|
3496
|
-
const change = mergeReplicationChanges(changeOrChanges);
|
|
3497
3604
|
const changed = false;
|
|
3498
3605
|
|
|
3499
3606
|
try {
|
|
@@ -3503,13 +3610,13 @@ export class SharedLog<
|
|
|
3503
3610
|
> = new Map();
|
|
3504
3611
|
|
|
3505
3612
|
for await (const entryReplicated of toRebalance<R>(
|
|
3506
|
-
|
|
3613
|
+
changeOrChanges,
|
|
3507
3614
|
this.entryCoordinatesIndex,
|
|
3615
|
+
this.recentlyRebalanced,
|
|
3508
3616
|
)) {
|
|
3509
3617
|
if (this.closed) {
|
|
3510
3618
|
break;
|
|
3511
3619
|
}
|
|
3512
|
-
|
|
3513
3620
|
let oldPeersSet = this._gidPeersHistory.get(entryReplicated.gid);
|
|
3514
3621
|
let isLeader = false;
|
|
3515
3622
|
|
|
@@ -3660,7 +3767,7 @@ export class SharedLog<
|
|
|
3660
3767
|
// TODO can not reuse old range, since it will (potentially) affect the index because of sideeffects
|
|
3661
3768
|
dynamicRange = new this.indexableDomain.constructorRange({
|
|
3662
3769
|
offset: dynamicRange.start1,
|
|
3663
|
-
|
|
3770
|
+
width: this.indexableDomain.numbers.denormalize(newFactor),
|
|
3664
3771
|
publicKeyHash: dynamicRange.hash,
|
|
3665
3772
|
id: dynamicRange.id,
|
|
3666
3773
|
mode: dynamicRange.mode,
|
|
@@ -3729,7 +3836,7 @@ export class SharedLog<
|
|
|
3729
3836
|
if (!range) {
|
|
3730
3837
|
range = new this.indexableDomain.constructorRange({
|
|
3731
3838
|
offset: this.getDynamicRangeOffset(),
|
|
3732
|
-
|
|
3839
|
+
width: this.indexableDomain.numbers.zero,
|
|
3733
3840
|
publicKeyHash: this.node.identity.publicKey.hashcode(),
|
|
3734
3841
|
mode: ReplicationIntent.NonStrict,
|
|
3735
3842
|
timestamp: BigInt(+new Date()),
|