@peerbit/shared-log 12.1.3 → 12.2.0-3333888

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.
Files changed (40) hide show
  1. package/dist/benchmark/pid-convergence.d.ts +2 -0
  2. package/dist/benchmark/pid-convergence.d.ts.map +1 -0
  3. package/dist/benchmark/pid-convergence.js +138 -0
  4. package/dist/benchmark/pid-convergence.js.map +1 -0
  5. package/dist/benchmark/rateless-iblt-sender-startsync.d.ts +2 -0
  6. package/dist/benchmark/rateless-iblt-sender-startsync.d.ts.map +1 -0
  7. package/dist/benchmark/rateless-iblt-sender-startsync.js +104 -0
  8. package/dist/benchmark/rateless-iblt-sender-startsync.js.map +1 -0
  9. package/dist/benchmark/rateless-iblt-startsync-cache.d.ts +2 -0
  10. package/dist/benchmark/rateless-iblt-startsync-cache.d.ts.map +1 -0
  11. package/dist/benchmark/rateless-iblt-startsync-cache.js +112 -0
  12. package/dist/benchmark/rateless-iblt-startsync-cache.js.map +1 -0
  13. package/dist/benchmark/sync-catchup.d.ts +3 -0
  14. package/dist/benchmark/sync-catchup.d.ts.map +1 -0
  15. package/dist/benchmark/sync-catchup.js +109 -0
  16. package/dist/benchmark/sync-catchup.js.map +1 -0
  17. package/dist/src/index.d.ts +10 -3
  18. package/dist/src/index.d.ts.map +1 -1
  19. package/dist/src/index.js +66 -32
  20. package/dist/src/index.js.map +1 -1
  21. package/dist/src/like.d.ts +71 -0
  22. package/dist/src/like.d.ts.map +1 -0
  23. package/dist/src/like.js +2 -0
  24. package/dist/src/like.js.map +1 -0
  25. package/dist/src/sync/index.d.ts +14 -0
  26. package/dist/src/sync/index.d.ts.map +1 -1
  27. package/dist/src/sync/rateless-iblt.d.ts +14 -22
  28. package/dist/src/sync/rateless-iblt.d.ts.map +1 -1
  29. package/dist/src/sync/rateless-iblt.js +139 -22
  30. package/dist/src/sync/rateless-iblt.js.map +1 -1
  31. package/dist/src/sync/simple.d.ts +3 -1
  32. package/dist/src/sync/simple.d.ts.map +1 -1
  33. package/dist/src/sync/simple.js +24 -2
  34. package/dist/src/sync/simple.js.map +1 -1
  35. package/package.json +20 -20
  36. package/src/index.ts +95 -37
  37. package/src/like.ts +84 -0
  38. package/src/sync/index.ts +19 -0
  39. package/src/sync/rateless-iblt.ts +193 -40
  40. package/src/sync/simple.ts +26 -3
@@ -4,18 +4,24 @@ import { type PublicSignKey, randomBytes, toBase64 } from "@peerbit/crypto";
4
4
  import { type Index } from "@peerbit/indexer-interface";
5
5
  import type { Entry, Log } from "@peerbit/log";
6
6
  import { logger as loggerFn } from "@peerbit/logger";
7
- import { DecoderWrapper, EncoderWrapper } from "@peerbit/riblt";
7
+ import {
8
+ DecoderWrapper,
9
+ EncoderWrapper,
10
+ ready as ribltReady,
11
+ } from "@peerbit/riblt";
8
12
  import type { RPC, RequestContext } from "@peerbit/rpc";
9
13
  import { SilentDelivery } from "@peerbit/stream-interface";
10
14
  import { type EntryWithRefs } from "../exchange-heads.js";
11
- import { type Numbers } from "../integers.js";
12
15
  import { TransportMessage } from "../message.js";
13
16
  import {
14
17
  type EntryReplicated,
15
- type ReplicationRangeIndexable,
16
18
  matchEntriesInRangeQuery,
17
19
  } from "../ranges.js";
