@rocicorp/zero 0.25.10-canary.11 → 0.25.10-canary.13

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.
@@ -39,6 +39,7 @@ import "../../db/specs.js";
39
39
  import { ResetPipelinesSignal } from "./snapshotter.js";
40
40
  import { versionString, cmpVersions, EMPTY_CVR_VERSION, versionToCookie, versionFromString } from "./schema/types.js";
41
41
  import { ttlClockFromNumber, ttlClockAsNumber } from "./ttl-clock.js";
42
+ import { isPriorityOpRunning, noPriorityOpRunningPromise } from "../../server/priority-op.js";
42
43
  const tracer = trace.getTracer("view-syncer", version);
43
44
  const PROTOCOL_VERSION_ATTR = "protocol.version";
44
45
  const DEFAULT_KEEPALIVE_MS = 5e3;
@@ -155,7 +156,8 @@ class ViewSyncerService {
155
156
  );
156
157
  #inspectorDelegate;
157
158
  #config;
158
- constructor(config, lc, shard, taskID, clientGroupID, cvrDb, upstreamDb, pipelineDriver, versionChanges, drainCoordinator, slowHydrateThreshold, inspectorDelegate, customQueryTransformer, keepaliveMs = DEFAULT_KEEPALIVE_MS, setTimeoutFn = setTimeout.bind(globalThis)) {
159
+ #runPriorityOp;
160
+ constructor(config, lc, shard, taskID, clientGroupID, cvrDb, upstreamDb, pipelineDriver, versionChanges, drainCoordinator, slowHydrateThreshold, inspectorDelegate, customQueryTransformer, runPriorityOp, keepaliveMs = DEFAULT_KEEPALIVE_MS, setTimeoutFn = setTimeout.bind(globalThis)) {
159
161
  const queryConfig = config.query?.url ? config.query : config.getQueries;
160
162
  this.#config = config;
161
163
  this.id = clientGroupID;
@@ -181,6 +183,15 @@ class ViewSyncerService {
181
183
  () => this.#stateChanges.cancel()
182
184
  );
183
185
  this.#setTimeout = setTimeoutFn;
186
+ this.#runPriorityOp = async (lc2, description, op) => {
187
+ const start = Date.now();
188
+ lc2.debug?.(`running priority op ${description}`);
189
+ const result = await runPriorityOp(op);
190
+ lc2.debug?.(
191
+ `finished priority op ${description} after ${Date.now() - start} ms`
192
+ );
193
+ return result;
194
+ };
184
195
  this.keepalive();
185
196
  }
186
197
  #getHeaderOptions(forwardCookie) {
@@ -212,8 +223,12 @@ class ViewSyncerService {
212
223
  return;
213
224
  }
214
225
  if (!this.#cvr) {
215
- this.#lc.debug?.("loading CVR");
216
- this.#cvr = await this.#cvrStore.load(lc, this.#lastConnectTime);
226
+ this.#lc.debug?.("loading cvr");
227
+ this.#cvr = await this.#runPriorityOp(
228
+ lc,
229
+ "loading cvr",
230
+ () => this.#cvrStore.load(lc, this.#lastConnectTime)
231
+ );
217
232
  this.#ttlClock = this.#cvr.ttlClock;
218
233
  this.#ttlClockBase = Date.now();
219
234
  } else {
@@ -488,19 +503,21 @@ class ViewSyncerService {
488
503
  this.#ttlClockBase = now;
489
504
  return ttlClock;
490
505
  }
491
- async #flushUpdater(lc, updater) {
492
- const now = Date.now();
493
- const ttlClock = this.#getTTLClock(now);
494
- const { cvr, flushed } = await updater.flush(
495
- lc,
496
- this.#lastConnectTime,
497
- now,
498
- ttlClock
499
- );
500
- if (flushed) {
501
- this.#startTTLClockInterval(lc);
502
- }
503
- return cvr;
506
+ #flushUpdater(lc, updater) {
507
+ return this.#runPriorityOp(lc, "flushing cvr", async () => {
508
+ const now = Date.now();
509
+ const ttlClock = this.#getTTLClock(now);
510
+ const { cvr, flushed } = await updater.flush(
511
+ lc,
512
+ this.#lastConnectTime,
513
+ now,
514
+ ttlClock
515
+ );
516
+ if (flushed) {
517
+ this.#startTTLClockInterval(lc);
518
+ }
519
+ return cvr;
520
+ });
504
521
  }
505
522
  #startTTLClockInterval(lc) {
506
523
  this.#stopTTLClockInterval();
@@ -757,19 +774,26 @@ class ViewSyncerService {
757
774
  "Custom/named queries were requested but no `ZERO_QUERY_URL` is configured for Zero Cache."
758
775
  );
759
776
  }
