@peerbit/shared-log 13.1.15 → 13.1.17

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 (39) hide show
  1. package/dist/benchmark/native-graph.d.ts +2 -0
  2. package/dist/benchmark/native-graph.d.ts.map +1 -0
  3. package/dist/benchmark/native-graph.js +249 -0
  4. package/dist/benchmark/native-graph.js.map +1 -0
  5. package/dist/benchmark/sync-batch-sweep.js +72 -24
  6. package/dist/benchmark/sync-batch-sweep.js.map +1 -1
  7. package/dist/benchmark/sync-phase-profile.d.ts +2 -0
  8. package/dist/benchmark/sync-phase-profile.d.ts.map +1 -0
  9. package/dist/benchmark/sync-phase-profile.js +303 -0
  10. package/dist/benchmark/sync-phase-profile.js.map +1 -0
  11. package/dist/src/checked-prune.d.ts +55 -0
  12. package/dist/src/checked-prune.d.ts.map +1 -0
  13. package/dist/src/checked-prune.js +244 -0
  14. package/dist/src/checked-prune.js.map +1 -0
  15. package/dist/src/index.d.ts +10 -9
  16. package/dist/src/index.d.ts.map +1 -1
  17. package/dist/src/index.js +285 -186
  18. package/dist/src/index.js.map +1 -1
  19. package/dist/src/sync/index.d.ts +9 -1
  20. package/dist/src/sync/index.d.ts.map +1 -1
  21. package/dist/src/sync/profile.d.ts +3 -0
  22. package/dist/src/sync/profile.d.ts.map +1 -0
  23. package/dist/src/sync/profile.js +2 -0
  24. package/dist/src/sync/profile.js.map +1 -0
  25. package/dist/src/sync/rateless-iblt.d.ts +25 -11
  26. package/dist/src/sync/rateless-iblt.d.ts.map +1 -1
  27. package/dist/src/sync/rateless-iblt.js +597 -106
  28. package/dist/src/sync/rateless-iblt.js.map +1 -1
  29. package/dist/src/sync/simple.d.ts +5 -0
  30. package/dist/src/sync/simple.d.ts.map +1 -1
  31. package/dist/src/sync/simple.js +224 -74
  32. package/dist/src/sync/simple.js.map +1 -1
  33. package/package.json +16 -12
  34. package/src/checked-prune.ts +331 -0
  35. package/src/index.ts +451 -302
  36. package/src/sync/index.ts +11 -1
  37. package/src/sync/profile.ts +9 -0
  38. package/src/sync/rateless-iblt.ts +768 -128
  39. package/src/sync/simple.ts +256 -94
@@ -9,7 +9,10 @@ import {
9
9
  } from "@peerbit/indexer-interface";
10
10
  import { Entry, Log } from "@peerbit/log";
11
11
  import type { RPC, RequestContext } from "@peerbit/rpc";
12
- import { SilentDelivery } from "@peerbit/stream-interface";
12
+ import {
13
+ CONVERGENCE_MESSAGE_PRIORITY,
14
+ SilentDelivery,
15
+ } from "@peerbit/stream-interface";
13
16
  import {
14
17
  EntryWithRefs,
15
18
  createExchangeHeadsMessages,
@@ -24,6 +27,7 @@ import type {
24
27
  SyncableKey,
25
28
  Syncronizer,
26
29
  } from "./index.js";
30
+ import { emitSyncProfileDuration, syncProfileStart } from "./profile.js";
27
31
 
28
32
  @variant([0, 1])
29
33
  export class RequestMaybeSync extends TransportMessage {
@@ -120,6 +124,9 @@ const SESSION_POLL_INTERVAL_MS = 100;
120
124
  const DEFAULT_MAX_HASHES_PER_MESSAGE = 1_024;
121
125
  const DEFAULT_MAX_COORDINATES_PER_MESSAGE = 1_024;
122
126
  const DEFAULT_MAX_CONVERGENT_TRACKED_HASHES = 4_096;
127
+ // Keep convergence sync above the default/background lane. Dropping it to the
128
+ // background priority lets repair traffic starve behind foreground work.
129
+ export const SYNC_MESSAGE_PRIORITY = CONVERGENCE_MESSAGE_PRIORITY;
123
130
  // Retry missing entry requests when the first response was lost (for example, due to
124
131
  // pubsub stream warmup). Keep it coarse-grained so we do not hammer the network under
125
132
  // large historical backfills.
@@ -427,6 +434,25 @@ export class SimpleSyncronizer<R extends "u32" | "u64">
427
434
  }
428
435
  }