18
- import type { SyncableKey, Syncronizer } from "./index.js";
20
+ import type {
21
+ SyncableKey,
22
+ SynchronizerComponents,
23
+ Syncronizer,
24
+ } from "./index.js";
19
25
  import { SimpleSyncronizer } from "./simple.js";
20
26
 
21
27
  export const logger = loggerFn("peerbit:shared-log:rateless");
@@ -142,6 +148,7 @@ const buildEncoderOrDecoderFromRange = async <
142
148
  entryIndex: Index<EntryReplicated<D>>,
143
149
  type: T,
144
150
  ): Promise<E | false> => {
151
+ await ribltReady;
145
152
  const encoder =
146
153
  type === "encoder" ? new EncoderWrapper() : new DecoderWrapper();
147
154
 
@@ -180,6 +187,13 @@ export class RatelessIBLTSynchronizer<D extends "u32" | "u64">
180
187
  simple: SimpleSyncronizer<D>;
181
188
 
182
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
+
183
197
  ingoingSyncProcesses: Map<
184
198
  string,
185
199
  {
@@ -207,14 +221,7 @@ export class RatelessIBLTSynchronizer<D extends "u32" | "u64">
207
221
  >;
208
222
 
209
223
  constructor(
210
- readonly properties: {
211
- rpc: RPC<TransportMessage, TransportMessage>;
212
- rangeIndex: Index<ReplicationRangeIndexable<D>, any>;
213
- entryIndex: Index<EntryReplicated<D>, any>;
214
- log: Log<any>;
215
- coordinateToHash: Cache<string>;
216
- numbers: Numbers<D>;
217
- },
224
+ readonly properties: SynchronizerComponents<D>,
218
225
  ) {
219
226
  this.simple = new SimpleSyncronizer(properties);
220
227
  this.outgoingSyncProcesses = new Map();
@@ -222,6 +229,91 @@ export class RatelessIBLTSynchronizer<D extends "u32" | "u64">
222
229
  this.startedOrCompletedSynchronizations = new Cache({ max: 1e4 });
223
230
  }
224
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
+
225
317
  async onMaybeMissingEntries(properties: {
226
318
  entries: Map<string, EntryReplicated<D>>;
227
319
  targets: string[];
@@ -233,7 +325,6 @@ export class RatelessIBLTSynchronizer<D extends "u32" | "u64">
233
325
  // such as those assigned to range boundaries.
234
326
 
235
327
  let entriesToSyncNaively: Map<string, EntryReplicated<D>> = new Map();
236
- let allCoordinatesToSyncWithIblt: bigint[] = [];
237
328
  let minSyncIbltSize = 333; // TODO: make configurable
238
329
  let maxSyncWithSimpleMethod = 1e3;
239
330
 
@@ -246,13 +337,59 @@ export class RatelessIBLTSynchronizer<D extends "u32" | "u64">
246
337
  return;
247
338
  }
248
339
 
249
- // Mixed strategy for larger batches
340
+ const nonBoundaryEntries: EntryReplicated<D>[] = [];
250
341
  for (const entry of properties.entries.values()) {
251
342
  if (entry.assignedToRangeBoundary) {
252
343
  entriesToSyncNaively.set(entry.hash, entry);
253
344
  } else {
254
- allCoordinatesToSyncWithIblt.push(coerceBigInt(entry.hashNumber));
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;
255
391
  }
392
+ allCoordinatesToSyncWithIblt.push(coerceBigInt(entry.hashNumber));
256
393
  }
257
394
 
258
395
  if (entriesToSyncNaively.size > 0) {
@@ -268,31 +405,44 @@ export class RatelessIBLTSynchronizer<D extends "u32" | "u64">
268
405
  entriesToSyncNaively.size > maxSyncWithSimpleMethod
269
406
  ) {
270
407
  // Fallback: if nothing left for IBLT (or simple set is too large), include all in IBLT
271
- allCoordinatesToSyncWithIblt = Array.from(
272
- properties.entries.values(),
273
- ).map((x) => coerceBigInt(x.hashNumber));
408
+ allCoordinatesToSyncWithIblt = [];
409
+ for (const entry of properties.entries.values()) {
410
+ allCoordinatesToSyncWithIblt.push(coerceBigInt(entry.hashNumber));
411
+ }
274
412
  }
275
413
 
276
414
  if (allCoordinatesToSyncWithIblt.length === 0) {
277
415
  return;
278
416
  }
279
417
 
280
- const sortedEntries = allCoordinatesToSyncWithIblt.sort((a, b) => {
281
- if (a > b) {
282
- return 1;
283
- } else if (a < b) {
284
- return -1;
285
- } else {
286
- return 0;
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];
287
425
  }
288
- });
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
+ }
289
439
 
290
440
  // assume sorted, and find the largest gap
291
441
  let largestGap = 0n;
292
442
  let largestGapIndex = 0;
293
- for (let i = 0; i < sortedEntries.length - 1; i++) {
443
+ for (let i = 0; i < sortedEntries.length; i++) {
294
444
  const current = sortedEntries[i];
295
- const next = sortedEntries[i + 1];
445
+ const next = sortedEntries[(i + 1) % sortedEntries.length];
296
446
  const gap =
297
447
  next >= current
298
448
  ? next - current
@@ -322,8 +472,12 @@ export class RatelessIBLTSynchronizer<D extends "u32" | "u64">
322
472
 
323
473
  const startSync = new StartSync({ from: start, to: end, symbols: [] });
324
474
  const encoder = new EncoderWrapper();
325
- for (const entry of sortedEntries) {
326
- encoder.add_symbol(coerceBigInt(entry));
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
+ }
327
481
  }
328
482
 
329
483
  let initialSymbols = Math.round(
@@ -399,16 +553,12 @@ export class RatelessIBLTSynchronizer<D extends "u32" | "u64">
399
553
  this.startedOrCompletedSynchronizations.add(syncId);
400
554
 
401
555
  const wrapped = message.end < message.start;
402
- const decoder = await buildEncoderOrDecoderFromRange(
403
- {
404
- start1: message.start,
405
- end1: wrapped ? this.properties.numbers.maxValue : message.end,
406
- start2: 0n,
407
- end2: wrapped ? message.end : 0n,
408
- },
409
- this.properties.entryIndex,
410
- "decoder",
411
- );
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
+ });
412
562
 
413
563
  if (!decoder) {
414
564
  await this.simple.rpc.send(
@@ -613,10 +763,12 @@ export class RatelessIBLTSynchronizer<D extends "u32" | "u64">
613
763
  }
614
764
 
615
765
  onEntryAdded(entry: Entry<any>): void {
766
+ this.invalidateLocalRangeEncoderCache();
616
767
  return this.simple.onEntryAdded(entry);
617
768
  }
618
769
 
619
770
  onEntryRemoved(hash: string) {
771
+ this.invalidateLocalRangeEncoderCache();
620
772
  return this.simple.onEntryRemoved(hash);
621
773
  }
622
774
 
@@ -635,6 +787,7 @@ export class RatelessIBLTSynchronizer<D extends "u32" | "u64">
635
787
  for (const [, obj] of this.outgoingSyncProcesses) {
636
788
  obj.free();
637
789
  }
790
+ this.clearLocalRangeEncoderCache();
638
791
  return this.simple.close();
639
792
  }
640
793
 
@@ -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 {
@@ -57,7 +57,7 @@ const getHashesFromSymbols = async (
57
57
  coordinateToHash: Cache<string>,
58
58
  ) => {
59
59
  let queries: IntegerCompare[] = [];
60
- let batchSize = 1; // TODO arg
60
+ let batchSize = 128; // TODO arg
61
61
  let results = new Set<string>();
62
62
  const handleBatch = async (end = false) => {
63
63
  if (queries.length >= batchSize || (end && queries.length > 0)) {
@@ -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: [...properties.entries.keys()] }),
161
+ new RequestMaybeSync({ hashes }),
139
162
  {
140
163
  priority: 1,
141
164
  mode: new SilentDelivery({ to: properties.targets, redundancy: 1 }),