760
- if (this.#customQueryTransformer && customQueries.size > 0) {
761
- const transformedCustomQueries = await this.#customQueryTransformer.transform(
762
- this.#getHeaderOptions(this.#queryConfig.forwardCookies),
763
- customQueries.values(),
764
- this.userQueryURL
765
- );
766
- this.#processTransformedCustomQueries(
767
- lc,
768
- transformedCustomQueries,
769
- (q) => transformedQueries.push(q),
770
- customQueries
771
- );
772
- }
777
+ await this.#runPriorityOp(lc, "hydrating unchanged queries", async () => {
778
+ const customQueryTransformer = this.#customQueryTransformer;
779
+ if (customQueryTransformer && customQueries.size > 0) {
780
+ const transformedCustomQueries = await this.#runPriorityOp(
781
+ lc,
782
+ "#hydrateUnchangedQueries transforming custom queries",
783
+ () => customQueryTransformer.transform(
784
+ this.#getHeaderOptions(this.#queryConfig.forwardCookies),
785
+ customQueries.values(),
786
+ this.userQueryURL
787
+ )
788
+ );
789
+ this.#processTransformedCustomQueries(
790
+ lc,
791
+ transformedCustomQueries,
792
+ (q) => transformedQueries.push(q),
793
+ customQueries
794
+ );
795
+ }
796
+ });
773
797
  for (const q of otherQueries) {
774
798
  const transformed = transformAndHashQuery(
775
799
  lc,
@@ -790,7 +814,7 @@ class ViewSyncerService {
790
814
  transformationHash,
791
815
  transformedAst
792
816
  } of transformedQueries) {
793
- const timer = new TimeSliceTimer();
817
+ const timer = new TimeSliceTimer(lc);
794
818
  let count = 0;
795
819
  await startAsyncSpan(
796
820
  tracer,
@@ -936,14 +960,19 @@ class ViewSyncerService {
936
960
  );
937
961
  }
938
962
  let erroredQueryIDs;
939
- if (this.#customQueryTransformer && customQueries.size > 0) {
963
+ const customQueryTransformer = this.#customQueryTransformer;
964
+ if (customQueryTransformer && customQueries.size > 0) {
940
965
  const transformStart = performance.now();
941
966
  let transformedCustomQueries;
942
967
  try {
943
- transformedCustomQueries = await this.#customQueryTransformer.transform(
944
- this.#getHeaderOptions(true),
945
- customQueries.values(),
946
- this.userQueryURL
968
+ transformedCustomQueries = await this.#runPriorityOp(
969
+ lc,
970
+ "#syncQueryPipelineSet transforming custom queries",
971
+ () => customQueryTransformer.transform(
972
+ this.#getHeaderOptions(true),
973
+ customQueries.values(),
974
+ this.userQueryURL
975
+ )
947
976
  );
948
977
  this.#queryTransformations.add(1, { result: "success" });
949
978
  } catch (e) {
@@ -1133,12 +1162,12 @@ class ViewSyncerService {
1133
1162
  }
1134
1163
  }
1135
1164
  let totalProcessTime = 0;
1136
- const timer = new TimeSliceTimer();
1165
+ const timer = new TimeSliceTimer(lc);
1137
1166
  const pipelines = this.#pipelines;
1138
1167
  const hydrations = this.#hydrations;
1139
1168
  const hydrationTime = this.#hydrationTime;
1140
1169
  const self = this;
1141
- await yieldProcess();
1170
+ await yieldProcess(lc);
1142
1171
  function* generateRowChanges(slowHydrateThreshold) {
1143
1172
  for (const q of addQueries) {
1144
1173
  lc = lc.withContext("hash", q.id).withContext("transformationHash", q.transformationHash);
@@ -1359,7 +1388,7 @@ class ViewSyncerService {
1359
1388
  "pipelines must be initialized (advancePipelines"
1360
1389
  );
1361
1390
  const start = performance.now();
1362
- const timer = new TimeSliceTimer();
1391
+ const timer = new TimeSliceTimer(lc);
1363
1392
  const { version: version2, numChanges, changes } = this.#pipelines.advance(timer);
1364
1393
  lc = lc.withContext("newVersion", version2);
1365
1394
  const updater = new CVRQueryDrivenUpdater(
@@ -1464,8 +1493,23 @@ function createHashToIDs(cvr) {
1464
1493
  return hashToIDs;
1465
1494
  }
1466
1495
  const timeSliceQueue = new Lock();
1467
- function yieldProcess() {
1468
- return timeSliceQueue.withLock(() => new Promise(setImmediate));
1496
+ async function yieldProcess(lc) {
1497
+ async function wait() {
1498
+ if (isPriorityOpRunning()) {
1499
+ const start = Date.now();
1500
+ lc.debug?.("yieldProcess waiting for priority op");
1501
+ await noPriorityOpRunningPromise();
1502
+ lc.debug?.(
1503
+ `yieldProcess waited for priority op ${Date.now() - start} ms`
1504
+ );
1505
+ }
1506
+ await new Promise(setImmediate);
1507
+ if (isPriorityOpRunning()) {
1508
+ lc.debug?.("yieldProcess recursing because priority op is running");
1509
+ await wait();
1510
+ }
1511
+ }
1512
+ await timeSliceQueue.withLock(wait);
1469
1513
  }
1470
1514
  function contentsAndVersion(row) {
1471
1515
  const { [ZERO_VERSION_COLUMN_NAME]: version2, ...contents } = row;
@@ -1552,8 +1596,12 @@ function hasExpiredQueries(cvr) {
1552
1596
  class TimeSliceTimer {
1553
1597
  #total = 0;
1554
1598
  #start = 0;
1599
+ #lc;
1600
+ constructor(lc) {
1601
+ this.#lc = lc;
1602
+ }
1555
1603
  async start() {
1556
- await yieldProcess();
1604
+ await yieldProcess(this.#lc);
1557
1605
  return this.startWithoutYielding();
1558
1606
  }
1559
1607
  startWithoutYielding() {
@@ -1563,7 +1611,7 @@ class TimeSliceTimer {
1563
1611
  }
1564
1612
  async yieldProcess(_msgForTesting) {
1565
1613
  this.#stopLap();
1566
- await yieldProcess();
1614
+ await yieldProcess(this.#lc);
1567
1615
  this.#startLap();
1568
1616
  }
1569
1617
  #startLap() {