@peerbit/shared-log 10.2.0 → 10.3.0-a4ac71a

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/src/index.ts CHANGED
@@ -37,10 +37,12 @@ import {
37
37
  import { RPC, type RequestContext } from "@peerbit/rpc";
38
38
  import {
39
39
  AcknowledgeDelivery,
40
+ AnyWhere,
40
41
  DeliveryMode,
41
42
  NotStartedError,
42
43
  SeekDelivery,
43
44
  SilentDelivery,
45
+ type WithMode,
44
46
  } from "@peerbit/stream-interface";
45
47
  import { AbortError, waitFor } from "@peerbit/time";
46
48
  import pDefer, { type DeferredPromise } from "p-defer";
@@ -83,7 +85,6 @@ import {
83
85
  ReplicationRangeIndexableU32,
84
86
  ReplicationRangeIndexableU64,
85
87
  ReplicationRangeMessage,
86
- SyncStatus,
87
88
  appromixateCoverage,
88
89
  countCoveringRangesSameOwner,
89
90
  debounceAggregationChanges,
@@ -195,6 +196,7 @@ export type ReplicationOptions<R extends "u32" | "u64" = any> =
195
196
  | number
196
197
  | boolean
197
198
  | "resume";
199
+ export { BlocksMessage };
198
200
 
199
201
  const isAdaptiveReplicatorOption = (
200
202
  options: ReplicationOptions<any>,
@@ -307,6 +309,7 @@ export type SharedLogOptions<
307
309
  distributionDebounceTime?: number;
308
310
  compatibility?: number;
309
311
  domain?: ReplicationDomainConstructor<D>;
312
+ earlyBlocks?: boolean | { cacheSize?: number };
310
313
  };
311
314
 
312
315
  export const DEFAULT_MIN_REPLICAS = 2;
@@ -568,15 +571,17 @@ export class SharedLog<
568
571
  checkDuplicates,
569
572
  announce,
570
573
  mergeSegments,
574
+ rebalance,
571
575
  }: {
572
576
  reset?: boolean;
573
577
  checkDuplicates?: boolean;
574
578
  mergeSegments?: boolean;
579
+ rebalance?: boolean;
575
580
  announce?: (
576
581
  msg: AddedReplicationSegmentMessage | AllReplicatingSegmentsMessage,
577
582
  ) => void;
578
583
  } = {},
579
- ) {
584
+ ): Promise<ReplicationRangeIndexable<R>[]> {
580
585
  let offsetWasProvided = false;
581
586
  if (isUnreplicationOptions(options)) {
582
587
  await this.unreplicate();
@@ -603,7 +608,7 @@ export class SharedLog<
603
608
  const maybeRange = await this.getDynamicRange();
604
609
  if (!maybeRange) {
605
610
  // not allowed
606
- return;
611
+ return [];
607
612
  }
608
613
  rangesToReplicate = [maybeRange];
609
614
 
@@ -630,7 +635,7 @@ export class SharedLog<
630
635
 
631
636
  if (rangeArgs.length === 0) {
632
637
  // nothing to do
633
- return;
638
+ return [];
634
639
  }
635
640
 
636
641
  for (const rangeArg of rangeArgs) {
@@ -740,6 +745,7 @@ export class SharedLog<
740
745
  reset: resetRanges ?? false,
741
746
  checkDuplicates,
742
747
  announce,
748
+ rebalance,
743
749
  });
744
750
 
745
751
  if (rangesToUnreplicate.length > 0) {
@@ -755,6 +761,8 @@ export class SharedLog<
755
761
 
756
762
  return rangesToReplicate;
757
763
  }
764
+
765
+ return [];
758
766
  }
759
767
 
