@rocicorp/zero 0.22.2025071101 → 0.22.2025072500
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/{chunk-4FQJO7OM.js → chunk-CPTFADYI.js} +26 -7
- package/out/{chunk-4FQJO7OM.js.map → chunk-CPTFADYI.js.map} +2 -2
- package/out/{chunk-INLOZBST.js → chunk-P5M53J4D.js} +2 -3
- package/out/{chunk-INLOZBST.js.map → chunk-P5M53J4D.js.map} +2 -2
- package/out/{chunk-5A6QECBY.js → chunk-ROYQ5GXF.js} +230 -137
- package/out/chunk-ROYQ5GXF.js.map +7 -0
- package/out/{inspector-HDOYOVMS.js → inspector-TFZBDN6K.js} +2 -2
- package/out/otel/src/enabled.d.ts +5 -0
- package/out/otel/src/enabled.d.ts.map +1 -0
- package/out/otel/src/enabled.js +16 -0
- package/out/otel/src/enabled.js.map +1 -0
- package/out/react.js +2 -2
- package/out/replicache/src/kv/idb-store.d.ts.map +1 -1
- package/out/shared/src/options.d.ts +4 -1
- package/out/shared/src/options.d.ts.map +1 -1
- package/out/shared/src/options.js +1 -1
- package/out/shared/src/options.js.map +1 -1
- package/out/solid.js +7 -3
- package/out/solid.js.map +1 -1
- package/out/zero/package.json +161 -0
- package/out/zero-cache/src/config/normalize.d.ts +1 -0
- package/out/zero-cache/src/config/normalize.d.ts.map +1 -1
- package/out/zero-cache/src/config/normalize.js +10 -1
- package/out/zero-cache/src/config/normalize.js.map +1 -1
- package/out/zero-cache/src/config/zero-config.d.ts +44 -6
- package/out/zero-cache/src/config/zero-config.d.ts.map +1 -1
- package/out/zero-cache/src/config/zero-config.js +48 -54
- package/out/zero-cache/src/config/zero-config.js.map +1 -1
- package/out/zero-cache/src/custom/fetch.d.ts +14 -1
- package/out/zero-cache/src/custom/fetch.d.ts.map +1 -1
- package/out/zero-cache/src/custom/fetch.js +56 -1
- package/out/zero-cache/src/custom/fetch.js.map +1 -1
- package/out/zero-cache/src/custom-queries/transform-query.d.ts +1 -1
- package/out/zero-cache/src/custom-queries/transform-query.d.ts.map +1 -1
- package/out/zero-cache/src/custom-queries/transform-query.js +2 -1
- package/out/zero-cache/src/custom-queries/transform-query.js.map +1 -1
- package/out/zero-cache/src/server/anonymous-otel-start.d.ts +9 -0
- package/out/zero-cache/src/server/anonymous-otel-start.d.ts.map +1 -0
- package/out/zero-cache/src/server/anonymous-otel-start.js +293 -0
- package/out/zero-cache/src/server/anonymous-otel-start.js.map +1 -0
- package/out/zero-cache/src/server/logging.d.ts.map +1 -1
- package/out/zero-cache/src/server/logging.js +3 -3
- package/out/zero-cache/src/server/logging.js.map +1 -1
- package/out/zero-cache/src/server/main.d.ts.map +1 -1
- package/out/zero-cache/src/server/main.js +1 -5
- package/out/zero-cache/src/server/main.js.map +1 -1
- package/out/zero-cache/src/server/otel-start.d.ts.map +1 -1
- package/out/zero-cache/src/server/otel-start.js +24 -18
- package/out/zero-cache/src/server/otel-start.js.map +1 -1
- package/out/zero-cache/src/server/runner/zero-dispatcher.d.ts.map +1 -1
- package/out/zero-cache/src/server/runner/zero-dispatcher.js +2 -2
- package/out/zero-cache/src/server/runner/zero-dispatcher.js.map +1 -1
- package/out/zero-cache/src/server/syncer.d.ts.map +1 -1
- package/out/zero-cache/src/server/syncer.js +7 -4
- package/out/zero-cache/src/server/syncer.js.map +1 -1
- package/out/zero-cache/src/server/worker-dispatcher.js +3 -3
- package/out/zero-cache/src/server/worker-dispatcher.js.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/change-source.d.ts +0 -7
- package/out/zero-cache/src/services/change-source/pg/change-source.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/change-source.js +43 -32
- package/out/zero-cache/src/services/change-source/pg/change-source.js.map +1 -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 +24 -12
- 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/init.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/schema/init.js +12 -1
- package/out/zero-cache/src/services/change-source/pg/schema/init.js.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/schema/shard.d.ts +10 -0
- package/out/zero-cache/src/services/change-source/pg/schema/shard.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/schema/shard.js +21 -2
- package/out/zero-cache/src/services/change-source/pg/schema/shard.js.map +1 -1
- package/out/zero-cache/src/services/change-streamer/change-streamer-http.d.ts +1 -1
- 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 +4 -2
- package/out/zero-cache/src/services/change-streamer/change-streamer-http.js.map +1 -1
- package/out/zero-cache/src/services/litestream/commands.d.ts.map +1 -1
- package/out/zero-cache/src/services/litestream/commands.js +50 -23
- package/out/zero-cache/src/services/litestream/commands.js.map +1 -1
- package/out/zero-cache/src/services/mutagen/mutagen.d.ts.map +1 -1
- package/out/zero-cache/src/services/mutagen/mutagen.js +4 -0
- package/out/zero-cache/src/services/mutagen/mutagen.js.map +1 -1
- package/out/zero-cache/src/services/mutagen/pusher.d.ts +45 -6
- package/out/zero-cache/src/services/mutagen/pusher.d.ts.map +1 -1
- package/out/zero-cache/src/services/mutagen/pusher.js +22 -6
- package/out/zero-cache/src/services/mutagen/pusher.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/client-handler.d.ts.map +1 -1
- package/out/zero-cache/src/services/view-syncer/client-handler.js +28 -0
- package/out/zero-cache/src/services/view-syncer/client-handler.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/cvr-store.d.ts +11 -8
- package/out/zero-cache/src/services/view-syncer/cvr-store.d.ts.map +1 -1
- package/out/zero-cache/src/services/view-syncer/cvr-store.js +14 -13
- package/out/zero-cache/src/services/view-syncer/cvr-store.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/cvr.d.ts +9 -16
- package/out/zero-cache/src/services/view-syncer/cvr.d.ts.map +1 -1
- package/out/zero-cache/src/services/view-syncer/cvr.js +71 -28
- package/out/zero-cache/src/services/view-syncer/cvr.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/row-record-cache.d.ts.map +1 -1
- package/out/zero-cache/src/services/view-syncer/row-record-cache.js +3 -0
- package/out/zero-cache/src/services/view-syncer/row-record-cache.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/schema/cvr.d.ts +3 -2
- package/out/zero-cache/src/services/view-syncer/schema/cvr.d.ts.map +1 -1
- package/out/zero-cache/src/services/view-syncer/schema/cvr.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/schema/types.d.ts +11 -11
- package/out/zero-cache/src/services/view-syncer/schema/types.d.ts.map +1 -1
- package/out/zero-cache/src/services/view-syncer/schema/types.js +3 -2
- package/out/zero-cache/src/services/view-syncer/schema/types.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/ttl-clock.d.ts +10 -0
- package/out/zero-cache/src/services/view-syncer/ttl-clock.d.ts.map +1 -0
- package/out/zero-cache/src/services/view-syncer/ttl-clock.js +9 -0
- package/out/zero-cache/src/services/view-syncer/ttl-clock.js.map +1 -0
- package/out/zero-cache/src/services/view-syncer/view-syncer.d.ts +14 -13
- 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 +81 -124
- package/out/zero-cache/src/services/view-syncer/view-syncer.js.map +1 -1
- package/out/zero-cache/src/types/processes.d.ts +6 -6
- package/out/zero-cache/src/types/processes.d.ts.map +1 -1
- package/out/zero-cache/src/types/processes.js +2 -2
- package/out/zero-cache/src/types/processes.js.map +1 -1
- package/out/zero-cache/src/types/websocket-handoff.d.ts +2 -2
- package/out/zero-cache/src/types/websocket-handoff.d.ts.map +1 -1
- package/out/zero-cache/src/types/websocket-handoff.js +4 -4
- package/out/zero-cache/src/types/websocket-handoff.js.map +1 -1
- package/out/zero-cache/src/workers/connection.d.ts +1 -1
- package/out/zero-cache/src/workers/connection.d.ts.map +1 -1
- package/out/zero-cache/src/workers/connection.js +2 -0
- package/out/zero-cache/src/workers/connection.js.map +1 -1
- package/out/zero-cache/src/workers/syncer-ws-message-handler.d.ts.map +1 -1
- package/out/zero-cache/src/workers/syncer-ws-message-handler.js +6 -1
- package/out/zero-cache/src/workers/syncer-ws-message-handler.js.map +1 -1
- package/out/zero-cache/src/workers/syncer.d.ts.map +1 -1
- package/out/zero-cache/src/workers/syncer.js +3 -1
- package/out/zero-cache/src/workers/syncer.js.map +1 -1
- package/out/zero-client/src/client/active-clients-manager.d.ts.map +1 -1
- package/out/zero-client/src/client/mutation-tracker.d.ts +7 -2
- package/out/zero-client/src/client/mutation-tracker.d.ts.map +1 -1
- package/out/zero-client/src/client/options.d.ts +9 -4
- package/out/zero-client/src/client/options.d.ts.map +1 -1
- package/out/zero-client/src/client/query-manager.d.ts.map +1 -1
- package/out/zero-client/src/client/zero-poke-handler.d.ts +6 -2
- package/out/zero-client/src/client/zero-poke-handler.d.ts.map +1 -1
- package/out/zero-client/src/client/zero.d.ts +2 -2
- package/out/zero-client/src/client/zero.d.ts.map +1 -1
- package/out/zero-client/src/mod.d.ts +1 -1
- package/out/zero-client/src/mod.d.ts.map +1 -1
- package/out/zero-protocol/src/connect.d.ts +24 -2
- package/out/zero-protocol/src/connect.d.ts.map +1 -1
- package/out/zero-protocol/src/connect.js +17 -2
- package/out/zero-protocol/src/connect.js.map +1 -1
- package/out/zero-protocol/src/down.d.ts +18 -0
- package/out/zero-protocol/src/down.d.ts.map +1 -1
- package/out/zero-protocol/src/mutations-patch.d.ts +65 -0
- package/out/zero-protocol/src/mutations-patch.d.ts.map +1 -0
- package/out/zero-protocol/src/mutations-patch.js +16 -0
- package/out/zero-protocol/src/mutations-patch.js.map +1 -0
- package/out/zero-protocol/src/poke.d.ts +36 -0
- package/out/zero-protocol/src/poke.d.ts.map +1 -1
- package/out/zero-protocol/src/poke.js +3 -0
- package/out/zero-protocol/src/poke.js.map +1 -1
- package/out/zero-protocol/src/protocol-version.d.ts +1 -1
- package/out/zero-protocol/src/protocol-version.d.ts.map +1 -1
- package/out/zero-protocol/src/protocol-version.js +4 -1
- package/out/zero-protocol/src/protocol-version.js.map +1 -1
- package/out/zero-protocol/src/push.d.ts +8 -3
- package/out/zero-protocol/src/push.d.ts.map +1 -1
- package/out/zero-protocol/src/push.js +7 -3
- package/out/zero-protocol/src/push.js.map +1 -1
- package/out/zero-protocol/src/up.d.ts +9 -1
- package/out/zero-protocol/src/up.d.ts.map +1 -1
- package/out/zero-protocol/src/up.js +2 -2
- package/out/zero-protocol/src/up.js.map +1 -1
- package/out/zero-server/src/queries/process-queries.d.ts +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/out/zero-solid/src/mod.d.ts +2 -2
- package/out/zero-solid/src/mod.d.ts.map +1 -1
- package/out/zero.js +9 -7
- package/out/zql/src/query/named.d.ts +11 -11
- package/out/zql/src/query/named.d.ts.map +1 -1
- package/out/zql/src/query/named.js +20 -2
- package/out/zql/src/query/named.js.map +1 -1
- package/out/zql/src/query/query-impl.js +1 -1
- package/out/zql/src/query/query-impl.js.map +1 -1
- package/out/zql/src/query/query.d.ts +12 -3
- package/out/zql/src/query/query.d.ts.map +1 -1
- package/out/zql/src/query/query.js.map +1 -1
- package/package.json +2 -2
- package/out/chunk-5A6QECBY.js.map +0 -7
- /package/out/{inspector-HDOYOVMS.js.map → inspector-TFZBDN6K.js.map} +0 -0
|
@@ -10,6 +10,7 @@ import { hasOwn } from "../../../../shared/src/has-own.js";
|
|
|
10
10
|
import { must } from "../../../../shared/src/must.js";
|
|
11
11
|
import { randInt } from "../../../../shared/src/rand.js";
|
|
12
12
|
import { ErrorKind } from "../../../../zero-protocol/src/error-kind.js";
|
|
13
|
+
import { clampTTL, MAX_TTL_MS } from "../../../../zql/src/query/ttl.js";
|
|
13
14
|
import { transformAndHashQuery, } from "../../auth/read-authorizer.js";
|
|
14
15
|
import {} from "../../config/zero-config.js";
|
|
15
16
|
import { CustomQueryTransformer } from "../../custom-queries/transform-query.js";
|
|
@@ -21,15 +22,13 @@ import { Subscription } from "../../types/subscription.js";
|
|
|
21
22
|
import { ZERO_VERSION_COLUMN_NAME } from "../replicator/schema/replication-state.js";
|
|
22
23
|
import { ClientHandler, startPoke, } from "./client-handler.js";
|
|
23
24
|
import { CVRStore } from "./cvr-store.js";
|
|
24
|
-
import { CVRConfigDrivenUpdater, CVRQueryDrivenUpdater, CVRUpdater,
|
|
25
|
+
import { CVRConfigDrivenUpdater, CVRQueryDrivenUpdater, CVRUpdater, nextEvictionTime, } from "./cvr.js";
|
|
25
26
|
import { PipelineDriver } from "./pipeline-driver.js";
|
|
26
27
|
import { cmpVersions, EMPTY_CVR_VERSION, versionFromString, versionString, versionToCookie, } from "./schema/types.js";
|
|
27
28
|
import { ResetPipelinesSignal } from "./snapshotter.js";
|
|
29
|
+
import { ttlClockAsNumber, ttlClockFromNumber, } from "./ttl-clock.js";
|
|
28
30
|
const tracer = trace.getTracer('view-syncer', version);
|
|
29
31
|
const DEFAULT_KEEPALIVE_MS = 5_000;
|
|
30
|
-
// We have previously said that the goal is to have 20MB on the client.
|
|
31
|
-
// If we assume each row is ~1KB, then we can have 20,000 rows.
|
|
32
|
-
const DEFAULT_MAX_ROW_COUNT = 20_000;
|
|
33
32
|
function randomID() {
|
|
34
33
|
return randInt(1, Number.MAX_SAFE_INTEGER).toString(36);
|
|
35
34
|
}
|
|
@@ -38,7 +37,14 @@ function randomID() {
|
|
|
38
37
|
* some flushes do not write to the CVR and in those cases we
|
|
39
38
|
* use a timer to update the ttlClock every minute.
|
|
40
39
|
*/
|
|
41
|
-
const TTL_CLOCK_INTERVAL = 60_000;
|
|
40
|
+
export const TTL_CLOCK_INTERVAL = 60_000;
|
|
41
|
+
/**
|
|
42
|
+
* This is some extra time we delay the TTL timer to allow for some
|
|
43
|
+
* slack in the timing of the timer. This is to allow multiple evictions
|
|
44
|
+
* to happen in a short period of time without having to wait for the
|
|
45
|
+
* next tick of the timer.
|
|
46
|
+
*/
|
|
47
|
+
export const TTL_TIMER_HYSTERESIS = 50; // ms
|
|
42
48
|
export class ViewSyncerService {
|
|
43
49
|
id;
|
|
44
50
|
#shard;
|
|
@@ -48,7 +54,7 @@ export class ViewSyncerService {
|
|
|
48
54
|
#drainCoordinator;
|
|
49
55
|
#keepaliveMs;
|
|
50
56
|
#slowHydrateThreshold;
|
|
51
|
-
#
|
|
57
|
+
#queryConfig;
|
|
52
58
|
// The ViewSyncerService is only started in response to a connection,
|
|
53
59
|
// so #lastConnectTime is always initialized to now(). This is necessary
|
|
54
60
|
// to handle race conditions in which, e.g. the replica is ready and the
|
|
@@ -60,7 +66,7 @@ export class ViewSyncerService {
|
|
|
60
66
|
* The TTL clock is used to determine the time at which queries are considered
|
|
61
67
|
* expired.
|
|
62
68
|
*/
|
|
63
|
-
#ttlClock
|
|
69
|
+
#ttlClock;
|
|
64
70
|
/**
|
|
65
71
|
* The base time for the TTL clock. This is used to compute the current TTL
|
|
66
72
|
* clock value. The first time a connection is made, this is set to the
|
|
@@ -73,9 +79,6 @@ export class ViewSyncerService {
|
|
|
73
79
|
* time that has passed since the last time we set it.
|
|
74
80
|
*/
|
|
75
81
|
#ttlClockBase = Date.now();
|
|
76
|
-
get ttlClockBase() {
|
|
77
|
-
return this.#ttlClockBase;
|
|
78
|
-
}
|
|
79
82
|
/**
|
|
80
83
|
* We update the ttlClock every minute to ensure that it is not too much
|
|
81
84
|
* out of sync with the current time.
|
|
@@ -95,25 +98,13 @@ export class ViewSyncerService {
|
|
|
95
98
|
// auth and cookie headers directly
|
|
96
99
|
#authData;
|
|
97
100
|
#httpCookie;
|
|
98
|
-
/**
|
|
99
|
-
* The {@linkcode maxRowCount} is used for the eviction of inactive queries.
|
|
100
|
-
* An inactive query is a query that is no longer desired but is kept alive
|
|
101
|
-
* due to its TTL. When the number of rows in the CVR exceeds
|
|
102
|
-
* {@linkcode maxRowCount} we keep removing inactive queries (even if they are
|
|
103
|
-
* not expired yet) until the actual row count is below the max row count.
|
|
104
|
-
*
|
|
105
|
-
* There is no guarantee that the number of rows in the CVR will be below this
|
|
106
|
-
* if there are active queries that have a lot of rows.
|
|
107
|
-
*/
|
|
108
|
-
maxRowCount;
|
|
109
101
|
#expiredQueriesTimer = 0;
|
|
110
|
-
#nextExpiredQueryTime = 0;
|
|
111
102
|
#setTimeout;
|
|
112
103
|
#customQueryTransformer;
|
|
113
|
-
constructor(pullConfig, lc, shard, taskID, clientGroupID, db, pipelineDriver, versionChanges, drainCoordinator, slowHydrateThreshold, keepaliveMs = DEFAULT_KEEPALIVE_MS,
|
|
104
|
+
constructor(pullConfig, lc, shard, taskID, clientGroupID, db, pipelineDriver, versionChanges, drainCoordinator, slowHydrateThreshold, keepaliveMs = DEFAULT_KEEPALIVE_MS, setTimeoutFn = setTimeout.bind(globalThis)) {
|
|
114
105
|
this.id = clientGroupID;
|
|
115
106
|
this.#shard = shard;
|
|
116
|
-
this.#
|
|
107
|
+
this.#queryConfig = pullConfig;
|
|
117
108
|
this.#lc = lc;
|
|
118
109
|
this.#pipelines = pipelineDriver;
|
|
119
110
|
this.#stateChanges = versionChanges;
|
|
@@ -124,7 +115,6 @@ export class ViewSyncerService {
|
|
|
124
115
|
// On failure, cancel the #stateChanges subscription. The run()
|
|
125
116
|
// loop will then await #cvrStore.flushed() which rejects if necessary.
|
|
126
117
|
() => this.#stateChanges.cancel());
|
|
127
|
-
this.maxRowCount = maxRowCount;
|
|
128
118
|
this.#setTimeout = setTimeoutFn;
|
|
129
119
|
if (pullConfig.url) {
|
|
130
120
|
this.#customQueryTransformer = new CustomQueryTransformer({
|
|
@@ -155,6 +145,16 @@ export class ViewSyncerService {
|
|
|
155
145
|
if (!this.#cvr) {
|
|
156
146
|
this.#lc.debug?.('loading CVR');
|
|
157
147
|
this.#cvr = await this.#cvrStore.load(lc, this.#lastConnectTime);
|
|
148
|
+
this.#ttlClock = this.#cvr.ttlClock;
|
|
149
|
+
this.#ttlClockBase = Date.now();
|
|
150
|
+
}
|
|
151
|
+
else {
|
|
152
|
+
// Make sure the CVR ttlClock is up to date.
|
|
153
|
+
const now = Date.now();
|
|
154
|
+
this.#cvr = {
|
|
155
|
+
...this.#cvr,
|
|
156
|
+
ttlClock: this.#getTTLClock(now),
|
|
157
|
+
};
|
|
158
158
|
}
|
|
159
159
|
try {
|
|
160
160
|
await fn(lc, this.#cvr);
|
|
@@ -231,16 +231,17 @@ export class ViewSyncerService {
|
|
|
231
231
|
}
|
|
232
232
|
// must be called from within #lock
|
|
233
233
|
#removeExpiredQueries = async (lc, cvr) => {
|
|
234
|
-
|
|
235
|
-
const ttlClock = this.#getTTLClock(now);
|
|
236
|
-
if (hasExpiredQueries(cvr, ttlClock)) {
|
|
234
|
+
if (hasExpiredQueries(cvr)) {
|
|
237
235
|
lc = lc.withContext('method', '#removeExpiredQueries');
|
|
238
|
-
lc.
|
|
236
|
+
lc.debug?.('Queries have expired');
|
|
239
237
|
// #syncQueryPipelineSet() will remove the expired queries.
|
|
240
238
|
await this.#syncQueryPipelineSet(lc, cvr);
|
|
241
239
|
this.#pipelinesSynced = true;
|
|
242
|
-
this.#scheduleExpireEviction(lc, cvr);
|
|
243
240
|
}
|
|
241
|
+
// Even if we have expired queries, we still need to schedule next eviction
|
|
242
|
+
// since there might be inactivated queries that need to be expired queries
|
|
243
|
+
// in the future.
|
|
244
|
+
this.#scheduleExpireEviction(lc, cvr);
|
|
244
245
|
};
|
|
245
246
|
#totalHydrationTimeMs() {
|
|
246
247
|
return this.#pipelines.totalHydrationTimeMs();
|
|
@@ -290,7 +291,7 @@ export class ViewSyncerService {
|
|
|
290
291
|
// If no clients have connected while waiting for the row flush, shutdown.
|
|
291
292
|
return this.#clients.size === 0;
|
|
292
293
|
}
|
|
293
|
-
#
|
|
294
|
+
#deleteClientDueToDisconnect(clientID, client) {
|
|
294
295
|
// Note: It is okay to delete / cleanup clients without acquiring the lock.
|
|
295
296
|
// In fact, it is important to do so in order to guarantee that idle cleanup
|
|
296
297
|
// is performed in a timely manner, regardless of the amount of work
|
|
@@ -299,7 +300,11 @@ export class ViewSyncerService {
|
|
|
299
300
|
if (c === client) {
|
|
300
301
|
this.#clients.delete(clientID);
|
|
301
302
|
if (this.#clients.size === 0) {
|
|
302
|
-
|
|
303
|
+
// It is possible to delete a client before we read the ttl clock from
|
|
304
|
+
// the CVR.
|
|
305
|
+
if (this.#ttlClock !== undefined) {
|
|
306
|
+
this.#updateTTLClockInCVRWithoutLock(this.#lc);
|
|
307
|
+
}
|
|
303
308
|
this.#stopExpireTimer();
|
|
304
309
|
this.#scheduleShutdown();
|
|
305
310
|
}
|
|
@@ -309,7 +314,6 @@ export class ViewSyncerService {
|
|
|
309
314
|
this.#lc.debug?.('Stopping expired queries timer');
|
|
310
315
|
clearTimeout(this.#expiredQueriesTimer);
|
|
311
316
|
this.#expiredQueriesTimer = 0;
|
|
312
|
-
this.#nextExpiredQueryTime = 0;
|
|
313
317
|
}
|
|
314
318
|
initConnection(ctx, initConnectionMessage) {
|
|
315
319
|
this.#lc.debug?.('viewSyncer.initConnection');
|
|
@@ -327,7 +331,7 @@ export class ViewSyncerService {
|
|
|
327
331
|
err
|
|
328
332
|
? lc[getLogLevel(err)]?.(`client closed with error`, err)
|
|
329
333
|
: lc.info?.('client closed');
|
|
330
|
-
this.#
|
|
334
|
+
this.#deleteClientDueToDisconnect(clientID, newClient);
|
|
331
335
|
},
|
|
332
336
|
});
|
|
333
337
|
if (this.#clients.size === 0) {
|
|
@@ -336,15 +340,6 @@ export class ViewSyncerService {
|
|
|
336
340
|
// subscription is returned immediately.
|
|
337
341
|
const now = Date.now();
|
|
338
342
|
this.#ttlClockBase = now;
|
|
339
|
-
// Get the TTL clock from the CVR store, or initialize it to now.
|
|
340
|
-
this.#cvrStore
|
|
341
|
-
.getTTLClock()
|
|
342
|
-
.then(ttlClock => {
|
|
343
|
-
this.#ttlClock = ttlClock ?? now;
|
|
344
|
-
})
|
|
345
|
-
.catch(e => {
|
|
346
|
-
this.#lc.error?.('failed to get TTL clock', e);
|
|
347
|
-
});
|
|
348
343
|
}
|
|
349
344
|
const newClient = new ClientHandler(lc, this.id, clientID, wsID, this.#shard, baseCookie, schemaVersion, downstream);
|
|
350
345
|
this.#clients.get(clientID)?.close(`replaced by wsID: ${wsID}`);
|
|
@@ -372,7 +367,9 @@ export class ViewSyncerService {
|
|
|
372
367
|
#getTTLClock(now) {
|
|
373
368
|
// We will update ttlClock with delta from the ttlClockBase to the current time.
|
|
374
369
|
const delta = now - this.#ttlClockBase;
|
|
375
|
-
|
|
370
|
+
assert(this.#ttlClock !== undefined);
|
|
371
|
+
const ttlClock = ttlClockFromNumber(ttlClockAsNumber(this.#ttlClock) + delta);
|
|
372
|
+
assert(ttlClockAsNumber(ttlClock) <= now);
|
|
376
373
|
this.#ttlClock = ttlClock;
|
|
377
374
|
this.#ttlClockBase = now;
|
|
378
375
|
return ttlClock;
|
|
@@ -493,6 +490,7 @@ export class ViewSyncerService {
|
|
|
493
490
|
const deletedClientIDs = [];
|
|
494
491
|
const deletedClientGroupIDs = [];
|
|
495
492
|
cvr = await this.#updateCVRConfig(lc, cvr, clientID, updater => {
|
|
493
|
+
const { ttlClock } = cvr;
|
|
496
494
|
const patches = [];
|
|
497
495
|
if (clientSchema) {
|
|
498
496
|
updater.setClientSchema(lc, clientSchema);
|
|
@@ -500,14 +498,13 @@ export class ViewSyncerService {
|
|
|
500
498
|
// Apply requested patches.
|
|
501
499
|
lc.debug?.(`applying ${desiredQueriesPatch?.length} query patches`);
|
|
502
500
|
if (desiredQueriesPatch?.length) {
|
|
503
|
-
const now = Date.now();
|
|
504
501
|
for (const patch of desiredQueriesPatch) {
|
|
505
502
|
switch (patch.op) {
|
|
506
503
|
case 'put':
|
|
507
504
|
patches.push(...updater.putDesiredQueries(clientID, [patch]));
|
|
508
505
|
break;
|
|
509
506
|
case 'del':
|
|
510
|
-
patches.push(...updater.markDesiredQueriesAsInactive(clientID, [patch.hash],
|
|
507
|
+
patches.push(...updater.markDesiredQueriesAsInactive(clientID, [patch.hash], ttlClock));
|
|
511
508
|
break;
|
|
512
509
|
case 'clear':
|
|
513
510
|
patches.push(...updater.clearDesiredQueries(clientID));
|
|
@@ -533,7 +530,7 @@ export class ViewSyncerService {
|
|
|
533
530
|
}
|
|
534
531
|
}
|
|
535
532
|
for (const cid of clientIDsToDelete) {
|
|
536
|
-
const patchesDueToClient = updater.deleteClient(cid);
|
|
533
|
+
const patchesDueToClient = updater.deleteClient(cid, ttlClock);
|
|
537
534
|
patches.push(...patchesDueToClient);
|
|
538
535
|
deletedClientIDs.push(cid);
|
|
539
536
|
}
|
|
@@ -554,35 +551,32 @@ export class ViewSyncerService {
|
|
|
554
551
|
await Promise.allSettled(clients.map(client => client.sendDeleteClients(lc, deletedClientIDs, deletedClientGroupIDs)));
|
|
555
552
|
}
|
|
556
553
|
this.#scheduleExpireEviction(lc, cvr);
|
|
557
|
-
await this.#evictInactiveQueries(lc, cvr);
|
|
558
554
|
});
|
|
559
555
|
#scheduleExpireEviction(lc, cvr) {
|
|
556
|
+
const { ttlClock } = cvr;
|
|
557
|
+
this.#stopExpireTimer();
|
|
560
558
|
// first see if there is any inactive query with a ttl.
|
|
561
559
|
const next = nextEvictionTime(cvr);
|
|
562
560
|
if (next === undefined) {
|
|
563
561
|
lc.debug?.('no inactive queries with ttl');
|
|
564
562
|
// no inactive queries with a ttl. Cancel existing timeout if any.
|
|
565
|
-
this.#stopExpireTimer();
|
|
566
|
-
return;
|
|
567
|
-
}
|
|
568
|
-
if (this.#nextExpiredQueryTime === next) {
|
|
569
|
-
lc.debug?.('eviction timer already scheduled');
|
|
570
563
|
return;
|
|
571
564
|
}
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
565
|
+
// It is common for many queries to be evicted close to the same time, so
|
|
566
|
+
// we add a small delay so we can collapse multiple evictions into a
|
|
567
|
+
// single timer. However, don't add the delay if we're already at the
|
|
568
|
+
// maximum timer limit, as that's not about collapsing.
|
|
569
|
+
const delay = Math.max(TTL_TIMER_HYSTERESIS, Math.min(ttlClockAsNumber(next) -
|
|
570
|
+
ttlClockAsNumber(ttlClock) +
|
|
571
|
+
TTL_TIMER_HYSTERESIS, MAX_TTL_MS));
|
|
572
|
+
lc.debug?.('Scheduling eviction timer to run in ', delay, 'ms');
|
|
573
|
+
this.#expiredQueriesTimer = this.#setTimeout(() => {
|
|
574
|
+
this.#expiredQueriesTimer = 0;
|
|
575
|
+
this.#runInLockWithCVR((lc, cvr) => this.#removeExpiredQueries(lc, cvr)).catch(e =>
|
|
576
|
+
// If an error occurs (e.g. ownership change), propagate the error
|
|
577
|
+
// to the main run() loop via the #stateChanges Subscription.
|
|
578
|
+
this.#stateChanges.fail(e));
|
|
579
|
+
}, delay);
|
|
586
580
|
}
|
|
587
581
|
/**
|
|
588
582
|
* Adds and hydrates pipelines for queries whose results are already
|
|
@@ -628,9 +622,9 @@ export class ViewSyncerService {
|
|
|
628
622
|
}
|
|
629
623
|
if (this.#customQueryTransformer && customQueries.length > 0) {
|
|
630
624
|
const transformedCustomQueries = await this.#customQueryTransformer.transform({
|
|
631
|
-
apiKey: this.#
|
|
625
|
+
apiKey: this.#queryConfig.apiKey,
|
|
632
626
|
token: this.#authData?.raw,
|
|
633
|
-
cookie: this.#
|
|
627
|
+
cookie: this.#queryConfig.forwardCookies
|
|
634
628
|
? this.#httpCookie
|
|
635
629
|
: undefined,
|
|
636
630
|
}, customQueries);
|
|
@@ -695,6 +689,10 @@ export class ViewSyncerService {
|
|
|
695
689
|
const hydratedQueries = this.#pipelines.addedQueries();
|
|
696
690
|
// Convert queries to their transformed ast's and hashes
|
|
697
691
|
const hashToIDs = new Map();
|
|
692
|
+
if (this.#ttlClock === undefined) {
|
|
693
|
+
// Get it from the CVR or initialize it to now.
|
|
694
|
+
this.#ttlClock = cvr.ttlClock;
|
|
695
|
+
}
|
|
698
696
|
const now = Date.now();
|
|
699
697
|
const ttlClock = this.#getTTLClock(now);
|
|
700
698
|
// group cvr queries into:
|
|
@@ -733,7 +731,7 @@ export class ViewSyncerService {
|
|
|
733
731
|
}
|
|
734
732
|
if (this.#customQueryTransformer && customQueries.size > 0) {
|
|
735
733
|
const transformedCustomQueries = await this.#customQueryTransformer.transform({
|
|
736
|
-
apiKey: this.#
|
|
734
|
+
apiKey: this.#queryConfig.apiKey,
|
|
737
735
|
token: this.#authData?.raw,
|
|
738
736
|
cookie: this.#httpCookie,
|
|
739
737
|
}, customQueries.values());
|
|
@@ -795,7 +793,7 @@ export class ViewSyncerService {
|
|
|
795
793
|
assert(addQueries.length > 0 ||
|
|
796
794
|
removeQueries.length > 0 ||
|
|
797
795
|
unhydrateQueries.length > 0);
|
|
798
|
-
const start =
|
|
796
|
+
const start = performance.now();
|
|
799
797
|
const stateVersion = this.#pipelines.currentVersion();
|
|
800
798
|
lc = lc.withContext('stateVersion', stateVersion);
|
|
801
799
|
lc.info?.(`hydrating ${addQueries.length} queries`);
|
|
@@ -850,7 +848,7 @@ export class ViewSyncerService {
|
|
|
850
848
|
await this.#catchupClients(lc, cvr, finalVersion, addQueries.map(q => q.id), pokers);
|
|
851
849
|
// Signal clients to commit.
|
|
852
850
|
await pokers.end(finalVersion);
|
|
853
|
-
const wallTime =
|
|
851
|
+
const wallTime = performance.now() - start;
|
|
854
852
|
lc.info?.(`finished processing queries (process: ${totalProcessTime} ms, wall: ${wallTime} ms)`);
|
|
855
853
|
});
|
|
856
854
|
}
|
|
@@ -924,11 +922,11 @@ export class ViewSyncerService {
|
|
|
924
922
|
}
|
|
925
923
|
#processChanges(lc, timer, changes, updater, pokers, hashToIDs) {
|
|
926
924
|
return startAsyncSpan(tracer, 'vs.#processChanges', async () => {
|
|
927
|
-
const start =
|
|
925
|
+
const start = performance.now();
|
|
928
926
|
const rows = new CustomKeyMap(rowIDString);
|
|
929
927
|
let total = 0;
|
|
930
928
|
const processBatch = () => startAsyncSpan(tracer, 'processBatch', async () => {
|
|
931
|
-
const wallElapsed =
|
|
929
|
+
const wallElapsed = performance.now() - start;
|
|
932
930
|
total += rows.size;
|
|
933
931
|
lc.debug?.(`processing ${rows.size} (of ${total}) rows (${wallElapsed} ms)`);
|
|
934
932
|
const patches = await updater.received(lc, rows);
|
|
@@ -1027,65 +1025,23 @@ export class ViewSyncerService {
|
|
|
1027
1025
|
const finalVersion = this.#cvr.version;
|
|
1028
1026
|
// Signal clients to commit.
|
|
1029
1027
|
await pokers.end(finalVersion);
|
|
1030
|
-
await this.#evictInactiveQueries(lc, this.#cvr);
|
|
1031
1028
|
const elapsed = performance.now() - start;
|
|
1032
1029
|
lc.info?.(`finished processing advancement of ${numChanges} changes (${elapsed} ms)`);
|
|
1033
1030
|
histograms.transactionAdvanceTime().record(elapsed);
|
|
1034
1031
|
return 'success';
|
|
1035
1032
|
});
|
|
1036
1033
|
}
|
|
1037
|
-
// This must be called from within the #lock.
|
|
1038
|
-
#evictInactiveQueries(lc, cvr) {
|
|
1039
|
-
lc = lc.withContext('method', '#evictInactiveQueries');
|
|
1040
|
-
return startAsyncSpan(tracer, 'vs.#evictInactiveQueries', async () => {
|
|
1041
|
-
const { rowCount: rowCountBeforeEvictions } = this.#cvrStore;
|
|
1042
|
-
if (rowCountBeforeEvictions <= this.maxRowCount) {
|
|
1043
|
-
lc.debug?.(`rowCount: ${rowCountBeforeEvictions} <= maxRowCount: ${this.maxRowCount}`);
|
|
1044
|
-
return;
|
|
1045
|
-
}
|
|
1046
|
-
lc.info?.(`Trying to evict inactive queries, rowCount: ${rowCountBeforeEvictions} > maxRowCount: ${this.maxRowCount}`);
|
|
1047
|
-
const inactiveQueries = getInactiveQueries(cvr);
|
|
1048
|
-
if (!inactiveQueries.length) {
|
|
1049
|
-
lc.info?.('No inactive queries to evict');
|
|
1050
|
-
return;
|
|
1051
|
-
}
|
|
1052
|
-
const hashToIDs = createHashToIDs(cvr);
|
|
1053
|
-
for (const inactiveQuery of inactiveQueries) {
|
|
1054
|
-
const { hash } = inactiveQuery;
|
|
1055
|
-
const q = cvr.queries[hash];
|
|
1056
|
-
assert(q, 'query not found in CVR');
|
|
1057
|
-
assert(q.type !== 'internal', 'internal queries should not be evicted');
|
|
1058
|
-
const rowCountBeforeCurrentEviction = this.#cvrStore.rowCount;
|
|
1059
|
-
await this.#addAndRemoveQueries(lc, cvr, [], [
|
|
1060
|
-
{
|
|
1061
|
-
id: hash,
|
|
1062
|
-
transformationHash: must(q.transformationHash),
|
|
1063
|
-
},
|
|
1064
|
-
], [], hashToIDs);
|
|
1065
|
-
lc.debug?.('Evicted', hash, 'Reduced rowCount from', rowCountBeforeCurrentEviction, 'to', this.#cvrStore.rowCount);
|
|
1066
|
-
if (this.#cvrStore.rowCount <= this.maxRowCount) {
|
|
1067
|
-
lc.info?.('Evicted', hash, 'Reduced rowCount from', rowCountBeforeEvictions, 'to', this.#cvrStore.rowCount);
|
|
1068
|
-
break;
|
|
1069
|
-
}
|
|
1070
|
-
// We continue with the updated/current state of the CVR.
|
|
1071
|
-
cvr = must(this.#cvr);
|
|
1072
|
-
}
|
|
1073
|
-
const cvrVersion = must(this.#cvr).version;
|
|
1074
|
-
const dbVersion = this.#pipelines.currentVersion();
|
|
1075
|
-
assert(cvrVersion.stateVersion === dbVersion, `CVR@${versionString(cvrVersion)}" does not match DB@${dbVersion}`);
|
|
1076
|
-
});
|
|
1077
|
-
}
|
|
1078
1034
|
inspect(context, msg) {
|
|
1079
1035
|
return this.#runInLockForClient(context, msg, this.#handleInspect);
|
|
1080
1036
|
}
|
|
1081
1037
|
// eslint-disable-next-line require-await
|
|
1082
|
-
#handleInspect = async (lc, clientID, body,
|
|
1038
|
+
#handleInspect = async (lc, clientID, body, cvr) => {
|
|
1083
1039
|
const client = must(this.#clients.get(clientID));
|
|
1084
1040
|
body.op;
|
|
1085
1041
|
client.sendInspectResponse(lc, {
|
|
1086
1042
|
op: 'queries',
|
|
1087
1043
|
id: body.id,
|
|
1088
|
-
value: await this.#cvrStore.inspectQueries(lc, body.clientID),
|
|
1044
|
+
value: await this.#cvrStore.inspectQueries(lc, cvr.ttlClock, body.clientID),
|
|
1089
1045
|
});
|
|
1090
1046
|
};
|
|
1091
1047
|
stop() {
|
|
@@ -1095,9 +1051,7 @@ export class ViewSyncerService {
|
|
|
1095
1051
|
}
|
|
1096
1052
|
#cleanup(err) {
|
|
1097
1053
|
this.#stopTTLClockInterval();
|
|
1098
|
-
|
|
1099
|
-
this.#expiredQueriesTimer = 0;
|
|
1100
|
-
this.#nextExpiredQueryTime = 0;
|
|
1054
|
+
this.#stopExpireTimer();
|
|
1101
1055
|
this.#pipelines.destroy();
|
|
1102
1056
|
for (const client of this.#clients.values()) {
|
|
1103
1057
|
if (err) {
|
|
@@ -1204,17 +1158,20 @@ function expired(ttlClock, q) {
|
|
|
1204
1158
|
for (const clientID in clientState) {
|
|
1205
1159
|
if (hasOwn(clientState, clientID)) {
|
|
1206
1160
|
const { ttl, inactivatedAt } = clientState[clientID];
|
|
1207
|
-
if (
|
|
1161
|
+
if (inactivatedAt === undefined) {
|
|
1208
1162
|
return false;
|
|
1209
1163
|
}
|
|
1210
|
-
|
|
1164
|
+
const clampedTTL = clampTTL(ttl);
|
|
1165
|
+
if (ttlClockAsNumber(inactivatedAt) + clampedTTL >
|
|
1166
|
+
ttlClockAsNumber(ttlClock)) {
|
|
1211
1167
|
return false;
|
|
1212
1168
|
}
|
|
1213
1169
|
}
|
|
1214
1170
|
}
|
|
1215
1171
|
return true;
|
|
1216
1172
|
}
|
|
1217
|
-
function hasExpiredQueries(cvr
|
|
1173
|
+
function hasExpiredQueries(cvr) {
|
|
1174
|
+
const { ttlClock } = cvr;
|
|
1218
1175
|
for (const q of Object.values(cvr.queries)) {
|
|
1219
1176
|
if (expired(ttlClock, q)) {
|
|
1220
1177
|
return true;
|