@peerbit/shared-log 12.1.3 → 12.2.0-874976b

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/sync/index.ts CHANGED
@@ -8,6 +8,24 @@ import type { Numbers } from "../integers.js";
8
8
  import type { TransportMessage } from "../message.js";
9
9
  import type { EntryReplicated, ReplicationRangeIndexable } from "../ranges.js";
10
10
 
11
+ export type SyncPriorityFn<R extends "u32" | "u64"> = (
12
+ entry: EntryReplicated<R>,
13
+ ) => number;
14
+
15
+ export type SyncOptions<R extends "u32" | "u64"> = {
16
+ /**
17
+ * Higher numbers are synced first.
18
+ * The callback should be fast and side-effect free.
19
+ */
20
+ priority?: SyncPriorityFn<R>;
21
+
22
+ /**
23
+ * When using rateless IBLT sync, optionally pre-sync up to this many
24
+ * high-priority entries using the simple synchronizer.
25
+ */
26
+ maxSimpleEntries?: number;
27
+ };
28
+
11
29
  export type SynchronizerComponents<R extends "u32" | "u64"> = {
12
30
  rpc: RPC<TransportMessage, TransportMessage>;
13
31
  rangeIndex: Index<ReplicationRangeIndexable<R>, any>;
@@ -15,6 +33,7 @@ export type SynchronizerComponents<R extends "u32" | "u64"> = {
15
33
  log: Log<any>;
16
34
  coordinateToHash: Cache<string>;
17
35
  numbers: Numbers<R>;
36
+ sync?: SyncOptions<R>;
18
37
  };
19
38
  export type SynchronizerConstructor<R extends "u32" | "u64"> = new (
20
39
  properties: SynchronizerComponents<R>,
@@ -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
 
@@ -207,14 +214,7 @@ export class RatelessIBLTSynchronizer<D extends "u32" | "u64">
207
214
  >;
208
215
 
209
216
  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
- },
217
+ readonly properties: SynchronizerComponents<D>,
218
218
  ) {
219
219
  this.simple = new SimpleSyncronizer(properties);
220
220
  this.outgoingSyncProcesses = new Map();
@@ -233,7 +233,6 @@ export class RatelessIBLTSynchronizer<D extends "u32" | "u64">
233
233
  // such as those assigned to range boundaries.
234
234
 
235
235
  let entriesToSyncNaively: Map<string, EntryReplicated<D>> = new Map();
236
- let allCoordinatesToSyncWithIblt: bigint[] = [];
237
236
  let minSyncIbltSize = 333; // TODO: make configurable
238
237
  let maxSyncWithSimpleMethod = 1e3;
239
238
 
@@ -246,15 +245,57 @@ export class RatelessIBLTSynchronizer<D extends "u32" | "u64">
246
245
  return;
247
246
  }
248
247
 
249
- // Mixed strategy for larger batches
248
+ const nonBoundaryEntries: EntryReplicated<D>[] = [];
250
249
  for (const entry of properties.entries.values()) {
251
250
  if (entry.assignedToRangeBoundary) {
252
251
  entriesToSyncNaively.set(entry.hash, entry);
253
252
  } else {
254
- allCoordinatesToSyncWithIblt.push(coerceBigInt(entry.hashNumber));
253
+ nonBoundaryEntries.push(entry);
255
254
  }
256
255
  }
257
256
 
257
+ const priorityFn = this.properties.sync?.priority;
258
+ const maxSimpleEntries = this.properties.sync?.maxSimpleEntries;
259
+ const maxAdditionalNaive =
260
+ priorityFn &&
261
+ typeof maxSimpleEntries === "number" &&
262
+ Number.isFinite(maxSimpleEntries) &&
263
+ maxSimpleEntries > 0
264
+ ? Math.max(
265
+ 0,
266
+ Math.min(
267
+ Math.floor(maxSimpleEntries),
268
+ maxSyncWithSimpleMethod - entriesToSyncNaively.size,
269
+ ),
270
+ )
271
+ : 0;
272
+
273
+ if (priorityFn && maxAdditionalNaive > 0 && nonBoundaryEntries.length > 0) {
274
+ let index = 0;
275
+ const scored: {
276
+ entry: EntryReplicated<D>;
277
+ index: number;
278
+ priority: number;
279
+ }[] = [];
280
+ for (const entry of nonBoundaryEntries) {
281
+ const priorityValue = priorityFn(entry);
282
+ scored.push({
283
+ entry,
284
+ index,
285
+ priority: Number.isFinite(priorityValue) ? priorityValue : 0,
286
+ });
287
+ index += 1;
288
+ }
289
+ scored.sort((a, b) => b.priority - a.priority || a.index - b.index);
290
+ for (const { entry } of scored.slice(0, maxAdditionalNaive)) {
291
+ entriesToSyncNaively.set(entry.hash, entry);
292
+ }
293
+ }
294
+
295
+ let allCoordinatesToSyncWithIblt = nonBoundaryEntries
296
+ .filter((entry) => !entriesToSyncNaively.has(entry.hash))
297
+ .map((entry) => coerceBigInt(entry.hashNumber));
298
+
258
299
  if (entriesToSyncNaively.size > 0) {
259
300
  // If there are special-case entries, sync them simply in parallel
260
301
  await this.simple.onMaybeMissingEntries({
@@ -277,6 +318,8 @@ export class RatelessIBLTSynchronizer<D extends "u32" | "u64">
277
318
  return;
278
319
  }
279
320
 
321
+ await ribltReady;
322
+
280
323
  const sortedEntries = allCoordinatesToSyncWithIblt.sort((a, b) => {
281
324
  if (a > b) {
282
325
  return 1;
@@ -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 }),