429
436
 
437
+ private getQueuedSyncKey(key: SyncableKey): SyncableKey | undefined {
438
+ if (this.syncInFlightQueue.has(key)) {
439
+ return key;
440
+ }
441
+ if (typeof key === "string") {
442
+ for (const queuedKey of this.syncInFlightQueue.keys()) {
443
+ if (
444
+ typeof queuedKey === "bigint" &&
445
+ this.coordinateToHash.get(queuedKey) === key
446
+ ) {
447
+ return queuedKey;
448
+ }
449
+ }
450
+ return undefined;
451
+ }
452
+ const hash = this.coordinateToHash.get(key);
453
+ return hash && this.syncInFlightQueue.has(hash) ? hash : undefined;
454
+ }
455
+
430
456
  startRepairSession(properties: {
431
457
  entries: Map<string, EntryReplicated<R>>;
432
458
  targets: string[];
@@ -451,7 +477,8 @@ export class SimpleSyncronizer<R extends "u32" | "u64">
451
477
  );
452
478
  const allHashes = this.getPrioritizedHashes(properties.entries);
453
479
  const trackedHashes =
454
- mode === "convergent" && allHashes.length > this.maxConvergentTrackedHashes
480
+ mode === "convergent" &&
481
+ allHashes.length > this.maxConvergentTrackedHashes
455
482
  ? allHashes.slice(0, this.maxConvergentTrackedHashes)
456
483
  : allHashes;
457
484
  const truncated = trackedHashes.length < allHashes.length;
@@ -489,7 +516,7 @@ export class SimpleSyncronizer<R extends "u32" | "u64">
489
516
  cancel: () => {
490
517
  // no-op
491
518
  },
492
- };
519
+ };
493
520
  }
494
521
 
495
522
  // For capped convergent sessions, still dispatch the full set once so large
@@ -522,25 +549,38 @@ export class SimpleSyncronizer<R extends "u32" | "u64">
522
549
  };
523
550
  }
524
551
 
