@rocicorp/zero 1.11.0-canary.3 → 1.11.0-canary.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/out/analyze-query/src/analyze-cli.js +2 -2
- package/out/analyze-query/src/analyze-cli.js.map +1 -1
- 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 +4 -0
- package/out/zero-cache/src/config/zero-config.d.ts.map +1 -1
- package/out/zero-cache/src/config/zero-config.js +12 -0
- package/out/zero-cache/src/config/zero-config.js.map +1 -1
- package/out/zero-cache/src/services/change-source/custom/change-source.js +2 -2
- package/out/zero-cache/src/services/change-source/custom/change-source.js.map +1 -1
- package/out/zero-cache/src/services/change-streamer/change-streamer-http.js +3 -3
- package/out/zero-cache/src/services/change-streamer/change-streamer-http.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/cvr.d.ts +28 -0
- package/out/zero-cache/src/services/view-syncer/cvr.d.ts.map +1 -1
- package/out/zero-cache/src/services/view-syncer/cvr.js +74 -1
- package/out/zero-cache/src/services/view-syncer/cvr.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/hydration-budget.d.ts +33 -0
- package/out/zero-cache/src/services/view-syncer/hydration-budget.d.ts.map +1 -0
- package/out/zero-cache/src/services/view-syncer/hydration-budget.js +62 -0
- package/out/zero-cache/src/services/view-syncer/hydration-budget.js.map +1 -0
- package/out/zero-cache/src/services/view-syncer/view-syncer.d.ts +11 -1
- 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 +162 -55
- package/out/zero-cache/src/services/view-syncer/view-syncer.js.map +1 -1
- package/out/zero-cache/src/workers/connection.js +3 -3
- package/out/zero-cache/src/workers/connection.js.map +1 -1
- package/out/zero-client/src/client/version.js +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"view-syncer.d.ts","sourceRoot":"","sources":["../../../../../../zero-cache/src/services/view-syncer/view-syncer.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,kBAAkB,CAAC;AAcjD,OAAO,KAAK,EAAC,2BAA2B,EAAC,MAAM,yDAAyD,CAAC;AAKzG,OAAO,KAAK,EAEV,qBAAqB,EACtB,MAAM,0CAA0C,CAAC;AAElD,OAAO,KAAK,EAAC,oBAAoB,EAAC,MAAM,iDAAiD,CAAC;AAQ1F,OAAO,KAAK,EAEV,wBAAwB,EACzB,MAAM,6CAA6C,CAAC;AACrD,OAAO,KAAK,EAAC,iBAAiB,EAAC,MAAM,8CAA8C,CAAC;AAGpF,OAAO,EAAkB,KAAK,IAAI,EAAC,MAAM,oBAAoB,CAAC;AAK9D,OAAO,KAAK,EAAC,oBAAoB,EAAC,MAAM,2BAA2B,CAAC;AACpE,OAAO,KAAK,EACV,sBAAsB,EAEvB,MAAM,yCAAyC,CAAC;
|
|
1
|
+
{"version":3,"file":"view-syncer.d.ts","sourceRoot":"","sources":["../../../../../../zero-cache/src/services/view-syncer/view-syncer.ts"],"names":[],"mappings":"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"}
|
|
@@ -10,7 +10,7 @@ import { manualSpan, startAsyncSpan, startSpan } from "../../../../otel/src/span
|
|
|
10
10
|
import { ZERO_VERSION_COLUMN_NAME } from "../replicator/schema/constants.js";
|
|
11
11
|
import { transformAndHashQuery } from "../../auth/read-authorizer.js";
|
|
12
12
|
import { randInt } from "../../../../shared/src/rand.js";
|
|
13
|
-
import { getOrCreateCounter, getOrCreateLatencyHistogram, getOrCreateNativeHistogram, getOrCreateUpDownCounter } from "../../observability/metrics.js";
|
|
13
|
+
import { getOrCreateCounter, getOrCreateLatencyHistogram, getOrCreateNativeHistogram, getOrCreateUpDownCounter, getOrCreateValueHistogram } from "../../observability/metrics.js";
|
|
14
14
|
import { rowIDString } from "../../types/row-key.js";
|
|
15
15
|
import "../replicator/schema/replication-state.js";
|
|
16
16
|
import { parseSignature } from "./row-set-signature.js";
|
|
@@ -25,8 +25,9 @@ import { isAuthErrorBody } from "../../auth/auth.js";
|
|
|
25
25
|
import { ClientHandler, startPoke } from "./client-handler.js";
|
|
26
26
|
import { tracer } from "./tracer.js";
|
|
27
27
|
import { CVRStore, ClientNotFoundError } from "./cvr-store.js";
|
|
28
|
-
import { CVRConfigDrivenUpdater, CVRQueryDrivenUpdater, nextEvictionTime } from "./cvr.js";
|
|
28
|
+
import { CVRConfigDrivenUpdater, CVRQueryDrivenUpdater, classifyQueriesForHydration, nextEvictionTime } from "./cvr.js";
|
|
29
29
|
import { E2EServingLagTracker } from "./e2e-serving-lag.js";
|
|
30
|
+
import { HydrationBudget } from "./hydration-budget.js";
|
|
30
31
|
import { handleInspect } from "./inspect-handler.js";
|
|
31
32
|
import { QueryCoveringIndex } from "./query-covering.js";
|
|
32
33
|
import { resolver } from "@rocicorp/resolver";
|
|
@@ -110,6 +111,7 @@ var ViewSyncerService = class {
|
|
|
110
111
|
#expiredQueriesTimer = 0;
|
|
111
112
|
#authMaintenanceTimer = 0;
|
|
112
113
|
#setTimeout;
|
|
114
|
+
#now;
|
|
113
115
|
#customQueryTransformer;
|
|
114
116
|
#queryReplacements = /* @__PURE__ */ new Map();
|
|
115
117
|
#activeClients = getOrCreateUpDownCounter("sync", "active-clients", "Number of active sync clients");
|
|
@@ -136,10 +138,56 @@ var ViewSyncerService = class {
|
|
|
136
138
|
#pipelineResets = getOrCreateCounter("sync", "pipeline-resets", "Number of pipeline resets");
|
|
137
139
|
#rowSetSignatureDrifts = getOrCreateCounter("sync", "query.row-set-signature-drifts", "Number of times re-hydration of an unchanged query produced a different row-set signature than what is stored in the CVR (forcing a configVersion bump and full re-execution). Expected to be near-zero in steady state; persistent non-zero values indicate non-deterministic query execution (e.g. Cap operator picking different N-row subsets).");
|
|
138
140
|
#sameHashRehydrationVersionBumps = getOrCreateCounter("sync", "query.same-hash-rehydrations-forced-bump", "Number of times query-set reconciliation forced a configVersion bump for already-gotten same-transformation-hash query rehydration because trackQueries would not otherwise bump. Expected to be near-zero; non-zero values indicate pipeline/CVR row-set drift reached query-set reconciliation.");
|
|
141
|
+
#hydrationBudgetExhaustions = getOrCreateCounter("sync", "hydration_budget_exhaustions", {
|
|
142
|
+
description: "Number of hydration passes that exhaust their budget.",
|
|
143
|
+
unit: "{pass}"
|
|
144
|
+
});
|
|
145
|
+
#hydrationBudgetEvictions = getOrCreateCounter("sync", "hydration_budget_evictions", {
|
|
146
|
+
description: "Number of inactive queries removed after hydration budget exhaustion.",
|
|
147
|
+
unit: "{query}"
|
|
148
|
+
});
|
|
149
|
+
#hydrationBudgetElapsed = getOrCreateValueHistogram("sync", "hydration_budget_elapsed", {
|
|
150
|
+
description: "Elapsed milliseconds when optional query hydration stopped.",
|
|
151
|
+
unit: "ms",
|
|
152
|
+
bucketBoundaries: [
|
|
153
|
+
1,
|
|
154
|
+
2,
|
|
155
|
+
5,
|
|
156
|
+
10,
|
|
157
|
+
20,
|
|
158
|
+
50,
|
|
159
|
+
100,
|
|
160
|
+
200,
|
|
161
|
+
500,
|
|
162
|
+
1e3,
|
|
163
|
+
5e3
|
|
164
|
+
]
|
|
165
|
+
});
|
|
166
|
+
#hydrationBudgetOvershoot = getOrCreateValueHistogram("sync", "hydration_budget_overshoot", {
|
|
167
|
+
description: "Milliseconds elapsed beyond the configured hydration budget.",
|
|
168
|
+
unit: "ms",
|
|
169
|
+
bucketBoundaries: [
|
|
170
|
+
0,
|
|
171
|
+
1,
|
|
172
|
+
2,
|
|
173
|
+
5,
|
|
174
|
+
10,
|
|
175
|
+
20,
|
|
176
|
+
50,
|
|
177
|
+
100,
|
|
178
|
+
200,
|
|
179
|
+
500,
|
|
180
|
+
1e3
|
|
181
|
+
]
|
|
182
|
+
});
|
|
183
|
+
#queryEvictions = getOrCreateCounter("sync", "query_evictions", {
|
|
184
|
+
description: "Number of inactive queries evicted, grouped by reason.",
|
|
185
|
+
unit: "{query}"
|
|
186
|
+
});
|
|
139
187
|
#inspectorDelegate;
|
|
140
188
|
#config;
|
|
141
189
|
#runPriorityOp;
|
|
142
|
-
constructor(config, lc, shard, taskID, clientGroupID, cvrDb, pipelineDriver, versionChanges, drainCoordinator, slowHydrateThreshold, inspectorDelegate, connContextManager, customQueryTransformer, runPriorityOp, keepaliveMs = DEFAULT_KEEPALIVE_MS, setTimeoutFn = setTimeout.bind(globalThis)) {
|
|
190
|
+
constructor(config, lc, shard, taskID, clientGroupID, cvrDb, pipelineDriver, versionChanges, drainCoordinator, slowHydrateThreshold, inspectorDelegate, connContextManager, customQueryTransformer, runPriorityOp, keepaliveMs = DEFAULT_KEEPALIVE_MS, setTimeoutFn = setTimeout.bind(globalThis), now = performance.now.bind(performance)) {
|
|
143
191
|
this.#config = config;
|
|
144
192
|
this.id = clientGroupID;
|
|
145
193
|
this.connContextManager = connContextManager;
|
|
@@ -154,6 +202,7 @@ var ViewSyncerService = class {
|
|
|
154
202
|
this.#customQueryTransformer = customQueryTransformer;
|
|
155
203
|
this.#cvrStore = new CVRStore(lc, cvrDb, shard, taskID, clientGroupID, () => this.#stateChanges.cancel());
|
|
156
204
|
this.#setTimeout = setTimeoutFn;
|
|
205
|
+
this.#now = now;
|
|
157
206
|
this.#runPriorityOp = runPriorityOp;
|
|
158
207
|
this.keepalive();
|
|
159
208
|
}
|
|
@@ -284,8 +333,13 @@ var ViewSyncerService = class {
|
|
|
284
333
|
return;
|
|
285
334
|
}
|
|
286
335
|
lc.info?.(`init pipelines@${version} (cvr@${cvrVer})`);
|
|
287
|
-
const
|
|
288
|
-
|
|
336
|
+
const hydrationBudget = new HydrationBudget(this.#config.viewSyncerHydrationBudgetMs ?? 0, this.#now);
|
|
337
|
+
const hydrationPassStats = {
|
|
338
|
+
activeHydratedQueries: 0,
|
|
339
|
+
inactiveHydratedQueries: 0
|
|
340
|
+
};
|
|
341
|
+
const driftedQueryIDs = await this.#hydrateUnchangedQueries(lc, cvr, connCtx, hydrationPassStats, hydrationBudget);
|
|
342
|
+
await this.#syncQueryPipelineSet(lc, cvr, "missing", connCtx, driftedQueryIDs, hydrationBudget, hydrationPassStats);
|
|
289
343
|
this.#pipelinesHydrated = true;
|
|
290
344
|
this.connContextManager.setSharedRetransformReady(true);
|
|
291
345
|
}
|
|
@@ -699,7 +753,7 @@ var ViewSyncerService = class {
|
|
|
699
753
|
*
|
|
700
754
|
* This must be called from within the #lock.
|
|
701
755
|
*/
|
|
702
|
-
async #hydrateUnchangedQueries(lc, cvr, connCtx) {
|
|
756
|
+
async #hydrateUnchangedQueries(lc, cvr, connCtx, hydrationPassStats, hydrationBudget) {
|
|
703
757
|
assert(this.#pipelines.initialized(), "pipelines must be initialized");
|
|
704
758
|
const dbVersion = this.#pipelines.currentVersion();
|
|
705
759
|
const cvrVersion = cvr.version;
|
|
@@ -708,17 +762,12 @@ var ViewSyncerService = class {
|
|
|
708
762
|
return /* @__PURE__ */ new Set();
|
|
709
763
|
}
|
|
710
764
|
const gotQueries = Object.entries(cvr.queries).filter(([_, state]) => state.transformationHash !== void 0);
|
|
765
|
+
const { required: requiredGotQueries } = classifyQueriesForHydration(gotQueries.map(([, query]) => query));
|
|
711
766
|
const customQueries = /* @__PURE__ */ new Map();
|
|
712
767
|
const otherQueries = [];
|
|
713
|
-
|
|
714
|
-
for (const
|
|
715
|
-
|
|
716
|
-
inactivatedCount++;
|
|
717
|
-
continue;
|
|
718
|
-
}
|
|
719
|
-
if (query.type === "custom") customQueries.set(query.id, query);
|
|
720
|
-
else otherQueries.push(query);
|
|
721
|
-
}
|
|
768
|
+
const inactivatedCount = gotQueries.length - requiredGotQueries.length;
|
|
769
|
+
for (const query of requiredGotQueries) if (query.type === "custom") customQueries.set(query.id, query);
|
|
770
|
+
else otherQueries.push(query);
|
|
722
771
|
const transformedQueries = [];
|
|
723
772
|
let customErrorCount = 0;
|
|
724
773
|
let customHashMismatchCount = 0;
|
|
@@ -726,7 +775,7 @@ var ViewSyncerService = class {
|
|
|
726
775
|
if (customQueries.size > 0 && !this.#customQueryTransformer) lc.warn?.("Custom/named queries were requested but no `ZERO_QUERY_URL` is configured for Zero Cache.");
|
|
727
776
|
const customQueryTransformer = this.#customQueryTransformer;
|
|
728
777
|
if (customQueryTransformer && customQueries.size > 0) {
|
|
729
|
-
const transformedCustomQueries = await this.#runPriorityOp(lc, "#hydrateUnchangedQueries transforming custom queries", () => customQueryTransformer.transform(connCtx, customQueries.values()));
|
|
778
|
+
const transformedCustomQueries = await hydrationBudget.excluding(() => this.#runPriorityOp(lc, "#hydrateUnchangedQueries transforming custom queries", () => customQueryTransformer.transform(connCtx, customQueries.values())));
|
|
730
779
|
if (transformedCustomQueries.kind === "success" && !transformedCustomQueries.cached) this.connContextManager.validateConnection(connCtx, connCtx.revision, transformedCustomQueries.validation);
|
|
731
780
|
if (Array.isArray(transformedCustomQueries.result)) for (const q of transformedCustomQueries.result) if ("error" in q) customErrorCount++;
|
|
732
781
|
else if (q.transformationHash !== customQueries.get(q.id)?.transformationHash) customHashMismatchCount++;
|
|
@@ -763,6 +812,7 @@ var ViewSyncerService = class {
|
|
|
763
812
|
else count++;
|
|
764
813
|
});
|
|
765
814
|
const elapsed = timer.totalElapsed();
|
|
815
|
+
hydrationPassStats.activeHydratedQueries++;
|
|
766
816
|
this.#hydrations.add(1);
|
|
767
817
|
this.#hydrationTime.recordMs(elapsed);
|
|
768
818
|
this.#addQueryMaterializationServerMetric(transformationHash, elapsed);
|
|
@@ -867,7 +917,10 @@ var ViewSyncerService = class {
|
|
|
867
917
|
*
|
|
868
918
|
* This must be called from within the #lock.
|
|
869
919
|
*/
|
|
870
|
-
#syncQueryPipelineSet(lc, cvr, customQueryTransformMode, connCtx, driftedQueryIDs = /* @__PURE__ */ new Set()) {
|
|
920
|
+
#syncQueryPipelineSet(lc, cvr, customQueryTransformMode, connCtx, driftedQueryIDs = /* @__PURE__ */ new Set(), hydrationBudget = new HydrationBudget(this.#config.viewSyncerHydrationBudgetMs ?? 0, this.#now), hydrationPassStats = {
|
|
921
|
+
activeHydratedQueries: 0,
|
|
922
|
+
inactiveHydratedQueries: 0
|
|
923
|
+
}) {
|
|
871
924
|
return startAsyncSpan(tracer, "vs.#syncQueryPipelineSet", async (span) => {
|
|
872
925
|
const start = performance.now();
|
|
873
926
|
span.setAttribute("clientGroupID", this.id);
|
|
@@ -876,34 +929,36 @@ var ViewSyncerService = class {
|
|
|
876
929
|
const now = Date.now();
|
|
877
930
|
const ttlClock = this.#getTTLClock(now);
|
|
878
931
|
const cvrQueryEntires = Object.entries(cvr.queries);
|
|
879
|
-
const customQueries = /* @__PURE__ */ new Map();
|
|
880
|
-
const otherQueries = [];
|
|
881
932
|
const transformedQueries = [];
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
}
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
for (const { id, query: origQuery } of otherQueries) {
|
|
890
|
-
assert(id === origQuery.id, "query id mismatch");
|
|
891
|
-
const transformed = transformAndHashQuery(lc, origQuery.id, origQuery.ast, must(this.#pipelines.currentPermissions()).permissions ?? { tables: {} }, connCtx.auth?.type === "jwt" ? connCtx.auth : void 0, origQuery.type === "internal");
|
|
892
|
-
transformedQueries.push({
|
|
893
|
-
id,
|
|
894
|
-
origQuery,
|
|
895
|
-
transformed
|
|
896
|
-
});
|
|
897
|
-
}
|
|
898
|
-
if (customQueries.size > 0 && !this.#customQueryTransformer) lc.warn?.("Custom/named queries were requested but no `ZERO_QUERY_URL` is configured for Zero Cache.");
|
|
899
|
-
let erroredQueryIDs;
|
|
900
|
-
const customQueriesToTransform = customQueryTransformMode === "all" ? [...customQueries.values()] : customQueryTransformMode && [...customQueries.values()].filter((q) => !this.#pipelines.queries().has(q.id));
|
|
933
|
+
const naturallyExpiredQueryIDs = new Set(cvrQueryEntires.map(([, query]) => query).filter((query) => expired(ttlClock, query)).map((query) => query.id));
|
|
934
|
+
if (naturallyExpiredQueryIDs.size > 0) this.#queryEvictions.add(naturallyExpiredQueryIDs.size, { reason: "ttl" });
|
|
935
|
+
const hydrationCandidates = cvrQueryEntires.map(([, query]) => query).filter((query) => !naturallyExpiredQueryIDs.has(query.id));
|
|
936
|
+
const { required, optional } = classifyQueriesForHydration(hydrationCandidates);
|
|
937
|
+
const requiredQueryIDs = new Set(required.map((query) => query.id));
|
|
938
|
+
const optionalToHydrate = hydrationBudget.limitMs === 0 ? optional : optional.filter((query) => query.patchVersion !== void 0);
|
|
939
|
+
if (!this.#customQueryTransformer && hydrationCandidates.some((query) => query.type === "custom")) lc.warn?.("Custom/named queries were requested but no `ZERO_QUERY_URL` is configured for Zero Cache.");
|
|
901
940
|
const customQueryTransformer = this.#customQueryTransformer;
|
|
902
|
-
|
|
941
|
+
const erroredQueryIDs = [];
|
|
942
|
+
let transformedCustomQueryCount = 0;
|
|
943
|
+
const transformOtherQueries = (queries) => {
|
|
944
|
+
for (const origQuery of queries) {
|
|
945
|
+
if (origQuery.type === "custom") continue;
|
|
946
|
+
const transformed = transformAndHashQuery(lc, origQuery.id, origQuery.ast, must(this.#pipelines.currentPermissions()).permissions ?? { tables: {} }, connCtx.auth?.type === "jwt" ? connCtx.auth : void 0, origQuery.type === "internal");
|
|
947
|
+
transformedQueries.push({
|
|
948
|
+
id: origQuery.id,
|
|
949
|
+
origQuery,
|
|
950
|
+
transformed
|
|
951
|
+
});
|
|
952
|
+
}
|
|
953
|
+
};
|
|
954
|
+
const shouldTransformCustomQuery = (query) => customQueryTransformMode === "all" || customQueryTransformMode && !this.#pipelines.queries().has(query.id);
|
|
955
|
+
const transformCustomQueries = async (queries) => {
|
|
956
|
+
if (!customQueryTransformer || queries.length === 0) return;
|
|
957
|
+
transformedCustomQueryCount += queries.length;
|
|
903
958
|
const transformStart = performance.now();
|
|
904
959
|
let transformedCustomQueries;
|
|
905
960
|
try {
|
|
906
|
-
transformedCustomQueries = await this.#runPriorityOp(lc, "#syncQueryPipelineSet transforming custom queries", () => customQueryTransformer.transform(connCtx,
|
|
961
|
+
transformedCustomQueries = await hydrationBudget.excluding(() => this.#runPriorityOp(lc, "#syncQueryPipelineSet transforming custom queries", () => customQueryTransformer.transform(connCtx, queries)));
|
|
907
962
|
if (transformedCustomQueries.kind === "failed") throw new ProtocolErrorWithLevel(transformedCustomQueries.result, "warn");
|
|
908
963
|
else {
|
|
909
964
|
if (!transformedCustomQueries.cached) this.connContextManager.validateConnection(connCtx, connCtx.revision, transformedCustomQueries.validation);
|
|
@@ -917,8 +972,9 @@ var ViewSyncerService = class {
|
|
|
917
972
|
this.#queryTransformationTime.recordMs(transformDuration);
|
|
918
973
|
}
|
|
919
974
|
const successfullyTransformedCustomQueries = /* @__PURE__ */ new Map();
|
|
920
|
-
|
|
921
|
-
|
|
975
|
+
const customQueryMap = new Map(queries.map((query) => [query.id, query]));
|
|
976
|
+
erroredQueryIDs.push(...this.#processTransformedCustomQueries(lc, transformedCustomQueries.result, (q) => {
|
|
977
|
+
const origQuery = customQueryMap.get(q.id);
|
|
922
978
|
if (origQuery) {
|
|
923
979
|
successfullyTransformedCustomQueries.set(q.id, q);
|
|
924
980
|
transformedQueries.push({
|
|
@@ -927,7 +983,7 @@ var ViewSyncerService = class {
|
|
|
927
983
|
transformed: q
|
|
928
984
|
});
|
|
929
985
|
}
|
|
930
|
-
},
|
|
986
|
+
}, customQueryMap));
|
|
931
987
|
for (const [queryID, newTransform] of successfullyTransformedCustomQueries) {
|
|
932
988
|
const existingTransformHash = cvr.queries[queryID]?.transformationHash;
|
|
933
989
|
if (existingTransformHash) {
|
|
@@ -940,21 +996,28 @@ var ViewSyncerService = class {
|
|
|
940
996
|
} else this.#queryTransformationNoOps.add(1);
|
|
941
997
|
}
|
|
942
998
|
}
|
|
943
|
-
}
|
|
944
|
-
const
|
|
999
|
+
};
|
|
1000
|
+
const queriesToTransform = [...required, ...optionalToHydrate];
|
|
1001
|
+
transformOtherQueries(queriesToTransform);
|
|
1002
|
+
await transformCustomQueries(queriesToTransform.filter((query) => query.type === "custom" && shouldTransformCustomQuery(query)));
|
|
1003
|
+
const removeQueriesQueryIds = new Set([...naturallyExpiredQueryIDs, ...erroredQueryIDs]);
|
|
945
1004
|
const addQueries = transformedQueries.map(({ id, origQuery, transformed }) => ({
|
|
946
1005
|
id,
|
|
947
1006
|
ast: transformed.transformedAst,
|
|
948
1007
|
transformationHash: transformed.transformationHash,
|
|
949
1008
|
name: origQuery.type === "custom" ? origQuery.name : void 0
|
|
950
1009
|
})).filter((q) => !removeQueriesQueryIds.has(q.id) && this.#pipelines.queries().get(q.id)?.transformationHash !== q.transformationHash);
|
|
951
|
-
|
|
1010
|
+
const hydrationOrder = new Map([...required, ...optional].map((query, index) => [query.id, index]));
|
|
1011
|
+
addQueries.sort((a, b) => must(hydrationOrder.get(a.id)) - must(hydrationOrder.get(b.id)));
|
|
1012
|
+
const requiredAddQueries = addQueries.filter((query) => requiredQueryIDs.has(query.id));
|
|
1013
|
+
const optionalAddQueries = addQueries.filter((query) => !requiredQueryIDs.has(query.id));
|
|
1014
|
+
lc.info?.(`syncQueryPipelineSet: ${cvrQueryEntires.length} CVR queries, ${transformedCustomQueryCount} custom re-transformed, ${erroredQueryIDs.length} errored, ${removeQueriesQueryIds.size} to remove, ${addQueries.length} to add`);
|
|
952
1015
|
for (const q of addQueries) {
|
|
953
1016
|
const orig = cvr.queries[q.id];
|
|
954
1017
|
lc.debug?.("ViewSyncer adding query", q.ast, "transformed from", orig.type === "custom" ? orig.name : orig.ast);
|
|
955
1018
|
}
|
|
956
1019
|
if (addQueries.length > 0 || removeQueriesQueryIds.size > 0) {
|
|
957
|
-
await this.#addAndRemoveQueries(lc, cvr,
|
|
1020
|
+
await this.#addAndRemoveQueries(lc, cvr, requiredAddQueries, optionalAddQueries, Array.from(removeQueriesQueryIds, (id) => ({ id })), hydrationBudget, hydrationPassStats, driftedQueryIDs);
|
|
958
1021
|
if (addQueries.length > 0) this.#viewSyncerHydration.recordMs(performance.now() - start);
|
|
959
1022
|
} else await this.#catchupClients(lc, cvr);
|
|
960
1023
|
});
|
|
@@ -987,8 +1050,27 @@ var ViewSyncerService = class {
|
|
|
987
1050
|
record.count++;
|
|
988
1051
|
if (record.count >= THRASH_THRESHOLD) this.#lc.warn?.(`Query thrashing detected for query ${queryID}. ${record.count} replacements in 60s. This may indicate clients with different auth contexts connecting to the same client group.`);
|
|
989
1052
|
}
|
|
990
|
-
#
|
|
1053
|
+
#recordHydrationBudgetExhaustion(lc, hydrationBudget, activeHydratedQueries, inactiveHydratedQueries, inactiveEvictedQueryIDs) {
|
|
1054
|
+
const elapsedMs = hydrationBudget.exhaustedAtMs;
|
|
1055
|
+
if (elapsedMs === void 0) return;
|
|
1056
|
+
this.#hydrationBudgetExhaustions.add(1);
|
|
1057
|
+
this.#hydrationBudgetEvictions.add(inactiveEvictedQueryIDs.length);
|
|
1058
|
+
this.#hydrationBudgetElapsed.record(elapsedMs);
|
|
1059
|
+
this.#hydrationBudgetOvershoot.record(Math.max(0, elapsedMs - hydrationBudget.limitMs));
|
|
1060
|
+
if (inactiveEvictedQueryIDs.length > 0) this.#queryEvictions.add(inactiveEvictedQueryIDs.length, { reason: "hydration-budget" });
|
|
1061
|
+
lc.info?.("view-syncer hydration budget exhausted", {
|
|
1062
|
+
clientGroupID: this.id,
|
|
1063
|
+
hydrationBudgetMs: hydrationBudget.limitMs,
|
|
1064
|
+
hydrationElapsedMs: elapsedMs,
|
|
1065
|
+
activeHydratedQueries,
|
|
1066
|
+
inactiveHydratedQueries,
|
|
1067
|
+
inactiveEvictedQueries: inactiveEvictedQueryIDs.length,
|
|
1068
|
+
firstEvictedQueryHash: inactiveEvictedQueryIDs[0] ?? null
|
|
1069
|
+
});
|
|
1070
|
+
}
|
|
1071
|
+
#addAndRemoveQueries(lc, cvr, requiredQueries, optionalQueries, removeQueries, hydrationBudget, hydrationPassStats, driftedQueryIDs = /* @__PURE__ */ new Set()) {
|
|
991
1072
|
return startAsyncSpan(tracer, "vs.#addAndRemoveQueries", async () => {
|
|
1073
|
+
const addQueries = [...requiredQueries, ...optionalQueries];
|
|
992
1074
|
assert(addQueries.length > 0 || removeQueries.length > 0, "Must have queries to add or remove");
|
|
993
1075
|
const start = performance.now();
|
|
994
1076
|
const stateVersion = this.#pipelines.currentVersion();
|
|
@@ -1005,6 +1087,7 @@ var ViewSyncerService = class {
|
|
|
1005
1087
|
updater.ensureNewVersion();
|
|
1006
1088
|
}
|
|
1007
1089
|
const { queryPatches, newVersion } = updater.trackQueries(lc, addQueries, removeQueries);
|
|
1090
|
+
if (hydrationBudget.limitMs !== 0 && optionalQueries.length > 0) assert(cmpVersions(cvr.version, newVersion) < 0, "Optional hydration requires a final poke version before row processing so a removal is actually poked");
|
|
1008
1091
|
const pokers = startPoke(this.#getClients(), newVersion);
|
|
1009
1092
|
for (const patch of queryPatches) await pokers.addPatch(patch);
|
|
1010
1093
|
for (const q of removeQueries) {
|
|
@@ -1021,10 +1104,17 @@ var ViewSyncerService = class {
|
|
|
1021
1104
|
let totalHydratedQueries = 0;
|
|
1022
1105
|
let coveredHydratedQueries = 0;
|
|
1023
1106
|
let firstCoveredQuery;
|
|
1107
|
+
const hydratedQueryIDs = [];
|
|
1108
|
+
const budgetEvictedQueryIDs = [];
|
|
1024
1109
|
const self = this;
|
|
1025
1110
|
await yieldProcess(lc);
|
|
1026
|
-
function*
|
|
1027
|
-
for (
|
|
1111
|
+
function* hydrateQueries(queries, optional, slowHydrateThreshold) {
|
|
1112
|
+
for (let i = 0; i < queries.length; i++) {
|
|
1113
|
+
if (optional && hydrationBudget.exhausted()) {
|
|
1114
|
+
budgetEvictedQueryIDs.push(...queries.slice(i).map((query) => query.id));
|
|
1115
|
+
return;
|
|
1116
|
+
}
|
|
1117
|
+
const q = must(queries[i]);
|
|
1028
1118
|
let queryLC = lc.withContext("hash", q.id).withContext("queryHash", q.id).withContext("transformationHash", q.transformationHash);
|
|
1029
1119
|
if (q.name !== void 0) queryLC = queryLC.withContext("queryName", q.name);
|
|
1030
1120
|
queryLC.debug?.(`adding pipeline for query`, q.ast);
|
|
@@ -1037,6 +1127,9 @@ var ViewSyncerService = class {
|
|
|
1037
1127
|
yield* pipelines.addQuery(q.transformationHash, q.id, q.ast, timer.startWithoutYielding(), q.name, "query-set-sync");
|
|
1038
1128
|
const elapsed = timer.stop();
|
|
1039
1129
|
totalProcessTime += elapsed;
|
|
1130
|
+
hydratedQueryIDs.push(q.id);
|
|
1131
|
+
if (optional) hydrationPassStats.inactiveHydratedQueries++;
|
|
1132
|
+
else hydrationPassStats.activeHydratedQueries++;
|
|
1040
1133
|
self.#addQueryMaterializationServerMetric(q.id, elapsed);
|
|
1041
1134
|
self.#inspectorDelegate.addQuery(q.id, q.ast);
|
|
1042
1135
|
queryCoveringIndex?.add(q.id, {
|
|
@@ -1050,18 +1143,30 @@ var ViewSyncerService = class {
|
|
|
1050
1143
|
transformationHash: q.transformationHash,
|
|
1051
1144
|
...q.name !== void 0 && { name: q.name }
|
|
1052
1145
|
});
|
|
1146
|
+
hydrations.add(1);
|
|
1147
|
+
hydrationTime.recordMs(elapsed);
|
|
1053
1148
|
}
|
|
1054
|
-
|
|
1055
|
-
|
|
1149
|
+
}
|
|
1150
|
+
function* generateRowChanges(slowHydrateThreshold) {
|
|
1151
|
+
yield* hydrateQueries(requiredQueries, false, slowHydrateThreshold);
|
|
1152
|
+
yield* hydrateQueries(optionalQueries, true, slowHydrateThreshold);
|
|
1056
1153
|
}
|
|
1057
1154
|
await this.#processChanges(lc, timer, generateRowChanges(this.#slowHydrateThreshold), updater, pokers);
|
|
1155
|
+
for (const patch of updater.removeTrackedQueries(budgetEvictedQueryIDs)) await pokers.addPatch(patch);
|
|
1156
|
+
for (const queryID of budgetEvictedQueryIDs) {
|
|
1157
|
+
this.#pipelines.removeQuery(queryID);
|
|
1158
|
+
this.#inspectorDelegate.removeQuery(queryID);
|
|
1159
|
+
this.#queryReplacements.delete(queryID);
|
|
1160
|
+
}
|
|
1161
|
+
this.#recordHydrationBudgetExhaustion(lc, hydrationBudget, hydrationPassStats.activeHydratedQueries, hydrationPassStats.inactiveHydratedQueries, budgetEvictedQueryIDs);
|
|
1058
1162
|
this.#logQueryCoverageShadowSummary(lc, "add", totalHydratedQueries, coveredHydratedQueries, firstCoveredQuery);
|
|
1059
1163
|
await startAsyncSpan(tracer, "vs.#syncQueryPipelineSet.deleteUnreferencedRows", async () => {
|
|
1060
1164
|
for (const patch of await updater.deleteUnreferencedRows(lc)) await pokers.addPatch(patch);
|
|
1061
1165
|
});
|
|
1062
1166
|
this.#cvr = await this.#flushUpdater(lc, updater);
|
|
1167
|
+
if (budgetEvictedQueryIDs.length > 0) this.#scheduleExpireEviction(lc, this.#cvr);
|
|
1063
1168
|
const finalVersion = this.#cvr.version;
|
|
1064
|
-
await this.#catchupClients(lc, cvr, finalVersion,
|
|
1169
|
+
await this.#catchupClients(lc, cvr, finalVersion, hydratedQueryIDs, pokers);
|
|
1065
1170
|
await startAsyncSpan(tracer, "vs.#syncQueryPipelineSet.pokeEnd", () => pokers.end(finalVersion));
|
|
1066
1171
|
this.#markVersionServed(stateVersion);
|
|
1067
1172
|
const wallTime = performance.now() - start;
|
|
@@ -1377,8 +1482,10 @@ function isTransformFailedError(error) {
|
|
|
1377
1482
|
return error.errorBody.kind === "TransformFailed" && !isAuthErrorBody(error.errorBody);
|
|
1378
1483
|
}
|
|
1379
1484
|
/**
|
|
1380
|
-
* A query must be expired for all clients
|
|
1381
|
-
* expired.
|
|
1485
|
+
* Whether a query's TTL has elapsed. A query must be expired for all clients
|
|
1486
|
+
* in order to be considered expired.
|
|
1487
|
+
*
|
|
1488
|
+
* Exported for testing.
|
|
1382
1489
|
*/
|
|
1383
1490
|
function expired(ttlClock, q) {
|
|
1384
1491
|
if (q.type === "internal") return false;
|