@peerbit/shared-log 12.1.3-e209d2e → 12.2.0-10dfe9b
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/rateless-iblt-sender-startsync.d.ts +2 -0
- package/dist/benchmark/rateless-iblt-sender-startsync.d.ts.map +1 -0
- package/dist/benchmark/rateless-iblt-sender-startsync.js +104 -0
- package/dist/benchmark/rateless-iblt-sender-startsync.js.map +1 -0
- package/dist/benchmark/rateless-iblt-startsync-cache.d.ts +2 -0
- package/dist/benchmark/rateless-iblt-startsync-cache.d.ts.map +1 -0
- package/dist/benchmark/rateless-iblt-startsync-cache.js +112 -0
- package/dist/benchmark/rateless-iblt-startsync-cache.js.map +1 -0
- package/dist/src/index.d.ts +2 -1
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +3 -0
- package/dist/src/index.js.map +1 -1
- package/dist/src/sync/index.d.ts +14 -0
- package/dist/src/sync/index.d.ts.map +1 -1
- package/dist/src/sync/rateless-iblt.d.ts +14 -22
- package/dist/src/sync/rateless-iblt.d.ts.map +1 -1
- package/dist/src/sync/rateless-iblt.js +137 -22
- package/dist/src/sync/rateless-iblt.js.map +1 -1
- package/dist/src/sync/simple.d.ts +3 -1
- package/dist/src/sync/simple.d.ts.map +1 -1
- package/dist/src/sync/simple.js +23 -1
- package/dist/src/sync/simple.js.map +1 -1
- package/package.json +18 -18
- package/src/index.ts +9 -1
- package/src/sync/index.ts +19 -0
- package/src/sync/rateless-iblt.ts +187 -41
- package/src/sync/simple.ts +25 -2
|
@@ -12,14 +12,16 @@ import {
|
|
|
12
12
|
import type { RPC, RequestContext } from "@peerbit/rpc";
|
|
13
13
|
import { SilentDelivery } from "@peerbit/stream-interface";
|
|
14
14
|
import { type EntryWithRefs } from "../exchange-heads.js";
|
|
15
|
-
import { type Numbers } from "../integers.js";
|
|
16
15
|
import { TransportMessage } from "../message.js";
|
|
17
16
|
import {
|
|
18
17
|
type EntryReplicated,
|
|
19
|
-
type ReplicationRangeIndexable,
|
|
20
18
|
matchEntriesInRangeQuery,
|
|
21
19
|
} from "../ranges.js";
|
|
22
|
-
import type {
|
|
20
|
+
import type {
|
|
21
|
+
SyncableKey,
|
|
22
|
+
SynchronizerComponents,
|
|
23
|
+
Syncronizer,
|
|
24
|
+
} from "./index.js";
|
|
23
25
|
import { SimpleSyncronizer } from "./simple.js";
|
|
24
26
|
|
|
25
27
|
export const logger = loggerFn("peerbit:shared-log:rateless");
|
|
@@ -185,6 +187,13 @@ export class RatelessIBLTSynchronizer<D extends "u32" | "u64">
|
|
|
185
187
|
simple: SimpleSyncronizer<D>;
|
|
186
188
|
|
|
187
189
|
startedOrCompletedSynchronizations: Cache<string>;
|
|
190
|
+
private localRangeEncoderCacheVersion = 0;
|
|
191
|
+
private localRangeEncoderCache: Map<
|
|
192
|
+
string,
|
|
193
|
+
{ encoder: EncoderWrapper; version: number; lastUsed: number }
|
|
194
|
+
> = new Map();
|
|
195
|
+
private localRangeEncoderCacheMax = 2;
|
|
196
|
+
|
|
188
197
|
ingoingSyncProcesses: Map<
|
|
189
198
|
string,
|
|
190
199
|
{
|
|
@@ -212,14 +221,7 @@ export class RatelessIBLTSynchronizer<D extends "u32" | "u64">
|
|
|
212
221
|
>;
|
|
213
222
|
|
|
214
223
|
constructor(
|
|
215
|
-
readonly properties:
|
|
216
|
-
rpc: RPC<TransportMessage, TransportMessage>;
|
|
217
|
-
rangeIndex: Index<ReplicationRangeIndexable<D>, any>;
|
|
218
|
-
entryIndex: Index<EntryReplicated<D>, any>;
|
|
219
|
-
log: Log<any>;
|
|
220
|
-
coordinateToHash: Cache<string>;
|
|
221
|
-
numbers: Numbers<D>;
|
|
222
|
-
},
|
|
224
|
+
readonly properties: SynchronizerComponents<D>,
|
|
223
225
|
) {
|
|
224
226
|
this.simple = new SimpleSyncronizer(properties);
|
|
225
227
|
this.outgoingSyncProcesses = new Map();
|
|
@@ -227,6 +229,91 @@ export class RatelessIBLTSynchronizer<D extends "u32" | "u64">
|
|
|
227
229
|
this.startedOrCompletedSynchronizations = new Cache({ max: 1e4 });
|
|
228
230
|
}
|
|
229
231
|
|
|
232
|
+
private clearLocalRangeEncoderCache() {
|
|
233
|
+
for (const [, cached] of this.localRangeEncoderCache) {
|
|
234
|
+
cached.encoder.free();
|
|
235
|
+
}
|
|
236
|
+
this.localRangeEncoderCache.clear();
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
private invalidateLocalRangeEncoderCache() {
|
|
240
|
+
this.localRangeEncoderCacheVersion += 1;
|
|
241
|
+
this.clearLocalRangeEncoderCache();
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
private localRangeEncoderCacheKey(ranges: {
|
|
245
|
+
start1: NumberOrBigint;
|
|
246
|
+
end1: NumberOrBigint;
|
|
247
|
+
start2: NumberOrBigint;
|
|
248
|
+
end2: NumberOrBigint;
|
|
249
|
+
}) {
|
|
250
|
+
return `${String(ranges.start1)}:${String(ranges.end1)}:${String(
|
|
251
|
+
ranges.start2,
|
|
252
|
+
)}:${String(ranges.end2)}`;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
private decoderFromCachedEncoder(encoder: EncoderWrapper): DecoderWrapper {
|
|
256
|
+
const clone = encoder.clone();
|
|
257
|
+
const decoder = clone.to_decoder();
|
|
258
|
+
clone.free();
|
|
259
|
+
return decoder;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
private async getLocalDecoderForRange(ranges: {
|
|
263
|
+
start1: NumberOrBigint;
|
|
264
|
+
end1: NumberOrBigint;
|
|
265
|
+
start2: NumberOrBigint;
|
|
266
|
+
end2: NumberOrBigint;
|
|
267
|
+
}): Promise<DecoderWrapper | false> {
|
|
268
|
+
const key = this.localRangeEncoderCacheKey(ranges);
|
|
269
|
+
const cached = this.localRangeEncoderCache.get(key);
|
|
270
|
+
if (cached && cached.version === this.localRangeEncoderCacheVersion) {
|
|
271
|
+
cached.lastUsed = Date.now();
|
|
272
|
+
return this.decoderFromCachedEncoder(cached.encoder);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
const encoder = (await buildEncoderOrDecoderFromRange(
|
|
276
|
+
ranges,
|
|
277
|
+
this.properties.entryIndex,
|
|
278
|
+
"encoder",
|
|
279
|
+
)) as EncoderWrapper | false;
|
|
280
|
+
if (!encoder) {
|
|
281
|
+
return false;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
const now = Date.now();
|
|
285
|
+
const existing = this.localRangeEncoderCache.get(key);
|
|
286
|
+
if (existing) {
|
|
287
|
+
existing.encoder.free();
|
|
288
|
+
}
|
|
289
|
+
this.localRangeEncoderCache.set(key, {
|
|
290
|
+
encoder,
|
|
291
|
+
version: this.localRangeEncoderCacheVersion,
|
|
292
|
+
lastUsed: now,
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
while (this.localRangeEncoderCache.size > this.localRangeEncoderCacheMax) {
|
|
296
|
+
let oldestKey: string | undefined;
|
|
297
|
+
let oldestUsed = Number.POSITIVE_INFINITY;
|
|
298
|
+
for (const [candidateKey, value] of this.localRangeEncoderCache) {
|
|
299
|
+
if (value.lastUsed < oldestUsed) {
|
|
300
|
+
oldestUsed = value.lastUsed;
|
|
301
|
+
oldestKey = candidateKey;
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
if (!oldestKey) {
|
|
305
|
+
break;
|
|
306
|
+
}
|
|
307
|
+
const victim = this.localRangeEncoderCache.get(oldestKey);
|
|
308
|
+
if (victim) {
|
|
309
|
+
victim.encoder.free();
|
|
310
|
+
}
|
|
311
|
+
this.localRangeEncoderCache.delete(oldestKey);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
return this.decoderFromCachedEncoder(encoder);
|
|
315
|
+
}
|
|
316
|
+
|
|
230
317
|
async onMaybeMissingEntries(properties: {
|
|
231
318
|
entries: Map<string, EntryReplicated<D>>;
|
|
232
319
|
targets: string[];
|
|
@@ -238,7 +325,6 @@ export class RatelessIBLTSynchronizer<D extends "u32" | "u64">
|
|
|
238
325
|
// such as those assigned to range boundaries.
|
|
239
326
|
|
|
240
327
|
let entriesToSyncNaively: Map<string, EntryReplicated<D>> = new Map();
|
|
241
|
-
let allCoordinatesToSyncWithIblt: bigint[] = [];
|
|
242
328
|
let minSyncIbltSize = 333; // TODO: make configurable
|
|
243
329
|
let maxSyncWithSimpleMethod = 1e3;
|
|
244
330
|
|
|
@@ -251,15 +337,59 @@ export class RatelessIBLTSynchronizer<D extends "u32" | "u64">
|
|
|
251
337
|
return;
|
|
252
338
|
}
|
|
253
339
|
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
// Mixed strategy for larger batches
|
|
340
|
+
const nonBoundaryEntries: EntryReplicated<D>[] = [];
|
|
257
341
|
for (const entry of properties.entries.values()) {
|
|
258
342
|
if (entry.assignedToRangeBoundary) {
|
|
259
343
|
entriesToSyncNaively.set(entry.hash, entry);
|
|
260
344
|
} else {
|
|
261
|
-
|
|
345
|
+
nonBoundaryEntries.push(entry);
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
const priorityFn = this.properties.sync?.priority;
|
|
350
|
+
const maxSimpleEntries = this.properties.sync?.maxSimpleEntries;
|
|
351
|
+
const maxAdditionalNaive =
|
|
352
|
+
priorityFn &&
|
|
353
|
+
typeof maxSimpleEntries === "number" &&
|
|
354
|
+
Number.isFinite(maxSimpleEntries) &&
|
|
355
|
+
maxSimpleEntries > 0
|
|
356
|
+
? Math.max(
|
|
357
|
+
0,
|
|
358
|
+
Math.min(
|
|
359
|
+
Math.floor(maxSimpleEntries),
|
|
360
|
+
maxSyncWithSimpleMethod - entriesToSyncNaively.size,
|
|
361
|
+
),
|
|
362
|
+
)
|
|
363
|
+
: 0;
|
|
364
|
+
|
|
365
|
+
if (priorityFn && maxAdditionalNaive > 0 && nonBoundaryEntries.length > 0) {
|
|
366
|
+
let index = 0;
|
|
367
|
+
const scored: {
|
|
368
|
+
entry: EntryReplicated<D>;
|
|
369
|
+
index: number;
|
|
370
|
+
priority: number;
|
|
371
|
+
}[] = [];
|
|
372
|
+
for (const entry of nonBoundaryEntries) {
|
|
373
|
+
const priorityValue = priorityFn(entry);
|
|
374
|
+
scored.push({
|
|
375
|
+
entry,
|
|
376
|
+
index,
|
|
377
|
+
priority: Number.isFinite(priorityValue) ? priorityValue : 0,
|
|
378
|
+
});
|
|
379
|
+
index += 1;
|
|
380
|
+
}
|
|
381
|
+
scored.sort((a, b) => b.priority - a.priority || a.index - b.index);
|
|
382
|
+
for (const { entry } of scored.slice(0, maxAdditionalNaive)) {
|
|
383
|
+
entriesToSyncNaively.set(entry.hash, entry);
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
let allCoordinatesToSyncWithIblt: bigint[] = [];
|
|
388
|
+
for (const entry of nonBoundaryEntries) {
|
|
389
|
+
if (entriesToSyncNaively.has(entry.hash)) {
|
|
390
|
+
continue;
|
|
262
391
|
}
|
|
392
|
+
allCoordinatesToSyncWithIblt.push(coerceBigInt(entry.hashNumber));
|
|
263
393
|
}
|
|
264
394
|
|
|
265
395
|
if (entriesToSyncNaively.size > 0) {
|
|
@@ -275,31 +405,44 @@ export class RatelessIBLTSynchronizer<D extends "u32" | "u64">
|
|
|
275
405
|
entriesToSyncNaively.size > maxSyncWithSimpleMethod
|
|
276
406
|
) {
|
|
277
407
|
// Fallback: if nothing left for IBLT (or simple set is too large), include all in IBLT
|
|
278
|
-
allCoordinatesToSyncWithIblt =
|
|
279
|
-
|
|
280
|
-
|
|
408
|
+
allCoordinatesToSyncWithIblt = [];
|
|
409
|
+
for (const entry of properties.entries.values()) {
|
|
410
|
+
allCoordinatesToSyncWithIblt.push(coerceBigInt(entry.hashNumber));
|
|
411
|
+
}
|
|
281
412
|
}
|
|
282
413
|
|
|
283
414
|
if (allCoordinatesToSyncWithIblt.length === 0) {
|
|
284
415
|
return;
|
|
285
416
|
}
|
|
286
417
|
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
418
|
+
await ribltReady;
|
|
419
|
+
|
|
420
|
+
let sortedEntries: bigint[] | BigUint64Array;
|
|
421
|
+
if (typeof BigUint64Array !== "undefined") {
|
|
422
|
+
const typed = new BigUint64Array(allCoordinatesToSyncWithIblt.length);
|
|
423
|
+
for (let i = 0; i < allCoordinatesToSyncWithIblt.length; i++) {
|
|
424
|
+
typed[i] = allCoordinatesToSyncWithIblt[i];
|
|
294
425
|
}
|
|
295
|
-
|
|
426
|
+
typed.sort();
|
|
427
|
+
sortedEntries = typed;
|
|
428
|
+
} else {
|
|
429
|
+
sortedEntries = allCoordinatesToSyncWithIblt.sort((a, b) => {
|
|
430
|
+
if (a > b) {
|
|
431
|
+
return 1;
|
|
432
|
+
} else if (a < b) {
|
|
433
|
+
return -1;
|
|
434
|
+
} else {
|
|
435
|
+
return 0;
|
|
436
|
+
}
|
|
437
|
+
});
|
|
438
|
+
}
|
|
296
439
|
|
|
297
440
|
// assume sorted, and find the largest gap
|
|
298
441
|
let largestGap = 0n;
|
|
299
442
|
let largestGapIndex = 0;
|
|
300
|
-
for (let i = 0; i < sortedEntries.length
|
|
443
|
+
for (let i = 0; i < sortedEntries.length; i++) {
|
|
301
444
|
const current = sortedEntries[i];
|
|
302
|
-
const next = sortedEntries[i + 1];
|
|
445
|
+
const next = sortedEntries[(i + 1) % sortedEntries.length];
|
|
303
446
|
const gap =
|
|
304
447
|
next >= current
|
|
305
448
|
? next - current
|
|
@@ -329,8 +472,12 @@ export class RatelessIBLTSynchronizer<D extends "u32" | "u64">
|
|
|
329
472
|
|
|
330
473
|
const startSync = new StartSync({ from: start, to: end, symbols: [] });
|
|
331
474
|
const encoder = new EncoderWrapper();
|
|
332
|
-
|
|
333
|
-
encoder.
|
|
475
|
+
if (typeof BigUint64Array !== "undefined" && sortedEntries instanceof BigUint64Array) {
|
|
476
|
+
encoder.add_symbols(sortedEntries);
|
|
477
|
+
} else {
|
|
478
|
+
for (const entry of sortedEntries) {
|
|
479
|
+
encoder.add_symbol(coerceBigInt(entry));
|
|
480
|
+
}
|
|
334
481
|
}
|
|
335
482
|
|
|
336
483
|
let initialSymbols = Math.round(
|
|
@@ -406,16 +553,12 @@ export class RatelessIBLTSynchronizer<D extends "u32" | "u64">
|
|
|
406
553
|
this.startedOrCompletedSynchronizations.add(syncId);
|
|
407
554
|
|
|
408
555
|
const wrapped = message.end < message.start;
|
|
409
|
-
const decoder = await
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
},
|
|
416
|
-
this.properties.entryIndex,
|
|
417
|
-
"decoder",
|
|
418
|
-
);
|
|
556
|
+
const decoder = await this.getLocalDecoderForRange({
|
|
557
|
+
start1: message.start,
|
|
558
|
+
end1: wrapped ? this.properties.numbers.maxValue : message.end,
|
|
559
|
+
start2: 0n,
|
|
560
|
+
end2: wrapped ? message.end : 0n,
|
|
561
|
+
});
|
|
419
562
|
|
|
420
563
|
if (!decoder) {
|
|
421
564
|
await this.simple.rpc.send(
|
|
@@ -620,10 +763,12 @@ export class RatelessIBLTSynchronizer<D extends "u32" | "u64">
|
|
|
620
763
|
}
|
|
621
764
|
|
|
622
765
|
onEntryAdded(entry: Entry<any>): void {
|
|
766
|
+
this.invalidateLocalRangeEncoderCache();
|
|
623
767
|
return this.simple.onEntryAdded(entry);
|
|
624
768
|
}
|
|
625
769
|
|
|
626
770
|
onEntryRemoved(hash: string) {
|
|
771
|
+
this.invalidateLocalRangeEncoderCache();
|
|
627
772
|
return this.simple.onEntryRemoved(hash);
|
|
628
773
|
}
|
|
629
774
|
|
|
@@ -642,6 +787,7 @@ export class RatelessIBLTSynchronizer<D extends "u32" | "u64">
|
|
|
642
787
|
for (const [, obj] of this.outgoingSyncProcesses) {
|
|
643
788
|
obj.free();
|
|
644
789
|
}
|
|
790
|
+
this.clearLocalRangeEncoderCache();
|
|
645
791
|
return this.simple.close();
|
|
646
792
|
}
|
|
647
793
|
|
package/src/sync/simple.ts
CHANGED
|
@@ -16,7 +16,7 @@ import {
|
|
|
16
16
|
} from "../exchange-heads.js";
|
|
17
17
|
import { TransportMessage } from "../message.js";
|
|
18
18
|
import type { EntryReplicated } from "../ranges.js";
|
|
19
|
-
import type { SyncableKey, Syncronizer } from "./index.js";
|
|
19
|
+
import type { SyncableKey, SyncOptions, Syncronizer } from "./index.js";
|
|
20
20
|
|
|
21
21
|
@variant([0, 1])
|
|
22
22
|
export class RequestMaybeSync extends TransportMessage {
|
|
@@ -109,6 +109,7 @@ export class SimpleSyncronizer<R extends "u32" | "u64">
|
|
|
109
109
|
log: Log<any>;
|
|
110
110
|
entryIndex: Index<EntryReplicated<R>, any>;
|
|
111
111
|
coordinateToHash: Cache<string>;
|
|
112
|
+
private syncOptions?: SyncOptions<R>;
|
|
112
113
|
|
|
113
114
|
// Syncing and dedeplucation work
|
|
114
115
|
syncMoreInterval?: ReturnType<typeof setTimeout>;
|
|
@@ -120,6 +121,7 @@ export class SimpleSyncronizer<R extends "u32" | "u64">
|
|
|
120
121
|
entryIndex: Index<EntryReplicated<R>, any>;
|
|
121
122
|
log: Log<any>;
|
|
122
123
|
coordinateToHash: Cache<string>;
|
|
124
|
+
sync?: SyncOptions<R>;
|
|
123
125
|
}) {
|
|
124
126
|
this.syncInFlightQueue = new Map();
|
|
125
127
|
this.syncInFlightQueueInverted = new Map();
|
|
@@ -128,14 +130,35 @@ export class SimpleSyncronizer<R extends "u32" | "u64">
|
|
|
128
130
|
this.log = properties.log;
|
|
129
131
|
this.entryIndex = properties.entryIndex;
|
|
130
132
|
this.coordinateToHash = properties.coordinateToHash;
|
|
133
|
+
this.syncOptions = properties.sync;
|
|
131
134
|
}
|
|
132
135
|
|
|
133
136
|
onMaybeMissingEntries(properties: {
|
|
134
137
|
entries: Map<string, EntryReplicated<R>>;
|
|
135
138
|
targets: string[];
|
|
136
139
|
}): Promise<void> {
|
|
140
|
+
let hashes: string[];
|
|
141
|
+
const priorityFn = this.syncOptions?.priority;
|
|
142
|
+
if (priorityFn) {
|
|
143
|
+
let index = 0;
|
|
144
|
+
const scored: { hash: string; index: number; priority: number }[] = [];
|
|
145
|
+
for (const [hash, entry] of properties.entries) {
|
|
146
|
+
const priorityValue = priorityFn(entry);
|
|
147
|
+
scored.push({
|
|
148
|
+
hash,
|
|
149
|
+
index,
|
|
150
|
+
priority: Number.isFinite(priorityValue) ? priorityValue : 0,
|
|
151
|
+
});
|
|
152
|
+
index += 1;
|
|
153
|
+
}
|
|
154
|
+
scored.sort((a, b) => b.priority - a.priority || a.index - b.index);
|
|
155
|
+
hashes = scored.map((x) => x.hash);
|
|
156
|
+
} else {
|
|
157
|
+
hashes = [...properties.entries.keys()];
|
|
158
|
+
}
|
|
159
|
+
|
|
137
160
|
return this.rpc.send(
|
|
138
|
-
new RequestMaybeSync({ hashes
|
|
161
|
+
new RequestMaybeSync({ hashes }),
|
|
139
162
|
{
|
|
140
163
|
priority: 1,
|
|
141
164
|
mode: new SilentDelivery({ to: properties.targets, redundancy: 1 }),
|