760
768
  setupDebouncedRebalancing(options?: DynamicReplicationOptions<R>) {
@@ -792,6 +800,7 @@ export class SharedLog<
792
800
  options?: {
793
801
  reset?: boolean;
794
802
  checkDuplicates?: boolean;
803
+ rebalance?: boolean;
795
804
  mergeSegments?: boolean;
796
805
  announce?: (
797
806
  msg: AllReplicatingSegmentsMessage | AddedReplicationSegmentMessage,
@@ -1032,13 +1041,20 @@ export class SharedLog<
1032
1041
  reset,
1033
1042
  checkDuplicates,
1034
1043
  timestamp: ts,
1035
- }: { reset?: boolean; checkDuplicates?: boolean; timestamp?: number } = {},
1044
+ rebalance,
1045
+ }: {
1046
+ reset?: boolean;
1047
+ rebalance?: boolean;
1048
+ checkDuplicates?: boolean;
1049
+ timestamp?: number;
1050
+ } = {},
1036
1051
  ) {
1037
1052
  if (this._isTrustedReplicator && !(await this._isTrustedReplicator(from))) {
1038
1053
  return undefined;
1039
1054
  }
1040
1055
  let isNewReplicator = false;
1041
1056
  let timestamp = BigInt(ts ?? +new Date());
1057
+ rebalance = rebalance == null ? true : rebalance;
1042
1058
 
1043
1059
  let diffs: ReplicationChanges<ReplicationRangeIndexable<R>>;
1044
1060
  let deleted: ReplicationRangeIndexable<R>[] | undefined = undefined;
@@ -1066,16 +1082,25 @@ export class SharedLog<
1066
1082
 
1067
1083
  isNewReplicator = prevCount === 0 && ranges.length > 0;
1068
1084
  } else {
1069
- let existing = await this.replicationIndex
1070
- .iterate(
1071
- {
1072
- query: ranges.map(
1073
- (x) => new ByteMatchQuery({ key: "id", value: x.id }),
1074
- ),
1075
- },
1076
- { reference: true },
1077
- )
1078
- .all();
1085
+ let batchSize = 100;
1086
+ let existing: ReplicationRangeIndexable<R>[] = [];
1087
+ for (let i = 0; i < ranges.length; i += batchSize) {
1088
+ const results = await this.replicationIndex
1089
+ .iterate(
1090
+ {
1091
+ query: (ranges.length <= batchSize
1092
+ ? ranges
1093
+ : ranges.slice(i, i + batchSize)
1094
+ ).map((x) => new ByteMatchQuery({ key: "id", value: x.id })),
1095
+ },
1096
+ { reference: true },
1097
+ )
1098
+ .all();
1099
+ for (const result of results) {
1100
+ existing.push(result.value);
1101
+ }
1102
+ }
1103
+
1079
1104
  if (existing.length === 0) {
1080
1105
  let prevCount = await this.replicationIndex.count({
1081
1106
  query: new StringMatch({ key: "hash", value: from.hashcode() }),
@@ -1100,7 +1125,7 @@ export class SharedLog<
1100
1125
  }
1101
1126
  let existingMap = new Map<string, ReplicationRangeIndexable<any>>();
1102
1127
  for (const result of existing) {
1103
- existingMap.set(result.value.idString, result.value);
1128
+ existingMap.set(result.idString, result);
1104
1129
  }
1105
1130
 
1106
1131
  let changes: ReplicationChanges<ReplicationRangeIndexable<R>> = ranges
@@ -1177,7 +1202,13 @@ export class SharedLog<
1177
1202
  }),
1178
1203
  );
1179
1204
 
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!
1205
+ if (rebalance && diff.range.mode !== ReplicationIntent.Strict) {
1206
+ // TODO this statement (might) cause issues with triggering pruning if the segment is strict and maturity timings will affect the outcome of rebalancing
1207
+ this.replicationChangeDebounceFn.add({
1208
+ ...diff,
1209
+ matured: true,
1210
+ }); // 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!
1211
+ }
1181
1212
  pendingRanges.delete(diff.range.idString);
1182
1213
  if (pendingRanges.size === 0) {
1183
1214
  this.pendingMaturity.delete(diff.range.hash);
@@ -1238,7 +1269,7 @@ export class SharedLog<
1238
1269
  }
1239
1270
  }
1240
1271
 
