@rocicorp/zero 1.11.0-canary.35 → 1.11.0-canary.37
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/zero/package.js +1 -1
- package/out/zero/package.js.map +1 -1
- package/out/zero-cache/src/config/zero-config.d.ts +28 -0
- package/out/zero-cache/src/config/zero-config.d.ts.map +1 -1
- package/out/zero-cache/src/config/zero-config.js +83 -0
- package/out/zero-cache/src/config/zero-config.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 +2 -1
- package/out/zero-cache/src/server/change-streamer.js.map +1 -1
- package/out/zero-cache/src/server/main.d.ts.map +1 -1
- package/out/zero-cache/src/server/main.js +5 -1
- package/out/zero-cache/src/server/main.js.map +1 -1
- package/out/zero-cache/src/server/replicator.d.ts +4 -0
- package/out/zero-cache/src/server/replicator.d.ts.map +1 -1
- package/out/zero-cache/src/server/replicator.js +18 -4
- package/out/zero-cache/src/server/replicator.js.map +1 -1
- package/out/zero-cache/src/server/runner/run-worker.d.ts.map +1 -1
- package/out/zero-cache/src/server/runner/run-worker.js +12 -6
- package/out/zero-cache/src/server/runner/run-worker.js.map +1 -1
- package/out/zero-cache/src/server/syncer.d.ts.map +1 -1
- package/out/zero-cache/src/server/syncer.js +17 -1
- package/out/zero-cache/src/server/syncer.js.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/backfill-stream.js +1 -1
- package/out/zero-cache/src/services/change-source/pg/backfill-stream.js.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/replication-slots.d.ts +27 -7
- package/out/zero-cache/src/services/change-source/pg/replication-slots.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/replication-slots.js +124 -62
- package/out/zero-cache/src/services/change-source/pg/replication-slots.js.map +1 -1
- package/out/zero-cache/src/services/change-streamer/change-streamer-http.d.ts +9 -3
- package/out/zero-cache/src/services/change-streamer/change-streamer-http.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-streamer/change-streamer-http.js +19 -6
- package/out/zero-cache/src/services/change-streamer/change-streamer-http.js.map +1 -1
- package/out/zero-cache/src/services/change-streamer/change-streamer-service.d.ts +8 -0
- 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 +131 -62
- 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 +124 -2
- package/out/zero-cache/src/services/change-streamer/change-streamer.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-streamer/change-streamer.js +33 -2
- package/out/zero-cache/src/services/change-streamer/change-streamer.js.map +1 -1
- package/out/zero-cache/src/services/change-streamer/drain-tracker.d.ts +24 -0
- package/out/zero-cache/src/services/change-streamer/drain-tracker.d.ts.map +1 -0
- package/out/zero-cache/src/services/change-streamer/drain-tracker.js +67 -0
- package/out/zero-cache/src/services/change-streamer/drain-tracker.js.map +1 -0
- package/out/zero-cache/src/services/change-streamer/restore-admission.d.ts +214 -0
- package/out/zero-cache/src/services/change-streamer/restore-admission.d.ts.map +1 -0
- package/out/zero-cache/src/services/change-streamer/restore-admission.js +294 -0
- package/out/zero-cache/src/services/change-streamer/restore-admission.js.map +1 -0
- package/out/zero-cache/src/services/change-streamer/snapshot-reservations.d.ts +13 -1
- package/out/zero-cache/src/services/change-streamer/snapshot-reservations.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-streamer/snapshot-reservations.js +7 -6
- package/out/zero-cache/src/services/change-streamer/snapshot-reservations.js.map +1 -1
- package/out/zero-cache/src/services/change-streamer/snapshot.d.ts +2 -2
- package/out/zero-cache/src/services/change-streamer/snapshot.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-streamer/snapshot.js +2 -2
- package/out/zero-cache/src/services/change-streamer/snapshot.js.map +1 -1
- package/out/zero-cache/src/services/change-streamer/subscribe.d.ts +39 -0
- package/out/zero-cache/src/services/change-streamer/subscribe.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-streamer/subscribe.js +19 -2
- package/out/zero-cache/src/services/change-streamer/subscribe.js.map +1 -1
- package/out/zero-cache/src/services/change-streamer/subscriber.d.ts +22 -1
- package/out/zero-cache/src/services/change-streamer/subscriber.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-streamer/subscriber.js +41 -15
- package/out/zero-cache/src/services/change-streamer/subscriber.js.map +1 -1
- package/out/zero-cache/src/services/http-service.d.ts +6 -0
- package/out/zero-cache/src/services/http-service.d.ts.map +1 -1
- package/out/zero-cache/src/services/http-service.js +14 -2
- package/out/zero-cache/src/services/http-service.js.map +1 -1
- package/out/zero-cache/src/services/life-cycle.d.ts +33 -0
- package/out/zero-cache/src/services/life-cycle.d.ts.map +1 -1
- package/out/zero-cache/src/services/life-cycle.js +56 -10
- package/out/zero-cache/src/services/life-cycle.js.map +1 -1
- package/out/zero-cache/src/services/litestream/commands.d.ts +8 -0
- package/out/zero-cache/src/services/litestream/commands.d.ts.map +1 -1
- package/out/zero-cache/src/services/litestream/commands.js +22 -1
- package/out/zero-cache/src/services/litestream/commands.js.map +1 -1
- package/out/zero-cache/src/services/replicator/drain-handler.d.ts +74 -0
- package/out/zero-cache/src/services/replicator/drain-handler.d.ts.map +1 -0
- package/out/zero-cache/src/services/replicator/drain-handler.js +185 -0
- package/out/zero-cache/src/services/replicator/drain-handler.js.map +1 -0
- package/out/zero-cache/src/services/replicator/incremental-sync.d.ts +2 -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 +66 -13
- package/out/zero-cache/src/services/replicator/incremental-sync.js.map +1 -1
- package/out/zero-cache/src/services/replicator/replicator.d.ts +2 -1
- package/out/zero-cache/src/services/replicator/replicator.d.ts.map +1 -1
- package/out/zero-cache/src/services/replicator/replicator.js +2 -2
- package/out/zero-cache/src/services/replicator/replicator.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/batch-pacer.d.ts +79 -0
- package/out/zero-cache/src/services/view-syncer/batch-pacer.d.ts.map +1 -0
- package/out/zero-cache/src/services/view-syncer/batch-pacer.js +249 -0
- package/out/zero-cache/src/services/view-syncer/batch-pacer.js.map +1 -0
- package/out/zero-cache/src/services/view-syncer/cvr.d.ts +27 -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 +107 -17
- 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 +85 -3
- 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 +301 -20
- package/out/zero-cache/src/services/view-syncer/pipeline-driver.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/push-cost.d.ts +132 -0
- package/out/zero-cache/src/services/view-syncer/push-cost.d.ts.map +1 -0
- package/out/zero-cache/src/services/view-syncer/push-cost.js +164 -0
- package/out/zero-cache/src/services/view-syncer/push-cost.js.map +1 -0
- package/out/zero-cache/src/services/view-syncer/push-cutoff.d.ts +55 -0
- package/out/zero-cache/src/services/view-syncer/push-cutoff.d.ts.map +1 -0
- package/out/zero-cache/src/services/view-syncer/push-cutoff.js +56 -0
- package/out/zero-cache/src/services/view-syncer/push-cutoff.js.map +1 -0
- package/out/zero-cache/src/services/view-syncer/query-stats.d.ts +7 -0
- package/out/zero-cache/src/services/view-syncer/query-stats.d.ts.map +1 -1
- package/out/zero-cache/src/services/view-syncer/query-stats.js +20 -1
- package/out/zero-cache/src/services/view-syncer/query-stats.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/snapshotter.d.ts +136 -5
- package/out/zero-cache/src/services/view-syncer/snapshotter.d.ts.map +1 -1
- package/out/zero-cache/src/services/view-syncer/snapshotter.js +246 -18
- package/out/zero-cache/src/services/view-syncer/snapshotter.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/view-syncer.d.ts +8 -2
- package/out/zero-cache/src/services/view-syncer/view-syncer.d.ts.map +1 -1
- package/out/zero-cache/src/services/view-syncer/view-syncer.js +381 -16
- package/out/zero-cache/src/services/view-syncer/view-syncer.js.map +1 -1
- package/out/zero-cache/src/types/processes.d.ts +8 -0
- package/out/zero-cache/src/types/processes.d.ts.map +1 -1
- package/out/zero-cache/src/types/processes.js.map +1 -1
- package/out/zero-client/src/client/version.js +1 -1
- package/out/zero-react/src/use-query.js +1 -1
- package/out/zero-react/src/use-query.js.map +1 -1
- package/out/zero-server/src/queries/process-queries.d.ts.map +1 -1
- package/out/zero-server/src/queries/process-queries.js +1 -1
- package/out/zero-server/src/queries/process-queries.js.map +1 -1
- package/package.json +1 -1
|
@@ -9,7 +9,7 @@ import type { Service } from '../service.ts';
|
|
|
9
9
|
import type { PreSerializedBatch } from './broadcast.ts';
|
|
10
10
|
import * as ErrorType from './error-type-enum.ts';
|
|
11
11
|
import type { SnapshotMessage } from './snapshot-message.ts';
|
|
12
|
-
import type { SubscribeDownstream } from './subscribe.ts';
|
|
12
|
+
import type { LocalReplica, SubscribeDownstream } from './subscribe.ts';
|
|
13
13
|
type ErrorType = Enum<typeof ErrorType>;
|
|
14
14
|
export type ChangeTag = ChangeStreamData[1]['tag'];
|
|
15
15
|
/**
|
|
@@ -109,6 +109,21 @@ export type SubscriberContext = {
|
|
|
109
109
|
* subscribers as of protocol v8. `undefined` for older subscribers.
|
|
110
110
|
*/
|
|
111
111
|
backfills?: BackfillRequest[] | undefined;
|
|
112
|
+
/**
|
|
113
|
+
* Whether the subscriber understands the `draining` status, i.e. it will
|
|
114
|
+
* switch over to another replication-manager when the current one shuts
|
|
115
|
+
* down. A draining replication-manager waits for drainable subscribers to
|
|
116
|
+
* leave before exiting.
|
|
117
|
+
*/
|
|
118
|
+
drainable?: boolean | undefined;
|
|
119
|
+
/**
|
|
120
|
+
* Whether the subscriber is switching over from another subscription with
|
|
121
|
+
* a replica that it is currently serving. If the replica version is
|
|
122
|
+
* incompatible, such a subscriber is held (see {@link Status.restore})
|
|
123
|
+
* rather than being immediately rejected. Subscribers that are booting up
|
|
124
|
+
* never set this.
|
|
125
|
+
*/
|
|
126
|
+
switchover?: boolean | undefined;
|
|
112
127
|
};
|
|
113
128
|
/**
|
|
114
129
|
* The StatusMessage payload for now is empty, but can be extended to
|
|
@@ -124,6 +139,33 @@ export declare const statusSchema: v.ObjectType<{
|
|
|
124
139
|
} | undefined;
|
|
125
140
|
nextSendTimeMs: number;
|
|
126
141
|
}>;
|
|
142
|
+
/**
|
|
143
|
+
* Sent by a replication-manager that is shutting down to its (drainable)
|
|
144
|
+
* serving subscribers. The subscriber should switch over to another
|
|
145
|
+
* replication-manager (see {@link SubscriberContext.switchover}) while
|
|
146
|
+
* continuing to consume this subscription until the switchover succeeds.
|
|
147
|
+
*/
|
|
148
|
+
draining: v.Optional<boolean>;
|
|
149
|
+
/**
|
|
150
|
+
* Sent on a {@link SubscriberContext.switchover switchover} subscription
|
|
151
|
+
* to a replication-manager running an incompatible replica version.
|
|
152
|
+
* Rather than being immediately rejected with
|
|
153
|
+
* {@link ErrorType.WrongReplicaVersion}, the subscriber is held until it
|
|
154
|
+
* is granted permission to restore the replication-manager's backup,
|
|
155
|
+
* which limits the number of subscribers that are restoring (and thus not
|
|
156
|
+
* serving) at the same time.
|
|
157
|
+
*
|
|
158
|
+
* * `queued`: the subscriber is waiting for permission to restore. It
|
|
159
|
+
* should continue consuming its existing subscription (if any).
|
|
160
|
+
* * `granted`: the subscriber should (gracefully) restart to restore a
|
|
161
|
+
* new replica.
|
|
162
|
+
*/
|
|
163
|
+
restore: v.Optional<{
|
|
164
|
+
state: "queued";
|
|
165
|
+
position: number;
|
|
166
|
+
} | {
|
|
167
|
+
state: "granted";
|
|
168
|
+
}>;
|
|
127
169
|
}, undefined>;
|
|
128
170
|
export type Status = v.Infer<typeof statusSchema>;
|
|
129
171
|
export declare const statusMessageSchema: v.TupleType<[v.Type<"status">, v.ObjectType<{
|
|
@@ -136,6 +178,33 @@ export declare const statusMessageSchema: v.TupleType<[v.Type<"status">, v.Objec
|
|
|
136
178
|
} | undefined;
|
|
137
179
|
nextSendTimeMs: number;
|
|
138
180
|
}>;
|
|
181
|
+
/**
|
|
182
|
+
* Sent by a replication-manager that is shutting down to its (drainable)
|
|
183
|
+
* serving subscribers. The subscriber should switch over to another
|
|
184
|
+
* replication-manager (see {@link SubscriberContext.switchover}) while
|
|
185
|
+
* continuing to consume this subscription until the switchover succeeds.
|
|
186
|
+
*/
|
|
187
|
+
draining: v.Optional<boolean>;
|
|
188
|
+
/**
|
|
189
|
+
* Sent on a {@link SubscriberContext.switchover switchover} subscription
|
|
190
|
+
* to a replication-manager running an incompatible replica version.
|
|
191
|
+
* Rather than being immediately rejected with
|
|
192
|
+
* {@link ErrorType.WrongReplicaVersion}, the subscriber is held until it
|
|
193
|
+
* is granted permission to restore the replication-manager's backup,
|
|
194
|
+
* which limits the number of subscribers that are restoring (and thus not
|
|
195
|
+
* serving) at the same time.
|
|
196
|
+
*
|
|
197
|
+
* * `queued`: the subscriber is waiting for permission to restore. It
|
|
198
|
+
* should continue consuming its existing subscription (if any).
|
|
199
|
+
* * `granted`: the subscriber should (gracefully) restart to restore a
|
|
200
|
+
* new replica.
|
|
201
|
+
*/
|
|
202
|
+
restore: v.Optional<{
|
|
203
|
+
state: "queued";
|
|
204
|
+
position: number;
|
|
205
|
+
} | {
|
|
206
|
+
state: "granted";
|
|
207
|
+
}>;
|
|
139
208
|
}, undefined>]>;
|
|
140
209
|
/**
|
|
141
210
|
* A StatusMessage will be immediately sent on a (v2+) subscription to
|
|
@@ -147,11 +216,23 @@ export type StatusMessage = v.Infer<typeof statusMessageSchema>;
|
|
|
147
216
|
declare const subscriptionErrorSchema: v.ObjectType<{
|
|
148
217
|
type: v.Type<number>;
|
|
149
218
|
message: v.Optional<string>;
|
|
219
|
+
/**
|
|
220
|
+
* The replica version of the replication-manager, sent with
|
|
221
|
+
* {@link ErrorType.WrongReplicaVersion} so that a subscriber with a newer
|
|
222
|
+
* replica can avoid restoring an older one.
|
|
223
|
+
*/
|
|
224
|
+
replicaVersion: v.Optional<string>;
|
|
150
225
|
}, undefined>;
|
|
151
226
|
export type SubscriptionError = v.Infer<typeof subscriptionErrorSchema>;
|
|
152
227
|
declare const errorSchema: v.TupleType<[v.Type<"error">, v.ObjectType<{
|
|
153
228
|
type: v.Type<number>;
|
|
154
229
|
message: v.Optional<string>;
|
|
230
|
+
/**
|
|
231
|
+
* The replica version of the replication-manager, sent with
|
|
232
|
+
* {@link ErrorType.WrongReplicaVersion} so that a subscriber with a newer
|
|
233
|
+
* replica can avoid restoring an older one.
|
|
234
|
+
*/
|
|
235
|
+
replicaVersion: v.Optional<string>;
|
|
155
236
|
}, undefined>]>;
|
|
156
237
|
export declare const downstreamSchema: v.UnionType<[v.TupleType<[v.Type<"status">, v.ObjectType<{
|
|
157
238
|
tag: v.Type<"status">;
|
|
@@ -163,6 +244,33 @@ export declare const downstreamSchema: v.UnionType<[v.TupleType<[v.Type<"status"
|
|
|
163
244
|
} | undefined;
|
|
164
245
|
nextSendTimeMs: number;
|
|
165
246
|
}>;
|
|
247
|
+
/**
|
|
248
|
+
* Sent by a replication-manager that is shutting down to its (drainable)
|
|
249
|
+
* serving subscribers. The subscriber should switch over to another
|
|
250
|
+
* replication-manager (see {@link SubscriberContext.switchover}) while
|
|
251
|
+
* continuing to consume this subscription until the switchover succeeds.
|
|
252
|
+
*/
|
|
253
|
+
draining: v.Optional<boolean>;
|
|
254
|
+
/**
|
|
255
|
+
* Sent on a {@link SubscriberContext.switchover switchover} subscription
|
|
256
|
+
* to a replication-manager running an incompatible replica version.
|
|
257
|
+
* Rather than being immediately rejected with
|
|
258
|
+
* {@link ErrorType.WrongReplicaVersion}, the subscriber is held until it
|
|
259
|
+
* is granted permission to restore the replication-manager's backup,
|
|
260
|
+
* which limits the number of subscribers that are restoring (and thus not
|
|
261
|
+
* serving) at the same time.
|
|
262
|
+
*
|
|
263
|
+
* * `queued`: the subscriber is waiting for permission to restore. It
|
|
264
|
+
* should continue consuming its existing subscription (if any).
|
|
265
|
+
* * `granted`: the subscriber should (gracefully) restart to restore a
|
|
266
|
+
* new replica.
|
|
267
|
+
*/
|
|
268
|
+
restore: v.Optional<{
|
|
269
|
+
state: "queued";
|
|
270
|
+
position: number;
|
|
271
|
+
} | {
|
|
272
|
+
state: "granted";
|
|
273
|
+
}>;
|
|
166
274
|
}, undefined>]>, v.UnionType<[v.TupleType<[v.Type<"begin">, v.ObjectType<{
|
|
167
275
|
tag: v.Type<"begin">;
|
|
168
276
|
json: v.Optional<"p" | "s">;
|
|
@@ -412,6 +520,12 @@ export declare const downstreamSchema: v.UnionType<[v.TupleType<[v.Type<"status"
|
|
|
412
520
|
}, undefined>]>]>, v.TupleType<[v.Type<"error">, v.ObjectType<{
|
|
413
521
|
type: v.Type<number>;
|
|
414
522
|
message: v.Optional<string>;
|
|
523
|
+
/**
|
|
524
|
+
* The replica version of the replication-manager, sent with
|
|
525
|
+
* {@link ErrorType.WrongReplicaVersion} so that a subscriber with a newer
|
|
526
|
+
* replica can avoid restoring an older one.
|
|
527
|
+
*/
|
|
528
|
+
replicaVersion: v.Optional<string>;
|
|
415
529
|
}, undefined>]>]>;
|
|
416
530
|
export type Error = v.Infer<typeof errorSchema>;
|
|
417
531
|
export declare function errorTypeToReadableName(val: ErrorType): "Unknown" | "WatermarkTooOld" | "WrongReplicaVersion";
|
|
@@ -447,7 +561,15 @@ export interface ChangeStreamerService extends Omit<ChangeStreamer, 'subscribe'>
|
|
|
447
561
|
* until the client with that `taskID` subscribes, or if the snapshot
|
|
448
562
|
* connection terminates.
|
|
449
563
|
*/
|
|
450
|
-
startSnapshotReservation(taskID: string
|
|
564
|
+
startSnapshotReservation(taskID: string, opts?: {
|
|
565
|
+
localReplica?: LocalReplica | undefined;
|
|
566
|
+
}): Promise<Source<SnapshotMessage>>;
|
|
567
|
+
/**
|
|
568
|
+
* Signals (drainable) serving subscribers to switch over to another
|
|
569
|
+
* replication-manager, and resolves when they have all disconnected.
|
|
570
|
+
* Subscribers continue to be served in the meantime.
|
|
571
|
+
*/
|
|
572
|
+
drain?(): Promise<void>;
|
|
451
573
|
/**
|
|
452
574
|
* Informs the change-streamer of the watermark up to which the replica has
|
|
453
575
|
* been backed up. This serves two purposes:
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"change-streamer.d.ts","sourceRoot":"","sources":["../../../../../../zero-cache/src/services/change-streamer/change-streamer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,IAAI,EAAC,MAAM,gCAAgC,CAAC;AACzD,OAAO,KAAK,CAAC,MAAM,kCAAkC,CAAC;AACtD,OAAO,KAAK,EAAC,aAAa,EAAE,KAAK,EAAE,MAAM,EAAC,MAAM,wBAAwB,CAAC;AACzE,OAAO,KAAK,EAAC,YAAY,EAAC,MAAM,6BAA6B,CAAC;AAC9D,OAAO,EAEL,KAAK,gBAAgB,EACtB,MAAM,iDAAiD,CAAC;AACzD,OAAO,KAAK,EAAC,eAAe,EAAC,MAAM,+CAA+C,CAAC;AACnF,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,6BAA6B,CAAC;AAEhE,OAAO,KAAK,EAAC,OAAO,EAAC,MAAM,eAAe,CAAC;AAC3C,OAAO,KAAK,EAAC,kBAAkB,EAAC,MAAM,gBAAgB,CAAC;AACvD,OAAO,KAAK,SAAS,MAAM,sBAAsB,CAAC;AAClD,OAAO,KAAK,EAAC,eAAe,EAAC,MAAM,uBAAuB,CAAC;AAC3D,OAAO,KAAK,EAAC,mBAAmB,EAAC,MAAM,gBAAgB,CAAC;
|
|
1
|
+
{"version":3,"file":"change-streamer.d.ts","sourceRoot":"","sources":["../../../../../../zero-cache/src/services/change-streamer/change-streamer.ts"],"names":[],"mappings":"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"}
|
|
@@ -11,12 +11,43 @@ var statusSchema = valita_exports.object({
|
|
|
11
11
|
lagReport: valita_exports.object({
|
|
12
12
|
lastTimings: changeSourceTimingsSchema.optional(),
|
|
13
13
|
nextSendTimeMs: valita_exports.number()
|
|
14
|
-
}).optional()
|
|
14
|
+
}).optional(),
|
|
15
|
+
/**
|
|
16
|
+
* Sent by a replication-manager that is shutting down to its (drainable)
|
|
17
|
+
* serving subscribers. The subscriber should switch over to another
|
|
18
|
+
* replication-manager (see {@link SubscriberContext.switchover}) while
|
|
19
|
+
* continuing to consume this subscription until the switchover succeeds.
|
|
20
|
+
*/
|
|
21
|
+
draining: valita_exports.boolean().optional(),
|
|
22
|
+
/**
|
|
23
|
+
* Sent on a {@link SubscriberContext.switchover switchover} subscription
|
|
24
|
+
* to a replication-manager running an incompatible replica version.
|
|
25
|
+
* Rather than being immediately rejected with
|
|
26
|
+
* {@link ErrorType.WrongReplicaVersion}, the subscriber is held until it
|
|
27
|
+
* is granted permission to restore the replication-manager's backup,
|
|
28
|
+
* which limits the number of subscribers that are restoring (and thus not
|
|
29
|
+
* serving) at the same time.
|
|
30
|
+
*
|
|
31
|
+
* * `queued`: the subscriber is waiting for permission to restore. It
|
|
32
|
+
* should continue consuming its existing subscription (if any).
|
|
33
|
+
* * `granted`: the subscriber should (gracefully) restart to restore a
|
|
34
|
+
* new replica.
|
|
35
|
+
*/
|
|
36
|
+
restore: valita_exports.union(valita_exports.object({
|
|
37
|
+
state: valita_exports.literal("queued"),
|
|
38
|
+
position: valita_exports.number()
|
|
39
|
+
}), valita_exports.object({ state: valita_exports.literal("granted") })).optional()
|
|
15
40
|
});
|
|
16
41
|
var statusMessageSchema = valita_exports.tuple([valita_exports.literal("status"), statusSchema]);
|
|
17
42
|
var subscriptionErrorSchema = valita_exports.object({
|
|
18
43
|
type: valita_exports.number(),
|
|
19
|
-
message: valita_exports.string().optional()
|
|
44
|
+
message: valita_exports.string().optional(),
|
|
45
|
+
/**
|
|
46
|
+
* The replica version of the replication-manager, sent with
|
|
47
|
+
* {@link ErrorType.WrongReplicaVersion} so that a subscriber with a newer
|
|
48
|
+
* replica can avoid restoring an older one.
|
|
49
|
+
*/
|
|
50
|
+
replicaVersion: valita_exports.string().optional()
|
|
20
51
|
});
|
|
21
52
|
var errorSchema = valita_exports.tuple([valita_exports.literal("error"), subscriptionErrorSchema]);
|
|
22
53
|
var downstreamSchema = valita_exports.union(statusMessageSchema, changeStreamDataSchema, errorSchema);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"change-streamer.js","names":[],"sources":["../../../../../../zero-cache/src/services/change-streamer/change-streamer.ts"],"sourcesContent":["import type {Enum} from '../../../../shared/src/enum.ts';\nimport * as v from '../../../../shared/src/valita.ts';\nimport type {PreSerialized, Sized, Source} from '../../types/streams.ts';\nimport type {Subscription} from '../../types/subscription.ts';\nimport {\n changeStreamDataSchema,\n type ChangeStreamData,\n} from '../change-source/protocol/current/downstream.ts';\nimport type {BackfillRequest} from '../change-source/protocol/current/upstream.ts';\nimport type {ReplicatorMode} from '../replicator/replicator.ts';\nimport {changeSourceTimingsSchema} from '../replicator/reporter/report-schema.ts';\nimport type {Service} from '../service.ts';\nimport type {PreSerializedBatch} from './broadcast.ts';\nimport * as ErrorType from './error-type-enum.ts';\nimport type {SnapshotMessage} from './snapshot-message.ts';\nimport type {SubscribeDownstream} from './subscribe.ts';\n\ntype ErrorType = Enum<typeof ErrorType>;\n\nexport type ChangeTag = ChangeStreamData[1]['tag'];\n\n/**\n * Internally all downstream messages (not just commits) are given a watermark.\n * These are used for internal ordering while replaying stored changes and\n * filtering changes already seen by a subscriber.\n *\n * Only commit watermarks are exposed to subscribers. The JSON string is the\n * canonical serialization shared by storage and live forwarding.\n */\nexport type WatermarkedChange = [\n watermark: string,\n tag: ChangeTag,\n json: string,\n];\n\n/**\n * The ChangeStreamer is the component between replicators (\"subscribers\")\n * and a canonical upstream source of changes (e.g. a Postgres logical\n * replication slot). It facilitates multiple subscribers without incurring\n * the associated upstream expense (e.g. PG replication slots are resource\n * intensive) with a \"forward-store-ack\" procedure.\n *\n * * Changes from the upstream source are immediately **forwarded** to\n * connected subscribers to minimize latency.\n *\n * * They are then **stored** in a separate DB to facilitate catchup\n * of connecting subscribers that are behind.\n *\n * * **Acknowledgements** are sent upstream after they are successfully\n * stored.\n *\n * Unlike Postgres replication slots, in which the progress of a static\n * subscriber is tracked in the replication slot, the ChangeStreamer\n * supports a dynamic set of subscribers (i.e.. zero-caches) that can\n * can continually change.\n *\n * However, it is not the case that the ChangeStreamer needs to support\n * arbitrarily old subscribers. Because the replica is continually\n * backed up to a global location and used to initialize new subscriber\n * tasks, an initial subscription request from a subscriber constitutes\n * a signal for how \"behind\" a new subscriber task can be. This is\n * reflected in the {@link SubscriberContext}, which indicates whether\n * the watermark corresponds to an \"initial\" watermark derived from the\n * replica at task startup.\n *\n * The ChangeStreamer uses a combination of this signal with ACK\n * responses from connected subscribers to determine the watermark up\n * to which it is safe to purge old change log entries.\n */\nexport interface ChangeStreamer {\n /**\n * Subscribes to changes based on the supplied subscriber `ctx`,\n * which indicates the watermark at which the subscriber is up to\n * date. Each result contains parsed, validated data and its approximate\n * serialized transport size.\n */\n subscribe(\n ctx: SubscriberContext,\n ): Promise<Source<Sized<SubscribeDownstream>>>;\n}\n\n// v1: v0.18\n// - Client-side support for JSON_FORMAT. Introduced in 0.18.\n// v2: v0.19\n// - Adds the \"status\" message which is initially used to signal that the\n// subscription is valid (i.e. starting at the requested watermark).\n// v3: v0.20\n// - Adds the \"taskID\" to the subscription context, and support for\n// the backup monitor-mediated \"/snapshot\" request.\n// v4: v0.25\n// - Adds the \"replicaVersion\" and \"minWatermark\" fields to the \"/snapshot\"\n// status request so that a subscriber can verify whether its replica,\n// whether it be restored or existing in a permanent volume, is compatible\n// with the change-streamer.\n// v5: v0.26\n// - Moves relation.keyColumns and relation.replicaIdentity to\n// relation.rowKey: { columns, type }.\n// - Adds `metadata` to `create-table` message\n// - Adds `tableMetadata` to `add-column` message\n// - Adds `table-update-metadata` message\n// v6: v0.26\n// - Adds support for `backfill` messages\n// v6: v1.0.1 (backwards compatible, no version change)\n// - Adds lag reporting to status messages\n// v6: (backwards compatible, no version change)\n// - Adds the optional `commitTimeMs` field to `commit` messages, carrying\n// the upstream commit timestamp for end-to-end serving lag measurement.\n// The stream is parsed in 'passthrough' mode, so an older peer ignores the\n// field, and a newer peer treats its absence (including in changes\n// replayed from the Change DB) as \"no commit time reported\".\n// v7: v1.12\n// - Merges the separate `/snapshot` and `/changes` connections into a single\n// bidirectional `/subscribe` connection (see subscribe.ts). The view-syncer\n// drives the connection with upstream `reserve-snapshot` / `start-subscription`\n// application messages; the change-streamer replies with a `reserved`\n// message and then the change stream on the same socket. This guarantees the\n// replication-manager that reserves the change log is the one that serves the\n// subscription. The legacy `/snapshot` + `/changes` endpoints remain for\n// v4-v6 subscribers during rollout.\n// - Drops the legacy \"initial\" and \"logsChangeStream\" query parameters from the\n// subscriber context\n// v8:\n// - Adds `backfills` to the `start-subscription` context, with which the\n// subscriber reports its pending backfills (and their progress).\n// - Adds `progressMarks` to `backfill` and `backfill-completed` messages,\n// which subscribers use to ignore backfill data that would otherwise\n// create a gap (see `acceptBackfill()`).\n// Both fields are optional in the schemas so that v7 peers (and changes\n// persisted in the change-log by v7 replication-managers) remain compatible,\n// but they should be considered non-optional as of v8.\n\nexport const PROTOCOL_VERSION = 8;\n\nexport type SubscriberContext = {\n /**\n * The supported change-streamer protocol version.\n */\n protocolVersion: number;\n\n /**\n * Task ID. This is used to link the request with a preceding snapshot\n * reservation.\n */\n taskID: string;\n\n /**\n * Subscriber id. This is only used for debugging.\n */\n id: string;\n\n /**\n * The ReplicatorMode of the subscriber. 'backup' indicates that the\n * subscriber is local to the `change-streamer` in the `replication-manager`,\n * while 'serving' indicates that user-facing requests depend on the subscriber.\n */\n mode: ReplicatorMode;\n\n /**\n * The ChangeStreamer will return an Error if the subscriber is\n * on a different replica version (i.e. the initial snapshot associated\n * with the replication slot).\n */\n replicaVersion: string;\n\n /**\n * The watermark up to which the subscriber is up to date.\n * Only changes after the watermark will be streamed.\n */\n watermark: string;\n\n /**\n * Whether the subscriber supports batched WebSocket frames.\n */\n wsBatched?: boolean | undefined;\n\n /**\n * The subscriber's pending backfills as of its `watermark`. Reported by\n * subscribers as of protocol v8. `undefined` for older subscribers.\n */\n backfills?: BackfillRequest[] | undefined;\n};\n\n/**\n * The StatusMessage payload for now is empty, but can be extended to\n * include meta-level information in the future.\n */\nexport const statusSchema = v.object({\n tag: v.literal('status'),\n\n lagReport: v\n .object({\n lastTimings: changeSourceTimingsSchema.optional(),\n nextSendTimeMs: v.number(),\n })\n .optional(),\n});\n\nexport type Status = v.Infer<typeof statusSchema>;\n\nexport const statusMessageSchema = v.tuple([v.literal('status'), statusSchema]);\n\n/**\n * A StatusMessage will be immediately sent on a (v2+) subscription to\n * indicate that the subscription is valid (i.e. starting at the requested\n * watermark). Invalid subscriptions will instead result in a\n * SubscriptionError as the first message.\n */\nexport type StatusMessage = v.Infer<typeof statusMessageSchema>;\n\nconst subscriptionErrorSchema = v.object({\n type: v.number(), // ErrorType\n message: v.string().optional(),\n});\n\nexport type SubscriptionError = v.Infer<typeof subscriptionErrorSchema>;\n\nconst errorSchema = v.tuple([v.literal('error'), subscriptionErrorSchema]);\n\nexport const downstreamSchema = v.union(\n statusMessageSchema,\n changeStreamDataSchema,\n errorSchema,\n);\n\nexport type Error = v.Infer<typeof errorSchema>;\n\nexport function errorTypeToReadableName(val: ErrorType) {\n switch (val) {\n case ErrorType.WrongReplicaVersion:\n return 'WrongReplicaVersion';\n case ErrorType.WatermarkTooOld:\n return 'WatermarkTooOld';\n case ErrorType.Unknown:\n return 'Unknown';\n default:\n return 'Unknown';\n }\n}\n\n/**\n * A stream of transactions, each starting with a {@link Begin} message,\n * containing one or more {@link Data} messages, and ending with a\n * {@link Commit} or {@link Rollback} message. The 'commit' tuple\n * includes a `watermark` that should be stored with the committed\n * data and used for resuming a subscription (e.g. in the\n * {@link SubscriberContext}).\n *\n * A {@link SubscriptionError} indicates an unrecoverable error that requires\n * manual intervention (e.g. configuration / operational error).\n */\nexport type Downstream = v.Infer<typeof downstreamSchema>;\n\n/** A downstream message and its approximate serialized transport size. */\nexport type SizedDownstream = Sized<Downstream>;\n\nexport interface ChangeStreamerService\n extends Omit<ChangeStreamer, 'subscribe'>, Service {\n /**\n * The server-side interface overrides `subscribe()` to return a stream\n * of already-stringified {@link Downstream} payloads or pre-serialized Buffers.\n *\n * If a `downstream` subscription is supplied it is used directly as the\n * returned Source. Otherwise, a new one is created.\n */\n subscribe(\n ctx: SubscriberContext,\n downstream?: Subscription<string | PreSerializedBatch>,\n ): Promise<Source<string | PreSerialized>>;\n\n /**\n * Starts a snapshot reservation to preserve change-log entries while\n * a soon-to-be-subscriber downloads the current replica backup. Once\n * the change-streamer knows that a backup is available\n * (via {@link trackBackupWatermark}), it sends a confirmation\n * SnapshotMessage and reserves the corresponding entries in the change-log\n * until the client with that `taskID` subscribes, or if the snapshot\n * connection terminates.\n */\n startSnapshotReservation(taskID: string): Promise<Source<SnapshotMessage>>;\n\n /**\n * Informs the change-streamer of the watermark up to which the replica has\n * been backed up. This serves two purposes:\n * - It serves as the maximum watermark up to which the change-log can be\n * purged. Note that the change-streamer also takes into account the\n * positions of its subscribers and snapshot reservations when purging\n * changes in the change-log.\n * - In RMv2, this ACK's the upstream change-source to allow its buffer of\n * changes (e.g. the replication_slot) to advance.\n */\n trackBackupWatermark(watermark: string): void;\n}\n"],"mappings":";;;;;;;;AA0LA,IAAa,eAAe,eAAE,OAAO;CACnC,KAAK,eAAE,QAAQ,QAAQ;CAEvB,WAAW,eACR,OAAO;EACN,aAAa,0BAA0B,SAAS;EAChD,gBAAgB,eAAE,OAAO;CAC3B,CAAC,EACA,SAAS;AACd,CAAC;AAID,IAAa,sBAAsB,eAAE,MAAM,CAAC,eAAE,QAAQ,QAAQ,GAAG,YAAY,CAAC;AAU9E,IAAM,0BAA0B,eAAE,OAAO;CACvC,MAAM,eAAE,OAAO;CACf,SAAS,eAAE,OAAO,EAAE,SAAS;AAC/B,CAAC;AAID,IAAM,cAAc,eAAE,MAAM,CAAC,eAAE,QAAQ,OAAO,GAAG,uBAAuB,CAAC;AAEzE,IAAa,mBAAmB,eAAE,MAChC,qBACA,wBACA,WACF;AAIA,SAAgB,wBAAwB,KAAgB;CACtD,QAAQ,KAAR;EACE,KAAK,GACH,OAAO;EACT,KAAK,GACH,OAAO;EACT,KAAK,GACH,OAAO;EACT,SACE,OAAO;CACX;AACF"}
|
|
1
|
+
{"version":3,"file":"change-streamer.js","names":[],"sources":["../../../../../../zero-cache/src/services/change-streamer/change-streamer.ts"],"sourcesContent":["import type {Enum} from '../../../../shared/src/enum.ts';\nimport * as v from '../../../../shared/src/valita.ts';\nimport type {PreSerialized, Sized, Source} from '../../types/streams.ts';\nimport type {Subscription} from '../../types/subscription.ts';\nimport {\n changeStreamDataSchema,\n type ChangeStreamData,\n} from '../change-source/protocol/current/downstream.ts';\nimport type {BackfillRequest} from '../change-source/protocol/current/upstream.ts';\nimport type {ReplicatorMode} from '../replicator/replicator.ts';\nimport {changeSourceTimingsSchema} from '../replicator/reporter/report-schema.ts';\nimport type {Service} from '../service.ts';\nimport type {PreSerializedBatch} from './broadcast.ts';\nimport * as ErrorType from './error-type-enum.ts';\nimport type {SnapshotMessage} from './snapshot-message.ts';\nimport type {LocalReplica, SubscribeDownstream} from './subscribe.ts';\n\ntype ErrorType = Enum<typeof ErrorType>;\n\nexport type ChangeTag = ChangeStreamData[1]['tag'];\n\n/**\n * Internally all downstream messages (not just commits) are given a watermark.\n * These are used for internal ordering while replaying stored changes and\n * filtering changes already seen by a subscriber.\n *\n * Only commit watermarks are exposed to subscribers. The JSON string is the\n * canonical serialization shared by storage and live forwarding.\n */\nexport type WatermarkedChange = [\n watermark: string,\n tag: ChangeTag,\n json: string,\n];\n\n/**\n * The ChangeStreamer is the component between replicators (\"subscribers\")\n * and a canonical upstream source of changes (e.g. a Postgres logical\n * replication slot). It facilitates multiple subscribers without incurring\n * the associated upstream expense (e.g. PG replication slots are resource\n * intensive) with a \"forward-store-ack\" procedure.\n *\n * * Changes from the upstream source are immediately **forwarded** to\n * connected subscribers to minimize latency.\n *\n * * They are then **stored** in a separate DB to facilitate catchup\n * of connecting subscribers that are behind.\n *\n * * **Acknowledgements** are sent upstream after they are successfully\n * stored.\n *\n * Unlike Postgres replication slots, in which the progress of a static\n * subscriber is tracked in the replication slot, the ChangeStreamer\n * supports a dynamic set of subscribers (i.e.. zero-caches) that can\n * can continually change.\n *\n * However, it is not the case that the ChangeStreamer needs to support\n * arbitrarily old subscribers. Because the replica is continually\n * backed up to a global location and used to initialize new subscriber\n * tasks, an initial subscription request from a subscriber constitutes\n * a signal for how \"behind\" a new subscriber task can be. This is\n * reflected in the {@link SubscriberContext}, which indicates whether\n * the watermark corresponds to an \"initial\" watermark derived from the\n * replica at task startup.\n *\n * The ChangeStreamer uses a combination of this signal with ACK\n * responses from connected subscribers to determine the watermark up\n * to which it is safe to purge old change log entries.\n */\nexport interface ChangeStreamer {\n /**\n * Subscribes to changes based on the supplied subscriber `ctx`,\n * which indicates the watermark at which the subscriber is up to\n * date. Each result contains parsed, validated data and its approximate\n * serialized transport size.\n */\n subscribe(\n ctx: SubscriberContext,\n ): Promise<Source<Sized<SubscribeDownstream>>>;\n}\n\n// v1: v0.18\n// - Client-side support for JSON_FORMAT. Introduced in 0.18.\n// v2: v0.19\n// - Adds the \"status\" message which is initially used to signal that the\n// subscription is valid (i.e. starting at the requested watermark).\n// v3: v0.20\n// - Adds the \"taskID\" to the subscription context, and support for\n// the backup monitor-mediated \"/snapshot\" request.\n// v4: v0.25\n// - Adds the \"replicaVersion\" and \"minWatermark\" fields to the \"/snapshot\"\n// status request so that a subscriber can verify whether its replica,\n// whether it be restored or existing in a permanent volume, is compatible\n// with the change-streamer.\n// v5: v0.26\n// - Moves relation.keyColumns and relation.replicaIdentity to\n// relation.rowKey: { columns, type }.\n// - Adds `metadata` to `create-table` message\n// - Adds `tableMetadata` to `add-column` message\n// - Adds `table-update-metadata` message\n// v6: v0.26\n// - Adds support for `backfill` messages\n// v6: v1.0.1 (backwards compatible, no version change)\n// - Adds lag reporting to status messages\n// v6: (backwards compatible, no version change)\n// - Adds the optional `commitTimeMs` field to `commit` messages, carrying\n// the upstream commit timestamp for end-to-end serving lag measurement.\n// The stream is parsed in 'passthrough' mode, so an older peer ignores the\n// field, and a newer peer treats its absence (including in changes\n// replayed from the Change DB) as \"no commit time reported\".\n// v7: v1.12\n// - Merges the separate `/snapshot` and `/changes` connections into a single\n// bidirectional `/subscribe` connection (see subscribe.ts). The view-syncer\n// drives the connection with upstream `reserve-snapshot` / `start-subscription`\n// application messages; the change-streamer replies with a `reserved`\n// message and then the change stream on the same socket. This guarantees the\n// replication-manager that reserves the change log is the one that serves the\n// subscription. The legacy `/snapshot` + `/changes` endpoints remain for\n// v4-v6 subscribers during rollout.\n// - Drops the legacy \"initial\" and \"logsChangeStream\" query parameters from the\n// subscriber context\n// v8:\n// - Adds `backfills` to the `start-subscription` context, with which the\n// subscriber reports its pending backfills (and their progress).\n// - Adds `progressMarks` to `backfill` and `backfill-completed` messages,\n// which subscribers use to ignore backfill data that would otherwise\n// create a gap (see `acceptBackfill()`).\n// Both fields are optional in the schemas so that v7 peers (and changes\n// persisted in the change-log by v7 replication-managers) remain compatible,\n// but they should be considered non-optional as of v8.\n\nexport const PROTOCOL_VERSION = 8;\n\nexport type SubscriberContext = {\n /**\n * The supported change-streamer protocol version.\n */\n protocolVersion: number;\n\n /**\n * Task ID. This is used to link the request with a preceding snapshot\n * reservation.\n */\n taskID: string;\n\n /**\n * Subscriber id. This is only used for debugging.\n */\n id: string;\n\n /**\n * The ReplicatorMode of the subscriber. 'backup' indicates that the\n * subscriber is local to the `change-streamer` in the `replication-manager`,\n * while 'serving' indicates that user-facing requests depend on the subscriber.\n */\n mode: ReplicatorMode;\n\n /**\n * The ChangeStreamer will return an Error if the subscriber is\n * on a different replica version (i.e. the initial snapshot associated\n * with the replication slot).\n */\n replicaVersion: string;\n\n /**\n * The watermark up to which the subscriber is up to date.\n * Only changes after the watermark will be streamed.\n */\n watermark: string;\n\n /**\n * Whether the subscriber supports batched WebSocket frames.\n */\n wsBatched?: boolean | undefined;\n\n /**\n * The subscriber's pending backfills as of its `watermark`. Reported by\n * subscribers as of protocol v8. `undefined` for older subscribers.\n */\n backfills?: BackfillRequest[] | undefined;\n\n /**\n * Whether the subscriber understands the `draining` status, i.e. it will\n * switch over to another replication-manager when the current one shuts\n * down. A draining replication-manager waits for drainable subscribers to\n * leave before exiting.\n */\n drainable?: boolean | undefined;\n\n /**\n * Whether the subscriber is switching over from another subscription with\n * a replica that it is currently serving. If the replica version is\n * incompatible, such a subscriber is held (see {@link Status.restore})\n * rather than being immediately rejected. Subscribers that are booting up\n * never set this.\n */\n switchover?: boolean | undefined;\n};\n\n/**\n * The StatusMessage payload for now is empty, but can be extended to\n * include meta-level information in the future.\n */\nexport const statusSchema = v.object({\n tag: v.literal('status'),\n\n lagReport: v\n .object({\n lastTimings: changeSourceTimingsSchema.optional(),\n nextSendTimeMs: v.number(),\n })\n .optional(),\n\n /**\n * Sent by a replication-manager that is shutting down to its (drainable)\n * serving subscribers. The subscriber should switch over to another\n * replication-manager (see {@link SubscriberContext.switchover}) while\n * continuing to consume this subscription until the switchover succeeds.\n */\n draining: v.boolean().optional(),\n\n /**\n * Sent on a {@link SubscriberContext.switchover switchover} subscription\n * to a replication-manager running an incompatible replica version.\n * Rather than being immediately rejected with\n * {@link ErrorType.WrongReplicaVersion}, the subscriber is held until it\n * is granted permission to restore the replication-manager's backup,\n * which limits the number of subscribers that are restoring (and thus not\n * serving) at the same time.\n *\n * * `queued`: the subscriber is waiting for permission to restore. It\n * should continue consuming its existing subscription (if any).\n * * `granted`: the subscriber should (gracefully) restart to restore a\n * new replica.\n */\n restore: v\n .union(\n v.object({state: v.literal('queued'), position: v.number()}),\n v.object({state: v.literal('granted')}),\n )\n .optional(),\n});\n\nexport type Status = v.Infer<typeof statusSchema>;\n\nexport const statusMessageSchema = v.tuple([v.literal('status'), statusSchema]);\n\n/**\n * A StatusMessage will be immediately sent on a (v2+) subscription to\n * indicate that the subscription is valid (i.e. starting at the requested\n * watermark). Invalid subscriptions will instead result in a\n * SubscriptionError as the first message.\n */\nexport type StatusMessage = v.Infer<typeof statusMessageSchema>;\n\nconst subscriptionErrorSchema = v.object({\n type: v.number(), // ErrorType\n message: v.string().optional(),\n /**\n * The replica version of the replication-manager, sent with\n * {@link ErrorType.WrongReplicaVersion} so that a subscriber with a newer\n * replica can avoid restoring an older one.\n */\n replicaVersion: v.string().optional(),\n});\n\nexport type SubscriptionError = v.Infer<typeof subscriptionErrorSchema>;\n\nconst errorSchema = v.tuple([v.literal('error'), subscriptionErrorSchema]);\n\nexport const downstreamSchema = v.union(\n statusMessageSchema,\n changeStreamDataSchema,\n errorSchema,\n);\n\nexport type Error = v.Infer<typeof errorSchema>;\n\nexport function errorTypeToReadableName(val: ErrorType) {\n switch (val) {\n case ErrorType.WrongReplicaVersion:\n return 'WrongReplicaVersion';\n case ErrorType.WatermarkTooOld:\n return 'WatermarkTooOld';\n case ErrorType.Unknown:\n return 'Unknown';\n default:\n return 'Unknown';\n }\n}\n\n/**\n * A stream of transactions, each starting with a {@link Begin} message,\n * containing one or more {@link Data} messages, and ending with a\n * {@link Commit} or {@link Rollback} message. The 'commit' tuple\n * includes a `watermark` that should be stored with the committed\n * data and used for resuming a subscription (e.g. in the\n * {@link SubscriberContext}).\n *\n * A {@link SubscriptionError} indicates an unrecoverable error that requires\n * manual intervention (e.g. configuration / operational error).\n */\nexport type Downstream = v.Infer<typeof downstreamSchema>;\n\n/** A downstream message and its approximate serialized transport size. */\nexport type SizedDownstream = Sized<Downstream>;\n\nexport interface ChangeStreamerService\n extends Omit<ChangeStreamer, 'subscribe'>, Service {\n /**\n * The server-side interface overrides `subscribe()` to return a stream\n * of already-stringified {@link Downstream} payloads or pre-serialized Buffers.\n *\n * If a `downstream` subscription is supplied it is used directly as the\n * returned Source. Otherwise, a new one is created.\n */\n subscribe(\n ctx: SubscriberContext,\n downstream?: Subscription<string | PreSerializedBatch>,\n ): Promise<Source<string | PreSerialized>>;\n\n /**\n * Starts a snapshot reservation to preserve change-log entries while\n * a soon-to-be-subscriber downloads the current replica backup. Once\n * the change-streamer knows that a backup is available\n * (via {@link trackBackupWatermark}), it sends a confirmation\n * SnapshotMessage and reserves the corresponding entries in the change-log\n * until the client with that `taskID` subscribes, or if the snapshot\n * connection terminates.\n */\n startSnapshotReservation(\n taskID: string,\n opts?: {localReplica?: LocalReplica | undefined},\n ): Promise<Source<SnapshotMessage>>;\n\n /**\n * Signals (drainable) serving subscribers to switch over to another\n * replication-manager, and resolves when they have all disconnected.\n * Subscribers continue to be served in the meantime.\n */\n drain?(): Promise<void>;\n\n /**\n * Informs the change-streamer of the watermark up to which the replica has\n * been backed up. This serves two purposes:\n * - It serves as the maximum watermark up to which the change-log can be\n * purged. Note that the change-streamer also takes into account the\n * positions of its subscribers and snapshot reservations when purging\n * changes in the change-log.\n * - In RMv2, this ACK's the upstream change-source to allow its buffer of\n * changes (e.g. the replication_slot) to advance.\n */\n trackBackupWatermark(watermark: string): void;\n}\n"],"mappings":";;;;;;;;AA2MA,IAAa,eAAe,eAAE,OAAO;CACnC,KAAK,eAAE,QAAQ,QAAQ;CAEvB,WAAW,eACR,OAAO;EACN,aAAa,0BAA0B,SAAS;EAChD,gBAAgB,eAAE,OAAO;CAC3B,CAAC,EACA,SAAS;;;;;;;CAQZ,UAAU,eAAE,QAAQ,EAAE,SAAS;;;;;;;;;;;;;;;CAgB/B,SAAS,eACN,MACC,eAAE,OAAO;EAAC,OAAO,eAAE,QAAQ,QAAQ;EAAG,UAAU,eAAE,OAAO;CAAC,CAAC,GAC3D,eAAE,OAAO,EAAC,OAAO,eAAE,QAAQ,SAAS,EAAC,CAAC,CACxC,EACC,SAAS;AACd,CAAC;AAID,IAAa,sBAAsB,eAAE,MAAM,CAAC,eAAE,QAAQ,QAAQ,GAAG,YAAY,CAAC;AAU9E,IAAM,0BAA0B,eAAE,OAAO;CACvC,MAAM,eAAE,OAAO;CACf,SAAS,eAAE,OAAO,EAAE,SAAS;;;;;;CAM7B,gBAAgB,eAAE,OAAO,EAAE,SAAS;AACtC,CAAC;AAID,IAAM,cAAc,eAAE,MAAM,CAAC,eAAE,QAAQ,OAAO,GAAG,uBAAuB,CAAC;AAEzE,IAAa,mBAAmB,eAAE,MAChC,qBACA,wBACA,WACF;AAIA,SAAgB,wBAAwB,KAAgB;CACtD,QAAQ,KAAR;EACE,KAAK,GACH,OAAO;EACT,KAAK,GACH,OAAO;EACT,KAAK,GACH,OAAO;EACT,SACE,OAAO;CACX;AACF"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { LogContext } from '@rocicorp/logger';
|
|
2
|
+
import type { Subscription } from '../../types/subscription.ts';
|
|
3
|
+
import type { PreSerializedBatch } from './broadcast.ts';
|
|
4
|
+
import type { Subscriber } from './subscriber.ts';
|
|
5
|
+
/**
|
|
6
|
+
* Tracks the {@link Subscriber.drainable drainable} subscribers of the
|
|
7
|
+
* change-streamer, i.e. those that switch over to another
|
|
8
|
+
* replication-manager when this one drains.
|
|
9
|
+
*
|
|
10
|
+
* Upon {@link drain}, drainable subscribers are sent a `draining` status
|
|
11
|
+
* (as are drainable subscribers that subscribe while draining), and the
|
|
12
|
+
* returned promise resolves once they have all disconnected. Subscribers
|
|
13
|
+
* continue to be served in the meantime.
|
|
14
|
+
*/
|
|
15
|
+
export declare class DrainTracker {
|
|
16
|
+
#private;
|
|
17
|
+
constructor(lc: LogContext, setIntervalFn?: typeof setInterval, clearIntervalFn?: typeof clearInterval);
|
|
18
|
+
get draining(): boolean;
|
|
19
|
+
register(subscriber: Subscriber, downstream: Subscription<string | PreSerializedBatch>): void;
|
|
20
|
+
drain(): Promise<void>;
|
|
21
|
+
/** Resolves any pending drain, e.g. when the change-streamer stops. */
|
|
22
|
+
stop(): void;
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=drain-tracker.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"drain-tracker.d.ts","sourceRoot":"","sources":["../../../../../../zero-cache/src/services/change-streamer/drain-tracker.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,kBAAkB,CAAC;AAEjD,OAAO,KAAK,EAAC,YAAY,EAAC,MAAM,6BAA6B,CAAC;AAC9D,OAAO,KAAK,EAAC,kBAAkB,EAAC,MAAM,gBAAgB,CAAC;AACvD,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,iBAAiB,CAAC;AAIhD;;;;;;;;;GASG;AACH,qBAAa,YAAY;;IAOvB,YACE,EAAE,EAAE,UAAU,EAEd,aAAa,GAAE,OAAO,WAAyB,EAC/C,eAAe,GAAE,OAAO,aAA6B,EAKtD;IAED,IAAI,QAAQ,IAAI,OAAO,CAEtB;IAED,QAAQ,CACN,UAAU,EAAE,UAAU,EACtB,UAAU,EAAE,YAAY,CAAC,MAAM,GAAG,kBAAkB,CAAC,QActD;IAED,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAuBrB;IAED,uEAAuE;IACvE,IAAI,SAEH;CAQF"}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { resolver } from "@rocicorp/resolver";
|
|
2
|
+
//#region ../zero-cache/src/services/change-streamer/drain-tracker.ts
|
|
3
|
+
var PROGRESS_LOG_INTERVAL_MS = 3e4;
|
|
4
|
+
/**
|
|
5
|
+
* Tracks the {@link Subscriber.drainable drainable} subscribers of the
|
|
6
|
+
* change-streamer, i.e. those that switch over to another
|
|
7
|
+
* replication-manager when this one drains.
|
|
8
|
+
*
|
|
9
|
+
* Upon {@link drain}, drainable subscribers are sent a `draining` status
|
|
10
|
+
* (as are drainable subscribers that subscribe while draining), and the
|
|
11
|
+
* returned promise resolves once they have all disconnected. Subscribers
|
|
12
|
+
* continue to be served in the meantime.
|
|
13
|
+
*/
|
|
14
|
+
var DrainTracker = class {
|
|
15
|
+
#lc;
|
|
16
|
+
#subscribers = /* @__PURE__ */ new Set();
|
|
17
|
+
#setInterval;
|
|
18
|
+
#clearInterval;
|
|
19
|
+
#draining;
|
|
20
|
+
constructor(lc, setIntervalFn = setInterval, clearIntervalFn = clearInterval) {
|
|
21
|
+
this.#lc = lc.withContext("component", "drain-tracker");
|
|
22
|
+
this.#setInterval = setIntervalFn;
|
|
23
|
+
this.#clearInterval = clearIntervalFn;
|
|
24
|
+
}
|
|
25
|
+
get draining() {
|
|
26
|
+
return this.#draining !== void 0;
|
|
27
|
+
}
|
|
28
|
+
register(subscriber, downstream) {
|
|
29
|
+
if (!subscriber.drainable) return;
|
|
30
|
+
this.#subscribers.add(subscriber);
|
|
31
|
+
if (this.#draining) subscriber.sendControlStatus({
|
|
32
|
+
tag: "status",
|
|
33
|
+
draining: true
|
|
34
|
+
});
|
|
35
|
+
downstream.addCloseHandler(() => {
|
|
36
|
+
if (this.#subscribers.delete(subscriber)) this.#checkDrained();
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
drain() {
|
|
40
|
+
if (!this.#draining) {
|
|
41
|
+
const draining = this.#draining = resolver();
|
|
42
|
+
this.#lc.info?.(`draining: signaling ${this.#subscribers.size} subscribers to switch over`);
|
|
43
|
+
for (const sub of this.#subscribers) sub.sendControlStatus({
|
|
44
|
+
tag: "status",
|
|
45
|
+
draining: true
|
|
46
|
+
});
|
|
47
|
+
const progress = this.#setInterval(() => this.#lc.info?.(`draining: waiting for ${this.#subscribers.size} subscribers to switch over`, Array.from(this.#subscribers, (sub) => sub.id)), PROGRESS_LOG_INTERVAL_MS);
|
|
48
|
+
draining.promise.finally(() => this.#clearInterval(progress));
|
|
49
|
+
this.#checkDrained();
|
|
50
|
+
}
|
|
51
|
+
return this.#draining.promise;
|
|
52
|
+
}
|
|
53
|
+
/** Resolves any pending drain, e.g. when the change-streamer stops. */
|
|
54
|
+
stop() {
|
|
55
|
+
this.#draining?.resolve();
|
|
56
|
+
}
|
|
57
|
+
#checkDrained() {
|
|
58
|
+
if (this.#draining && this.#subscribers.size === 0) {
|
|
59
|
+
this.#lc.info?.(`drained all subscribers`);
|
|
60
|
+
this.#draining.resolve();
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
//#endregion
|
|
65
|
+
export { DrainTracker };
|
|
66
|
+
|
|
67
|
+
//# sourceMappingURL=drain-tracker.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"drain-tracker.js","names":["#lc","#subscribers","#setInterval","#clearInterval","#draining","#checkDrained"],"sources":["../../../../../../zero-cache/src/services/change-streamer/drain-tracker.ts"],"sourcesContent":["import type {LogContext} from '@rocicorp/logger';\nimport {resolver, type Resolver} from '@rocicorp/resolver';\nimport type {Subscription} from '../../types/subscription.ts';\nimport type {PreSerializedBatch} from './broadcast.ts';\nimport type {Subscriber} from './subscriber.ts';\n\nconst PROGRESS_LOG_INTERVAL_MS = 30_000;\n\n/**\n * Tracks the {@link Subscriber.drainable drainable} subscribers of the\n * change-streamer, i.e. those that switch over to another\n * replication-manager when this one drains.\n *\n * Upon {@link drain}, drainable subscribers are sent a `draining` status\n * (as are drainable subscribers that subscribe while draining), and the\n * returned promise resolves once they have all disconnected. Subscribers\n * continue to be served in the meantime.\n */\nexport class DrainTracker {\n readonly #lc: LogContext;\n readonly #subscribers = new Set<Subscriber>();\n readonly #setInterval: typeof setInterval;\n readonly #clearInterval: typeof clearInterval;\n #draining: Resolver<void> | undefined;\n\n constructor(\n lc: LogContext,\n // Injectable timers, for deterministic testing.\n setIntervalFn: typeof setInterval = setInterval,\n clearIntervalFn: typeof clearInterval = clearInterval,\n ) {\n this.#lc = lc.withContext('component', 'drain-tracker');\n this.#setInterval = setIntervalFn;\n this.#clearInterval = clearIntervalFn;\n }\n\n get draining(): boolean {\n return this.#draining !== undefined;\n }\n\n register(\n subscriber: Subscriber,\n downstream: Subscription<string | PreSerializedBatch>,\n ) {\n if (!subscriber.drainable) {\n return;\n }\n this.#subscribers.add(subscriber);\n if (this.#draining) {\n subscriber.sendControlStatus({tag: 'status', draining: true});\n }\n downstream.addCloseHandler(() => {\n if (this.#subscribers.delete(subscriber)) {\n this.#checkDrained();\n }\n });\n }\n\n drain(): Promise<void> {\n if (!this.#draining) {\n const draining = (this.#draining = resolver<void>());\n this.#lc.info?.(\n `draining: signaling ${this.#subscribers.size} subscribers ` +\n `to switch over`,\n );\n for (const sub of this.#subscribers) {\n sub.sendControlStatus({tag: 'status', draining: true});\n }\n const progress = this.#setInterval(\n () =>\n this.#lc.info?.(\n `draining: waiting for ${this.#subscribers.size} ` +\n `subscribers to switch over`,\n Array.from(this.#subscribers, sub => sub.id),\n ),\n PROGRESS_LOG_INTERVAL_MS,\n );\n void draining.promise.finally(() => this.#clearInterval(progress));\n this.#checkDrained();\n }\n return this.#draining.promise;\n }\n\n /** Resolves any pending drain, e.g. when the change-streamer stops. */\n stop() {\n this.#draining?.resolve();\n }\n\n #checkDrained() {\n if (this.#draining && this.#subscribers.size === 0) {\n this.#lc.info?.(`drained all subscribers`);\n this.#draining.resolve();\n }\n }\n}\n"],"mappings":";;AAMA,IAAM,2BAA2B;;;;;;;;;;;AAYjC,IAAa,eAAb,MAA0B;CACxB;CACA,+BAAwB,IAAI,IAAgB;CAC5C;CACA;CACA;CAEA,YACE,IAEA,gBAAoC,aACpC,kBAAwC,eACxC;EACA,KAAKA,MAAM,GAAG,YAAY,aAAa,eAAe;EACtD,KAAKE,eAAe;EACpB,KAAKC,iBAAiB;CACxB;CAEA,IAAI,WAAoB;EACtB,OAAO,KAAKC,cAAc,KAAA;CAC5B;CAEA,SACE,YACA,YACA;EACA,IAAI,CAAC,WAAW,WACd;EAEF,KAAKH,aAAa,IAAI,UAAU;EAChC,IAAI,KAAKG,WACP,WAAW,kBAAkB;GAAC,KAAK;GAAU,UAAU;EAAI,CAAC;EAE9D,WAAW,sBAAsB;GAC/B,IAAI,KAAKH,aAAa,OAAO,UAAU,GACrC,KAAKI,cAAc;EAEvB,CAAC;CACH;CAEA,QAAuB;EACrB,IAAI,CAAC,KAAKD,WAAW;GACnB,MAAM,WAAY,KAAKA,YAAY,SAAe;GAClD,KAAKJ,IAAI,OACP,uBAAuB,KAAKC,aAAa,KAAK,4BAEhD;GACA,KAAK,MAAM,OAAO,KAAKA,cACrB,IAAI,kBAAkB;IAAC,KAAK;IAAU,UAAU;GAAI,CAAC;GAEvD,MAAM,WAAW,KAAKC,mBAElB,KAAKF,IAAI,OACP,yBAAyB,KAAKC,aAAa,KAAK,8BAEhD,MAAM,KAAK,KAAKA,eAAc,QAAO,IAAI,EAAE,CAC7C,GACF,wBACF;GACA,SAAc,QAAQ,cAAc,KAAKE,eAAe,QAAQ,CAAC;GACjE,KAAKE,cAAc;EACrB;EACA,OAAO,KAAKD,UAAU;CACxB;;CAGA,OAAO;EACL,KAAKA,WAAW,QAAQ;CAC1B;CAEA,gBAAgB;EACd,IAAI,KAAKA,aAAa,KAAKH,aAAa,SAAS,GAAG;GAClD,KAAKD,IAAI,OAAO,yBAAyB;GACzC,KAAKI,UAAU,QAAQ;EACzB;CACF;AACF"}
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import type { LogContext } from '@rocicorp/logger';
|
|
2
|
+
import type { LocalReplica } from './subscribe.ts';
|
|
3
|
+
/**
|
|
4
|
+
* The default time to wait for a subscriber that was granted permission to
|
|
5
|
+
* restore to reconnect (i.e. to open a replacement reservation) after its
|
|
6
|
+
* held connection closes, or after its replacement reservation closes
|
|
7
|
+
* without subscribing (e.g. to retry a failed restore). This covers the
|
|
8
|
+
* subscriber's process exit and restart (e.g. a container restart, the
|
|
9
|
+
* backoff of which starts at 10 seconds in Kubernetes), but not the restore
|
|
10
|
+
* itself, which is tracked by the liveness of its snapshot reservation.
|
|
11
|
+
*/
|
|
12
|
+
export declare const DEFAULT_PENDING_GRANT_TTL_MS = 90000;
|
|
13
|
+
export type RestoreAdmissionOptions = {
|
|
14
|
+
/** The replica version of the replication-manager. */
|
|
15
|
+
replicaVersion: string;
|
|
16
|
+
/**
|
|
17
|
+
* The maximum number of subscribers that may be restoring (or granted
|
|
18
|
+
* permission to restore) at the same time.
|
|
19
|
+
*/
|
|
20
|
+
maxConcurrentRestores: number;
|
|
21
|
+
pendingGrantTTLMs?: number | undefined;
|
|
22
|
+
setTimeoutFn?: typeof setTimeout | undefined;
|
|
23
|
+
clearTimeoutFn?: typeof clearTimeout | undefined;
|
|
24
|
+
};
|
|
25
|
+
export type AdmissionRequest = {
|
|
26
|
+
/**
|
|
27
|
+
* Called when the request is queued, and again whenever its (1-based)
|
|
28
|
+
* position in the queue changes.
|
|
29
|
+
*/
|
|
30
|
+
onQueued: (position: number) => void;
|
|
31
|
+
/** Called (once) when the request is granted. */
|
|
32
|
+
onGranted: () => void;
|
|
33
|
+
};
|
|
34
|
+
export type AdmissionTicket = {
|
|
35
|
+
/**
|
|
36
|
+
* Must be called when the requesting connection closes. A queued request
|
|
37
|
+
* is removed from the queue. For a granted request, this starts the
|
|
38
|
+
* timer within which the subscriber is expected to reconnect and open a
|
|
39
|
+
* replacement reservation.
|
|
40
|
+
*/
|
|
41
|
+
release(): void;
|
|
42
|
+
};
|
|
43
|
+
/**
|
|
44
|
+
* Limits the number of view-syncers that replace their replica at the same
|
|
45
|
+
* time, so that view-syncers switching over from an incompatible
|
|
46
|
+
* replication-manager (e.g. after a resync) do not all stop serving at once.
|
|
47
|
+
*
|
|
48
|
+
* Capacity is consumed by:
|
|
49
|
+
* * Replacement restores: snapshot reservations of subscribers that are
|
|
50
|
+
* replacing an (incompatible or outdated) local replica, i.e. subscribers
|
|
51
|
+
* that were serving. Restores onto an empty disk (e.g. a new pod) do not
|
|
52
|
+
* take a serving subscriber out of service, and are not counted. The
|
|
53
|
+
* reservation's connection is held open for the duration of the restore,
|
|
54
|
+
* and the subscription that follows it is held open until the restored
|
|
55
|
+
* subscriber has caught up, so their liveness tracks that of the
|
|
56
|
+
* replacement without a timeout.
|
|
57
|
+
* * Outstanding grants: permission to restore that has not yet been
|
|
58
|
+
* followed by a replacement reservation (e.g. while the subscriber drains
|
|
59
|
+
* and restarts). Grants are not matched to reservations by identity:
|
|
60
|
+
* any replacement reservation consumes an outstanding grant, and a grant
|
|
61
|
+
* that is not consumed expires a short time after the granted connection
|
|
62
|
+
* closes (e.g. if the subscriber reconnected to a different
|
|
63
|
+
* replication-manager or was deleted).
|
|
64
|
+
*
|
|
65
|
+
* ## Life cycle of a switchover restore
|
|
66
|
+
*
|
|
67
|
+
* The following traces a view-syncer (VS) with a replica at version `R1`,
|
|
68
|
+
* served by an old replication-manager (RM) at `R1`, while a new RM (e.g.
|
|
69
|
+
* after a resync) is at `R2 > R1`. The RestoreAdmission is that of the new
|
|
70
|
+
* RM. Terms in `code` are those used in the code.
|
|
71
|
+
*
|
|
72
|
+
* 1. **Drain.** The old RM receives a SIGTERM, and its `DrainTracker` sends
|
|
73
|
+
* `status {draining: true}` to its `drainable` (i.e. serving) subscribers.
|
|
74
|
+
* It continues to stream changes to them, and exits once they have all
|
|
75
|
+
* disconnected.
|
|
76
|
+
*
|
|
77
|
+
* 2. **Switchover.** The VS's `DrainHandler` opens a second `/subscribe`
|
|
78
|
+
* with `switchover: true` at the watermark of its last committed
|
|
79
|
+
* transaction, which is routed to some RM. The VS continues to consume
|
|
80
|
+
* the old RM's stream. If the RM is compatible (i.e. another old RM),
|
|
81
|
+
* the subscription is accepted and the VS simply moves to it. If the RM
|
|
82
|
+
* is the new one, the subscriber's replica version is older, and the
|
|
83
|
+
* subscription proceeds to step 3.
|
|
84
|
+
*
|
|
85
|
+
* 3. **Queued.** `ChangeStreamerImpl.subscribe()` holds the subscription
|
|
86
|
+
* (it is not added to the forwarder) and calls {@link request}. The
|
|
87
|
+
* request is `queued` and `onQueued(position)` sends
|
|
88
|
+
* `status {restore: {state: 'queued', position}}`, again whenever the
|
|
89
|
+
* position changes. If the held connection closes while queued (e.g.
|
|
90
|
+
* the VS's old stream ended and it gave up on the switchover), the
|
|
91
|
+
* ticket is `release()`d and the request leaves the queue.
|
|
92
|
+
*
|
|
93
|
+
* 4. **Granted.** When the number in flight (`replacing` + `granted`) is
|
|
94
|
+
* below `maxConcurrentRestores`, the head of the queue is `granted`: a
|
|
95
|
+
* {@link Grant} is added (counting toward capacity), and `onGranted()`
|
|
96
|
+
* sends `status {restore: {state: 'granted'}}`. The VS keeps the granted
|
|
97
|
+
* connection open, and initiates a self-drain (`drainRequested`): it
|
|
98
|
+
* fails its health checks, drains its clients, and keeps replicating
|
|
99
|
+
* from the old RM in the meantime.
|
|
100
|
+
*
|
|
101
|
+
* 5. **Restart.** The VS process exits, closing the granted connection.
|
|
102
|
+
* The ticket is `release()`d, marking the grant as `released` and
|
|
103
|
+
* starting the pending grant TTL ({@link DEFAULT_PENDING_GRANT_TTL_MS}),
|
|
104
|
+
* within which a replacement reservation is expected. The drain itself
|
|
105
|
+
* does not count against the TTL.
|
|
106
|
+
*
|
|
107
|
+
* 6. **Reservation opened.** The restarted VS sends
|
|
108
|
+
* `reserve-snapshot {taskID, localReplica}`, and
|
|
109
|
+
* `ChangeStreamerImpl.startSnapshotReservation()` calls
|
|
110
|
+
* {@link reservationOpened}:
|
|
111
|
+
* * A `localReplica` of a different replica version (e.g. `R1`) is
|
|
112
|
+
* `replacing`: the reservation is counted, and consumes an outstanding
|
|
113
|
+
* grant (preferring a `released` one), so the number in flight is
|
|
114
|
+
* unchanged.
|
|
115
|
+
* * A `localReplica` of the same replica version is `unclassified` until
|
|
116
|
+
* the reservation is confirmed ({@link reservationConfirmed}) with the
|
|
117
|
+
* backup's minimum watermark. It is `replacing` if its watermark is
|
|
118
|
+
* older (i.e. it cannot catch up from the change log).
|
|
119
|
+
* * No `localReplica` (an empty disk) is not counted.
|
|
120
|
+
*
|
|
121
|
+
* If the VS reached a different RM instead, the grant here expires after
|
|
122
|
+
* the TTL and frees its capacity, while the replacement is counted (as
|
|
123
|
+
* an ungranted one) by the RM that it reached.
|
|
124
|
+
*
|
|
125
|
+
* 7. **Restoring.** The VS restores the backup while holding the
|
|
126
|
+
* reservation's connection open. A retried reservation from the same
|
|
127
|
+
* task that arrives while the previous one is still open replaces it
|
|
128
|
+
* (`SnapshotReservations` `onClose`) but does not end it (`onEnd`), so it
|
|
129
|
+
* remains `replacing`. If the reservation instead closes without
|
|
130
|
+
* subscribing (e.g. a failed or disconnected restore, after which the VS
|
|
131
|
+
* retries), {@link reservationClosed} converts its capacity into a
|
|
132
|
+
* `released` grant, which the retry's replacement reservation consumes
|
|
133
|
+
* (step 6), or which expires after the pending grant TTL (e.g. if the VS
|
|
134
|
+
* was deleted).
|
|
135
|
+
*
|
|
136
|
+
* 8. **Subscribed.** Once restored, the VS subscribes on the same
|
|
137
|
+
* connection at `R2`, which is accepted. `ChangeStreamerImpl.subscribe()`
|
|
138
|
+
* calls {@link reservationSubscribed}, which moves the replacement from
|
|
139
|
+
* its reservation (the key leaves `replacing`) to the subscription
|
|
140
|
+
* (`catchingUp`), and then closes the task's reservation. Its `onEnd`
|
|
141
|
+
* calls {@link reservationClosed}, which is a no-op for the moved key.
|
|
142
|
+
*
|
|
143
|
+
* 9. **Caught up.** The VS catches up on the change stream from the
|
|
144
|
+
* backup's watermark. Once it has acked all changes up to the head of
|
|
145
|
+
* the stream (`Subscriber.caughtUp`), or if it disconnects first, the
|
|
146
|
+
* replacement is released and the next queued request is granted.
|
|
147
|
+
*
|
|
148
|
+
* A VS that restarts outside of a switchover with an incompatible replica
|
|
149
|
+
* (e.g. after a crash) enters at step 6 without a grant, and its restore
|
|
150
|
+
* consumes capacity all the same, delaying queued requests.
|
|
151
|
+
*
|
|
152
|
+
* ## States
|
|
153
|
+
*
|
|
154
|
+
* | Term | Where | Meaning |
|
|
155
|
+
* | ------------- | ------------------------ | -------------------------------- |
|
|
156
|
+
* | queued | `Entry.state` | Held, waiting for capacity |
|
|
157
|
+
* | granted | `Entry.state`, `#grants` | Permitted to restore, pending a |
|
|
158
|
+
* | | | replacement reservation |
|
|
159
|
+
* | released | `Entry.state` | The held connection closed |
|
|
160
|
+
* | released | `Grant.released` | The granted connection, or an |
|
|
161
|
+
* | | | unsubscribed replacement |
|
|
162
|
+
* | | | reservation, closed; the TTL is |
|
|
163
|
+
* | | | running |
|
|
164
|
+
* | unclassified | `#unclassified` | Same-version reservation, not |
|
|
165
|
+
* | | | yet confirmed |
|
|
166
|
+
* | replacing | `#replacing` | Open replacement reservation |
|
|
167
|
+
* | catching up | `#catchingUp` | Subscribed replacement, not yet |
|
|
168
|
+
* | | | caught up |
|
|
169
|
+
* | in flight | `#inFlight` | `replacing` + `catchingUp` + |
|
|
170
|
+
* | | | `granted`, bounded by |
|
|
171
|
+
* | | | `maxConcurrentRestores` |
|
|
172
|
+
*/
|
|
173
|
+
export declare class RestoreAdmission {
|
|
174
|
+
#private;
|
|
175
|
+
constructor(lc: LogContext, opts: RestoreAdmissionOptions);
|
|
176
|
+
/** For testing and logging. */
|
|
177
|
+
get stats(): {
|
|
178
|
+
replacing: number;
|
|
179
|
+
granted: number;
|
|
180
|
+
queued: number;
|
|
181
|
+
};
|
|
182
|
+
/**
|
|
183
|
+
* Called when a snapshot reservation is opened by a subscriber with the
|
|
184
|
+
* given `localReplica` (if any). A subscriber with a local replica of a
|
|
185
|
+
* different replica version is replacing it. One with the same replica
|
|
186
|
+
* version is replacing it if it is outdated, which is determined by
|
|
187
|
+
* {@link reservationConfirmed}. Idempotent for the same `key` (e.g. a
|
|
188
|
+
* retried reservation).
|
|
189
|
+
*/
|
|
190
|
+
reservationOpened(key: string, localReplica: LocalReplica | undefined): void;
|
|
191
|
+
/**
|
|
192
|
+
* Called when a reservation is confirmed with the minimum watermark of the
|
|
193
|
+
* replica to restore.
|
|
194
|
+
*/
|
|
195
|
+
reservationConfirmed(key: string, minWatermark: string): void;
|
|
196
|
+
/**
|
|
197
|
+
* Called when a reservation ends. A replacement reservation normally ends
|
|
198
|
+
* after its task subscribes ({@link reservationSubscribed}), in which case
|
|
199
|
+
* this is a no-op. A replacement reservation that ends without subscribing
|
|
200
|
+
* (e.g. a failed or disconnected restore) is most likely followed by a
|
|
201
|
+
* retry, so its capacity is retained as a released grant (which the retry
|
|
202
|
+
* consumes) rather than released immediately.
|
|
203
|
+
*/
|
|
204
|
+
reservationClosed(key: string): void;
|
|
205
|
+
/**
|
|
206
|
+
* Called when the task of a reservation subscribes (before the reservation
|
|
207
|
+
* is closed). If the reservation is a replacement, it continues to be
|
|
208
|
+
* counted until the returned function is called, i.e. when the subscriber
|
|
209
|
+
* has caught up or disconnected.
|
|
210
|
+
*/
|
|
211
|
+
reservationSubscribed(key: string): (() => void) | undefined;
|
|
212
|
+
request(request: AdmissionRequest): AdmissionTicket;
|
|
213
|
+
}
|
|
214
|
+
//# sourceMappingURL=restore-admission.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"restore-admission.d.ts","sourceRoot":"","sources":["../../../../../../zero-cache/src/services/change-streamer/restore-admission.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,kBAAkB,CAAC;AAEjD,OAAO,KAAK,EAAC,YAAY,EAAC,MAAM,gBAAgB,CAAC;AAEjD;;;;;;;;GAQG;AACH,eAAO,MAAM,4BAA4B,QAAS,CAAC;AAEnD,MAAM,MAAM,uBAAuB,GAAG;IACpC,sDAAsD;IACtD,cAAc,EAAE,MAAM,CAAC;IACvB;;;OAGG;IACH,qBAAqB,EAAE,MAAM,CAAC;IAC9B,iBAAiB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAEvC,YAAY,CAAC,EAAE,OAAO,UAAU,GAAG,SAAS,CAAC;IAC7C,cAAc,CAAC,EAAE,OAAO,YAAY,GAAG,SAAS,CAAC;CAClD,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG;IAC7B;;;OAGG;IACH,QAAQ,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;IAErC,iDAAiD;IACjD,SAAS,EAAE,MAAM,IAAI,CAAC;CACvB,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG;IAC5B;;;;;OAKG;IACH,OAAO,IAAI,IAAI,CAAC;CACjB,CAAC;AAeF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiIG;AACH,qBAAa,gBAAgB;;IAoB3B,YAAY,EAAE,EAAE,UAAU,EAAE,IAAI,EAAE,uBAAuB,EAmBxD;IAMD,+BAA+B;IAC/B,IAAI,KAAK;QAEL,SAAS;QACT,OAAO;QACP,MAAM;MAET;IAED;;;;;;;OAOG;IACH,iBAAiB,CAAC,GAAG,EAAE,MAAM,EAAE,YAAY,EAAE,YAAY,GAAG,SAAS,QAUpE;IAED;;;OAGG;IACH,oBAAoB,CAAC,GAAG,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,QAMrD;IAED;;;;;;;OAOG;IACH,iBAAiB,CAAC,GAAG,EAAE,MAAM,QAO5B;IAED;;;;;OAKG;IACH,qBAAqB,CAAC,GAAG,EAAE,MAAM,GAAG,CAAC,MAAM,IAAI,CAAC,GAAG,SAAS,CAY3D;IAiBD,OAAO,CAAC,OAAO,EAAE,gBAAgB,GAAG,eAAe,CAKlD;CAoDF"}
|