@rocicorp/zero 0.26.0 → 0.26.1-canary.11
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/out/analyze-query/src/run-ast.d.ts.map +1 -1
- package/out/analyze-query/src/run-ast.js +4 -1
- package/out/analyze-query/src/run-ast.js.map +1 -1
- package/out/replicache/src/btree/node.js +4 -4
- package/out/replicache/src/btree/node.js.map +1 -1
- package/out/replicache/src/btree/write.js +2 -2
- package/out/replicache/src/btree/write.js.map +1 -1
- package/out/replicache/src/dag/gc.js +5 -2
- package/out/replicache/src/dag/gc.js.map +1 -1
- package/out/replicache/src/db/write.d.ts.map +1 -1
- package/out/replicache/src/db/write.js +21 -6
- package/out/replicache/src/db/write.js.map +1 -1
- package/out/replicache/src/error-responses.d.ts.map +1 -1
- package/out/replicache/src/error-responses.js +4 -1
- package/out/replicache/src/error-responses.js.map +1 -1
- package/out/replicache/src/persist/clients.d.ts.map +1 -1
- package/out/replicache/src/persist/clients.js +4 -1
- package/out/replicache/src/persist/clients.js.map +1 -1
- package/out/replicache/src/persist/collect-idb-databases.d.ts.map +1 -1
- package/out/replicache/src/persist/collect-idb-databases.js +2 -1
- package/out/replicache/src/persist/collect-idb-databases.js.map +1 -1
- package/out/replicache/src/persist/idb-databases-store.d.ts.map +1 -1
- package/out/replicache/src/persist/idb-databases-store.js +4 -1
- package/out/replicache/src/persist/idb-databases-store.js.map +1 -1
- package/out/replicache/src/process-scheduler.js +4 -1
- package/out/replicache/src/process-scheduler.js.map +1 -1
- package/out/replicache/src/replicache-impl.js +2 -2
- package/out/replicache/src/replicache-impl.js.map +1 -1
- package/out/replicache/src/subscriptions.d.ts.map +1 -1
- package/out/replicache/src/subscriptions.js +5 -2
- package/out/replicache/src/subscriptions.js.map +1 -1
- package/out/replicache/src/sync/diff.d.ts.map +1 -1
- package/out/replicache/src/sync/diff.js +4 -1
- package/out/replicache/src/sync/diff.js.map +1 -1
- package/out/replicache/src/sync/pull.d.ts.map +1 -1
- package/out/replicache/src/sync/pull.js +4 -1
- package/out/replicache/src/sync/pull.js.map +1 -1
- package/out/replicache/src/sync/push.d.ts.map +1 -1
- package/out/replicache/src/sync/push.js +5 -2
- package/out/replicache/src/sync/push.js.map +1 -1
- package/out/shared/src/asserts.d.ts +1 -1
- package/out/shared/src/asserts.d.ts.map +1 -1
- package/out/shared/src/asserts.js +1 -1
- package/out/shared/src/asserts.js.map +1 -1
- package/out/z2s/src/compiler.d.ts.map +1 -1
- package/out/z2s/src/compiler.js +8 -2
- package/out/z2s/src/compiler.js.map +1 -1
- package/out/zero/package.json.js +1 -1
- package/out/zero-cache/src/config/zero-config.d.ts +4 -0
- package/out/zero-cache/src/config/zero-config.d.ts.map +1 -1
- package/out/zero-cache/src/config/zero-config.js +17 -0
- package/out/zero-cache/src/config/zero-config.js.map +1 -1
- package/out/zero-cache/src/db/transaction-pool.d.ts.map +1 -1
- package/out/zero-cache/src/db/transaction-pool.js +17 -11
- package/out/zero-cache/src/db/transaction-pool.js.map +1 -1
- package/out/zero-cache/src/observability/events.d.ts.map +1 -1
- package/out/zero-cache/src/observability/events.js +28 -9
- package/out/zero-cache/src/observability/events.js.map +1 -1
- package/out/zero-cache/src/server/change-streamer.d.ts.map +1 -1
- package/out/zero-cache/src/server/change-streamer.js +3 -1
- package/out/zero-cache/src/server/change-streamer.js.map +1 -1
- package/out/zero-cache/src/services/analyze.js +1 -0
- package/out/zero-cache/src/services/analyze.js.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/backfill-stream.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/backfill-stream.js +29 -14
- package/out/zero-cache/src/services/change-source/pg/backfill-stream.js.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/initial-sync.d.ts +6 -1
- package/out/zero-cache/src/services/change-source/pg/initial-sync.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/initial-sync.js +69 -25
- package/out/zero-cache/src/services/change-source/pg/initial-sync.js.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/schema/ddl.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/schema/ddl.js +6 -1
- package/out/zero-cache/src/services/change-source/pg/schema/ddl.js.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/schema/init.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/schema/init.js +12 -8
- package/out/zero-cache/src/services/change-source/pg/schema/init.js.map +1 -1
- package/out/zero-cache/src/services/change-source/protocol/current/data.d.ts +26 -0
- package/out/zero-cache/src/services/change-source/protocol/current/data.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-source/protocol/current/data.js +15 -3
- package/out/zero-cache/src/services/change-source/protocol/current/data.js.map +1 -1
- package/out/zero-cache/src/services/change-source/protocol/current/downstream.d.ts +30 -0
- package/out/zero-cache/src/services/change-source/protocol/current/downstream.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-source/protocol/current.js +2 -1
- package/out/zero-cache/src/services/change-streamer/broadcast.d.ts +100 -0
- package/out/zero-cache/src/services/change-streamer/broadcast.d.ts.map +1 -0
- package/out/zero-cache/src/services/change-streamer/broadcast.js +171 -0
- package/out/zero-cache/src/services/change-streamer/broadcast.js.map +1 -0
- package/out/zero-cache/src/services/change-streamer/change-streamer-service.d.ts +1 -1
- package/out/zero-cache/src/services/change-streamer/change-streamer-service.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-streamer/change-streamer-service.js +22 -9
- package/out/zero-cache/src/services/change-streamer/change-streamer-service.js.map +1 -1
- package/out/zero-cache/src/services/change-streamer/change-streamer.d.ts +10 -0
- package/out/zero-cache/src/services/change-streamer/change-streamer.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-streamer/forwarder.d.ts +17 -1
- package/out/zero-cache/src/services/change-streamer/forwarder.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-streamer/forwarder.js +52 -4
- package/out/zero-cache/src/services/change-streamer/forwarder.js.map +1 -1
- package/out/zero-cache/src/services/change-streamer/subscriber.d.ts +18 -0
- package/out/zero-cache/src/services/change-streamer/subscriber.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-streamer/subscriber.js +68 -12
- package/out/zero-cache/src/services/change-streamer/subscriber.js.map +1 -1
- package/out/zero-cache/src/services/replicator/change-processor.d.ts +2 -0
- package/out/zero-cache/src/services/replicator/change-processor.d.ts.map +1 -1
- package/out/zero-cache/src/services/replicator/change-processor.js +8 -6
- package/out/zero-cache/src/services/replicator/change-processor.js.map +1 -1
- package/out/zero-cache/src/services/replicator/incremental-sync.d.ts.map +1 -1
- package/out/zero-cache/src/services/replicator/incremental-sync.js +39 -1
- package/out/zero-cache/src/services/replicator/incremental-sync.js.map +1 -1
- package/out/zero-cache/src/services/replicator/replication-status.d.ts +4 -3
- package/out/zero-cache/src/services/replicator/replication-status.d.ts.map +1 -1
- package/out/zero-cache/src/services/replicator/replication-status.js +25 -10
- package/out/zero-cache/src/services/replicator/replication-status.js.map +1 -1
- package/out/zero-cache/src/services/run-ast.d.ts.map +1 -1
- package/out/zero-cache/src/services/run-ast.js +22 -2
- package/out/zero-cache/src/services/run-ast.js.map +1 -1
- package/out/zero-cache/src/services/running-state.d.ts +1 -0
- package/out/zero-cache/src/services/running-state.d.ts.map +1 -1
- package/out/zero-cache/src/services/running-state.js +4 -0
- package/out/zero-cache/src/services/running-state.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/cvr.d.ts.map +1 -1
- package/out/zero-cache/src/services/view-syncer/cvr.js +8 -2
- package/out/zero-cache/src/services/view-syncer/cvr.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/pipeline-driver.d.ts.map +1 -1
- package/out/zero-cache/src/services/view-syncer/pipeline-driver.js +10 -1
- package/out/zero-cache/src/services/view-syncer/pipeline-driver.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/snapshotter.d.ts +1 -1
- package/out/zero-cache/src/services/view-syncer/snapshotter.d.ts.map +1 -1
- package/out/zero-cache/src/services/view-syncer/snapshotter.js +15 -7
- package/out/zero-cache/src/services/view-syncer/snapshotter.js.map +1 -1
- package/out/zero-cache/src/types/subscription.d.ts +3 -1
- package/out/zero-cache/src/types/subscription.d.ts.map +1 -1
- package/out/zero-cache/src/types/subscription.js +21 -9
- package/out/zero-cache/src/types/subscription.js.map +1 -1
- package/out/zero-client/src/client/http-string.js.map +1 -1
- package/out/zero-client/src/client/version.js +1 -1
- package/out/zero-client/src/client/zero.js.map +1 -1
- package/out/zero-events/src/status.d.ts +8 -0
- package/out/zero-events/src/status.d.ts.map +1 -1
- package/out/zero-schema/src/permissions.d.ts.map +1 -1
- package/out/zero-schema/src/permissions.js +4 -1
- package/out/zero-schema/src/permissions.js.map +1 -1
- package/out/zero-server/src/process-mutations.d.ts.map +1 -1
- package/out/zero-server/src/process-mutations.js +13 -19
- package/out/zero-server/src/process-mutations.js.map +1 -1
- package/out/zql/src/builder/filter.d.ts.map +1 -1
- package/out/zql/src/builder/filter.js +5 -2
- package/out/zql/src/builder/filter.js.map +1 -1
- package/out/zql/src/ivm/constraint.js.map +1 -1
- package/package.json +1 -1
|
@@ -71,6 +71,11 @@ declare const data: v.TupleType<[v.Type<"data">, v.UnionType<[v.UnionType<[v.Obj
|
|
|
71
71
|
columns: v.ArrayType<v.Type<string>>;
|
|
72
72
|
watermark: v.Type<string>;
|
|
73
73
|
rowValues: v.ArrayType<v.ArrayType<v.Type<import("../../../../../../shared/src/bigint-json.ts").JSONValue>>>;
|
|
74
|
+
status: v.Optional<{
|
|
75
|
+
totalBytes?: number | undefined;
|
|
76
|
+
rows: number;
|
|
77
|
+
totalRows: number;
|
|
78
|
+
}>;
|
|
74
79
|
}, undefined>]>, v.UnionType<[v.ObjectType<{
|
|
75
80
|
tag: v.Type<"create-table">;
|
|
76
81
|
spec: v.ObjectType<Omit<{
|
|
@@ -209,6 +214,11 @@ declare const data: v.TupleType<[v.Type<"data">, v.UnionType<[v.UnionType<[v.Obj
|
|
|
209
214
|
}, undefined>;
|
|
210
215
|
columns: v.ArrayType<v.Type<string>>;
|
|
211
216
|
watermark: v.Type<string>;
|
|
217
|
+
status: v.Optional<{
|
|
218
|
+
totalBytes?: number | undefined;
|
|
219
|
+
rows: number;
|
|
220
|
+
totalRows: number;
|
|
221
|
+
}>;
|
|
212
222
|
}, undefined>]>]>]>;
|
|
213
223
|
declare const commit: v.TupleType<[v.Type<"commit">, v.ObjectType<{
|
|
214
224
|
tag: v.Type<"commit">;
|
|
@@ -293,6 +303,11 @@ export declare const changeStreamDataSchema: v.UnionType<[v.TupleType<[v.Type<"b
|
|
|
293
303
|
columns: v.ArrayType<v.Type<string>>;
|
|
294
304
|
watermark: v.Type<string>;
|
|
295
305
|
rowValues: v.ArrayType<v.ArrayType<v.Type<import("../../../../../../shared/src/bigint-json.ts").JSONValue>>>;
|
|
306
|
+
status: v.Optional<{
|
|
307
|
+
totalBytes?: number | undefined;
|
|
308
|
+
rows: number;
|
|
309
|
+
totalRows: number;
|
|
310
|
+
}>;
|
|
296
311
|
}, undefined>]>, v.UnionType<[v.ObjectType<{
|
|
297
312
|
tag: v.Type<"create-table">;
|
|
298
313
|
spec: v.ObjectType<Omit<{
|
|
@@ -431,6 +446,11 @@ export declare const changeStreamDataSchema: v.UnionType<[v.TupleType<[v.Type<"b
|
|
|
431
446
|
}, undefined>;
|
|
432
447
|
columns: v.ArrayType<v.Type<string>>;
|
|
433
448
|
watermark: v.Type<string>;
|
|
449
|
+
status: v.Optional<{
|
|
450
|
+
totalBytes?: number | undefined;
|
|
451
|
+
rows: number;
|
|
452
|
+
totalRows: number;
|
|
453
|
+
}>;
|
|
434
454
|
}, undefined>]>]>]>, v.TupleType<[v.Type<"commit">, v.ObjectType<{
|
|
435
455
|
tag: v.Type<"commit">;
|
|
436
456
|
}, undefined>, v.ObjectType<{
|
|
@@ -517,6 +537,11 @@ export declare const changeStreamMessageSchema: v.UnionType<[v.UnionType<[v.Tupl
|
|
|
517
537
|
columns: v.ArrayType<v.Type<string>>;
|
|
518
538
|
watermark: v.Type<string>;
|
|
519
539
|
rowValues: v.ArrayType<v.ArrayType<v.Type<import("../../../../../../shared/src/bigint-json.ts").JSONValue>>>;
|
|
540
|
+
status: v.Optional<{
|
|
541
|
+
totalBytes?: number | undefined;
|
|
542
|
+
rows: number;
|
|
543
|
+
totalRows: number;
|
|
544
|
+
}>;
|
|
520
545
|
}, undefined>]>, v.UnionType<[v.ObjectType<{
|
|
521
546
|
tag: v.Type<"create-table">;
|
|
522
547
|
spec: v.ObjectType<Omit<{
|
|
@@ -655,6 +680,11 @@ export declare const changeStreamMessageSchema: v.UnionType<[v.UnionType<[v.Tupl
|
|
|
655
680
|
}, undefined>;
|
|
656
681
|
columns: v.ArrayType<v.Type<string>>;
|
|
657
682
|
watermark: v.Type<string>;
|
|
683
|
+
status: v.Optional<{
|
|
684
|
+
totalBytes?: number | undefined;
|
|
685
|
+
rows: number;
|
|
686
|
+
totalRows: number;
|
|
687
|
+
}>;
|
|
658
688
|
}, undefined>]>]>]>, v.TupleType<[v.Type<"commit">, v.ObjectType<{
|
|
659
689
|
tag: v.Type<"commit">;
|
|
660
690
|
}, undefined>, v.ObjectType<{
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"downstream.d.ts","sourceRoot":"","sources":["../../../../../../../../zero-cache/src/services/change-source/protocol/current/downstream.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,CAAC,MAAM,wCAAwC,CAAC;AAW5D,QAAA,MAAM,KAAK;;;;;;eAIT,CAAC;AACH,QAAA,MAAM,IAAI
|
|
1
|
+
{"version":3,"file":"downstream.d.ts","sourceRoot":"","sources":["../../../../../../../../zero-cache/src/services/change-source/protocol/current/downstream.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,CAAC,MAAM,wCAAwC,CAAC;AAW5D,QAAA,MAAM,KAAK;;;;;;eAIT,CAAC;AACH,QAAA,MAAM,IAAI;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;mBAGR,CAAC;AACH,QAAA,MAAM,MAAM;;;;eAIV,CAAC;AACH,QAAA,MAAM,QAAQ;;eAAmD,CAAC;AAElE,MAAM,MAAM,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,KAAK,CAAC,CAAC;AAC1C,MAAM,MAAM,IAAI,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,IAAI,CAAC,CAAC;AACxC,MAAM,MAAM,MAAM,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,MAAM,CAAC,CAAC;AAC5C,MAAM,MAAM,QAAQ,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,QAAQ,CAAC,CAAC;AAEhD,eAAO,MAAM,sBAAsB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAAyC,CAAC;AAC7E,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,sBAAsB,CAAC,CAAC;AAEtE,eAAO,MAAM,yBAAyB;;;;eAGpC,CAAC;AACH,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,yBAAyB,CAAC,CAAC;AAE5E,4EAA4E;AAC5E,eAAO,MAAM,yBAAyB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAIrC,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,yBAAyB,CAAC,CAAC"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { resetRequiredSchema } from "./current/control.js";
|
|
2
|
-
import { addColumnSchema, backfillCompletedSchema, backfillIDSchema, backfillSchema, beginSchema, commitSchema, createIndexSchema, createTableSchema, dataChangeSchema, deleteSchema, dropColumnSchema, dropIndexSchema, dropTableSchema, identifierSchema, insertSchema, isDataChange, isSchemaChange, newRelationSchema, relationSchema, renameTableSchema, rollbackSchema, rowSchema, schemaChangeSchema, tableMetadataSchema, truncateSchema, updateColumnSchema, updateSchema, updateTableMetadataSchema } from "./current/data.js";
|
|
2
|
+
import { addColumnSchema, backfillCompletedSchema, backfillIDSchema, backfillSchema, beginSchema, commitSchema, createIndexSchema, createTableSchema, dataChangeSchema, deleteSchema, downloadStatusSchema, dropColumnSchema, dropIndexSchema, dropTableSchema, identifierSchema, insertSchema, isDataChange, isSchemaChange, newRelationSchema, relationSchema, renameTableSchema, rollbackSchema, rowSchema, schemaChangeSchema, tableMetadataSchema, truncateSchema, updateColumnSchema, updateSchema, updateTableMetadataSchema } from "./current/data.js";
|
|
3
3
|
import { changeStreamControlSchema, changeStreamDataSchema, changeStreamMessageSchema } from "./current/downstream.js";
|
|
4
4
|
import { jsonObjectSchema, jsonValueSchema } from "./current/json.js";
|
|
5
5
|
import { CHANGE_SOURCE_PATH } from "./current/path.js";
|
|
@@ -22,6 +22,7 @@ export {
|
|
|
22
22
|
createTableSchema,
|
|
23
23
|
dataChangeSchema,
|
|
24
24
|
deleteSchema,
|
|
25
|
+
downloadStatusSchema,
|
|
25
26
|
downstreamStatusMessageSchema,
|
|
26
27
|
downstreamStatusSchema,
|
|
27
28
|
dropColumnSchema,
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import type { LogContext } from '@rocicorp/logger';
|
|
2
|
+
import type { WatermarkedChange } from './change-streamer-service.ts';
|
|
3
|
+
import type { Subscriber } from './subscriber.ts';
|
|
4
|
+
/**
|
|
5
|
+
* Initiates and tracks the progress of a change broadcasted to
|
|
6
|
+
* a set of subscribers.
|
|
7
|
+
*
|
|
8
|
+
* Creating a `Broadcast` automatically initiates the send.
|
|
9
|
+
*
|
|
10
|
+
* By default, {@link Broadcast.done} resolves when all subscribers
|
|
11
|
+
* have acked the change. However, {@link Broadcast.checkProgress()}
|
|
12
|
+
* can be called to resolve the broadcast earlier based on the flow
|
|
13
|
+
* control policy.
|
|
14
|
+
*/
|
|
15
|
+
export declare class Broadcast {
|
|
16
|
+
#private;
|
|
17
|
+
/**
|
|
18
|
+
* Sends the change to the subscribers without the tracking machinery.
|
|
19
|
+
* This is suitable for fire-and-forget (i.e. pipelined) sends.
|
|
20
|
+
*/
|
|
21
|
+
static withoutTracking(subscribers: Iterable<Subscriber>, change: WatermarkedChange): void;
|
|
22
|
+
/**
|
|
23
|
+
* Broadcasts the `change` to the `subscribers` and tracks their
|
|
24
|
+
* completion.
|
|
25
|
+
*/
|
|
26
|
+
constructor(subscribers: Iterable<Subscriber>, change: WatermarkedChange);
|
|
27
|
+
get isDone(): boolean;
|
|
28
|
+
get done(): Promise<void>;
|
|
29
|
+
/**
|
|
30
|
+
* Checks for pathological situations in which flow should be reenabled
|
|
31
|
+
* before all subscribers have acked.
|
|
32
|
+
*
|
|
33
|
+
* ### Background
|
|
34
|
+
*
|
|
35
|
+
* The purpose of flow control is to pull upstream replication changes
|
|
36
|
+
* no faster than the rate as they are processed by downstream subscribers
|
|
37
|
+
* in the steady state. In the change-streamer, this is done by occasionally
|
|
38
|
+
* waiting for ACKs from subscribers before continuing; without doing so,
|
|
39
|
+
* I/O buffers fill up and cause the system to spend most of its time in GC.
|
|
40
|
+
*
|
|
41
|
+
* However, the naive algorithm of always waiting for all subscribers (e.g.
|
|
42
|
+
* `Promise.all()`) can behave poorly in scenarios where subscribers
|
|
43
|
+
* are imbalanced:
|
|
44
|
+
* * New subscribers may have a backlog of changes to catch up with.
|
|
45
|
+
* Having all subscribers wait for the new subscriber to catch up results
|
|
46
|
+
* in delaying the entire application.
|
|
47
|
+
* * Broken TCP connections similarly require all subscribers to wait until
|
|
48
|
+
* connection liveness checks kick in and disconnect the subscriber.
|
|
49
|
+
*
|
|
50
|
+
* A simplistic approach is to add a limit to the amount of time waiting for
|
|
51
|
+
* subscribers, i.e. an ack timeout. However, deciding what this timeout
|
|
52
|
+
* should be is non-trivial because of the heterogeneous nature of changes;
|
|
53
|
+
* while most changes operate on single rows and are relatively predictable
|
|
54
|
+
* in terms of running time, some changes are table-wide operations and can
|
|
55
|
+
* legitimately take an arbitrary amount of time. In such scenarios, a
|
|
56
|
+
* timeout that is too short can stop progress on replication altogether.
|
|
57
|
+
*
|
|
58
|
+
* ### Consensus-based Timeout Algorithm
|
|
59
|
+
*
|
|
60
|
+
* To address these shortcomings, a "consensus-based timeout" algorithm is
|
|
61
|
+
* used:
|
|
62
|
+
* * Wait for more than half of the subscribers to finish. (In
|
|
63
|
+
* case of a single node, or the case of one replication-manager
|
|
64
|
+
* and one view-syncer, this reduces to waiting for all subscribers.)
|
|
65
|
+
* * Once more than half of the subscribers have finished, proceed after
|
|
66
|
+
* a fixed timeout elapses (e.g. 1 second), even if not all subscribers
|
|
67
|
+
* have finished.
|
|
68
|
+
*
|
|
69
|
+
* In other words, the subscribers themselves are used to determine the
|
|
70
|
+
* timeout of each batch of changes; the majority determines this when
|
|
71
|
+
* they complete, upon which a timeout is logically started.
|
|
72
|
+
*
|
|
73
|
+
* In the common case, the remaining subscribers finish soon afterward and
|
|
74
|
+
* the timeout never elapses. However, in pathological cases where a minority
|
|
75
|
+
* of subscribers have a disproportionate amount of load, some will still
|
|
76
|
+
* be processing (or otherwise unresponsive). These subscribers are given
|
|
77
|
+
* a bounded amount of time to catch up at each flushed batch, up to the
|
|
78
|
+
* timeout interval. This guarantees eventual catchup because the
|
|
79
|
+
* subscribers with a backlog of changes necessarily have a higher
|
|
80
|
+
* processing rate than the subscribers that finished (and are made to wait).
|
|
81
|
+
*
|
|
82
|
+
* ### Not implemented: Broken connection detection
|
|
83
|
+
*
|
|
84
|
+
* If a subscriber has not made progress for a certain interval, the
|
|
85
|
+
* algorithm could theoretically drop it preemptively, supplementing the
|
|
86
|
+
* existing websocket-level liveness checks.
|
|
87
|
+
*
|
|
88
|
+
* However, a more reliable approach would be to change the replicator
|
|
89
|
+
* to use non-blocking writes, and subsequently increase the frequency of
|
|
90
|
+
* connection-level liveness checks. The current synchronous replica writes
|
|
91
|
+
* can delay both ping responsiveness and change progress arbitrarily (e.g.
|
|
92
|
+
* a large index creation); an independently liveness check that is not
|
|
93
|
+
* delayed by synchronous writes on the subscriber would be a more failsafe
|
|
94
|
+
* solution.
|
|
95
|
+
*
|
|
96
|
+
* @returns `true` if the broadcast was already done or was marked done.
|
|
97
|
+
*/
|
|
98
|
+
checkProgress(lc: LogContext, flowControlConsensusPaddingMs: number, now: number): boolean;
|
|
99
|
+
}
|
|
100
|
+
//# sourceMappingURL=broadcast.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"broadcast.d.ts","sourceRoot":"","sources":["../../../../../../zero-cache/src/services/change-streamer/broadcast.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,kBAAkB,CAAC;AAEjD,OAAO,KAAK,EAAC,iBAAiB,EAAC,MAAM,8BAA8B,CAAC;AACpE,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,iBAAiB,CAAC;AAEhD;;;;;;;;;;GAUG;AACH,qBAAa,SAAS;;IACpB;;;OAGG;IACH,MAAM,CAAC,eAAe,CACpB,WAAW,EAAE,QAAQ,CAAC,UAAU,CAAC,EACjC,MAAM,EAAE,iBAAiB;IAkB3B;;;OAGG;gBACS,WAAW,EAAE,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,EAAE,iBAAiB;IAkCxE,IAAI,MAAM,IAAI,OAAO,CAEpB;IAED,IAAI,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAExB;IAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAoEG;IACH,aAAa,CACX,EAAE,EAAE,UAAU,EACd,6BAA6B,EAAE,MAAM,EACrC,GAAG,EAAE,MAAM;CA+Cd"}
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import { resolver } from "@rocicorp/resolver";
|
|
2
|
+
class Broadcast {
|
|
3
|
+
/**
|
|
4
|
+
* Sends the change to the subscribers without the tracking machinery.
|
|
5
|
+
* This is suitable for fire-and-forget (i.e. pipelined) sends.
|
|
6
|
+
*/
|
|
7
|
+
static withoutTracking(subscribers, change) {
|
|
8
|
+
for (const sub of subscribers) {
|
|
9
|
+
void sub.send(change);
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
#pending;
|
|
13
|
+
#completed;
|
|
14
|
+
#done = resolver();
|
|
15
|
+
#isDone = false;
|
|
16
|
+
#watermark;
|
|
17
|
+
#majority;
|
|
18
|
+
#start = performance.now();
|
|
19
|
+
#latestCompleted = Number.MAX_VALUE;
|
|
20
|
+
/**
|
|
21
|
+
* Broadcasts the `change` to the `subscribers` and tracks their
|
|
22
|
+
* completion.
|
|
23
|
+
*/
|
|
24
|
+
constructor(subscribers, change) {
|
|
25
|
+
this.#pending = new Set(subscribers);
|
|
26
|
+
this.#completed = [];
|
|
27
|
+
this.#watermark = change[0];
|
|
28
|
+
this.#majority = Math.floor(this.#pending.size / 2) + 1;
|
|
29
|
+
for (const sub of this.#pending) {
|
|
30
|
+
const changes = sub.numPending + 1;
|
|
31
|
+
void sub.send(change).catch(() => {
|
|
32
|
+
}).finally(() => this.#markCompleted(sub, changes));
|
|
33
|
+
}
|
|
34
|
+
if (this.#pending.size === 0) {
|
|
35
|
+
this.#setDone();
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
#markCompleted(sub, changes) {
|
|
39
|
+
const elapsed = (this.#latestCompleted = performance.now()) - this.#start;
|
|
40
|
+
this.#completed.push({ sub, changes, elapsed });
|
|
41
|
+
this.#pending.delete(sub);
|
|
42
|
+
if (this.#pending.size === 0) {
|
|
43
|
+
this.#setDone();
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
#setDone() {
|
|
47
|
+
this.#isDone = true;
|
|
48
|
+
this.#done.resolve();
|
|
49
|
+
}
|
|
50
|
+
get isDone() {
|
|
51
|
+
return this.#isDone;
|
|
52
|
+
}
|
|
53
|
+
get done() {
|
|
54
|
+
return this.#done.promise;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Checks for pathological situations in which flow should be reenabled
|
|
58
|
+
* before all subscribers have acked.
|
|
59
|
+
*
|
|
60
|
+
* ### Background
|
|
61
|
+
*
|
|
62
|
+
* The purpose of flow control is to pull upstream replication changes
|
|
63
|
+
* no faster than the rate as they are processed by downstream subscribers
|
|
64
|
+
* in the steady state. In the change-streamer, this is done by occasionally
|
|
65
|
+
* waiting for ACKs from subscribers before continuing; without doing so,
|
|
66
|
+
* I/O buffers fill up and cause the system to spend most of its time in GC.
|
|
67
|
+
*
|
|
68
|
+
* However, the naive algorithm of always waiting for all subscribers (e.g.
|
|
69
|
+
* `Promise.all()`) can behave poorly in scenarios where subscribers
|
|
70
|
+
* are imbalanced:
|
|
71
|
+
* * New subscribers may have a backlog of changes to catch up with.
|
|
72
|
+
* Having all subscribers wait for the new subscriber to catch up results
|
|
73
|
+
* in delaying the entire application.
|
|
74
|
+
* * Broken TCP connections similarly require all subscribers to wait until
|
|
75
|
+
* connection liveness checks kick in and disconnect the subscriber.
|
|
76
|
+
*
|
|
77
|
+
* A simplistic approach is to add a limit to the amount of time waiting for
|
|
78
|
+
* subscribers, i.e. an ack timeout. However, deciding what this timeout
|
|
79
|
+
* should be is non-trivial because of the heterogeneous nature of changes;
|
|
80
|
+
* while most changes operate on single rows and are relatively predictable
|
|
81
|
+
* in terms of running time, some changes are table-wide operations and can
|
|
82
|
+
* legitimately take an arbitrary amount of time. In such scenarios, a
|
|
83
|
+
* timeout that is too short can stop progress on replication altogether.
|
|
84
|
+
*
|
|
85
|
+
* ### Consensus-based Timeout Algorithm
|
|
86
|
+
*
|
|
87
|
+
* To address these shortcomings, a "consensus-based timeout" algorithm is
|
|
88
|
+
* used:
|
|
89
|
+
* * Wait for more than half of the subscribers to finish. (In
|
|
90
|
+
* case of a single node, or the case of one replication-manager
|
|
91
|
+
* and one view-syncer, this reduces to waiting for all subscribers.)
|
|
92
|
+
* * Once more than half of the subscribers have finished, proceed after
|
|
93
|
+
* a fixed timeout elapses (e.g. 1 second), even if not all subscribers
|
|
94
|
+
* have finished.
|
|
95
|
+
*
|
|
96
|
+
* In other words, the subscribers themselves are used to determine the
|
|
97
|
+
* timeout of each batch of changes; the majority determines this when
|
|
98
|
+
* they complete, upon which a timeout is logically started.
|
|
99
|
+
*
|
|
100
|
+
* In the common case, the remaining subscribers finish soon afterward and
|
|
101
|
+
* the timeout never elapses. However, in pathological cases where a minority
|
|
102
|
+
* of subscribers have a disproportionate amount of load, some will still
|
|
103
|
+
* be processing (or otherwise unresponsive). These subscribers are given
|
|
104
|
+
* a bounded amount of time to catch up at each flushed batch, up to the
|
|
105
|
+
* timeout interval. This guarantees eventual catchup because the
|
|
106
|
+
* subscribers with a backlog of changes necessarily have a higher
|
|
107
|
+
* processing rate than the subscribers that finished (and are made to wait).
|
|
108
|
+
*
|
|
109
|
+
* ### Not implemented: Broken connection detection
|
|
110
|
+
*
|
|
111
|
+
* If a subscriber has not made progress for a certain interval, the
|
|
112
|
+
* algorithm could theoretically drop it preemptively, supplementing the
|
|
113
|
+
* existing websocket-level liveness checks.
|
|
114
|
+
*
|
|
115
|
+
* However, a more reliable approach would be to change the replicator
|
|
116
|
+
* to use non-blocking writes, and subsequently increase the frequency of
|
|
117
|
+
* connection-level liveness checks. The current synchronous replica writes
|
|
118
|
+
* can delay both ping responsiveness and change progress arbitrarily (e.g.
|
|
119
|
+
* a large index creation); an independently liveness check that is not
|
|
120
|
+
* delayed by synchronous writes on the subscriber would be a more failsafe
|
|
121
|
+
* solution.
|
|
122
|
+
*
|
|
123
|
+
* @returns `true` if the broadcast was already done or was marked done.
|
|
124
|
+
*/
|
|
125
|
+
checkProgress(lc, flowControlConsensusPaddingMs, now) {
|
|
126
|
+
if (this.#pending.size === 0) {
|
|
127
|
+
return true;
|
|
128
|
+
}
|
|
129
|
+
const elapsed = now - this.#start;
|
|
130
|
+
if (this.#completed.length < this.#majority) {
|
|
131
|
+
if (elapsed >= 1e3) {
|
|
132
|
+
this.#logWithState(
|
|
133
|
+
lc,
|
|
134
|
+
`waiting for at least ${this.#majority} subscribers to finish`,
|
|
135
|
+
elapsed
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
return false;
|
|
139
|
+
}
|
|
140
|
+
if (now - this.#latestCompleted >= flowControlConsensusPaddingMs) {
|
|
141
|
+
this.#logWithState(
|
|
142
|
+
lc,
|
|
143
|
+
`continuing with ${this.#pending.size} subscriber(s) still pending`,
|
|
144
|
+
elapsed
|
|
145
|
+
);
|
|
146
|
+
this.#setDone();
|
|
147
|
+
return true;
|
|
148
|
+
}
|
|
149
|
+
return false;
|
|
150
|
+
}
|
|
151
|
+
#logWithState(lc, msg, elapsed) {
|
|
152
|
+
lc.withContext("watermark", this.#watermark).info?.(
|
|
153
|
+
`${msg} (${elapsed.toFixed(3)} ms)`,
|
|
154
|
+
{
|
|
155
|
+
completed: this.#completed.map((d) => ({
|
|
156
|
+
id: d.sub.id,
|
|
157
|
+
processed: d.changes,
|
|
158
|
+
elapsed: d.elapsed
|
|
159
|
+
})),
|
|
160
|
+
pending: [...this.#pending].map((sub) => ({
|
|
161
|
+
id: sub.id,
|
|
162
|
+
...sub.getStats()
|
|
163
|
+
}))
|
|
164
|
+
}
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
export {
|
|
169
|
+
Broadcast
|
|
170
|
+
};
|
|
171
|
+
//# sourceMappingURL=broadcast.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"broadcast.js","sources":["../../../../../../zero-cache/src/services/change-streamer/broadcast.ts"],"sourcesContent":["import type {LogContext} from '@rocicorp/logger';\nimport {resolver} from '@rocicorp/resolver';\nimport type {WatermarkedChange} from './change-streamer-service.ts';\nimport type {Subscriber} from './subscriber.ts';\n\n/**\n * Initiates and tracks the progress of a change broadcasted to\n * a set of subscribers.\n *\n * Creating a `Broadcast` automatically initiates the send.\n *\n * By default, {@link Broadcast.done} resolves when all subscribers\n * have acked the change. However, {@link Broadcast.checkProgress()}\n * can be called to resolve the broadcast earlier based on the flow\n * control policy.\n */\nexport class Broadcast {\n /**\n * Sends the change to the subscribers without the tracking machinery.\n * This is suitable for fire-and-forget (i.e. pipelined) sends.\n */\n static withoutTracking(\n subscribers: Iterable<Subscriber>,\n change: WatermarkedChange,\n ) {\n for (const sub of subscribers) {\n void sub.send(change);\n }\n }\n\n readonly #pending: Set<Subscriber>;\n readonly #completed: Completed[];\n readonly #done = resolver();\n #isDone = false;\n\n readonly #watermark: string;\n readonly #majority: number;\n\n readonly #start = performance.now();\n #latestCompleted = Number.MAX_VALUE;\n\n /**\n * Broadcasts the `change` to the `subscribers` and tracks their\n * completion.\n */\n constructor(subscribers: Iterable<Subscriber>, change: WatermarkedChange) {\n this.#pending = new Set(subscribers);\n this.#completed = [];\n this.#watermark = change[0];\n this.#majority = Math.floor(this.#pending.size / 2) + 1;\n\n for (const sub of this.#pending) {\n const changes = sub.numPending + 1; // add one for this `change`\n void sub\n .send(change)\n .catch(() => {})\n .finally(() => this.#markCompleted(sub, changes));\n }\n\n // set done if there are no subscribers (mainly for tests)\n if (this.#pending.size === 0) {\n this.#setDone();\n }\n }\n\n #markCompleted(sub: Subscriber, changes: number) {\n const elapsed = (this.#latestCompleted = performance.now()) - this.#start;\n this.#completed.push({sub, changes, elapsed});\n this.#pending.delete(sub);\n if (this.#pending.size === 0) {\n this.#setDone();\n }\n }\n\n #setDone() {\n this.#isDone = true;\n this.#done.resolve();\n }\n\n get isDone(): boolean {\n return this.#isDone;\n }\n\n get done(): Promise<void> {\n return this.#done.promise;\n }\n\n /**\n * Checks for pathological situations in which flow should be reenabled\n * before all subscribers have acked.\n *\n * ### Background\n *\n * The purpose of flow control is to pull upstream replication changes\n * no faster than the rate as they are processed by downstream subscribers\n * in the steady state. In the change-streamer, this is done by occasionally\n * waiting for ACKs from subscribers before continuing; without doing so,\n * I/O buffers fill up and cause the system to spend most of its time in GC.\n *\n * However, the naive algorithm of always waiting for all subscribers (e.g.\n * `Promise.all()`) can behave poorly in scenarios where subscribers\n * are imbalanced:\n * * New subscribers may have a backlog of changes to catch up with.\n * Having all subscribers wait for the new subscriber to catch up results\n * in delaying the entire application.\n * * Broken TCP connections similarly require all subscribers to wait until\n * connection liveness checks kick in and disconnect the subscriber.\n *\n * A simplistic approach is to add a limit to the amount of time waiting for\n * subscribers, i.e. an ack timeout. However, deciding what this timeout\n * should be is non-trivial because of the heterogeneous nature of changes;\n * while most changes operate on single rows and are relatively predictable\n * in terms of running time, some changes are table-wide operations and can\n * legitimately take an arbitrary amount of time. In such scenarios, a\n * timeout that is too short can stop progress on replication altogether.\n *\n * ### Consensus-based Timeout Algorithm\n *\n * To address these shortcomings, a \"consensus-based timeout\" algorithm is\n * used:\n * * Wait for more than half of the subscribers to finish. (In\n * case of a single node, or the case of one replication-manager\n * and one view-syncer, this reduces to waiting for all subscribers.)\n * * Once more than half of the subscribers have finished, proceed after\n * a fixed timeout elapses (e.g. 1 second), even if not all subscribers\n * have finished.\n *\n * In other words, the subscribers themselves are used to determine the\n * timeout of each batch of changes; the majority determines this when\n * they complete, upon which a timeout is logically started.\n *\n * In the common case, the remaining subscribers finish soon afterward and\n * the timeout never elapses. However, in pathological cases where a minority\n * of subscribers have a disproportionate amount of load, some will still\n * be processing (or otherwise unresponsive). These subscribers are given\n * a bounded amount of time to catch up at each flushed batch, up to the\n * timeout interval. This guarantees eventual catchup because the\n * subscribers with a backlog of changes necessarily have a higher\n * processing rate than the subscribers that finished (and are made to wait).\n *\n * ### Not implemented: Broken connection detection\n *\n * If a subscriber has not made progress for a certain interval, the\n * algorithm could theoretically drop it preemptively, supplementing the\n * existing websocket-level liveness checks.\n *\n * However, a more reliable approach would be to change the replicator\n * to use non-blocking writes, and subsequently increase the frequency of\n * connection-level liveness checks. The current synchronous replica writes\n * can delay both ping responsiveness and change progress arbitrarily (e.g.\n * a large index creation); an independently liveness check that is not\n * delayed by synchronous writes on the subscriber would be a more failsafe\n * solution.\n *\n * @returns `true` if the broadcast was already done or was marked done.\n */\n checkProgress(\n lc: LogContext,\n flowControlConsensusPaddingMs: number,\n now: number,\n ) {\n if (this.#pending.size === 0) {\n return true;\n }\n const elapsed = now - this.#start;\n if (this.#completed.length < this.#majority) {\n if (elapsed >= 1000) {\n this.#logWithState(\n lc,\n `waiting for at least ${this.#majority} subscribers to finish`,\n elapsed,\n );\n }\n return false;\n }\n // Note: In the implementation, #latestCompleted is always updated,\n // even after the majority is reached. This is fine and does not affect\n // the important properties of the algorithm.\n if (now - this.#latestCompleted >= flowControlConsensusPaddingMs) {\n this.#logWithState(\n lc,\n `continuing with ${this.#pending.size} subscriber(s) still pending`,\n elapsed,\n );\n this.#setDone();\n return true;\n }\n return false;\n }\n\n #logWithState(lc: LogContext, msg: string, elapsed: number) {\n lc.withContext('watermark', this.#watermark).info?.(\n `${msg} (${elapsed.toFixed(3)} ms)`,\n {\n completed: this.#completed.map(d => ({\n id: d.sub.id,\n processed: d.changes,\n elapsed: d.elapsed,\n })),\n pending: [...this.#pending].map(sub => ({\n id: sub.id,\n ...sub.getStats(),\n })),\n },\n );\n }\n}\n\n/** Tracks the completed result of a single subscriber. */\ntype Completed = {\n sub: Subscriber;\n /** The number of changes processed. */\n changes: number;\n /** The elapsed milliseconds. */\n elapsed: number;\n};\n"],"names":[],"mappings":"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"}
|
|
@@ -8,7 +8,7 @@ import { type ChangeStreamerService } from './change-streamer.ts';
|
|
|
8
8
|
/**
|
|
9
9
|
* Performs initialization and schema migrations to initialize a ChangeStreamerImpl.
|
|
10
10
|
*/
|
|
11
|
-
export declare function initializeStreamer(lc: LogContext, shard: ShardID, taskID: string, discoveryAddress: string, discoveryProtocol: string, changeDB: PostgresDB, changeSource: ChangeSource, subscriptionState: SubscriptionState, autoReset: boolean, backPressureLimitHeapProportion: number, setTimeoutFn?: typeof setTimeout): Promise<ChangeStreamerService>;
|
|
11
|
+
export declare function initializeStreamer(lc: LogContext, shard: ShardID, taskID: string, discoveryAddress: string, discoveryProtocol: string, changeDB: PostgresDB, changeSource: ChangeSource, subscriptionState: SubscriptionState, autoReset: boolean, backPressureLimitHeapProportion: number, flowControlConsensusPaddingSeconds: number, setTimeoutFn?: typeof setTimeout): Promise<ChangeStreamerService>;
|
|
12
12
|
/**
|
|
13
13
|
* Internally all Downstream messages (not just commits) are given a watermark.
|
|
14
14
|
* These are used for internal ordering for:
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"change-streamer-service.d.ts","sourceRoot":"","sources":["../../../../../../zero-cache/src/services/change-streamer/change-streamer-service.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"change-streamer-service.d.ts","sourceRoot":"","sources":["../../../../../../zero-cache/src/services/change-streamer/change-streamer-service.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,kBAAkB,CAAC;AAYjD,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,mBAAmB,CAAC;AAClD,OAAO,KAAK,EAAC,OAAO,EAAC,MAAM,uBAAuB,CAAC;AAGnD,OAAO,KAAK,EACV,YAAY,EAEb,MAAM,mCAAmC,CAAC;AAC3C,OAAO,EAEL,KAAK,gBAAgB,EACtB,MAAM,iDAAiD,CAAC;AAKzD,OAAO,KAAK,EAAC,iBAAiB,EAAC,MAAM,2CAA2C,CAAC;AAMjF,OAAO,EACL,KAAK,qBAAqB,EAG3B,MAAM,sBAAsB,CAAC;AAY9B;;GAEG;AACH,wBAAsB,kBAAkB,CACtC,EAAE,EAAE,UAAU,EACd,KAAK,EAAE,OAAO,EACd,MAAM,EAAE,MAAM,EACd,gBAAgB,EAAE,MAAM,EACxB,iBAAiB,EAAE,MAAM,EACzB,QAAQ,EAAE,UAAU,EACpB,YAAY,EAAE,YAAY,EAC1B,iBAAiB,EAAE,iBAAiB,EACpC,SAAS,EAAE,OAAO,EAClB,+BAA+B,EAAE,MAAM,EACvC,kCAAkC,EAAE,MAAM,EAC1C,YAAY,oBAAa,GACxB,OAAO,CAAC,qBAAqB,CAAC,CA0BhC;AAED;;;;;;;;;GASG;AACH,MAAM,MAAM,iBAAiB,GAAG,CAAC,SAAS,EAAE,MAAM,EAAE,gBAAgB,CAAC,CAAC"}
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { resolver } from "@rocicorp/resolver";
|
|
2
2
|
import { getDefaultHighWaterMark } from "node:stream";
|
|
3
3
|
import { unreachable } from "../../../../shared/src/asserts.js";
|
|
4
|
+
import { promiseVoid } from "../../../../shared/src/resolved-promises.js";
|
|
5
|
+
import { publishCriticalEvent } from "../../observability/events.js";
|
|
4
6
|
import { getOrCreateCounter } from "../../observability/metrics.js";
|
|
5
7
|
import { min } from "../../types/lexi-version.js";
|
|
6
8
|
import { Subscription } from "../../types/subscription.js";
|
|
7
9
|
import "../change-source/protocol/current/downstream.js";
|
|
8
|
-
import { publishReplicationError } from "../replicator/replication-status.js";
|
|
10
|
+
import { replicationStatusError, publishReplicationError } from "../replicator/replication-status.js";
|
|
9
11
|
import { RunningState, UnrecoverableError, DEFAULT_MAX_RETRY_DELAY_MS } from "../running-state.js";
|
|
10
12
|
import "./change-streamer.js";
|
|
11
13
|
import { WrongReplicaVersion } from "./error-type-enum.js";
|
|
@@ -14,7 +16,7 @@ import { initChangeStreamerSchema } from "./schema/init.js";
|
|
|
14
16
|
import { ensureReplicationConfig, markResetRequired, AutoResetSignal } from "./schema/tables.js";
|
|
15
17
|
import { Storer } from "./storer.js";
|
|
16
18
|
import { Subscriber } from "./subscriber.js";
|
|
17
|
-
async function initializeStreamer(lc, shard, taskID, discoveryAddress, discoveryProtocol, changeDB, changeSource, subscriptionState, autoReset, backPressureLimitHeapProportion, setTimeoutFn = setTimeout) {
|
|
19
|
+
async function initializeStreamer(lc, shard, taskID, discoveryAddress, discoveryProtocol, changeDB, changeSource, subscriptionState, autoReset, backPressureLimitHeapProportion, flowControlConsensusPaddingSeconds, setTimeoutFn = setTimeout) {
|
|
18
20
|
await initChangeStreamerSchema(lc, changeDB, shard);
|
|
19
21
|
await ensureReplicationConfig(
|
|
20
22
|
lc,
|
|
@@ -35,6 +37,7 @@ async function initializeStreamer(lc, shard, taskID, discoveryAddress, discovery
|
|
|
35
37
|
changeSource,
|
|
36
38
|
autoReset,
|
|
37
39
|
backPressureLimitHeapProportion,
|
|
40
|
+
flowControlConsensusPaddingSeconds,
|
|
38
41
|
setTimeoutFn
|
|
39
42
|
);
|
|
40
43
|
}
|
|
@@ -65,7 +68,7 @@ class ChangeStreamerImpl {
|
|
|
65
68
|
"Count of replicated transactions"
|
|
66
69
|
);
|
|
67
70
|
#stream;
|
|
68
|
-
constructor(lc, shard, taskID, discoveryAddress, discoveryProtocol, changeDB, replicaVersion, source, autoReset, backPressureLimitHeapProportion, setTimeoutFn = setTimeout) {
|
|
71
|
+
constructor(lc, shard, taskID, discoveryAddress, discoveryProtocol, changeDB, replicaVersion, source, autoReset, backPressureLimitHeapProportion, flowControlConsensusPaddingSeconds, setTimeoutFn = setTimeout) {
|
|
69
72
|
this.id = `change-streamer`;
|
|
70
73
|
this.#lc = lc.withContext("component", "change-streamer");
|
|
71
74
|
this.#shard = shard;
|
|
@@ -84,12 +87,15 @@ class ChangeStreamerImpl {
|
|
|
84
87
|
(err) => this.stop(err),
|
|
85
88
|
backPressureLimitHeapProportion
|
|
86
89
|
);
|
|
87
|
-
this.#forwarder = new Forwarder(
|
|
90
|
+
this.#forwarder = new Forwarder(lc, {
|
|
91
|
+
flowControlConsensusPaddingSeconds
|
|
92
|
+
});
|
|
88
93
|
this.#autoReset = autoReset;
|
|
89
94
|
this.#state = new RunningState(this.id, void 0, setTimeoutFn);
|
|
90
95
|
}
|
|
91
96
|
async run() {
|
|
92
97
|
this.#lc.info?.("starting change stream");
|
|
98
|
+
this.#forwarder.startProgressMonitor();
|
|
93
99
|
await this.#storer.assumeOwnership();
|
|
94
100
|
const flushBytesThreshold = getDefaultHighWaterMark(false);
|
|
95
101
|
while (this.#state.shouldRun()) {
|
|
@@ -137,10 +143,12 @@ class ChangeStreamerImpl {
|
|
|
137
143
|
}
|
|
138
144
|
break;
|
|
139
145
|
}
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
if (unflushedBytes
|
|
143
|
-
|
|
146
|
+
const entry = [watermark, change];
|
|
147
|
+
unflushedBytes += this.#storer.store(entry);
|
|
148
|
+
if (unflushedBytes < flushBytesThreshold) {
|
|
149
|
+
this.#forwarder.forward(entry);
|
|
150
|
+
} else {
|
|
151
|
+
await this.#forwarder.forwardWithFlowControl(entry);
|
|
144
152
|
unflushedBytes = 0;
|
|
145
153
|
}
|
|
146
154
|
if (type === "commit" || type === "rollback") {
|
|
@@ -167,9 +175,14 @@ class ChangeStreamerImpl {
|
|
|
167
175
|
}
|
|
168
176
|
await Promise.all([
|
|
169
177
|
this.#storer.stop(),
|
|
170
|
-
this.#state.backoff(this.#lc, err)
|
|
178
|
+
this.#state.backoff(this.#lc, err),
|
|
179
|
+
this.#state.retryDelay > 5e3 ? publishCriticalEvent(
|
|
180
|
+
this.#lc,
|
|
181
|
+
replicationStatusError(this.#lc, "Replicating", err)
|
|
182
|
+
) : promiseVoid
|
|
171
183
|
]);
|
|
172
184
|
}
|
|
185
|
+
this.#forwarder.stopProgressMonitor();
|
|
173
186
|
this.#lc.info?.("ChangeStreamer stopped");
|
|
174
187
|
}
|
|
175
188
|
async #handleControlMessage(msg) {
|