@peerbit/shared-log 9.0.10 → 9.1.0-57b8640
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/benchmark/index.js +2 -2
- package/dist/benchmark/index.js.map +1 -1
- package/dist/benchmark/replication.js +3 -3
- package/dist/benchmark/replication.js.map +1 -1
- package/dist/src/index.d.ts +51 -31
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +435 -230
- package/dist/src/index.js.map +1 -1
- package/dist/src/pid.d.ts.map +1 -1
- package/dist/src/pid.js +20 -19
- package/dist/src/pid.js.map +1 -1
- package/dist/src/ranges.d.ts +22 -3
- package/dist/src/ranges.d.ts.map +1 -1
- package/dist/src/ranges.js +231 -336
- package/dist/src/ranges.js.map +1 -1
- package/dist/src/replication-domain-hash.d.ts +5 -0
- package/dist/src/replication-domain-hash.d.ts.map +1 -0
- package/dist/src/replication-domain-hash.js +30 -0
- package/dist/src/replication-domain-hash.js.map +1 -0
- package/dist/src/replication-domain-time.d.ts +14 -0
- package/dist/src/replication-domain-time.d.ts.map +1 -0
- package/dist/src/replication-domain-time.js +59 -0
- package/dist/src/replication-domain-time.js.map +1 -0
- package/dist/src/replication-domain.d.ts +33 -0
- package/dist/src/replication-domain.d.ts.map +1 -0
- package/dist/src/replication-domain.js +6 -0
- package/dist/src/replication-domain.js.map +1 -0
- package/dist/src/replication.d.ts +10 -8
- package/dist/src/replication.d.ts.map +1 -1
- package/dist/src/replication.js +64 -46
- package/dist/src/replication.js.map +1 -1
- package/dist/src/role.d.ts +2 -1
- package/dist/src/role.d.ts.map +1 -1
- package/dist/src/role.js +6 -5
- package/dist/src/role.js.map +1 -1
- package/package.json +70 -70
- package/src/index.ts +621 -312
- package/src/pid.ts +20 -19
- package/src/ranges.ts +328 -375
- package/src/replication-domain-hash.ts +43 -0
- package/src/replication-domain-time.ts +85 -0
- package/src/replication-domain.ts +50 -0
- package/src/replication.ts +50 -46
- package/src/role.ts +6 -5
package/src/ranges.ts
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { PublicSignKey, equals } from "@peerbit/crypto";
|
|
2
2
|
import {
|
|
3
3
|
And,
|
|
4
|
+
ByteMatchQuery,
|
|
4
5
|
Compare,
|
|
5
6
|
type Index,
|
|
6
7
|
type IndexIterator,
|
|
7
8
|
type IndexedResult,
|
|
8
9
|
type IndexedResults,
|
|
9
10
|
IntegerCompare,
|
|
11
|
+
Not,
|
|
10
12
|
Or,
|
|
11
13
|
type Query,
|
|
12
14
|
SearchRequest,
|
|
@@ -16,95 +18,12 @@ import {
|
|
|
16
18
|
iterate,
|
|
17
19
|
iteratorInSeries,
|
|
18
20
|
} from "@peerbit/indexer-interface";
|
|
21
|
+
import type { u32 } from "./replication-domain.js";
|
|
19
22
|
import {
|
|
20
|
-
|
|
23
|
+
ReplicationIntent,
|
|
21
24
|
type ReplicationRangeIndexable,
|
|
22
25
|
} from "./replication.js";
|
|
23
|
-
import {
|
|
24
|
-
|
|
25
|
-
/*
|
|
26
|
-
export const containsPoint = (
|
|
27
|
-
rect: { offset: number; length: number },
|
|
28
|
-
point: number,
|
|
29
|
-
eps = 0.00001 // we do this to handle numerical errors
|
|
30
|
-
) => {
|
|
31
|
-
if (rect.factor === 0) {
|
|
32
|
-
return false;
|
|
33
|
-
}
|
|
34
|
-
const start = rect.offset;
|
|
35
|
-
const width = rect.factor + eps; // we do this to handle numerical errors. It is better to be more inclusive
|
|
36
|
-
const endUnwrapped = rect.offset + width;
|
|
37
|
-
let end = endUnwrapped;
|
|
38
|
-
let wrapped = false;
|
|
39
|
-
if (endUnwrapped > 1) {
|
|
40
|
-
end = endUnwrapped % 1;
|
|
41
|
-
wrapped = true;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
const inFirstInterval = point >= start && point < Math.min(endUnwrapped, 1);
|
|
45
|
-
const inSecondInterval =
|
|
46
|
-
!inFirstInterval && wrapped && point >= 0 && point < end;
|
|
47
|
-
|
|
48
|
-
return inFirstInterval || inSecondInterval;
|
|
49
|
-
}; */
|
|
50
|
-
|
|
51
|
-
/* const resolveRectsThatContainPoint = async (
|
|
52
|
-
rects: Index<ReplicationRangeIndexable>,
|
|
53
|
-
point: number,
|
|
54
|
-
roleAgeLimit: number,
|
|
55
|
-
matured: boolean
|
|
56
|
-
): Promise<ReplicationRangeIndexable[]> => {
|
|
57
|
-
// point is between 0 and 1, and the range can start at any offset between 0 and 1 and have length between 0 and 1
|
|
58
|
-
// so we need to query for all ranges that contain the point
|
|
59
|
-
const scaledPoint = Math.round(point * SEGMENT_COORDINATE_SCALE)
|
|
60
|
-
let queries = [
|
|
61
|
-
new IntegerCompare({ key: 'start', compare: Compare.LessOrEqual, value: scaledPoint }),
|
|
62
|
-
new IntegerCompare({ key: 'end', compare: Compare.Greater, value: scaledPoint }),
|
|
63
|
-
new IntegerCompare({ key: 'timestamp', compare: matured ? Compare.LessOrEqual : Compare.Greater, value: Date.now() - roleAgeLimit })
|
|
64
|
-
]
|
|
65
|
-
|
|
66
|
-
const results = await rects.query(new SearchRequest({
|
|
67
|
-
query: [
|
|
68
|
-
new Nested({
|
|
69
|
-
path: 'segments',
|
|
70
|
-
query: queries
|
|
71
|
-
})
|
|
72
|
-
]
|
|
73
|
-
}))
|
|
74
|
-
return results.results.map(x => x.value)
|
|
75
|
-
} */
|
|
76
|
-
|
|
77
|
-
/* const resolveRectsInRange = async (rects: Index<ReplicationRangeIndexable>,
|
|
78
|
-
start: number,
|
|
79
|
-
end: number,
|
|
80
|
-
roleAgeLimit: number,
|
|
81
|
-
matured: boolean
|
|
82
|
-
): Promise<ReplicationRangeIndexable[]> => {
|
|
83
|
-
// point is between 0 and 1, and the range can start at any offset between 0 and 1 and have length between 0 and 1
|
|
84
|
-
// so we need to query for all ranges that contain the point
|
|
85
|
-
let endScaled = Math.round(end * SEGMENT_COORDINATE_SCALE);
|
|
86
|
-
let startScaled = Math.round(start * SEGMENT_COORDINATE_SCALE);
|
|
87
|
-
let queries = [
|
|
88
|
-
new Or([
|
|
89
|
-
new And([
|
|
90
|
-
new IntegerCompare({ key: 'start1', compare: Compare.Less, value: endScaled }),
|
|
91
|
-
new IntegerCompare({ key: 'end1', compare: Compare.GreaterOrEqual, value: startScaled }),
|
|
92
|
-
]),
|
|
93
|
-
new And([
|
|
94
|
-
new IntegerCompare({ key: 'start2', compare: Compare.Less, value: endScaled }),
|
|
95
|
-
new IntegerCompare({ key: 'end2', compare: Compare.GreaterOrEqual, value: startScaled }),
|
|
96
|
-
])
|
|
97
|
-
]),
|
|
98
|
-
new IntegerCompare({ key: 'timestamp', compare: matured ? Compare.LessOrEqual : Compare.Greater, value: BigInt(+new Date - roleAgeLimit) })
|
|
99
|
-
]
|
|
100
|
-
|
|
101
|
-
const results = await rects.query(new SearchRequest({
|
|
102
|
-
query: queries,
|
|
103
|
-
sort: [new Sort({ key: "start1" }), new Sort({ key: "start2" })],
|
|
104
|
-
fetch: 0xffffffff
|
|
105
|
-
}))
|
|
106
|
-
return results.results.map(x => x.value)
|
|
107
|
-
} */
|
|
26
|
+
import { MAX_U32, scaleToU32 } from "./role.js";
|
|
108
27
|
|
|
109
28
|
const containingPoint = (
|
|
110
29
|
rects: Index<ReplicationRangeIndexable>,
|
|
@@ -114,38 +33,34 @@ const containingPoint = (
|
|
|
114
33
|
now: number,
|
|
115
34
|
options?: {
|
|
116
35
|
sort?: Sort[];
|
|
117
|
-
scaled?: boolean;
|
|
118
36
|
},
|
|
119
37
|
): IndexIterator<ReplicationRangeIndexable> => {
|
|
120
38
|
// point is between 0 and 1, and the range can start at any offset between 0 and 1 and have length between 0 and 1
|
|
121
|
-
|
|
122
|
-
let pointScaled = Math.round(
|
|
123
|
-
point * (options?.scaled ? 1 : SEGMENT_COORDINATE_SCALE),
|
|
124
|
-
);
|
|
39
|
+
|
|
125
40
|
let queries = [
|
|
126
41
|
new Or([
|
|
127
42
|
new And([
|
|
128
43
|
new IntegerCompare({
|
|
129
44
|
key: "start1",
|
|
130
45
|
compare: Compare.LessOrEqual,
|
|
131
|
-
value:
|
|
46
|
+
value: point,
|
|
132
47
|
}),
|
|
133
48
|
new IntegerCompare({
|
|
134
49
|
key: "end1",
|
|
135
50
|
compare: Compare.Greater,
|
|
136
|
-
value:
|
|
51
|
+
value: point,
|
|
137
52
|
}),
|
|
138
53
|
]),
|
|
139
54
|
new And([
|
|
140
55
|
new IntegerCompare({
|
|
141
56
|
key: "start2",
|
|
142
57
|
compare: Compare.LessOrEqual,
|
|
143
|
-
value:
|
|
58
|
+
value: point,
|
|
144
59
|
}),
|
|
145
60
|
new IntegerCompare({
|
|
146
61
|
key: "end2",
|
|
147
62
|
compare: Compare.Greater,
|
|
148
|
-
value:
|
|
63
|
+
value: point,
|
|
149
64
|
}),
|
|
150
65
|
]),
|
|
151
66
|
]),
|
|
@@ -178,11 +93,8 @@ const getClosest = (
|
|
|
178
93
|
roleAgeLimit: number,
|
|
179
94
|
matured: boolean,
|
|
180
95
|
now: number,
|
|
181
|
-
|
|
96
|
+
includeStrict: boolean,
|
|
182
97
|
): IndexIterator<ReplicationRangeIndexable> => {
|
|
183
|
-
const scaledPoint = Math.round(
|
|
184
|
-
point * (scaled ? 1 : SEGMENT_COORDINATE_SCALE),
|
|
185
|
-
);
|
|
186
98
|
const createQueries = (p: number, equality: boolean) => {
|
|
187
99
|
let queries: Query[];
|
|
188
100
|
if (direction === "below") {
|
|
@@ -215,13 +127,23 @@ const getClosest = (
|
|
|
215
127
|
queries.push(
|
|
216
128
|
new IntegerCompare({ key: "width", compare: Compare.Greater, value: 0 }),
|
|
217
129
|
);
|
|
130
|
+
|
|
131
|
+
if (!includeStrict) {
|
|
132
|
+
queries.push(
|
|
133
|
+
new IntegerCompare({
|
|
134
|
+
key: "mode",
|
|
135
|
+
compare: Compare.Equal,
|
|
136
|
+
value: ReplicationIntent.NonStrict,
|
|
137
|
+
}),
|
|
138
|
+
);
|
|
139
|
+
}
|
|
218
140
|
return queries;
|
|
219
141
|
};
|
|
220
142
|
|
|
221
143
|
const iterator = iterate(
|
|
222
144
|
rects,
|
|
223
145
|
new SearchRequest({
|
|
224
|
-
query: createQueries(
|
|
146
|
+
query: createQueries(point, false),
|
|
225
147
|
sort:
|
|
226
148
|
direction === "below"
|
|
227
149
|
? new Sort({ key: ["end2"], direction: "desc" })
|
|
@@ -231,10 +153,7 @@ const getClosest = (
|
|
|
231
153
|
const iteratorWrapped = iterate(
|
|
232
154
|
rects,
|
|
233
155
|
new SearchRequest({
|
|
234
|
-
query: createQueries(
|
|
235
|
-
direction === "below" ? SEGMENT_COORDINATE_SCALE : 0,
|
|
236
|
-
true,
|
|
237
|
-
),
|
|
156
|
+
query: createQueries(direction === "below" ? MAX_U32 : 0, true),
|
|
238
157
|
sort:
|
|
239
158
|
direction === "below"
|
|
240
159
|
? new Sort({ key: ["end2"], direction: "desc" })
|
|
@@ -242,11 +161,83 @@ const getClosest = (
|
|
|
242
161
|
}),
|
|
243
162
|
);
|
|
244
163
|
|
|
245
|
-
return joinIterator(
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
164
|
+
return joinIterator([iterator, iteratorWrapped], point, direction);
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
export const hasCoveringRange = async (
|
|
168
|
+
rects: Index<ReplicationRangeIndexable>,
|
|
169
|
+
range: ReplicationRangeIndexable,
|
|
170
|
+
) => {
|
|
171
|
+
return (
|
|
172
|
+
(await rects.count(
|
|
173
|
+
new SearchRequest({
|
|
174
|
+
query: [
|
|
175
|
+
new Or([
|
|
176
|
+
new And([
|
|
177
|
+
new IntegerCompare({
|
|
178
|
+
key: "start1",
|
|
179
|
+
compare: Compare.LessOrEqual,
|
|
180
|
+
value: range.start1,
|
|
181
|
+
}),
|
|
182
|
+
new IntegerCompare({
|
|
183
|
+
key: "end1",
|
|
184
|
+
compare: Compare.GreaterOrEqual,
|
|
185
|
+
value: range.end1,
|
|
186
|
+
}),
|
|
187
|
+
]),
|
|
188
|
+
new And([
|
|
189
|
+
new IntegerCompare({
|
|
190
|
+
key: "start2",
|
|
191
|
+
compare: Compare.LessOrEqual,
|
|
192
|
+
value: range.start1,
|
|
193
|
+
}),
|
|
194
|
+
new IntegerCompare({
|
|
195
|
+
key: "end2",
|
|
196
|
+
compare: Compare.GreaterOrEqual,
|
|
197
|
+
value: range.end1,
|
|
198
|
+
}),
|
|
199
|
+
]),
|
|
200
|
+
]),
|
|
201
|
+
new Or([
|
|
202
|
+
new And([
|
|
203
|
+
new IntegerCompare({
|
|
204
|
+
key: "start1",
|
|
205
|
+
compare: Compare.LessOrEqual,
|
|
206
|
+
value: range.start2,
|
|
207
|
+
}),
|
|
208
|
+
new IntegerCompare({
|
|
209
|
+
key: "end1",
|
|
210
|
+
compare: Compare.GreaterOrEqual,
|
|
211
|
+
value: range.end2,
|
|
212
|
+
}),
|
|
213
|
+
]),
|
|
214
|
+
new And([
|
|
215
|
+
new IntegerCompare({
|
|
216
|
+
key: "start2",
|
|
217
|
+
compare: Compare.LessOrEqual,
|
|
218
|
+
value: range.start2,
|
|
219
|
+
}),
|
|
220
|
+
new IntegerCompare({
|
|
221
|
+
key: "end2",
|
|
222
|
+
compare: Compare.GreaterOrEqual,
|
|
223
|
+
value: range.end2,
|
|
224
|
+
}),
|
|
225
|
+
]),
|
|
226
|
+
]),
|
|
227
|
+
new StringMatch({
|
|
228
|
+
key: "hash",
|
|
229
|
+
value: range.hash,
|
|
230
|
+
}),
|
|
231
|
+
// assume that we are looking for other ranges, not want to update an existing one
|
|
232
|
+
new Not(
|
|
233
|
+
new ByteMatchQuery({
|
|
234
|
+
key: "id",
|
|
235
|
+
value: range.id,
|
|
236
|
+
}),
|
|
237
|
+
),
|
|
238
|
+
],
|
|
239
|
+
}),
|
|
240
|
+
)) > 0
|
|
250
241
|
);
|
|
251
242
|
};
|
|
252
243
|
|
|
@@ -254,7 +245,7 @@ export const getDistance = (
|
|
|
254
245
|
from: number,
|
|
255
246
|
to: number,
|
|
256
247
|
direction: "above" | "below" | "closest",
|
|
257
|
-
end =
|
|
248
|
+
end = MAX_U32,
|
|
258
249
|
) => {
|
|
259
250
|
// if direction is 'above' only measure distance from 'from to 'to' from above.
|
|
260
251
|
// i.e if from < to, then from needs to wrap around 0 to 1 and then to to
|
|
@@ -292,12 +283,8 @@ export const getDistance = (
|
|
|
292
283
|
const joinIterator = (
|
|
293
284
|
iterators: IndexIterator<ReplicationRangeIndexable>[],
|
|
294
285
|
point: number,
|
|
295
|
-
scaled: boolean,
|
|
296
286
|
direction: "above" | "below" | "closest",
|
|
297
287
|
) => {
|
|
298
|
-
const scaledPoint = Math.round(
|
|
299
|
-
point * (scaled ? 1 : SEGMENT_COORDINATE_SCALE),
|
|
300
|
-
);
|
|
301
288
|
let queues: {
|
|
302
289
|
kept: number;
|
|
303
290
|
elements: {
|
|
@@ -331,13 +318,13 @@ const joinIterator = (
|
|
|
331
318
|
let dist: number;
|
|
332
319
|
if (direction === "closest") {
|
|
333
320
|
dist = Math.min(
|
|
334
|
-
getDistance(closest.start1,
|
|
335
|
-
getDistance(closest.end2,
|
|
321
|
+
getDistance(closest.start1, point, direction),
|
|
322
|
+
getDistance(closest.end2, point, direction),
|
|
336
323
|
);
|
|
337
324
|
} else if (direction === "above") {
|
|
338
|
-
dist = getDistance(closest.start1,
|
|
325
|
+
dist = getDistance(closest.start1, point, direction);
|
|
339
326
|
} else if (direction === "below") {
|
|
340
|
-
dist = getDistance(closest.end2,
|
|
327
|
+
dist = getDistance(closest.end2, point, direction);
|
|
341
328
|
} else {
|
|
342
329
|
throw new Error("Invalid direction");
|
|
343
330
|
}
|
|
@@ -401,7 +388,8 @@ const getClosestAround = (
|
|
|
401
388
|
point: number,
|
|
402
389
|
roleAge: number,
|
|
403
390
|
now: number,
|
|
404
|
-
|
|
391
|
+
includeStrictBelow: boolean,
|
|
392
|
+
includeStrictAbove: boolean,
|
|
405
393
|
) => {
|
|
406
394
|
const closestBelow = getClosest(
|
|
407
395
|
"below",
|
|
@@ -410,7 +398,7 @@ const getClosestAround = (
|
|
|
410
398
|
roleAge,
|
|
411
399
|
true,
|
|
412
400
|
now,
|
|
413
|
-
|
|
401
|
+
includeStrictBelow,
|
|
414
402
|
);
|
|
415
403
|
const closestAbove = getClosest(
|
|
416
404
|
"above",
|
|
@@ -419,15 +407,13 @@ const getClosestAround = (
|
|
|
419
407
|
roleAge,
|
|
420
408
|
true,
|
|
421
409
|
now,
|
|
422
|
-
|
|
410
|
+
includeStrictAbove,
|
|
423
411
|
);
|
|
424
|
-
const containing = containingPoint(peers, point, roleAge, true, now
|
|
425
|
-
scaled: scaled,
|
|
426
|
-
});
|
|
412
|
+
const containing = containingPoint(peers, point, roleAge, true, now);
|
|
427
413
|
|
|
428
414
|
return iteratorInSeries(
|
|
429
415
|
containing,
|
|
430
|
-
joinIterator([closestBelow, closestAbove], point,
|
|
416
|
+
joinIterator([closestBelow, closestAbove], point, "closest"),
|
|
431
417
|
);
|
|
432
418
|
};
|
|
433
419
|
|
|
@@ -435,13 +421,11 @@ const collectNodesAroundPoint = async (
|
|
|
435
421
|
roleAge: number,
|
|
436
422
|
peers: Index<ReplicationRangeIndexable>,
|
|
437
423
|
collector: (rect: ReplicationRangeIndexable, matured: boolean) => void,
|
|
438
|
-
point:
|
|
424
|
+
point: u32,
|
|
439
425
|
now: number,
|
|
440
426
|
done: () => boolean = () => true,
|
|
441
427
|
) => {
|
|
442
|
-
const containing = containingPoint(peers, point, 0, true, now
|
|
443
|
-
scaled: false,
|
|
444
|
-
});
|
|
428
|
+
const containing = containingPoint(peers, point, 0, true, now);
|
|
445
429
|
|
|
446
430
|
const allContaining = await containing.next(0xffffffff);
|
|
447
431
|
for (const rect of allContaining.results) {
|
|
@@ -457,7 +441,6 @@ const collectNodesAroundPoint = async (
|
|
|
457
441
|
const aroundIterator = joinIterator(
|
|
458
442
|
[closestBelow, closestAbove],
|
|
459
443
|
point,
|
|
460
|
-
false,
|
|
461
444
|
"closest",
|
|
462
445
|
);
|
|
463
446
|
while (aroundIterator.done() === false && done() === false) {
|
|
@@ -480,13 +463,12 @@ export const isMatured = (
|
|
|
480
463
|
};
|
|
481
464
|
|
|
482
465
|
export const getSamples = async (
|
|
483
|
-
cursor:
|
|
466
|
+
cursor: u32,
|
|
484
467
|
peers: Index<ReplicationRangeIndexable>,
|
|
485
468
|
amount: number,
|
|
486
469
|
roleAge: number,
|
|
487
470
|
) => {
|
|
488
471
|
const leaders: Set<string> = new Set();
|
|
489
|
-
const width = 1;
|
|
490
472
|
if (!peers) {
|
|
491
473
|
return [];
|
|
492
474
|
}
|
|
@@ -504,14 +486,13 @@ export const getSamples = async (
|
|
|
504
486
|
const maturedLeaders = new Set();
|
|
505
487
|
for (let i = 0; i < amount; i++) {
|
|
506
488
|
// evenly distributed
|
|
507
|
-
const point = (
|
|
489
|
+
const point = Math.round(cursor + (i * MAX_U32) / amount) % MAX_U32;
|
|
508
490
|
|
|
509
491
|
// aquire at least one unique node for each point
|
|
510
492
|
await collectNodesAroundPoint(
|
|
511
493
|
roleAge,
|
|
512
494
|
peers,
|
|
513
495
|
(rect, m) => {
|
|
514
|
-
// console.log(m, rect.start1 / SEGMENT_COORDINATE_SCALE, rect.width / SEGMENT_COORDINATE_SCALE)
|
|
515
496
|
if (m) {
|
|
516
497
|
maturedLeaders.add(rect.hash);
|
|
517
498
|
}
|
|
@@ -537,82 +518,84 @@ const fetchOne = async (iterator: IndexIterator<ReplicationRangeIndexable>) => {
|
|
|
537
518
|
return value.results[0]?.value;
|
|
538
519
|
};
|
|
539
520
|
|
|
540
|
-
export const
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
startNodeIdentity?: PublicSignKey,
|
|
545
|
-
): Promise<Set<string>> => {
|
|
546
|
-
let now = +new Date();
|
|
521
|
+
export const minimumWidthToCover = async (
|
|
522
|
+
minReplicas: number /* , replicatorCount: number */,
|
|
523
|
+
) => {
|
|
524
|
+
/* minReplicas = Math.min(minReplicas, replicatorCount); */ // TODO do we need this?
|
|
547
525
|
|
|
548
|
-
//
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
526
|
+
// If min replicas = 2
|
|
527
|
+
// then we need to make sure we cover 0.5 of the total 'width' of the replication space
|
|
528
|
+
// to make sure we reach sufficient amount of nodes such that at least one one has
|
|
529
|
+
// the entry we are looking for
|
|
530
|
+
|
|
531
|
+
let widthToCoverScaled = Math.round(MAX_U32 / minReplicas);
|
|
532
|
+
return widthToCoverScaled;
|
|
533
|
+
};
|
|
534
|
+
|
|
535
|
+
export const getCoverSet = async (properties: {
|
|
536
|
+
peers: Index<ReplicationRangeIndexable>;
|
|
537
|
+
start: number | PublicSignKey | undefined;
|
|
538
|
+
widthToCoverScaled: number;
|
|
539
|
+
roleAge: number;
|
|
540
|
+
intervalWidth?: number;
|
|
541
|
+
eager?:
|
|
542
|
+
| {
|
|
543
|
+
unmaturedFetchCoverSize?: number;
|
|
544
|
+
}
|
|
545
|
+
| boolean;
|
|
546
|
+
}): Promise<Set<string>> => {
|
|
547
|
+
let intervalWidth: number = properties.intervalWidth ?? MAX_U32;
|
|
548
|
+
const { peers, start, widthToCoverScaled, roleAge } = properties;
|
|
549
|
+
|
|
550
|
+
const now = Date.now();
|
|
551
|
+
const { startNode, startLocation, endLocation } = await getStartAndEnd(
|
|
552
|
+
peers,
|
|
553
|
+
start,
|
|
554
|
+
widthToCoverScaled,
|
|
555
|
+
roleAge,
|
|
556
|
+
now,
|
|
557
|
+
intervalWidth,
|
|
558
|
+
);
|
|
559
|
+
|
|
560
|
+
let ret = new Set<string>();
|
|
561
|
+
|
|
562
|
+
// if start node (assume is self) and not mature, ask all known remotes if limited
|
|
563
|
+
// TODO consider a more robust stragety here in a scenario where there are many nodes, lets say
|
|
564
|
+
// a social media app with 1m user, then it does not makes sense to query "all" just because we started
|
|
565
|
+
if (properties.eager) {
|
|
566
|
+
const eagerFetch =
|
|
567
|
+
properties.eager === true
|
|
568
|
+
? 1000
|
|
569
|
+
: properties.eager.unmaturedFetchCoverSize;
|
|
570
|
+
|
|
571
|
+
// pull all umatured
|
|
572
|
+
const rects = await peers.query(
|
|
553
573
|
new SearchRequest({
|
|
574
|
+
fetch: eagerFetch,
|
|
554
575
|
query: [
|
|
555
|
-
new
|
|
576
|
+
new IntegerCompare({
|
|
577
|
+
key: "timestamp",
|
|
578
|
+
compare: Compare.GreaterOrEqual,
|
|
579
|
+
value: BigInt(now - roleAge),
|
|
580
|
+
}),
|
|
556
581
|
],
|
|
557
|
-
fetch: 1,
|
|
558
582
|
}),
|
|
559
583
|
);
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
if (startNode) {
|
|
563
|
-
if (!isMatured(startNode, now, roleAge)) {
|
|
564
|
-
const matured = await fetchOne(
|
|
565
|
-
getClosestAround(peers, startNode.start1, roleAge, now, true),
|
|
566
|
-
);
|
|
567
|
-
if (matured) {
|
|
568
|
-
startNode = matured;
|
|
569
|
-
}
|
|
570
|
-
}
|
|
584
|
+
for (const rect of rects.results) {
|
|
585
|
+
ret.add(rect.value.hash);
|
|
571
586
|
}
|
|
572
587
|
}
|
|
573
|
-
let startLocation: number;
|
|
574
588
|
|
|
575
|
-
if (!startNode) {
|
|
576
|
-
startLocation = Math.random() * SEGMENT_COORDINATE_SCALE;
|
|
577
|
-
startNode = await fetchOne(
|
|
578
|
-
getClosestAround(peers, startLocation, roleAge, now, true),
|
|
579
|
-
);
|
|
580
|
-
} else {
|
|
581
|
-
// TODO choose start location as the point with the longest range?
|
|
582
|
-
startLocation =
|
|
583
|
-
startNode.start1 ?? Math.random() * SEGMENT_COORDINATE_SCALE;
|
|
584
|
-
}
|
|
585
|
-
|
|
586
|
-
if (!startNode) {
|
|
587
|
-
return new Set();
|
|
588
|
-
}
|
|
589
|
-
|
|
590
|
-
let results: ReplicationRangeIndexable[] = [];
|
|
591
|
-
|
|
592
|
-
let widthToCoverScaled = coveringWidth * SEGMENT_COORDINATE_SCALE;
|
|
593
|
-
const endLocation =
|
|
594
|
-
(startLocation + widthToCoverScaled) % SEGMENT_COORDINATE_SCALE;
|
|
595
589
|
const endIsWrapped = endLocation <= startLocation;
|
|
596
590
|
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
getClosestAround(peers, endLocation, roleAge, now, true),
|
|
600
|
-
)) || (await fetchOne(getClosestAround(peers, endLocation, 0, now, true))); // (await getClosest('above', peers, nextLocation, roleAge, true, 1, true))[0]
|
|
601
|
-
|
|
602
|
-
if (!endRect) {
|
|
603
|
-
return new Set();
|
|
591
|
+
if (!startNode) {
|
|
592
|
+
return ret;
|
|
604
593
|
}
|
|
605
594
|
|
|
606
|
-
let current =
|
|
607
|
-
/* (await getClosestAround(peers, startLocation, roleAge, 1, true))[0] */ startNode ||
|
|
608
|
-
(await fetchOne(getClosestAround(peers, startLocation, 0, now, true))); //(await getClosest('above', peers, startLocation, roleAge, true, 1, true))[0]
|
|
609
|
-
let coveredLength = current.width;
|
|
610
|
-
let nextLocation = current.end2;
|
|
595
|
+
let current = startNode;
|
|
611
596
|
|
|
612
597
|
// push edges
|
|
613
|
-
|
|
614
|
-
results.push(current);
|
|
615
|
-
/* const endIsSameAsStart = equals(endRect.id, current.id); */
|
|
598
|
+
ret.add(current.hash);
|
|
616
599
|
|
|
617
600
|
const resolveNextContaining = async (
|
|
618
601
|
nextLocation: number,
|
|
@@ -620,7 +603,6 @@ export const getCoverSet = async (
|
|
|
620
603
|
) => {
|
|
621
604
|
let next = await fetchOne(
|
|
622
605
|
containingPoint(peers, nextLocation, roleAge, true, now, {
|
|
623
|
-
scaled: true,
|
|
624
606
|
sort: [new Sort({ key: "end2", direction: SortDirection.DESC })],
|
|
625
607
|
}),
|
|
626
608
|
); // get entersecting sort by largest end2
|
|
@@ -649,13 +631,25 @@ export const getCoverSet = async (
|
|
|
649
631
|
// fill the middle
|
|
650
632
|
let wrappedOnce = current.end2 < current.end1;
|
|
651
633
|
|
|
652
|
-
let
|
|
653
|
-
|
|
654
|
-
|
|
634
|
+
let coveredLength = 0;
|
|
635
|
+
const addLength = (from: number) => {
|
|
636
|
+
if (current.end2 < from || current.wrapped) {
|
|
637
|
+
wrappedOnce = true;
|
|
638
|
+
coveredLength += MAX_U32 - from;
|
|
639
|
+
coveredLength += current.end2;
|
|
640
|
+
} else {
|
|
641
|
+
coveredLength += current.end1 - from;
|
|
642
|
+
}
|
|
643
|
+
};
|
|
644
|
+
addLength(startLocation);
|
|
645
|
+
|
|
646
|
+
let maturedCoveredLength =
|
|
647
|
+
coveredLength; /* TODO only increase matured length when startNode is matured? i.e. do isMatured(startNode, now, roleAge) ? coveredLength : 0; */
|
|
648
|
+
let nextLocation = current.end2;
|
|
655
649
|
|
|
656
650
|
while (
|
|
657
|
-
maturedCoveredLength < widthToCoverScaled &&
|
|
658
|
-
coveredLength <=
|
|
651
|
+
maturedCoveredLength < widthToCoverScaled && // eslint-disable-line no-unmodified-loop-condition
|
|
652
|
+
coveredLength <= MAX_U32 // eslint-disable-line no-unmodified-loop-condition
|
|
659
653
|
) {
|
|
660
654
|
let nextCandidate = await resolveNext(nextLocation, roleAge);
|
|
661
655
|
/* let fromAbove = false; */
|
|
@@ -671,53 +665,44 @@ export const getCoverSet = async (
|
|
|
671
665
|
break;
|
|
672
666
|
}
|
|
673
667
|
|
|
668
|
+
let nextIsCurrent = equals(nextCandidate[0].id, current.id);
|
|
669
|
+
if (nextIsCurrent) {
|
|
670
|
+
break;
|
|
671
|
+
}
|
|
672
|
+
let last = current;
|
|
674
673
|
current = nextCandidate[0];
|
|
675
674
|
|
|
676
675
|
let distanceBefore = coveredLength;
|
|
677
676
|
|
|
678
|
-
|
|
679
|
-
wrappedOnce = true;
|
|
680
|
-
coveredLength += SEGMENT_COORDINATE_SCALE - nextLocation;
|
|
681
|
-
coveredLength += current.end2;
|
|
682
|
-
} else {
|
|
683
|
-
coveredLength += current.end1 - nextLocation;
|
|
684
|
-
}
|
|
677
|
+
addLength(nextLocation);
|
|
685
678
|
|
|
686
679
|
let isLast =
|
|
687
680
|
distanceBefore < widthToCoverScaled &&
|
|
688
681
|
coveredLength >= widthToCoverScaled;
|
|
682
|
+
|
|
689
683
|
if (
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
684
|
+
!isLast ||
|
|
685
|
+
nextCandidate[1] ||
|
|
686
|
+
Math.min(
|
|
687
|
+
getDistance(last.start1, endLocation, "closest"),
|
|
688
|
+
getDistance(last.end2, endLocation, "closest"),
|
|
689
|
+
) >
|
|
690
|
+
Math.min(
|
|
691
|
+
getDistance(current.start1, endLocation, "closest"),
|
|
692
|
+
getDistance(current.end2, endLocation, "closest"),
|
|
693
|
+
)
|
|
693
694
|
) {
|
|
694
|
-
|
|
695
|
-
// this is the end!
|
|
696
|
-
/* if (lastMatured && lastMatured.distanceTo(endLocation) < current.distanceTo(endLocation)) {
|
|
697
|
-
breaks;
|
|
698
|
-
} */
|
|
699
|
-
break;
|
|
695
|
+
ret.add(current.hash);
|
|
700
696
|
}
|
|
701
697
|
|
|
702
|
-
|
|
698
|
+
if (isLast && !nextCandidate[1] /* || equals(endRect.id, current.id) */) {
|
|
703
699
|
break;
|
|
704
|
-
}
|
|
705
|
-
|
|
706
|
-
// this is a skip condition to not include too many rects
|
|
700
|
+
}
|
|
707
701
|
|
|
708
702
|
if (matured) {
|
|
709
703
|
maturedCoveredLength = coveredLength;
|
|
710
|
-
/* lastMatured = current; */
|
|
711
704
|
}
|
|
712
705
|
|
|
713
|
-
results.push(current);
|
|
714
|
-
/*
|
|
715
|
-
|
|
716
|
-
if (current.start1 > endLocation && (wrappedOnce || !endIsWrapped)) {
|
|
717
|
-
break;
|
|
718
|
-
}
|
|
719
|
-
|
|
720
|
-
*/
|
|
721
706
|
nextLocation = endIsWrapped
|
|
722
707
|
? wrappedOnce
|
|
723
708
|
? Math.min(current.end2, endLocation)
|
|
@@ -725,154 +710,122 @@ export const getCoverSet = async (
|
|
|
725
710
|
: Math.min(current.end2, endLocation);
|
|
726
711
|
}
|
|
727
712
|
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
distanceStart = (1 - startPoint + currentNode.segment.offset) % 1;
|
|
753
|
-
} else {
|
|
754
|
-
distanceStart = (currentNode.segment.offset - startPoint) % 1;
|
|
755
|
-
}
|
|
756
|
-
|
|
757
|
-
const distanceEnd = distanceStart + currentNode.segment.factor;
|
|
758
|
-
|
|
759
|
-
return [nextPoint, distanceStart, distanceEnd, wrappedOnce];
|
|
760
|
-
};
|
|
761
|
-
|
|
762
|
-
const getNextMatured = async (from: ReplicatorRect) => {
|
|
763
|
-
let next = (await peers.query(new SearchRequest({ query: [new IntegerCompare({ key: ['segment', 'offset'], compare: Compare.Greater, value: from.segment.offset })], fetch: 1 })))?.results[0]?.value // (from.next || peers.head)!;
|
|
764
|
-
while (
|
|
765
|
-
next.hash !== from.hash &&
|
|
766
|
-
next.hash !== startNode.hash
|
|
767
|
-
) {
|
|
768
|
-
if (isMatured(next.segment, t, roleAge)) {
|
|
769
|
-
return next;
|
|
770
|
-
}
|
|
771
|
-
next = (next.next || peers.head)!;
|
|
772
|
-
}
|
|
773
|
-
return undefined;
|
|
774
|
-
}; */
|
|
775
|
-
|
|
776
|
-
/**
|
|
777
|
-
* The purpose of this loop is to cover at least coveringWidth
|
|
778
|
-
* so that if we query all nodes in this range, we know we will
|
|
779
|
-
* "query" all data in that range
|
|
780
|
-
*/
|
|
781
|
-
|
|
782
|
-
/* let isPastThePoint = false;
|
|
783
|
-
outer: while (currentNode) {
|
|
784
|
-
if (set.has(currentNode.hash)) break;
|
|
785
|
-
|
|
786
|
-
const [nextPoint, distanceStart, distanceEnd, wrapped] = getNextPoint();
|
|
787
|
-
|
|
788
|
-
if (distanceStart <= coveringWidth) {
|
|
789
|
-
set.add(currentNode.hash);
|
|
790
|
-
}
|
|
791
|
-
|
|
792
|
-
if (distanceEnd >= coveringWidth) {
|
|
793
|
-
break;
|
|
794
|
-
}
|
|
795
|
-
|
|
796
|
-
let next = currentNode.next || peers.head;
|
|
797
|
-
while (next) {
|
|
798
|
-
if (next.value.publicKey.equals(startNode.value.publicKey)) {
|
|
799
|
-
break outer;
|
|
800
|
-
}
|
|
801
|
-
|
|
802
|
-
const prevOffset = (next.prev || peers.tail)!.value.role.offset;
|
|
803
|
-
const nextOffset = next.value.role.offset;
|
|
804
|
-
const nextHasWrapped = nextOffset < prevOffset;
|
|
805
|
-
|
|
806
|
-
if (
|
|
807
|
-
(!wrapped && nextOffset > nextPoint) ||
|
|
808
|
-
(nextHasWrapped &&
|
|
809
|
-
(wrapped ? nextOffset > nextPoint : prevOffset < nextPoint)) ||
|
|
810
|
-
(!nextHasWrapped && prevOffset < nextPoint && nextPoint <= nextOffset)
|
|
811
|
-
) {
|
|
812
|
-
isPastThePoint = true;
|
|
813
|
-
}
|
|
814
|
-
|
|
815
|
-
if (isPastThePoint) {
|
|
816
|
-
break; // include this next in the set;
|
|
817
|
-
}
|
|
818
|
-
|
|
819
|
-
const overlapsRange = containsPoint(next.value.role, nextPoint);
|
|
820
|
-
|
|
821
|
-
if (overlapsRange) {
|
|
822
|
-
// Find out if there is a better choice ahead of us
|
|
823
|
-
const nextNext = await getNextMatured(next);
|
|
824
|
-
if (
|
|
825
|
-
nextNext &&
|
|
826
|
-
nextNext.hash === currentNode.hash &&
|
|
827
|
-
nextNext.segment.offset < nextPoint &&
|
|
828
|
-
nextNext.segment.offset + nextNext.segment.factor > nextPoint
|
|
829
|
-
) {
|
|
830
|
-
// nextNext is better (continue to iterate)
|
|
831
|
-
} else {
|
|
832
|
-
// done
|
|
833
|
-
break;
|
|
834
|
-
}
|
|
835
|
-
} else {
|
|
836
|
-
// (continue to iterate)
|
|
713
|
+
start instanceof PublicSignKey && ret.add(start.hashcode());
|
|
714
|
+
return ret;
|
|
715
|
+
};
|
|
716
|
+
|
|
717
|
+
export const fetchOneFromPublicKey = async (
|
|
718
|
+
publicKey: PublicSignKey,
|
|
719
|
+
index: Index<ReplicationRangeIndexable>,
|
|
720
|
+
roleAge: number,
|
|
721
|
+
now: number,
|
|
722
|
+
) => {
|
|
723
|
+
let result = await index.query(
|
|
724
|
+
new SearchRequest({
|
|
725
|
+
query: [new StringMatch({ key: "hash", value: publicKey.hashcode() })],
|
|
726
|
+
fetch: 1,
|
|
727
|
+
}),
|
|
728
|
+
);
|
|
729
|
+
let node = result.results[0]?.value;
|
|
730
|
+
if (node) {
|
|
731
|
+
if (!isMatured(node, now, roleAge)) {
|
|
732
|
+
const matured = await fetchOne(
|
|
733
|
+
getClosestAround(index, node.start1, roleAge, now, false, false),
|
|
734
|
+
);
|
|
735
|
+
if (matured) {
|
|
736
|
+
node = matured;
|
|
837
737
|
}
|
|
838
|
-
|
|
839
|
-
next = next.next || peers.head;
|
|
840
738
|
}
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
739
|
+
}
|
|
740
|
+
return node;
|
|
741
|
+
};
|
|
742
|
+
|
|
743
|
+
export const getStartAndEnd = async (
|
|
744
|
+
peers: Index<ReplicationRangeIndexable>,
|
|
745
|
+
start: number | PublicSignKey | undefined | undefined,
|
|
746
|
+
widthToCoverScaled: number,
|
|
747
|
+
roleAge: number,
|
|
748
|
+
now: number,
|
|
749
|
+
intervalWidth: number,
|
|
750
|
+
) => {
|
|
751
|
+
// find a good starting point
|
|
752
|
+
let startNode: ReplicationRangeIndexable | undefined = undefined;
|
|
753
|
+
let startLocation: number | undefined = undefined;
|
|
754
|
+
|
|
755
|
+
const nodeFromPoint = async (point = scaleToU32(Math.random())) => {
|
|
756
|
+
startLocation = point;
|
|
757
|
+
startNode = await fetchOneClosest(
|
|
855
758
|
peers,
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
} else {
|
|
862
|
-
set.add(rect.hash);
|
|
863
|
-
}
|
|
864
|
-
done = true;
|
|
865
|
-
} else {
|
|
866
|
-
unmatured.push(rect.hash);
|
|
867
|
-
}
|
|
868
|
-
},
|
|
869
|
-
point,
|
|
870
|
-
() => done
|
|
759
|
+
startLocation,
|
|
760
|
+
roleAge,
|
|
761
|
+
now,
|
|
762
|
+
false,
|
|
763
|
+
true,
|
|
871
764
|
);
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
765
|
+
};
|
|
766
|
+
|
|
767
|
+
if (start instanceof PublicSignKey) {
|
|
768
|
+
// start at our node (local first)
|
|
769
|
+
startNode = await fetchOneFromPublicKey(start, peers, roleAge, now);
|
|
770
|
+
if (!startNode) {
|
|
771
|
+
// fetch randomly
|
|
772
|
+
await nodeFromPoint();
|
|
773
|
+
} else {
|
|
774
|
+
startLocation = startNode.start1;
|
|
875
775
|
}
|
|
776
|
+
} else if (typeof start === "number") {
|
|
777
|
+
await nodeFromPoint(start);
|
|
778
|
+
} else {
|
|
779
|
+
await nodeFromPoint();
|
|
876
780
|
}
|
|
877
|
-
|
|
781
|
+
|
|
782
|
+
if (!startNode || startLocation == null) {
|
|
783
|
+
return { startNode: undefined, startLocation: 0, endLocation: 0 };
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
let endLocation = startLocation + widthToCoverScaled;
|
|
787
|
+
if (intervalWidth != null) {
|
|
788
|
+
endLocation = endLocation % intervalWidth;
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
// if start location is after endLocation and startNode is strict then return undefined because this is not a node we want to choose
|
|
792
|
+
let coveredDistanceToStart = 0;
|
|
793
|
+
if (startNode.start1 < startLocation) {
|
|
794
|
+
coveredDistanceToStart += intervalWidth - startLocation + startNode.start1;
|
|
795
|
+
} else {
|
|
796
|
+
coveredDistanceToStart += startNode.start1 - startLocation;
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
if (
|
|
800
|
+
startNode.mode === ReplicationIntent.Strict &&
|
|
801
|
+
coveredDistanceToStart > widthToCoverScaled
|
|
802
|
+
) {
|
|
803
|
+
return { startNode: undefined, startLocation: 0, endLocation: 0 };
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
return {
|
|
807
|
+
startNode,
|
|
808
|
+
startLocation: Math.round(startLocation),
|
|
809
|
+
endLocation: Math.round(endLocation),
|
|
810
|
+
};
|
|
811
|
+
};
|
|
812
|
+
|
|
813
|
+
export const fetchOneClosest = (
|
|
814
|
+
peers: Index<ReplicationRangeIndexable>,
|
|
815
|
+
point: number,
|
|
816
|
+
roleAge: number,
|
|
817
|
+
now: number,
|
|
818
|
+
includeStrictBelow: boolean,
|
|
819
|
+
includeStrictAbove: boolean,
|
|
820
|
+
) => {
|
|
821
|
+
return fetchOne(
|
|
822
|
+
getClosestAround(
|
|
823
|
+
peers,
|
|
824
|
+
point,
|
|
825
|
+
roleAge,
|
|
826
|
+
now,
|
|
827
|
+
includeStrictBelow,
|
|
828
|
+
includeStrictAbove,
|
|
829
|
+
),
|
|
830
|
+
);
|
|
878
831
|
};
|