525
- onMaybeMissingEntries(properties: {
552
+ async onMaybeMissingEntries(properties: {
526
553
  entries: Map<string, EntryReplicated<R>>;
527
554
  targets: string[];
528
555
  }): Promise<void> {
556
+ const profile = this.syncOptions?.profile;
557
+ const startedAt = syncProfileStart(profile);
529
558
  const hashes = this.getPrioritizedHashes(properties.entries);
530
559
  const chunks = this.chunk(hashes, this.maxHashesPerMessage);
531
- return chunks.reduce(
532
- (promise, chunk) =>
533
- promise.then(() =>
534
- this.rpc.send(new RequestMaybeSync({ hashes: chunk }), {
535
- priority: 1,
536
- mode: new SilentDelivery({
537
- to: properties.targets,
538
- redundancy: 1,
560
+ try {
561
+ await chunks.reduce(
562
+ (promise, chunk) =>
563
+ promise.then(() =>
564
+ this.rpc.send(new RequestMaybeSync({ hashes: chunk }), {
565
+ priority: SYNC_MESSAGE_PRIORITY,
566
+ mode: new SilentDelivery({
567
+ to: properties.targets,
568
+ redundancy: 1,
569
+ }),
539
570
  }),
540
- }),
541
- ),
542
- Promise.resolve(),
543
- );
571
+ ),
572
+ Promise.resolve(),
573
+ );
574
+ } finally {
575
+ if (profile) {
576
+ emitSyncProfileDuration(profile, startedAt, {
577
+ name: "simple.onMaybeMissingEntries",
578
+ entries: hashes.length,
579
+ messages: chunks.length,
580
+ targets: properties.targets.length,
581
+ });
582
+ }
583
+ }
544
584
  }
545
585
 
546
586
  async onMessage(
@@ -555,31 +595,72 @@ export class SimpleSyncronizer<R extends "u32" | "u64">
555
595
  // TODO perhaps send less messages to more receivers for performance reasons?
556
596
  // TODO wait for previous send to target before trying to send more?
557
597
 
558
- for await (const message of createExchangeHeadsMessages(
559
- this.log,
560
- msg.hashes,
561
- )) {
562
- await this.rpc.send(message, {
563
- mode: new SilentDelivery({ to: [context.from!], redundancy: 1 }),
564
- });
598
+ const profile = this.syncOptions?.profile;
599
+ const startedAt = syncProfileStart(profile);
600
+ let messages = 0;
601
+ try {
602
+ for await (const message of createExchangeHeadsMessages(
603
+ this.log,
604
+ msg.hashes,
605
+ )) {
606
+ messages += 1;
607
+ await this.rpc.send(message, {
608
+ mode: new SilentDelivery({ to: [context.from!], redundancy: 1 }),
609
+ });
610
+ }
611
+ } finally {
612
+ if (profile) {
613
+ emitSyncProfileDuration(profile, startedAt, {
614
+ name: "simple.exchangeHeads",
615
+ entries: msg.hashes.length,
616
+ messages,
617
+ targets: 1,
618
+ details: { source: "responseMaybeSync" },
619
+ });
620
+ }
565
621
  }
566
622
  return true;
567
623
  } else if (msg instanceof RequestMaybeSyncCoordinate) {
624
+ const profile = this.syncOptions?.profile;
625
+ const lookupStartedAt = syncProfileStart(profile);
568
626
  const hashes = await getHashesFromSymbols(
569
627
  msg.hashNumbers,
570
628
  this.entryIndex,
571
629
  this.coordinateToHash,
572
630
  );
573
- for await (const message of createExchangeHeadsMessages(
574
- this.log,
575
- hashes,
576
- )) {
577
- await this.rpc.send(message, {
578
- mode: new SilentDelivery({ to: [context.from!], redundancy: 1 }),
579
- // dont set priority 1 here because this will block other messages that should higher priority
631
+ if (profile) {
632
+ emitSyncProfileDuration(profile, lookupStartedAt, {
633
+ name: "simple.coordinateLookup",
634
+ entries: hashes.size,
635
+ symbols: msg.hashNumbers.length,
580
636
  });
581
637
  }
582
638
 
639
+ const exchangeStartedAt = syncProfileStart(profile);
640
+ let messages = 0;
641
+ try {
642
+ for await (const message of createExchangeHeadsMessages(
643
+ this.log,
644
+ hashes,
645
+ )) {
646
+ messages += 1;
647
+ await this.rpc.send(message, {
648
+ mode: new SilentDelivery({ to: [context.from!], redundancy: 1 }),
649
+ // dont set priority 1 here because this will block other messages that should higher priority
650
+ });
651
+ }
652
+ } finally {
653
+ if (profile) {
654
+ emitSyncProfileDuration(profile, exchangeStartedAt, {
655
+ name: "simple.exchangeHeads",
656
+ entries: hashes.size,
657
+ messages,
658
+ targets: 1,
659
+ details: { source: "requestMaybeSyncCoordinate" },
660
+ });
661
+ }
662
+ }
663
+
583
664
  return true;
584
665
  } else {
585
666
  return false; // no message was consumed
@@ -593,13 +674,10 @@ export class SimpleSyncronizer<R extends "u32" | "u64">
593
674
  const resolvedHashes: string[] = [];
594
675
  for (const entry of properties.entries) {
595
676
  resolvedHashes.push(entry.entry.hash);
596
- const set = this.syncInFlight.get(properties.from.hashcode());
597
- if (set) {
598
- set.delete(entry.entry.hash);
599
- if (set?.size === 0) {
600
- this.syncInFlight.delete(properties.from.hashcode());
601
- }
602
- }
677
+ this.clearSyncInFlightForPeer(
678
+ properties.from.hashcode(),
679
+ entry.entry.hash,
680
+ );
603
681
  }
604
682
  this.markRepairSessionResolvedHashes(resolvedHashes);
605
683
  }
@@ -610,84 +688,129 @@ export class SimpleSyncronizer<R extends "u32" | "u64">
610
688
  options?: { skipCheck?: boolean },
611
689
  ) {
612
690
  const requestHashes: SyncableKey[] = [];
613
-
614
- for (const coordinateOrHash of keys) {
615
- const inFlight = this.syncInFlightQueue.get(coordinateOrHash);
616
- if (inFlight) {
617
- if (!inFlight.find((x) => x.hashcode() === from.hashcode())) {
618
- inFlight.push(from);
691
+ const profile = this.syncOptions?.profile;
692
+ const startedAt = syncProfileStart(profile);
693
+
694
+ try {
695
+ for (const key of keys) {
696
+ const coordinateOrHash = this.getQueuedSyncKey(key) ?? key;
697
+ const inFlight = this.syncInFlightQueue.get(coordinateOrHash);
698
+ if (inFlight) {
699
+ if (!inFlight.find((x) => x.hashcode() === from.hashcode())) {
700
+ inFlight.push(from);
701
+ let inverted = this.syncInFlightQueueInverted.get(from.hashcode());
702
+ if (!inverted) {
703
+ inverted = new Set();
704
+ this.syncInFlightQueueInverted.set(from.hashcode(), inverted);
705
+ }
706
+ inverted.add(coordinateOrHash);
707
+ }
708
+ } else if (
709
+ options?.skipCheck ||
710
+ !(await this.checkHasCoordinateOrHash(coordinateOrHash))
711
+ ) {
712
+ // Track the initial sender so we can retry if the first request is lost.
713
+ this.syncInFlightQueue.set(coordinateOrHash, [from]);
619
714
  let inverted = this.syncInFlightQueueInverted.get(from.hashcode());
620
715
  if (!inverted) {
621
716
  inverted = new Set();
622
717
  this.syncInFlightQueueInverted.set(from.hashcode(), inverted);
623
718
  }
624
719
  inverted.add(coordinateOrHash);
720
+ requestHashes.push(coordinateOrHash); // request immediately (first time we have seen this hash)
625
721
  }
626
- } else if (
627
- options?.skipCheck ||
628
- !(await this.checkHasCoordinateOrHash(coordinateOrHash))
629
- ) {
630
- // Track the initial sender so we can retry if the first request is lost.
631
- this.syncInFlightQueue.set(coordinateOrHash, [from]);
632
- let inverted = this.syncInFlightQueueInverted.get(from.hashcode());
633
- if (!inverted) {
634
- inverted = new Set();
635
- this.syncInFlightQueueInverted.set(from.hashcode(), inverted);
636
- }
637
- inverted.add(coordinateOrHash);
638
- requestHashes.push(coordinateOrHash); // request immediately (first time we have seen this hash)
639
722
  }
640
- }
641
723
 
642
- requestHashes.length > 0 &&
643
- (await this.requestSync(requestHashes, [from!.hashcode()]));
724
+ requestHashes.length > 0 &&
725
+ (await this.requestSync(requestHashes, [from!.hashcode()]));
726
+ } finally {
727
+ if (profile) {
728
+ emitSyncProfileDuration(profile, startedAt, {
729
+ name: "simple.queueSync",
730
+ entries: keys.length,
731
+ targets: 1,
732
+ details: {
733
+ requested: requestHashes.length,
734
+ skipCheck: options?.skipCheck === true,
735
+ },
736
+ });
737
+ }
738
+ }
644
739
  }
645
740
 
646
741
  private async requestSync(hashes: SyncableKey[], to: Set<string> | string[]) {
647
742
  if (hashes.length === 0) {
648
743
  return;
649
744
  }
650
-
651
- const now = +new Date();
652
- for (const node of to) {
653
- let map = this.syncInFlight.get(node);
654
- if (!map) {
655
- map = new Map();
656
- this.syncInFlight.set(node, map);
745
+ const profile = this.syncOptions?.profile;
746
+ const startedAt = syncProfileStart(profile);
747
+ let coordinateMessages = 0;
748
+ let stringMessages = 0;
749
+ let coordinateHashCount = 0;
750
+ let stringHashCount = 0;
751
+
752
+ try {
753
+ const now = +new Date();
754
+ for (const node of to) {
755
+ let map = this.syncInFlight.get(node);
756
+ if (!map) {
757
+ map = new Map();
758
+ this.syncInFlight.set(node, map);
759
+ }
760
+ for (const hash of hashes) {
761
+ map.set(hash, { timestamp: now });
762
+ }
657
763
  }
764
+
765
+ const coordinateHashes: bigint[] = [];
766
+ const stringHashes: string[] = [];
658
767
  for (const hash of hashes) {
659
- map.set(hash, { timestamp: now });
768
+ if (typeof hash === "bigint") {
769
+ coordinateHashes.push(hash);
770
+ } else {
771
+ stringHashes.push(hash);
772
+ }
660
773
  }
661
- }
774
+ coordinateHashCount = coordinateHashes.length;
775
+ stringHashCount = stringHashes.length;
662
776
 
663
- const coordinateHashes: bigint[] = [];
664
- const stringHashes: string[] = [];
665
- for (const hash of hashes) {
666
- if (typeof hash === "bigint") {
667
- coordinateHashes.push(hash);
668
- } else {
669
- stringHashes.push(hash);
777
+ if (coordinateHashes.length > 0) {
778
+ const chunks = this.chunk(
779
+ coordinateHashes,
780
+ this.maxCoordinatesPerMessage,
781
+ );
782
+ coordinateMessages = chunks.length;
783
+ for (const chunk of chunks) {
784
+ await this.rpc.send(
785
+ new RequestMaybeSyncCoordinate({ hashNumbers: chunk }),
786
+ {
787
+ mode: new SilentDelivery({ to, redundancy: 1 }),
788
+ priority: SYNC_MESSAGE_PRIORITY,
789
+ },
790
+ );
791
+ }
670
792
  }
671
- }
672
-
673
- if (coordinateHashes.length > 0) {
674
- const chunks = this.chunk(coordinateHashes, this.maxCoordinatesPerMessage);
675
- for (const chunk of chunks) {
676
- await this.rpc.send(
677
- new RequestMaybeSyncCoordinate({ hashNumbers: chunk }),
678
- {
793
+ if (stringHashes.length > 0) {
794
+ const chunks = this.chunk(stringHashes, this.maxHashesPerMessage);
795
+ stringMessages = chunks.length;
796
+ for (const chunk of chunks) {
797
+ await this.rpc.send(new ResponseMaybeSync({ hashes: chunk }), {
679
798
  mode: new SilentDelivery({ to, redundancy: 1 }),
680
- priority: 1,
681
- },
682
- );
799
+ priority: SYNC_MESSAGE_PRIORITY,
800
+ });
801
+ }
683
802
  }
684
- }
685
- if (stringHashes.length > 0) {
686
- const chunks = this.chunk(stringHashes, this.maxHashesPerMessage);
687
- for (const chunk of chunks) {
688
- await this.rpc.send(new ResponseMaybeSync({ hashes: chunk }), {
689
- mode: new SilentDelivery({ to, redundancy: 1 }),
690
- priority: 1,
803
+ } finally {
804
+ if (profile) {
805
+ emitSyncProfileDuration(profile, startedAt, {
806
+ name: "simple.requestSync",
807
+ entries: hashes.length,
808
+ messages: coordinateMessages + stringMessages,
809
+ targets: Array.isArray(to) ? to.length : to.size,
810
+ details: {
811
+ coordinateHashes: coordinateHashCount,
812
+ stringHashes: stringHashCount,
813
+ },
691
814
  });
692
815
  }
693
816
  }
@@ -805,10 +928,49 @@ export class SimpleSyncronizer<R extends "u32" | "u64">
805
928
 
806
929
  this.syncInFlightQueue.delete(key);
807
930
  }
931
+
932
+ this.clearSyncInFlightKey(key);
933
+ }
934
+
935
+ private clearSyncInFlightKey(key: SyncableKey) {
936
+ for (const [peer, map] of this.syncInFlight) {
937
+ map.delete(key);
938
+ if (map.size === 0) {
939
+ this.syncInFlight.delete(peer);
940
+ }
941
+ }
942
+ }
943
+
944
+ private getKnownAliases(hash: string): SyncableKey[] {
945
+ const aliases = new Set<SyncableKey>([hash]);
946
+ for (const key of [
947
+ ...this.syncInFlightQueue.keys(),
948
+ ...[...this.syncInFlight.values()].flatMap((map) => [...map.keys()]),
949
+ ]) {
950
+ if (typeof key === "bigint" && this.coordinateToHash.get(key) === hash) {
951
+ aliases.add(key);
952
+ }
953
+ }
954
+ return [...aliases];
955
+ }
956
+
957
+ private clearSyncInFlightForPeer(publicKeyHash: string, hash: string) {
958
+ const map = this.syncInFlight.get(publicKeyHash);
959
+ if (!map) {
960
+ return;
961
+ }
962
+ for (const key of this.getKnownAliases(hash)) {
963
+ map.delete(key);
964
+ }
965
+ if (map.size === 0) {
966
+ this.syncInFlight.delete(publicKeyHash);
967
+ }
808
968
  }
809
969
 
810
970
  private clearSyncProcess(hash: string) {
811
- this.clearSyncProcessKey(hash);
971
+ for (const key of this.getKnownAliases(hash)) {
972
+ this.clearSyncProcessKey(key);
973
+ }
812
974
  }
813
975
 
814
976
  onPeerDisconnected(key: PublicSignKey | string): Promise<void> | void {