1241
- if (diffs.length > 0) {
1272
+ if (rebalance && diffs.length > 0) {
1242
1273
  for (const diff of diffs) {
1243
1274
  this.replicationChangeDebounceFn.add(diff);
1244
1275
  }
@@ -1254,9 +1285,9 @@ export class SharedLog<
1254
1285
  async startAnnounceReplicating(
1255
1286
  range: ReplicationRangeIndexable<R>[],
1256
1287
  options: {
1257
- syncStatus?: SyncStatus;
1258
1288
  reset?: boolean;
1259
1289
  checkDuplicates?: boolean;
1290
+ rebalance?: boolean;
1260
1291
  announce?: (
1261
1292
  msg: AllReplicatingSegmentsMessage | AddedReplicationSegmentMessage,
1262
1293
  ) => void;
@@ -1311,7 +1342,7 @@ export class SharedLog<
1311
1342
  }
1312
1343
  }
1313
1344
 
1314
- private addPeersToGidPeerHistory(
1345
+ addPeersToGidPeerHistory(
1315
1346
  gid: string,
1316
1347
  publicKeys: Iterable<string>,
1317
1348
  reset?: boolean,
@@ -1513,12 +1544,13 @@ export class SharedLog<
1513
1544
  this.remoteBlocks = new RemoteBlocks({
1514
1545
  local: localBlocks,
1515
1546
  publish: (message, options) =>
1516
- this.rpc.send(new BlocksMessage(message), {
1517
- mode: options?.to
1518
- ? new SilentDelivery({ to: options.to, redundancy: 1 })
1519
- : undefined,
1520
- }),
1547
+ this.rpc.send(
1548
+ new BlocksMessage(message),
1549
+ (options as WithMode).mode instanceof AnyWhere ? undefined : options,
1550
+ ),
1521
1551
  waitFor: this.rpc.waitFor.bind(this.rpc),
1552
+ publicKey: this.node.identity.publicKey,
1553
+ earlyBlocks: options?.earlyBlocks ?? true,
1522
1554
  });
1523
1555
 
1524
1556
  await this.remoteBlocks.start();
@@ -1691,7 +1723,7 @@ export class SharedLog<
1691
1723
  await this.rpc.open({
1692
1724
  queryType: TransportMessage,
1693
1725
  responseType: TransportMessage,
1694
- responseHandler: (query, context) => this._onMessage(query, context),
1726
+ responseHandler: (query, context) => this.onMessage(query, context),
1695
1727
  topic: this.topic,
1696
1728
  });
1697
1729
 
@@ -1917,7 +1949,6 @@ export class SharedLog<
1917
1949
  this.coordinateToHash.clear();
1918
1950
  this.recentlyRebalanced.clear();
1919
1951
  this.uniqueReplicators.clear();
1920
-
1921
1952
  this._closeController.abort();
1922
1953
 
1923
1954
  clearInterval(this.interval);
@@ -1984,7 +2015,7 @@ export class SharedLog<
1984
2015
  }
1985
2016
 
1986
2017
  // Callback for receiving a message from the network
1987
- async _onMessage(
2018
+ async onMessage(
1988
2019
  msg: TransportMessage,
1989
2020
  context: RequestContext,
1990
2021
  ): Promise<void> {
@@ -2332,48 +2363,11 @@ export class SharedLog<
2332
2363
  }
2333
2364
  } else if (await this.syncronizer.onMessage(msg, context)) {
2334
2365
  return; // the syncronizer has handled the message
2335
- } /* else if (msg instanceof RequestMaybeSync) {
2336
- const requestHashes: string[] = [];
2337
-
2338
- for (const hash of msg.hashes) {
2339
- const inFlight = this.syncInFlightQueue.get(hash);
2340
- if (inFlight) {
2341
- if (
2342
- !inFlight.find((x) => x.hashcode() === context.from!.hashcode())
2343
- ) {
2344
- inFlight.push(context.from);
2345
- let inverted = this.syncInFlightQueueInverted.get(
2346
- context.from.hashcode(),
2347
- );
2348
- if (!inverted) {
2349
- inverted = new Set();
2350
- this.syncInFlightQueueInverted.set(
2351
- context.from.hashcode(),
2352
- inverted,
2353
- );
2354
- }
2355
- inverted.add(hash);
2356
- }
2357
- } else if (!(await this.log.has(hash))) {
2358
- this.syncInFlightQueue.set(hash, []);
2359
- requestHashes.push(hash); // request immediately (first time we have seen this hash)
2360
- }
2361
- }
2362
- requestHashes.length > 0 &&
2363
- (await this.requestSync(requestHashes, [context.from.hashcode()]));
2364
- } else if (msg instanceof ResponseMaybeSync) {
2365
- // TODO perhaps send less messages to more receivers for performance reasons?
2366
- // TODO wait for previous send to target before trying to send more?
2367
- for await (const message of createExchangeHeadsMessages(
2368
- this.log,
2369
- msg.hashes,
2370
- )) {
2371
- await this.rpc.send(message, {
2372
- mode: new SilentDelivery({ to: [context.from!], redundancy: 1 }),
2373
- });
2374
- }
2375
- } */ else if (msg instanceof BlocksMessage) {
2376
- await this.remoteBlocks.onMessage(msg.message);
2366
+ } else if (msg instanceof BlocksMessage) {
2367
+ await this.remoteBlocks.onMessage(
2368
+ msg.message,
2369
+ context.from!.hashcode(),
2370
+ );
2377
2371
  } else if (msg instanceof RequestReplicationInfoMessage) {
2378
2372
  // TODO this message type is never used, should we remove it?
2379
2373
 
@@ -2699,6 +2693,10 @@ export class SharedLog<
2699
2693
  }
2700
2694
  : undefined;
2701
2695
 
2696
+ let assumeSynced =
2697
+ options?.replicate &&
2698
+ typeof options.replicate !== "boolean" &&
2699
+ options.replicate.assumeSynced;
2702
2700
  const persistCoordinate = async (entry: Entry<T>) => {
2703
2701
  const minReplicas = decodeReplicas(entry).getValue(this);
2704
2702
  const leaders = await this.findLeaders(
@@ -2707,11 +2705,7 @@ export class SharedLog<
2707
2705
  { persist: {} },
2708
2706
  );
2709
2707
 
2710
- if (
2711
- options?.replicate &&
2712
- typeof options.replicate !== "boolean" &&
2713
- options.replicate.assumeSynced
2714
- ) {
2708
+ if (assumeSynced) {
2715
2709
  // make sure we dont start to initate syncing process outwards for this entry
2716
2710
  this.addPeersToGidPeerHistory(entry.meta.gid, leaders.keys());
2717
2711
  }
@@ -2743,7 +2737,9 @@ export class SharedLog<
2743
2737
 
2744
2738
  if (options?.replicate) {
2745
2739
  let messageToSend: AddedReplicationSegmentMessage | undefined = undefined;
2740
+
2746
2741
  await this.replicate(entriesToReplicate, {
2742
+ rebalance: assumeSynced ? false : true,
2747
2743
  checkDuplicates: true,
2748
2744
  mergeSegments:
2749
2745
  typeof options.replicate !== "boolean" && options.replicate
@@ -2768,6 +2764,7 @@ export class SharedLog<
2768
2764
  },
2769
2765
  });
2770
2766
 
2767
+ // it is importat that we call persistCoordinate after this.replicate(entries) as else there might be a prune job deleting the entry before replication duties has been assigned to self
2771
2768
  for (const entry of entriesToPersist) {
2772
2769
  await persistCoordinate(entry);
2773
2770
  }
@@ -2779,39 +2776,6 @@ export class SharedLog<
2779
2776
  }
2780
2777
  }
2781
2778
  }
2782
- /*
2783
- private async updateLeaders(
2784
- cursor: NumberFromType<R>,
2785
- prev: EntryReplicated<R>,
2786
- options?: {
2787
- roleAge?: number;
2788
- },
2789
- ): Promise<{
2790
- isLeader: boolean;
2791
- leaders: Map<string, { intersecting: boolean }>;
2792
- }> {
2793
- // we consume a list of coordinates in this method since if we are leader of one coordinate we want to persist all of them
2794
- const leaders = await this._findLeaders(cursor, options);
2795
- const isLeader = leaders.has(this.node.identity.publicKey.hashcode());
2796
- const isAtRangeBoundary = shouldAssignToRangeBoundary(leaders, 1);
2797
-
2798
- // dont do anthing if nothing has changed
2799
- if (prev.assignedToRangeBoundary !== isAtRangeBoundary) {
2800
- return { isLeader, leaders };
2801
- }
2802
-
2803
- await this.entryCoordinatesIndex.put(
2804
- new this.indexableDomain.constructorEntry({
2805
- assignedToRangeBoundary: isAtRangeBoundary,
2806
- coordinate: cursor,
2807
- meta: prev.meta,
2808
- hash: prev.hash,
2809
- }),
2810
- );
2811
-
2812
- return { isLeader, leaders };
2813
- }
2814
- */
2815
2779
 
2816
2780
  private async _waitForReplicators(
2817
2781
  cursors: NumberFromType<R>[],
@@ -3443,7 +3407,6 @@ export class SharedLog<
3443
3407
  const minReplicasValue = minReplicasObj.getValue(this);
3444
3408
 
3445
3409
  // TODO is this check necessary
3446
-
3447
3410
  if (
3448
3411
  !(await this._waitForReplicators(
3449
3412
  cursor ??
@@ -3574,7 +3537,7 @@ export class SharedLog<
3574
3537
  }
3575
3538
 
3576
3539
  const timestamp = BigInt(+new Date());
3577
- this.onReplicationChange(
3540
+ return this.onReplicationChange(
3578
3541
  (await this.getAllReplicationSegments()).map((x) => {
3579
3542
  return { range: x, type: "added", timestamp };
3580
3543
  }),
@@ -3617,6 +3580,7 @@ export class SharedLog<
3617
3580
  if (this.closed) {
3618
3581
  break;
3619
3582
  }
3583
+
3620
3584
  let oldPeersSet = this._gidPeersHistory.get(entryReplicated.gid);
3621
3585
  let isLeader = false;
3622
3586
 
package/src/ranges.ts CHANGED
@@ -43,11 +43,6 @@ export enum ReplicationIntent {
43
43
  Strict = 1, // only replicate data in the segment to the specified replicator, not any other data
44
44
  }
45
45
 
46
- export enum SyncStatus {
47
- Unsynced = 0,
48
- Synced = 1,
49
- }
50
-
51
46
  const min = (a: number | bigint, b: number | bigint) => (a < b ? a : b);
52
47
 
53
48
  const getSegmentsFromOffsetAndRange = <T extends number | bigint>(
@@ -1897,7 +1892,6 @@ export const getSamples = async <R extends "u32" | "u64">(
1897
1892
  const now = +new Date();
1898
1893
  let matured = 0;
1899
1894
 
1900
- /* let missingForCursors: NumberFromType<R>[] = [] */
1901
1895
  let uniqueVisited = new Set<string>();
1902
1896
  for (let i = 0; i < cursor.length; i++) {
1903
1897
  let point = cursor[i];
@@ -1962,6 +1956,7 @@ export const getSamples = async <R extends "u32" | "u64">(
1962
1956
  /* if (leaders.size < cursor.length) {
1963
1957
  throw new Error("Missing leaders got: " + leaders.size + " -- expected -- " + cursor.length + " role age " + roleAge + " missing " + missingForCursors.length + " replication index size: " + (await peers.count()));
1964
1958
  } */
1959
+
1965
1960
  return leaders;
1966
1961
  };
1967
1962
 
@@ -2277,6 +2272,9 @@ export const debounceAggregationChanges = <
2277
2272
  ) => {
2278
2273
  return debounceAccumulator(
2279
2274
  (result) => {
2275
+ if (result.size === 0) {
2276
+ return;
2277
+ }
2280
2278
  return fn([...result.values()]);
2281
2279
  },
2282
2280
  () => {
@@ -2450,12 +2448,6 @@ export const toRebalance = <R extends "u32" | "u64">(
2450
2448
 
2451
2449
  while (iterator.done() !== true) {
2452
2450
  const entries = await iterator.all(); // TODO choose right batch sizes here for optimal memory usage / speed
2453
-
2454
- /* const grouped = await groupByGidSync(entries.map((x) => x.value));
2455
- for (const [gid, entries] of grouped.entries()) {
2456
- yield { gid, entries };
2457
- } */
2458
-
2459
2451
  for (const entry of entries) {
2460
2452
  yield entry.value;
2461
2453
  }
@@ -3,6 +3,7 @@ import { Cache } from "@peerbit/cache";
3
3
  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
+ import { logger as loggerFn } from "@peerbit/logger";
6
7
  import { DecoderWrapper, EncoderWrapper } from "@peerbit/riblt";
7
8
  import type { RPC, RequestContext } from "@peerbit/rpc";
8
9
  import { SilentDelivery } from "@peerbit/stream-interface";
@@ -17,6 +18,8 @@ import {
17
18
  } from "../ranges.js";
18
19
  import { SimpleSyncronizer } from "./simple.js";
19
20
 
21
+ export const logger = loggerFn({ module: "shared-log" });
22
+
20
23
  type NumberOrBigint = number | bigint;
21
24
 
22
25
  const coerceBigInt = (value: NumberOrBigint): bigint =>
@@ -470,7 +473,16 @@ export class RatelessIBLTSynchronizer<
470
473
  for (const symbol of message.symbols) {
471
474
  decoder.add_coded_symbol(symbol);
472
475
  }
473
- decoder.try_decode();
476
+ try {
477
+ decoder.try_decode();
478
+ } catch (error: any) {
479
+ if (error?.message === "Invalid degree") {
480
+ logger.error(error?.message);
481
+ return false;
482
+ } else {
483
+ throw error;
484
+ }
485
+ }
474
486
  count += message.symbols.length;
475
487
 
476
488
  if (decoder.decoded()) {