@rocicorp/zero 1.11.0-canary.12 → 1.11.0-canary.14
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/ast-to-zql/src/ast-to-zql.d.ts +8 -1
- package/out/ast-to-zql/src/ast-to-zql.d.ts.map +1 -1
- package/out/ast-to-zql/src/ast-to-zql.js +25 -25
- package/out/ast-to-zql/src/ast-to-zql.js.map +1 -1
- package/out/otel/src/log-options.d.ts +16 -0
- package/out/otel/src/log-options.d.ts.map +1 -1
- package/out/otel/src/log-options.js +54 -1
- package/out/otel/src/log-options.js.map +1 -1
- package/out/replicache/src/get-kv-store-provider.d.ts +1 -1
- package/out/replicache/src/get-kv-store-provider.d.ts.map +1 -1
- package/out/replicache/src/get-kv-store-provider.js +4 -4
- package/out/replicache/src/get-kv-store-provider.js.map +1 -1
- package/out/replicache/src/kv/idb-store.d.ts +10 -1
- package/out/replicache/src/kv/idb-store.d.ts.map +1 -1
- package/out/replicache/src/kv/idb-store.js +18 -2
- package/out/replicache/src/kv/idb-store.js.map +1 -1
- package/out/replicache/src/kv/mem-fallback-store.d.ts +54 -0
- package/out/replicache/src/kv/mem-fallback-store.d.ts.map +1 -0
- package/out/replicache/src/kv/mem-fallback-store.js +177 -0
- package/out/replicache/src/kv/mem-fallback-store.js.map +1 -0
- package/out/replicache/src/kv/op-sqlite/store.js +3 -3
- package/out/replicache/src/kv/op-sqlite/store.js.map +1 -1
- package/out/replicache/src/kv/sqlite-store.d.ts +9 -0
- package/out/replicache/src/kv/sqlite-store.d.ts.map +1 -1
- package/out/replicache/src/kv/sqlite-store.js +86 -6
- package/out/replicache/src/kv/sqlite-store.js.map +1 -1
- package/out/replicache/src/new-client-channel.js.map +1 -1
- package/out/replicache/src/persist/collect-idb-databases.d.ts +17 -3
- package/out/replicache/src/persist/collect-idb-databases.d.ts.map +1 -1
- package/out/replicache/src/persist/collect-idb-databases.js +81 -11
- package/out/replicache/src/persist/collect-idb-databases.js.map +1 -1
- package/out/replicache/src/persist/idb-databases-store-db-name.js +1 -1
- package/out/replicache/src/persist/idb-databases-store-db-name.js.map +1 -1
- package/out/replicache/src/persist/persist.d.ts.map +1 -1
- package/out/replicache/src/persist/persist.js +6 -3
- package/out/replicache/src/persist/persist.js.map +1 -1
- package/out/replicache/src/replicache-impl.d.ts +43 -3
- package/out/replicache/src/replicache-impl.d.ts.map +1 -1
- package/out/replicache/src/replicache-impl.js +135 -17
- package/out/replicache/src/replicache-impl.js.map +1 -1
- package/out/replicache/src/storage-failure.d.ts +37 -0
- package/out/replicache/src/storage-failure.d.ts.map +1 -0
- package/out/replicache/src/storage-failure.js +31 -0
- package/out/replicache/src/storage-failure.js.map +1 -0
- package/out/shared/src/random-values.js +12 -1
- package/out/shared/src/random-values.js.map +1 -1
- package/out/zero/package.js +1 -1
- package/out/zero/package.js.map +1 -1
- package/out/zero/src/zero.js +2 -1
- package/out/zero-cache/src/config/zero-config.d.ts +26 -0
- package/out/zero-cache/src/config/zero-config.d.ts.map +1 -1
- package/out/zero-cache/src/config/zero-config.js +36 -0
- package/out/zero-cache/src/config/zero-config.js.map +1 -1
- package/out/zero-cache/src/observability/log-throttle.d.ts +44 -0
- package/out/zero-cache/src/observability/log-throttle.d.ts.map +1 -0
- package/out/zero-cache/src/observability/log-throttle.js +76 -0
- package/out/zero-cache/src/observability/log-throttle.js.map +1 -0
- package/out/zero-cache/src/scripts/permissions.d.ts +16 -0
- package/out/zero-cache/src/scripts/permissions.d.ts.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 -0
- 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 +20 -2
- package/out/zero-cache/src/server/syncer.js.map +1 -1
- package/out/zero-cache/src/services/change-source/common/backfill-manager.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-source/common/backfill-manager.js +189 -27
- package/out/zero-cache/src/services/change-source/common/backfill-manager.js.map +1 -1
- package/out/zero-cache/src/services/change-source/common/replica-schema.d.ts +1 -0
- package/out/zero-cache/src/services/change-source/common/replica-schema.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-source/common/replica-schema.js +15 -3
- package/out/zero-cache/src/services/change-source/common/replica-schema.js.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/backfill-stream.d.ts +9 -0
- package/out/zero-cache/src/services/change-source/pg/backfill-stream.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/backfill-stream.js +204 -18
- package/out/zero-cache/src/services/change-source/pg/backfill-stream.js.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/initial-sync.d.ts +11 -2
- 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 +11 -2
- package/out/zero-cache/src/services/change-source/pg/initial-sync.js.map +1 -1
- package/out/zero-cache/src/services/change-source/protocol/backfill-progress.d.ts +68 -0
- package/out/zero-cache/src/services/change-source/protocol/backfill-progress.d.ts.map +1 -0
- package/out/zero-cache/src/services/change-source/protocol/backfill-progress.js +141 -0
- package/out/zero-cache/src/services/change-source/protocol/backfill-progress.js.map +1 -0
- package/out/zero-cache/src/services/change-source/protocol/current/data.d.ts +43 -0
- package/out/zero-cache/src/services/change-source/protocol/current/data.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-source/protocol/current/data.js +10 -1
- package/out/zero-cache/src/services/change-source/protocol/current/data.js.map +1 -1
- package/out/zero-cache/src/services/change-source/protocol/current/downstream.d.ts +57 -0
- package/out/zero-cache/src/services/change-source/protocol/current/downstream.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-source/protocol/current/upstream.d.ts +20 -6
- package/out/zero-cache/src/services/change-source/protocol/current/upstream.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-source/protocol/current/upstream.js +17 -7
- package/out/zero-cache/src/services/change-source/protocol/current/upstream.js.map +1 -1
- package/out/zero-cache/src/services/change-source/protocol/current.js +2 -1
- package/out/zero-cache/src/services/change-streamer/backfill-state-tracker.d.ts +66 -0
- package/out/zero-cache/src/services/change-streamer/backfill-state-tracker.d.ts.map +1 -0
- package/out/zero-cache/src/services/change-streamer/backfill-state-tracker.js +154 -0
- package/out/zero-cache/src/services/change-streamer/backfill-state-tracker.js.map +1 -0
- package/out/zero-cache/src/services/change-streamer/backfill-state.d.ts +50 -0
- package/out/zero-cache/src/services/change-streamer/backfill-state.d.ts.map +1 -0
- package/out/zero-cache/src/services/change-streamer/backfill-state.js +268 -0
- package/out/zero-cache/src/services/change-streamer/backfill-state.js.map +1 -0
- package/out/zero-cache/src/services/change-streamer/change-streamer-http.js +5 -4
- 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.map +1 -1
- package/out/zero-cache/src/services/change-streamer/change-streamer-service.js +20 -4
- 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 +26 -1
- 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.map +1 -1
- package/out/zero-cache/src/services/change-streamer/storer.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-streamer/storer.js +1 -1
- package/out/zero-cache/src/services/change-streamer/storer.js.map +1 -1
- package/out/zero-cache/src/services/change-streamer/subscribe.d.ts +88 -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 +9 -1
- package/out/zero-cache/src/services/change-streamer/subscribe.js.map +1 -1
- package/out/zero-cache/src/services/change-streamer/subscriber.d.ts +29 -0
- package/out/zero-cache/src/services/change-streamer/subscriber.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-streamer/subscriber.js +34 -0
- package/out/zero-cache/src/services/change-streamer/subscriber.js.map +1 -1
- package/out/zero-cache/src/services/plannerz.d.ts +159 -0
- package/out/zero-cache/src/services/plannerz.d.ts.map +1 -0
- package/out/zero-cache/src/services/plannerz.js +263 -0
- package/out/zero-cache/src/services/plannerz.js.map +1 -0
- package/out/zero-cache/src/services/replicator/change-log-cookies.js +1 -1
- package/out/zero-cache/src/services/replicator/change-log-cookies.js.map +1 -1
- package/out/zero-cache/src/services/replicator/change-processor.d.ts.map +1 -1
- package/out/zero-cache/src/services/replicator/change-processor.js +48 -5
- package/out/zero-cache/src/services/replicator/change-processor.js.map +1 -1
- package/out/zero-cache/src/services/replicator/incremental-sync.d.ts.map +1 -1
- package/out/zero-cache/src/services/replicator/incremental-sync.js +4 -3
- package/out/zero-cache/src/services/replicator/incremental-sync.js.map +1 -1
- package/out/zero-cache/src/services/replicator/schema/backfilling.d.ts +23 -2
- package/out/zero-cache/src/services/replicator/schema/backfilling.d.ts.map +1 -1
- package/out/zero-cache/src/services/replicator/schema/backfilling.js +54 -2
- package/out/zero-cache/src/services/replicator/schema/backfilling.js.map +1 -1
- package/out/zero-cache/src/services/replicator/write-worker-client.d.ts +11 -3
- package/out/zero-cache/src/services/replicator/write-worker-client.d.ts.map +1 -1
- package/out/zero-cache/src/services/replicator/write-worker-client.js.map +1 -1
- package/out/zero-cache/src/services/replicator/write-worker.js +5 -1
- package/out/zero-cache/src/services/replicator/write-worker.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/cvr-store.d.ts +11 -0
- 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 +20 -7
- 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.map +1 -1
- package/out/zero-cache/src/services/view-syncer/cvr.js +1 -1
- package/out/zero-cache/src/services/view-syncer/cvr.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/deferred-writes-budget.d.ts +81 -0
- package/out/zero-cache/src/services/view-syncer/deferred-writes-budget.d.ts.map +1 -0
- package/out/zero-cache/src/services/view-syncer/deferred-writes-budget.js +128 -0
- package/out/zero-cache/src/services/view-syncer/deferred-writes-budget.js.map +1 -0
- package/out/zero-cache/src/services/view-syncer/inspect-handler.js +1 -1
- package/out/zero-cache/src/services/view-syncer/pipeline-driver.d.ts +36 -1
- package/out/zero-cache/src/services/view-syncer/pipeline-driver.d.ts.map +1 -1
- package/out/zero-cache/src/services/view-syncer/pipeline-driver.js +393 -30
- package/out/zero-cache/src/services/view-syncer/pipeline-driver.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/plan-warnings.d.ts +7 -0
- package/out/zero-cache/src/services/view-syncer/plan-warnings.d.ts.map +1 -0
- package/out/zero-cache/src/services/view-syncer/plan-warnings.js +35 -0
- package/out/zero-cache/src/services/view-syncer/plan-warnings.js.map +1 -0
- package/out/zero-cache/src/services/view-syncer/query-shape.d.ts +25 -0
- package/out/zero-cache/src/services/view-syncer/query-shape.d.ts.map +1 -0
- package/out/zero-cache/src/services/view-syncer/query-shape.js +28 -0
- package/out/zero-cache/src/services/view-syncer/query-shape.js.map +1 -0
- package/out/zero-cache/src/services/view-syncer/query-stats.d.ts +77 -0
- package/out/zero-cache/src/services/view-syncer/query-stats.d.ts.map +1 -0
- package/out/zero-cache/src/services/view-syncer/query-stats.js +206 -0
- package/out/zero-cache/src/services/view-syncer/query-stats.js.map +1 -0
- package/out/zero-cache/src/services/view-syncer/snapshotter.d.ts +11 -1
- package/out/zero-cache/src/services/view-syncer/snapshotter.d.ts.map +1 -1
- package/out/zero-cache/src/services/view-syncer/snapshotter.js +7 -1
- 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.map +1 -1
- package/out/zero-cache/src/services/view-syncer/view-syncer.js +50 -3
- package/out/zero-cache/src/services/view-syncer/view-syncer.js.map +1 -1
- package/out/zero-client/src/client/options.d.ts +29 -0
- package/out/zero-client/src/client/options.d.ts.map +1 -1
- package/out/zero-client/src/client/options.js.map +1 -1
- package/out/zero-client/src/client/version.js +1 -1
- package/out/zero-client/src/client/zero.d.ts.map +1 -1
- package/out/zero-client/src/client/zero.js +8 -23
- package/out/zero-client/src/client/zero.js.map +1 -1
- package/out/zero-client/src/mod.d.ts +1 -0
- package/out/zero-client/src/mod.d.ts.map +1 -1
- package/out/zero-client/src/mod.js +1 -0
- package/out/zero-protocol/src/down.d.ts +2 -2
- package/out/zero-react/src/zero.js +1 -0
- package/out/zero-solid/src/zero.js +1 -0
- package/out/zql/src/builder/builder.d.ts +6 -0
- package/out/zql/src/builder/builder.d.ts.map +1 -1
- package/out/zql/src/builder/builder.js +5 -4
- package/out/zql/src/builder/builder.js.map +1 -1
- package/out/zql/src/ivm/flipped-join.d.ts +1 -1
- package/out/zql/src/ivm/flipped-join.d.ts.map +1 -1
- package/out/zql/src/ivm/flipped-join.js +19 -22
- package/out/zql/src/ivm/flipped-join.js.map +1 -1
- package/out/zql/src/ivm/join.d.ts +1 -1
- package/out/zql/src/ivm/join.d.ts.map +1 -1
- package/out/zql/src/ivm/join.js +34 -18
- package/out/zql/src/ivm/join.js.map +1 -1
- package/out/zql/src/ivm/memory-source.d.ts +13 -0
- package/out/zql/src/ivm/memory-source.d.ts.map +1 -1
- package/out/zql/src/ivm/memory-source.js +1 -1
- package/out/zql/src/ivm/memory-source.js.map +1 -1
- package/out/zql/src/ivm/take-gate.d.ts +23 -2
- package/out/zql/src/ivm/take-gate.d.ts.map +1 -1
- package/out/zql/src/ivm/take-gate.js +23 -1
- package/out/zql/src/ivm/take-gate.js.map +1 -1
- package/out/zql/src/planner/planner-builder.d.ts +12 -1
- package/out/zql/src/planner/planner-builder.d.ts.map +1 -1
- package/out/zql/src/planner/planner-builder.js +8 -1
- package/out/zql/src/planner/planner-builder.js.map +1 -1
- package/out/zql/src/planner/planner-connection.d.ts +43 -0
- package/out/zql/src/planner/planner-connection.d.ts.map +1 -1
- package/out/zql/src/planner/planner-connection.js +31 -4
- package/out/zql/src/planner/planner-connection.js.map +1 -1
- package/out/zql/src/planner/planner-graph.d.ts +10 -0
- package/out/zql/src/planner/planner-graph.d.ts.map +1 -1
- package/out/zql/src/planner/planner-graph.js +14 -0
- package/out/zql/src/planner/planner-graph.js.map +1 -1
- package/out/zql/src/planner/planner-warnings.d.ts +83 -0
- package/out/zql/src/planner/planner-warnings.d.ts.map +1 -0
- package/out/zql/src/planner/planner-warnings.js +78 -0
- package/out/zql/src/planner/planner-warnings.js.map +1 -0
- package/out/zql/src/query/measure-push-operator.d.ts.map +1 -1
- package/out/zql/src/query/measure-push-operator.js +30 -8
- package/out/zql/src/query/measure-push-operator.js.map +1 -1
- package/out/zqlite/src/pending-delta.d.ts +110 -0
- package/out/zqlite/src/pending-delta.d.ts.map +1 -0
- package/out/zqlite/src/pending-delta.js +278 -0
- package/out/zqlite/src/pending-delta.js.map +1 -0
- package/out/zqlite/src/query-delegate.d.ts.map +1 -1
- package/out/zqlite/src/query-delegate.js +5 -1
- package/out/zqlite/src/query-delegate.js.map +1 -1
- package/out/zqlite/src/sqlite-cost-model.d.ts.map +1 -1
- package/out/zqlite/src/sqlite-cost-model.js +52 -1
- package/out/zqlite/src/sqlite-cost-model.js.map +1 -1
- package/out/zqlite/src/sqlite-stat-fanout.d.ts.map +1 -1
- package/out/zqlite/src/sqlite-stat-fanout.js +2 -26
- package/out/zqlite/src/sqlite-stat-fanout.js.map +1 -1
- package/out/zqlite/src/sqlite-stat4-sample.d.ts +37 -0
- package/out/zqlite/src/sqlite-stat4-sample.d.ts.map +1 -0
- package/out/zqlite/src/sqlite-stat4-sample.js +61 -0
- package/out/zqlite/src/sqlite-stat4-sample.js.map +1 -0
- package/out/zqlite/src/table-source.d.ts +68 -0
- package/out/zqlite/src/table-source.d.ts.map +1 -1
- package/out/zqlite/src/table-source.js +206 -7
- package/out/zqlite/src/table-source.js.map +1 -1
- package/package.json +1 -1
- package/out/replicache/src/kv/idb-store-with-mem-fallback.d.ts +0 -35
- package/out/replicache/src/kv/idb-store-with-mem-fallback.d.ts.map +0 -1
- package/out/replicache/src/kv/idb-store-with-mem-fallback.js +0 -83
- package/out/replicache/src/kv/idb-store-with-mem-fallback.js.map +0 -1
- package/out/zero-cache/src/services/change-source/pg/backfill-resume.d.ts +0 -156
- package/out/zero-cache/src/services/change-source/pg/backfill-resume.d.ts.map +0 -1
- package/out/zero-cache/src/services/change-source/pg/backfill-resume.js +0 -15
- package/out/zero-cache/src/services/change-source/pg/backfill-resume.js.map +0 -1
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { assert } from "../../../shared/src/asserts.js";
|
|
2
|
+
//#region ../zero-cache/src/observability/log-throttle.ts
|
|
3
|
+
/**
|
|
4
|
+
* Rate limits a log statement per key, for logs that would otherwise be
|
|
5
|
+
* written for every client group, such as the slow hydration of a popular
|
|
6
|
+
* query.
|
|
7
|
+
*
|
|
8
|
+
* Deduplication alone (logging a key once and never again) is not enough:
|
|
9
|
+
* the one line is lost once the log rotates, and the set of logged keys
|
|
10
|
+
* grows without bound. Instead a key is admitted at most once per
|
|
11
|
+
* {@link LogThrottleOptions.windowMs}, and the occurrences suppressed in
|
|
12
|
+
* between are counted and reported with the next admitted one.
|
|
13
|
+
*/
|
|
14
|
+
var LogThrottle = class {
|
|
15
|
+
#windowMs;
|
|
16
|
+
#maxKeys;
|
|
17
|
+
#maxPerWindow;
|
|
18
|
+
#now;
|
|
19
|
+
#entries = /* @__PURE__ */ new Map();
|
|
20
|
+
#windowStart = -Infinity;
|
|
21
|
+
#admittedInWindow = 0;
|
|
22
|
+
constructor({ windowMs, maxKeys = 1e3, maxPerWindow = 100, now = performance.now.bind(performance) }) {
|
|
23
|
+
assert(windowMs >= 0, "windowMs must be nonnegative");
|
|
24
|
+
assert(maxKeys > 0, "maxKeys must be positive");
|
|
25
|
+
assert(maxPerWindow > 0, "maxPerWindow must be positive");
|
|
26
|
+
this.#windowMs = windowMs;
|
|
27
|
+
this.#maxKeys = maxKeys;
|
|
28
|
+
this.#maxPerWindow = maxPerWindow;
|
|
29
|
+
this.#now = now;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Records an occurrence of `key`.
|
|
33
|
+
*
|
|
34
|
+
* @returns `undefined` if the occurrence should not be logged, or else the
|
|
35
|
+
* number of occurrences of `key` suppressed since it was last logged.
|
|
36
|
+
*/
|
|
37
|
+
admit(key) {
|
|
38
|
+
const now = this.#now();
|
|
39
|
+
const entry = this.#entries.get(key);
|
|
40
|
+
if (entry && now - entry.admittedAt < this.#windowMs) {
|
|
41
|
+
entry.suppressed++;
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
if (now - this.#windowStart >= this.#windowMs) {
|
|
45
|
+
this.#windowStart = now;
|
|
46
|
+
this.#admittedInWindow = 0;
|
|
47
|
+
}
|
|
48
|
+
if (this.#admittedInWindow >= this.#maxPerWindow) {
|
|
49
|
+
if (entry) entry.suppressed++;
|
|
50
|
+
else this.#set(key, {
|
|
51
|
+
admittedAt: -Infinity,
|
|
52
|
+
suppressed: 1
|
|
53
|
+
});
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
this.#admittedInWindow++;
|
|
57
|
+
const suppressed = entry?.suppressed ?? 0;
|
|
58
|
+
this.#set(key, {
|
|
59
|
+
admittedAt: now,
|
|
60
|
+
suppressed: 0
|
|
61
|
+
});
|
|
62
|
+
return suppressed;
|
|
63
|
+
}
|
|
64
|
+
#set(key, entry) {
|
|
65
|
+
this.#entries.delete(key);
|
|
66
|
+
this.#entries.set(key, entry);
|
|
67
|
+
if (this.#entries.size > this.#maxKeys) {
|
|
68
|
+
const oldest = this.#entries.keys().next();
|
|
69
|
+
if (!oldest.done) this.#entries.delete(oldest.value);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
//#endregion
|
|
74
|
+
export { LogThrottle };
|
|
75
|
+
|
|
76
|
+
//# sourceMappingURL=log-throttle.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"log-throttle.js","names":["#windowMs","#maxKeys","#maxPerWindow","#now","#entries","#windowStart","#admittedInWindow","#set"],"sources":["../../../../../zero-cache/src/observability/log-throttle.ts"],"sourcesContent":["import {assert} from '../../../shared/src/asserts.ts';\n\nexport type LogThrottleOptions = {\n /**\n * An admitted key is suppressed for this long. A key that is still\n * occurring after the window is admitted again, so a log that is still\n * relevant keeps showing up (e.g. after the log has rotated) without\n * repeating for every occurrence.\n */\n readonly windowMs: number;\n\n /**\n * The most keys tracked at once. The least recently admitted key is\n * forgotten beyond this, which bounds memory when the key space is\n * unbounded (e.g. dynamically built query shapes).\n */\n readonly maxKeys?: number | undefined;\n\n /**\n * The most occurrences admitted per window across all keys, which bounds\n * log volume when many distinct keys occur at once.\n */\n readonly maxPerWindow?: number | undefined;\n\n readonly now?: (() => number) | undefined;\n};\n\ntype Entry = {\n admittedAt: number;\n suppressed: number;\n};\n\n/**\n * Rate limits a log statement per key, for logs that would otherwise be\n * written for every client group, such as the slow hydration of a popular\n * query.\n *\n * Deduplication alone (logging a key once and never again) is not enough:\n * the one line is lost once the log rotates, and the set of logged keys\n * grows without bound. Instead a key is admitted at most once per\n * {@link LogThrottleOptions.windowMs}, and the occurrences suppressed in\n * between are counted and reported with the next admitted one.\n */\nexport class LogThrottle {\n readonly #windowMs: number;\n readonly #maxKeys: number;\n readonly #maxPerWindow: number;\n readonly #now: () => number;\n\n // Insertion ordered by admission, so the first entry is the least recently\n // admitted key.\n readonly #entries = new Map<string, Entry>();\n #windowStart = -Infinity;\n #admittedInWindow = 0;\n\n constructor({\n windowMs,\n maxKeys = 1000,\n maxPerWindow = 100,\n now = performance.now.bind(performance),\n }: LogThrottleOptions) {\n assert(windowMs >= 0, 'windowMs must be nonnegative');\n assert(maxKeys > 0, 'maxKeys must be positive');\n assert(maxPerWindow > 0, 'maxPerWindow must be positive');\n this.#windowMs = windowMs;\n this.#maxKeys = maxKeys;\n this.#maxPerWindow = maxPerWindow;\n this.#now = now;\n }\n\n /**\n * Records an occurrence of `key`.\n *\n * @returns `undefined` if the occurrence should not be logged, or else the\n * number of occurrences of `key` suppressed since it was last logged.\n */\n admit(key: string): number | undefined {\n const now = this.#now();\n const entry = this.#entries.get(key);\n if (entry && now - entry.admittedAt < this.#windowMs) {\n entry.suppressed++;\n return undefined;\n }\n\n if (now - this.#windowStart >= this.#windowMs) {\n this.#windowStart = now;\n this.#admittedInWindow = 0;\n }\n if (this.#admittedInWindow >= this.#maxPerWindow) {\n // Over the global budget. The occurrence is counted against the key so\n // that it is reported once the key is admitted.\n if (entry) {\n entry.suppressed++;\n } else {\n this.#set(key, {admittedAt: -Infinity, suppressed: 1});\n }\n return undefined;\n }\n\n this.#admittedInWindow++;\n const suppressed = entry?.suppressed ?? 0;\n this.#set(key, {admittedAt: now, suppressed: 0});\n return suppressed;\n }\n\n #set(key: string, entry: Entry): void {\n // Delete first so that the key moves to the end of the insertion order.\n this.#entries.delete(key);\n this.#entries.set(key, entry);\n if (this.#entries.size > this.#maxKeys) {\n const oldest = this.#entries.keys().next();\n if (!oldest.done) {\n this.#entries.delete(oldest.value);\n }\n }\n }\n}\n"],"mappings":";;;;;;;;;;;;;AA2CA,IAAa,cAAb,MAAyB;CACvB;CACA;CACA;CACA;CAIA,2BAAoB,IAAI,IAAmB;CAC3C,eAAe;CACf,oBAAoB;CAEpB,YAAY,EACV,UACA,UAAU,KACV,eAAe,KACf,MAAM,YAAY,IAAI,KAAK,WAAW,KACjB;EACrB,OAAO,YAAY,GAAG,8BAA8B;EACpD,OAAO,UAAU,GAAG,0BAA0B;EAC9C,OAAO,eAAe,GAAG,+BAA+B;EACxD,KAAKA,YAAY;EACjB,KAAKC,WAAW;EAChB,KAAKC,gBAAgB;EACrB,KAAKC,OAAO;CACd;;;;;;;CAQA,MAAM,KAAiC;EACrC,MAAM,MAAM,KAAKA,KAAK;EACtB,MAAM,QAAQ,KAAKC,SAAS,IAAI,GAAG;EACnC,IAAI,SAAS,MAAM,MAAM,aAAa,KAAKJ,WAAW;GACpD,MAAM;GACN;EACF;EAEA,IAAI,MAAM,KAAKK,gBAAgB,KAAKL,WAAW;GAC7C,KAAKK,eAAe;GACpB,KAAKC,oBAAoB;EAC3B;EACA,IAAI,KAAKA,qBAAqB,KAAKJ,eAAe;GAGhD,IAAI,OACF,MAAM;QAEN,KAAKK,KAAK,KAAK;IAAC,YAAY;IAAW,YAAY;GAAC,CAAC;GAEvD;EACF;EAEA,KAAKD;EACL,MAAM,aAAa,OAAO,cAAc;EACxC,KAAKC,KAAK,KAAK;GAAC,YAAY;GAAK,YAAY;EAAC,CAAC;EAC/C,OAAO;CACT;CAEA,KAAK,KAAa,OAAoB;EAEpC,KAAKH,SAAS,OAAO,GAAG;EACxB,KAAKA,SAAS,IAAI,KAAK,KAAK;EAC5B,IAAI,KAAKA,SAAS,OAAO,KAAKH,UAAU;GACtC,MAAM,SAAS,KAAKG,SAAS,KAAK,EAAE,KAAK;GACzC,IAAI,CAAC,OAAO,MACV,KAAKA,SAAS,OAAO,OAAO,KAAK;EAErC;CACF;AACF"}
|
|
@@ -51,6 +51,22 @@ export declare const deployPermissionsOptions: {
|
|
|
51
51
|
type: v.Type<number>;
|
|
52
52
|
desc: string[];
|
|
53
53
|
};
|
|
54
|
+
slowAdvanceThreshold: {
|
|
55
|
+
type: v.Type<number>;
|
|
56
|
+
desc: string[];
|
|
57
|
+
};
|
|
58
|
+
planWarningRowThreshold: {
|
|
59
|
+
type: v.Type<number>;
|
|
60
|
+
desc: string[];
|
|
61
|
+
};
|
|
62
|
+
planWarningCostThreshold: {
|
|
63
|
+
type: v.Type<number>;
|
|
64
|
+
desc: string[];
|
|
65
|
+
};
|
|
66
|
+
queryStatsIntervalSeconds: {
|
|
67
|
+
type: v.Type<number>;
|
|
68
|
+
desc: string[];
|
|
69
|
+
};
|
|
54
70
|
ivmSampling: {
|
|
55
71
|
type: v.Type<number>;
|
|
56
72
|
desc: string[];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"permissions.d.ts","sourceRoot":"","sources":["../../../../../zero-cache/src/scripts/permissions.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,CAAC,MAAM,+BAA+B,CAAC;AACnD,OAAO,EAEL,KAAK,iBAAiB,EACvB,MAAM,kDAAkD,CAAC;AAE1D,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,mCAAmC,CAAC;AAG9D,eAAO,MAAM,wBAAwB;IACnC,MAAM;QACJ,IAAI;YACF,IAAI;YACJ,IAAI;YACJ,KAAK;;;IAIT,QAAQ;QACN,EAAE;YACA,IAAI;YACJ,IAAI;;QAMN,IAAI;;;;;;IAGN,GAAG;QAAG,EAAE;;;;;IAER,KAAK;;;;;;;;;;;IAEL,GAAG
|
|
1
|
+
{"version":3,"file":"permissions.d.ts","sourceRoot":"","sources":["../../../../../zero-cache/src/scripts/permissions.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,CAAC,MAAM,+BAA+B,CAAC;AACnD,OAAO,EAEL,KAAK,iBAAiB,EACvB,MAAM,kDAAkD,CAAC;AAE1D,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,mCAAmC,CAAC;AAG9D,eAAO,MAAM,wBAAwB;IACnC,MAAM;QACJ,IAAI;YACF,IAAI;YACJ,IAAI;YACJ,KAAK;;;IAIT,QAAQ;QACN,EAAE;YACA,IAAI;YACJ,IAAI;;QAMN,IAAI;;;;;;IAGN,GAAG;QAAG,EAAE;;;;;IAER,KAAK;;;;;;;;;;;IAEL,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IAEH,MAAM;QACJ,IAAI;YACF,IAAI;YACJ,IAAI;;QAKN,MAAM;YACJ,IAAI;YACJ,IAAI;;;IAYR,KAAK;QACH,IAAI;QACJ,IAAI;QACJ,KAAK;;CAER,CAAC;AAEF,wBAAsB,wBAAwB,CAC5C,UAAU,EAAE,MAAM,EAClB,YAAY,EAAE,IAAI,GACjB,OAAO,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,iBAAiB,CAAA;CAAC,GAAG,SAAS,CAAC,CAAC;AACzE,wBAAsB,wBAAwB,CAC5C,UAAU,EAAE,MAAM,EAClB,YAAY,CAAC,EAAE,KAAK,GACnB,OAAO,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,iBAAiB,CAAA;CAAC,CAAC,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"zero-dispatcher.d.ts","sourceRoot":"","sources":["../../../../../../zero-cache/src/server/runner/zero-dispatcher.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,kBAAkB,CAAC;AACjD,OAAO,KAAK,EAAC,oBAAoB,EAAC,MAAM,2BAA2B,CAAC;AAEpE,OAAO,EAAC,WAAW,EAAE,KAAK,OAAO,EAAC,MAAM,gCAAgC,CAAC;
|
|
1
|
+
{"version":3,"file":"zero-dispatcher.d.ts","sourceRoot":"","sources":["../../../../../../zero-cache/src/server/runner/zero-dispatcher.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,kBAAkB,CAAC;AACjD,OAAO,KAAK,EAAC,oBAAoB,EAAC,MAAM,2BAA2B,CAAC;AAEpE,OAAO,EAAC,WAAW,EAAE,KAAK,OAAO,EAAC,MAAM,gCAAgC,CAAC;AAQzE,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,0BAA0B,CAAC;AAMrD,qBAAa,cAAe,SAAQ,WAAW;;IAC7C,QAAQ,CAAC,EAAE,qBAAqB;IAIhC,YACE,MAAM,EAAE,oBAAoB,EAC5B,EAAE,EAAE,UAAU,EACd,IAAI,EAAE,OAAO,EACb,SAAS,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,EAChC,OAAO,CAAC,EAAE,MAAM,IAAI,EAsBrB;IAED,UAAmB,QAAQ,SAE1B;CAYF"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { handleHeapzRequest } from "../../services/heapz.js";
|
|
2
2
|
import { HttpService } from "../../services/http-service.js";
|
|
3
|
+
import { handlePlannerzRequest } from "../../services/plannerz.js";
|
|
3
4
|
import { handleProfrmzRequest, handleProfzRequest } from "../../services/profz.js";
|
|
4
5
|
import { handleStatzRequest } from "../../services/statz.js";
|
|
5
6
|
import { installWebSocketHandoff } from "../../types/websocket-handoff.js";
|
|
@@ -11,6 +12,7 @@ var ZeroDispatcher = class extends HttpService {
|
|
|
11
12
|
constructor(config, lc, opts, getWorker, onStart) {
|
|
12
13
|
super(`zero-dispatcher`, lc, opts, (fastify) => {
|
|
13
14
|
fastify.get("/statz", (req, res) => handleStatzRequest(lc, config, req, res));
|
|
15
|
+
fastify.get("/plannerz", (req, res) => handlePlannerzRequest(lc, config, req, res));
|
|
14
16
|
fastify.get("/heapz", (req, res) => handleHeapzRequest(lc, config, req, res));
|
|
15
17
|
fastify.get("/profz", (req, res) => handleProfzRequest(lc, config, req, res, this.#getWorker));
|
|
16
18
|
fastify.get("/profrmz", (req, res) => handleProfrmzRequest(lc, config, req, res, this.#getWorker));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"zero-dispatcher.js","names":["#getWorker","#onStart","#handoff"],"sources":["../../../../../../zero-cache/src/server/runner/zero-dispatcher.ts"],"sourcesContent":["import type {LogContext} from '@rocicorp/logger';\nimport type {NormalizedZeroConfig} from '../../config/normalize.ts';\nimport {handleHeapzRequest} from '../../services/heapz.ts';\nimport {HttpService, type Options} from '../../services/http-service.ts';\nimport {\n handleProfrmzRequest,\n handleProfzRequest,\n} from '../../services/profz.ts';\nimport {handleStatzRequest} from '../../services/statz.ts';\nimport type {IncomingMessageSubset} from '../../types/http.ts';\nimport type {Worker} from '../../types/processes.ts';\nimport {\n installWebSocketHandoff,\n type HandoffSpec,\n} from '../../types/websocket-handoff.ts';\n\nexport class ZeroDispatcher extends HttpService {\n readonly id = 'zero-dispatcher';\n readonly #getWorker: () => Promise<Worker>;\n readonly #onStart: (() => void) | undefined;\n\n constructor(\n config: NormalizedZeroConfig,\n lc: LogContext,\n opts: Options,\n getWorker: () => Promise<Worker>,\n onStart?: () => void,\n ) {\n super(`zero-dispatcher`, lc, opts, fastify => {\n fastify.get('/statz', (req, res) =>\n handleStatzRequest(lc, config, req, res),\n );\n fastify.get('/heapz', (req, res) =>\n handleHeapzRequest(lc, config, req, res),\n );\n fastify.get('/profz', (req, res) =>\n handleProfzRequest(lc, config, req, res, this.#getWorker),\n );\n fastify.get('/profrmz', (req, res) =>\n handleProfrmzRequest(lc, config, req, res, this.#getWorker),\n );\n installWebSocketHandoff(lc, this.#handoff, fastify.server);\n });\n this.#getWorker = getWorker;\n this.#onStart = onStart;\n }\n\n protected override _onStart() {\n this.#onStart?.();\n }\n\n readonly #handoff = (\n _req: IncomingMessageSubset,\n dispatch: (h: HandoffSpec<string>) => void,\n onError: (error: unknown) => void,\n ) => {\n void this.#getWorker().then(\n sender => dispatch({payload: 'unused', sender}),\n onError,\n );\n };\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"zero-dispatcher.js","names":["#getWorker","#onStart","#handoff"],"sources":["../../../../../../zero-cache/src/server/runner/zero-dispatcher.ts"],"sourcesContent":["import type {LogContext} from '@rocicorp/logger';\nimport type {NormalizedZeroConfig} from '../../config/normalize.ts';\nimport {handleHeapzRequest} from '../../services/heapz.ts';\nimport {HttpService, type Options} from '../../services/http-service.ts';\nimport {handlePlannerzRequest} from '../../services/plannerz.ts';\nimport {\n handleProfrmzRequest,\n handleProfzRequest,\n} from '../../services/profz.ts';\nimport {handleStatzRequest} from '../../services/statz.ts';\nimport type {IncomingMessageSubset} from '../../types/http.ts';\nimport type {Worker} from '../../types/processes.ts';\nimport {\n installWebSocketHandoff,\n type HandoffSpec,\n} from '../../types/websocket-handoff.ts';\n\nexport class ZeroDispatcher extends HttpService {\n readonly id = 'zero-dispatcher';\n readonly #getWorker: () => Promise<Worker>;\n readonly #onStart: (() => void) | undefined;\n\n constructor(\n config: NormalizedZeroConfig,\n lc: LogContext,\n opts: Options,\n getWorker: () => Promise<Worker>,\n onStart?: () => void,\n ) {\n super(`zero-dispatcher`, lc, opts, fastify => {\n fastify.get('/statz', (req, res) =>\n handleStatzRequest(lc, config, req, res),\n );\n fastify.get('/plannerz', (req, res) =>\n handlePlannerzRequest(lc, config, req, res),\n );\n fastify.get('/heapz', (req, res) =>\n handleHeapzRequest(lc, config, req, res),\n );\n fastify.get('/profz', (req, res) =>\n handleProfzRequest(lc, config, req, res, this.#getWorker),\n );\n fastify.get('/profrmz', (req, res) =>\n handleProfrmzRequest(lc, config, req, res, this.#getWorker),\n );\n installWebSocketHandoff(lc, this.#handoff, fastify.server);\n });\n this.#getWorker = getWorker;\n this.#onStart = onStart;\n }\n\n protected override _onStart() {\n this.#onStart?.();\n }\n\n readonly #handoff = (\n _req: IncomingMessageSubset,\n dispatch: (h: HandoffSpec<string>) => void,\n onError: (error: unknown) => void,\n ) => {\n void this.#getWorker().then(\n sender => dispatch({payload: 'unused', sender}),\n onError,\n );\n };\n}\n"],"mappings":";;;;;;;AAiBA,IAAa,iBAAb,cAAoC,YAAY;CAC9C,KAAc;CACd;CACA;CAEA,YACE,QACA,IACA,MACA,WACA,SACA;EACA,MAAM,mBAAmB,IAAI,OAAM,YAAW;GAC5C,QAAQ,IAAI,WAAW,KAAK,QAC1B,mBAAmB,IAAI,QAAQ,KAAK,GAAG,CACzC;GACA,QAAQ,IAAI,cAAc,KAAK,QAC7B,sBAAsB,IAAI,QAAQ,KAAK,GAAG,CAC5C;GACA,QAAQ,IAAI,WAAW,KAAK,QAC1B,mBAAmB,IAAI,QAAQ,KAAK,GAAG,CACzC;GACA,QAAQ,IAAI,WAAW,KAAK,QAC1B,mBAAmB,IAAI,QAAQ,KAAK,KAAK,KAAKA,UAAU,CAC1D;GACA,QAAQ,IAAI,aAAa,KAAK,QAC5B,qBAAqB,IAAI,QAAQ,KAAK,KAAK,KAAKA,UAAU,CAC5D;GACA,wBAAwB,IAAI,KAAKE,UAAU,QAAQ,MAAM;EAC3D,CAAC;EACD,KAAKF,aAAa;EAClB,KAAKC,WAAW;CAClB;CAEA,WAA8B;EAC5B,KAAKA,WAAW;CAClB;CAEA,YACE,MACA,UACA,YACG;EACH,KAAUD,WAAW,EAAE,MACrB,WAAU,SAAS;GAAC,SAAS;GAAU;EAAM,CAAC,GAC9C,OACF;CACF;AACF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"syncer.d.ts","sourceRoot":"","sources":["../../../../../zero-cache/src/server/syncer.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"syncer.d.ts","sourceRoot":"","sources":["../../../../../zero-cache/src/server/syncer.ts"],"names":[],"mappings":"AAqCA,OAAO,EAGL,KAAK,MAAM,EACZ,MAAM,uBAAuB,CAAC;AAqC/B,wBAA8B,SAAS,CACrC,MAAM,EAAE,MAAM,EACd,GAAG,EAAE,MAAM,CAAC,UAAU,EACtB,GAAG,IAAI,EAAE,MAAM,EAAE,GAChB,OAAO,CAAC,IAAI,CAAC,CA6Qf"}
|
|
@@ -6,6 +6,7 @@ import { parentWorker, singleProcessMode } from "../types/processes.js";
|
|
|
6
6
|
import { getShardID } from "../types/shards.js";
|
|
7
7
|
import { getNormalizedZeroConfig } from "../config/zero-config.js";
|
|
8
8
|
import { randInt } from "../../../shared/src/rand.js";
|
|
9
|
+
import { getOrCreateGauge } from "../observability/metrics.js";
|
|
9
10
|
import { Snapshotter } from "../services/view-syncer/snapshotter.js";
|
|
10
11
|
import { PipelineDriver } from "../services/view-syncer/pipeline-driver.js";
|
|
11
12
|
import { connectPgClient } from "../types/pg.js";
|
|
@@ -25,6 +26,8 @@ import { CustomQueryTransformer } from "../custom-queries/transform-query.js";
|
|
|
25
26
|
import { MutagenService } from "../services/mutagen/mutagen.js";
|
|
26
27
|
import { PusherService } from "../services/mutagen/pusher.js";
|
|
27
28
|
import { ConnectionContextManagerImpl } from "../services/view-syncer/connection-context-manager.js";
|
|
29
|
+
import { DeferredWritesBudget } from "../services/view-syncer/deferred-writes-budget.js";
|
|
30
|
+
import { QueryStats } from "../services/view-syncer/query-stats.js";
|
|
28
31
|
import { SnapshotRowCache } from "../services/view-syncer/snapshot-row-cache.js";
|
|
29
32
|
import { ViewSyncerService } from "../services/view-syncer/view-syncer.js";
|
|
30
33
|
import { Syncer } from "../workers/syncer.js";
|
|
@@ -102,6 +105,17 @@ async function runWorker(parent, env, ...args) {
|
|
|
102
105
|
};
|
|
103
106
|
};
|
|
104
107
|
const snapshotRowCache = config.snapshotRowCacheSize > 0 ? new SnapshotRowCache(config.snapshotRowCacheSize) : void 0;
|
|
108
|
+
const deferredWritesBudget = config.deferIvmWrites ? DeferredWritesBudget.forHeapProportion(config.deferIvmWritesHeapProportion) : void 0;
|
|
109
|
+
if (deferredWritesBudget) {
|
|
110
|
+
lc.info?.(`Deferred IVM writes may hold up to ${deferredWritesBudget.maxRows} rows (~${(deferredWritesBudget.maxBytes / 1024 ** 2).toFixed(2)} MB) across client groups`);
|
|
111
|
+
getOrCreateGauge("sync", "ivm.deferred-writes-reserved-rows", "Rows reserved by the client groups of a sync worker to hold IVM changes in memory (deferIvmWrites)").addCallback((o) => o.observe(deferredWritesBudget.reservedRows));
|
|
112
|
+
getOrCreateGauge("sync", "ivm.deferred-writes-held-bytes", {
|
|
113
|
+
description: "Estimated bytes of the IVM changes that the client groups of a sync worker hold in memory (deferIvmWrites)",
|
|
114
|
+
unit: "By"
|
|
115
|
+
}).addCallback((o) => o.observe(deferredWritesBudget.heldBytes));
|
|
116
|
+
}
|
|
117
|
+
const queryStats = config.log.queryStatsIntervalSeconds > 0 ? new QueryStats() : void 0;
|
|
118
|
+
const stopQueryStats = queryStats?.start(lc.withContext("taskID", config.taskID).withContext("component", "view-syncer"), config.log.queryStatsIntervalSeconds * 1e3);
|
|
105
119
|
const viewSyncerFactory = (id, sub, drainCoordinator) => {
|
|
106
120
|
const logger = lc.withContext("taskID", config.taskID).withContext("component", "view-syncer").withContext("appID", shard.appID).withContext("shardNum", shard.shardNum).withContext("clientGroupID", id).withContext("instance", randomID());
|
|
107
121
|
const customQueryTransformer = customQueryConfig && new CustomQueryTransformer(logger, shard);
|
|
@@ -110,12 +124,16 @@ async function runWorker(parent, env, ...args) {
|
|
|
110
124
|
const inspectorDelegate = new InspectorDelegate(customQueryTransformer);
|
|
111
125
|
const priorityOpRunningYieldThresholdMs = Math.max(config.yieldThresholdMs / 4, 2);
|
|
112
126
|
const normalYieldThresholdMs = Math.max(config.yieldThresholdMs, 2);
|
|
113
|
-
return new ViewSyncerService(config, logger, shard, config.taskID, id, cvrDB, new PipelineDriver(logger, config.log, new Snapshotter(logger, replicaFile, shard, void 0, snapshotRowCache), shard, operatorStorage.createClientGroupStorage(id), id, inspectorDelegate, () => isPriorityOpRunning() ? priorityOpRunningYieldThresholdMs : normalYieldThresholdMs, config.enableQueryPlanner, config), sub, drainCoordinator, config.log.slowHydrateThreshold, inspectorDelegate, connContextManager, customQueryTransformer, runPriorityOp);
|
|
127
|
+
return new ViewSyncerService(config, logger, shard, config.taskID, id, cvrDB, new PipelineDriver(logger, config.log, new Snapshotter(logger, replicaFile, shard, void 0, snapshotRowCache), shard, operatorStorage.createClientGroupStorage(id), id, inspectorDelegate, () => isPriorityOpRunning() ? priorityOpRunningYieldThresholdMs : normalYieldThresholdMs, config.enableQueryPlanner, config, deferredWritesBudget, queryStats), sub, drainCoordinator, config.log.slowHydrateThreshold, inspectorDelegate, connContextManager, customQueryTransformer, runPriorityOp);
|
|
114
128
|
};
|
|
115
129
|
const syncer = new Syncer(lc, config, viewSyncerFactory, upstreamDB ? (id) => new MutagenService(lc.withContext("component", "mutagen").withContext("clientGroupID", id), shard, id, upstreamDB, config, writeAuthzStorage) : void 0, pushConfig === void 0 ? void 0 : (id, connContextManager) => new PusherService(config, lc.withContext("clientGroupID", id), id, connContextManager), parent, validateLegacyJWT);
|
|
116
130
|
startAnonymousTelemetry(lc, config);
|
|
117
131
|
dbWarmup.then(() => parent.send(["ready", { ready: true }]));
|
|
118
|
-
|
|
132
|
+
try {
|
|
133
|
+
return await runUntilKilled(lc, parent, syncer);
|
|
134
|
+
} finally {
|
|
135
|
+
stopQueryStats?.();
|
|
136
|
+
}
|
|
119
137
|
}
|
|
120
138
|
if (!singleProcessMode()) exitAfter(() => lc, () => runWorker(must(parentWorker), process.env, ...process.argv.slice(2)));
|
|
121
139
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"syncer.js","names":[],"sources":["../../../../../zero-cache/src/server/syncer.ts"],"sourcesContent":["import {randomUUID} from 'node:crypto';\nimport {tmpdir} from 'node:os';\nimport path from 'node:path';\nimport {pid} from 'node:process';\nimport {consoleLogSink, LogContext} from '@rocicorp/logger';\nimport {assert} from '../../../shared/src/asserts.ts';\nimport {must} from '../../../shared/src/must.ts';\nimport {randInt} from '../../../shared/src/rand.ts';\nimport {promiseVoid} from '../../../shared/src/resolved-promises.ts';\nimport * as v from '../../../shared/src/valita.ts';\nimport {DatabaseStorage} from '../../../zqlite/src/database-storage.ts';\nimport type {ValidateLegacyJWT} from '../auth/auth.ts';\nimport {tokenConfigOptions, verifyToken} from '../auth/jwt.ts';\nimport type {NormalizedZeroConfig} from '../config/normalize.ts';\nimport {getNormalizedZeroConfig} from '../config/zero-config.ts';\nimport {CustomQueryTransformer} from '../custom-queries/transform-query.ts';\nimport {registerSQLiteCorruptionDiagnosticTarget} from '../db/sqlite-corruption.ts';\nimport {warmupConnections} from '../db/warmup.ts';\nimport {initEventSink} from '../observability/events.ts';\nimport {exitAfter, runUntilKilled} from '../services/life-cycle.ts';\nimport {MutagenService} from '../services/mutagen/mutagen.ts';\nimport {PusherService} from '../services/mutagen/pusher.ts';\nimport type {ReplicaState} from '../services/replicator/replicator.ts';\nimport {\n type ConnectionContextManager,\n ConnectionContextManagerImpl,\n} from '../services/view-syncer/connection-context-manager.ts';\nimport type {DrainCoordinator} from '../services/view-syncer/drain-coordinator.ts';\nimport {PipelineDriver} from '../services/view-syncer/pipeline-driver.ts';\nimport {SnapshotRowCache} from '../services/view-syncer/snapshot-row-cache.ts';\nimport {Snapshotter} from '../services/view-syncer/snapshotter.ts';\nimport {ViewSyncerService} from '../services/view-syncer/view-syncer.ts';\nimport {ProtocolErrorWithLevel} from '../types/error-with-level.ts';\nimport {connectPgClient} from '../types/pg.ts';\nimport {\n parentWorker,\n singleProcessMode,\n type Worker,\n} from '../types/processes.ts';\nimport {installProfileHandler} from '../types/profiler.ts';\nimport {getShardID} from '../types/shards.ts';\nimport type {Subscription} from '../types/subscription.ts';\nimport {replicaFileModeSchema, replicaFileName} from '../workers/replicator.ts';\nimport {Syncer} from '../workers/syncer.ts';\nimport {startAnonymousTelemetry} from './anonymous-otel-start.ts';\nimport {InspectorDelegate} from './inspector-delegate.ts';\nimport {createLogContext} from './logging.ts';\nimport {startOtelAuto} from './otel-start.ts';\nimport {isPriorityOpRunning, runPriorityOp} from './priority-op.ts';\n\nfunction randomID() {\n return randInt(1, Number.MAX_SAFE_INTEGER).toString(36);\n}\n\nfunction getCustomQueryConfig(\n config: Pick<NormalizedZeroConfig, 'query' | 'getQueries'>,\n) {\n const queryConfig = config.query?.url ? config.query : config.getQueries;\n\n if (!queryConfig?.url) {\n return undefined;\n }\n\n return {\n url: queryConfig.url,\n apiKey: queryConfig.apiKey,\n allowedClientHeaders: queryConfig.allowedClientHeaders,\n allowedRequestHeaders: queryConfig.allowedRequestHeaders,\n forwardCookies: queryConfig.forwardCookies ?? false,\n };\n}\n\n// Default LogContext, overridden in runWorker\nlet lc = new LogContext('info', {}, consoleLogSink);\n\nexport default async function runWorker(\n parent: Worker,\n env: NodeJS.ProcessEnv,\n ...args: string[]\n): Promise<void> {\n assert(args.length >= 2, `expected [fileMode, workerIndex, ...flags]`);\n const fileMode = v.parse(args[0], replicaFileModeSchema);\n const workerIndex = Number(args[1]);\n installProfileHandler(parent, `syncer-${workerIndex}`, workerIndex);\n const config = getNormalizedZeroConfig({env, argv: args.slice(2)});\n\n startOtelAuto(\n createLogContext(config, 'syncer', workerIndex, false),\n 'syncer',\n workerIndex,\n );\n lc = createLogContext(config, 'syncer', workerIndex);\n initEventSink(lc, config);\n\n const {cvr, upstream, enableCrudMutations} = config;\n\n const replicaFile = replicaFileName(config.replica.file, fileMode);\n registerSQLiteCorruptionDiagnosticTarget(\n {\n debugName: 'syncer replica',\n dbPath: replicaFile,\n },\n config.sqliteCorruptionChecks,\n );\n lc.debug?.(`running view-syncer on ${replicaFile}`);\n\n const cvrDB = await connectPgClient(lc, cvr.db, `sync-worker-${pid}-cvr`, {\n max: must(cvr.maxConnsPerWorker, 'cvr.maxConnsPerWorker must be set'),\n });\n\n const upstreamDB =\n enableCrudMutations && upstream.type === 'pg'\n ? await connectPgClient(lc, upstream.db, `sync-worker-${pid}-upstream`, {\n max: must(\n upstream.maxConnsPerWorker,\n 'upstream.maxConnsPerWorker must be set',\n ),\n })\n : undefined;\n\n const dbWarmup = Promise.allSettled([\n warmupConnections(lc, cvrDB, 'cvr'),\n upstreamDB ? warmupConnections(lc, upstreamDB, 'upstream') : promiseVoid,\n ]);\n\n const tmpDir = config.storageDBTmpDir ?? tmpdir();\n const operatorStorage = DatabaseStorage.create(\n lc,\n path.join(tmpDir, `sync-worker-${randomUUID()}`),\n );\n const writeAuthzStorage = DatabaseStorage.create(\n lc,\n path.join(tmpDir, `mutagen-${randomUUID()}`),\n );\n\n const shard = getShardID(config);\n const customQueryConfig = getCustomQueryConfig(config);\n const pushConfig =\n config.push.url === undefined && config.mutate.url === undefined\n ? undefined\n : {\n ...config.push,\n ...config.mutate,\n url: must(\n config.push.url ?? config.mutate.url,\n 'No push or mutate URL configured',\n ),\n };\n\n /** @deprecated used in JWT validation */\n let validateLegacyJWT: ValidateLegacyJWT | undefined = undefined;\n\n const tokenOptions = tokenConfigOptions(config.auth ?? {});\n if (tokenOptions.length === 1) {\n validateLegacyJWT = async (token, {userID}) => {\n if (!userID) {\n throw new ProtocolErrorWithLevel(\n {\n kind: 'Unauthorized',\n message: 'UserID is required for JWT validation.',\n origin: 'zeroCache',\n },\n 'warn',\n );\n }\n\n const decoded = await verifyToken(config.auth, token, {\n subject: userID,\n ...(config.auth?.issuer && {issuer: config.auth.issuer}),\n ...(config.auth?.audience && {\n audience: config.auth.audience,\n }),\n });\n return {\n type: 'jwt',\n raw: token,\n decoded,\n };\n };\n }\n\n // Shared by all of the view-syncers on this worker so that the row reads\n // performed when advancing their pipelines are done once per worker rather\n // than once per client group.\n const snapshotRowCache =\n config.snapshotRowCacheSize > 0\n ? new SnapshotRowCache(config.snapshotRowCacheSize)\n : undefined;\n\n const viewSyncerFactory = (\n id: string,\n sub: Subscription<ReplicaState>,\n drainCoordinator: DrainCoordinator,\n ) => {\n const logger = lc\n .withContext('taskID', config.taskID)\n .withContext('component', 'view-syncer')\n .withContext('appID', shard.appID)\n .withContext('shardNum', shard.shardNum)\n .withContext('clientGroupID', id)\n .withContext('instance', randomID());\n\n const customQueryTransformer =\n customQueryConfig && new CustomQueryTransformer(logger, shard);\n const connContextManager = new ConnectionContextManagerImpl(\n logger,\n config.auth.revalidateIntervalSeconds,\n config.auth.retransformIntervalSeconds,\n customQueryConfig,\n pushConfig,\n validateLegacyJWT,\n );\n\n lc.debug?.(\n `creating view syncer. Query Planner Enabled: ${config.enableQueryPlanner}`,\n );\n\n const inspectorDelegate = new InspectorDelegate(customQueryTransformer);\n\n const priorityOpRunningYieldThresholdMs = Math.max(\n config.yieldThresholdMs / 4,\n 2,\n );\n const normalYieldThresholdMs = Math.max(config.yieldThresholdMs, 2);\n\n return new ViewSyncerService(\n config,\n logger,\n shard,\n config.taskID,\n id,\n cvrDB,\n new PipelineDriver(\n logger,\n config.log,\n new Snapshotter(\n logger,\n replicaFile,\n shard,\n undefined,\n snapshotRowCache,\n ),\n shard,\n operatorStorage.createClientGroupStorage(id),\n id,\n inspectorDelegate,\n () =>\n isPriorityOpRunning()\n ? priorityOpRunningYieldThresholdMs\n : normalYieldThresholdMs,\n config.enableQueryPlanner,\n config,\n ),\n sub,\n drainCoordinator,\n config.log.slowHydrateThreshold,\n inspectorDelegate,\n connContextManager,\n customQueryTransformer,\n runPriorityOp,\n );\n };\n\n const mutagenFactory = upstreamDB\n ? (id: string) =>\n new MutagenService(\n lc\n .withContext('component', 'mutagen')\n .withContext('clientGroupID', id),\n shard,\n id,\n upstreamDB,\n config,\n writeAuthzStorage,\n )\n : undefined;\n\n const pusherFactory =\n pushConfig === undefined\n ? undefined\n : (id: string, connContextManager: ConnectionContextManager) =>\n new PusherService(\n config,\n lc.withContext('clientGroupID', id),\n id,\n connContextManager,\n );\n\n const syncer = new Syncer(\n lc,\n config,\n viewSyncerFactory,\n mutagenFactory,\n pusherFactory,\n parent,\n validateLegacyJWT,\n );\n\n startAnonymousTelemetry(lc, config);\n\n void dbWarmup.then(() => parent.send(['ready', {ready: true}]));\n\n return runUntilKilled(lc, parent, syncer);\n}\n\n// fork()\nif (!singleProcessMode()) {\n void exitAfter(\n () => lc,\n () => runWorker(must(parentWorker), process.env, ...process.argv.slice(2)),\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"syncer.js","names":[],"sources":["../../../../../zero-cache/src/server/syncer.ts"],"sourcesContent":["import {randomUUID} from 'node:crypto';\nimport {tmpdir} from 'node:os';\nimport path from 'node:path';\nimport {pid} from 'node:process';\nimport {consoleLogSink, LogContext} from '@rocicorp/logger';\nimport {assert} from '../../../shared/src/asserts.ts';\nimport {must} from '../../../shared/src/must.ts';\nimport {randInt} from '../../../shared/src/rand.ts';\nimport {promiseVoid} from '../../../shared/src/resolved-promises.ts';\nimport * as v from '../../../shared/src/valita.ts';\nimport {DatabaseStorage} from '../../../zqlite/src/database-storage.ts';\nimport type {ValidateLegacyJWT} from '../auth/auth.ts';\nimport {tokenConfigOptions, verifyToken} from '../auth/jwt.ts';\nimport type {NormalizedZeroConfig} from '../config/normalize.ts';\nimport {getNormalizedZeroConfig} from '../config/zero-config.ts';\nimport {CustomQueryTransformer} from '../custom-queries/transform-query.ts';\nimport {registerSQLiteCorruptionDiagnosticTarget} from '../db/sqlite-corruption.ts';\nimport {warmupConnections} from '../db/warmup.ts';\nimport {initEventSink} from '../observability/events.ts';\nimport {getOrCreateGauge} from '../observability/metrics.ts';\nimport {exitAfter, runUntilKilled} from '../services/life-cycle.ts';\nimport {MutagenService} from '../services/mutagen/mutagen.ts';\nimport {PusherService} from '../services/mutagen/pusher.ts';\nimport type {ReplicaState} from '../services/replicator/replicator.ts';\nimport {\n type ConnectionContextManager,\n ConnectionContextManagerImpl,\n} from '../services/view-syncer/connection-context-manager.ts';\nimport {DeferredWritesBudget} from '../services/view-syncer/deferred-writes-budget.ts';\nimport type {DrainCoordinator} from '../services/view-syncer/drain-coordinator.ts';\nimport {PipelineDriver} from '../services/view-syncer/pipeline-driver.ts';\nimport {QueryStats} from '../services/view-syncer/query-stats.ts';\nimport {SnapshotRowCache} from '../services/view-syncer/snapshot-row-cache.ts';\nimport {Snapshotter} from '../services/view-syncer/snapshotter.ts';\nimport {ViewSyncerService} from '../services/view-syncer/view-syncer.ts';\nimport {ProtocolErrorWithLevel} from '../types/error-with-level.ts';\nimport {connectPgClient} from '../types/pg.ts';\nimport {\n parentWorker,\n singleProcessMode,\n type Worker,\n} from '../types/processes.ts';\nimport {installProfileHandler} from '../types/profiler.ts';\nimport {getShardID} from '../types/shards.ts';\nimport type {Subscription} from '../types/subscription.ts';\nimport {replicaFileModeSchema, replicaFileName} from '../workers/replicator.ts';\nimport {Syncer} from '../workers/syncer.ts';\nimport {startAnonymousTelemetry} from './anonymous-otel-start.ts';\nimport {InspectorDelegate} from './inspector-delegate.ts';\nimport {createLogContext} from './logging.ts';\nimport {startOtelAuto} from './otel-start.ts';\nimport {isPriorityOpRunning, runPriorityOp} from './priority-op.ts';\n\nfunction randomID() {\n return randInt(1, Number.MAX_SAFE_INTEGER).toString(36);\n}\n\nfunction getCustomQueryConfig(\n config: Pick<NormalizedZeroConfig, 'query' | 'getQueries'>,\n) {\n const queryConfig = config.query?.url ? config.query : config.getQueries;\n\n if (!queryConfig?.url) {\n return undefined;\n }\n\n return {\n url: queryConfig.url,\n apiKey: queryConfig.apiKey,\n allowedClientHeaders: queryConfig.allowedClientHeaders,\n allowedRequestHeaders: queryConfig.allowedRequestHeaders,\n forwardCookies: queryConfig.forwardCookies ?? false,\n };\n}\n\n// Default LogContext, overridden in runWorker\nlet lc = new LogContext('info', {}, consoleLogSink);\n\nexport default async function runWorker(\n parent: Worker,\n env: NodeJS.ProcessEnv,\n ...args: string[]\n): Promise<void> {\n assert(args.length >= 2, `expected [fileMode, workerIndex, ...flags]`);\n const fileMode = v.parse(args[0], replicaFileModeSchema);\n const workerIndex = Number(args[1]);\n installProfileHandler(parent, `syncer-${workerIndex}`, workerIndex);\n const config = getNormalizedZeroConfig({env, argv: args.slice(2)});\n\n startOtelAuto(\n createLogContext(config, 'syncer', workerIndex, false),\n 'syncer',\n workerIndex,\n );\n lc = createLogContext(config, 'syncer', workerIndex);\n initEventSink(lc, config);\n\n const {cvr, upstream, enableCrudMutations} = config;\n\n const replicaFile = replicaFileName(config.replica.file, fileMode);\n registerSQLiteCorruptionDiagnosticTarget(\n {\n debugName: 'syncer replica',\n dbPath: replicaFile,\n },\n config.sqliteCorruptionChecks,\n );\n lc.debug?.(`running view-syncer on ${replicaFile}`);\n\n const cvrDB = await connectPgClient(lc, cvr.db, `sync-worker-${pid}-cvr`, {\n max: must(cvr.maxConnsPerWorker, 'cvr.maxConnsPerWorker must be set'),\n });\n\n const upstreamDB =\n enableCrudMutations && upstream.type === 'pg'\n ? await connectPgClient(lc, upstream.db, `sync-worker-${pid}-upstream`, {\n max: must(\n upstream.maxConnsPerWorker,\n 'upstream.maxConnsPerWorker must be set',\n ),\n })\n : undefined;\n\n const dbWarmup = Promise.allSettled([\n warmupConnections(lc, cvrDB, 'cvr'),\n upstreamDB ? warmupConnections(lc, upstreamDB, 'upstream') : promiseVoid,\n ]);\n\n const tmpDir = config.storageDBTmpDir ?? tmpdir();\n const operatorStorage = DatabaseStorage.create(\n lc,\n path.join(tmpDir, `sync-worker-${randomUUID()}`),\n );\n const writeAuthzStorage = DatabaseStorage.create(\n lc,\n path.join(tmpDir, `mutagen-${randomUUID()}`),\n );\n\n const shard = getShardID(config);\n const customQueryConfig = getCustomQueryConfig(config);\n const pushConfig =\n config.push.url === undefined && config.mutate.url === undefined\n ? undefined\n : {\n ...config.push,\n ...config.mutate,\n url: must(\n config.push.url ?? config.mutate.url,\n 'No push or mutate URL configured',\n ),\n };\n\n /** @deprecated used in JWT validation */\n let validateLegacyJWT: ValidateLegacyJWT | undefined = undefined;\n\n const tokenOptions = tokenConfigOptions(config.auth ?? {});\n if (tokenOptions.length === 1) {\n validateLegacyJWT = async (token, {userID}) => {\n if (!userID) {\n throw new ProtocolErrorWithLevel(\n {\n kind: 'Unauthorized',\n message: 'UserID is required for JWT validation.',\n origin: 'zeroCache',\n },\n 'warn',\n );\n }\n\n const decoded = await verifyToken(config.auth, token, {\n subject: userID,\n ...(config.auth?.issuer && {issuer: config.auth.issuer}),\n ...(config.auth?.audience && {\n audience: config.auth.audience,\n }),\n });\n return {\n type: 'jwt',\n raw: token,\n decoded,\n };\n };\n }\n\n // Shared by all of the view-syncers on this worker so that the row reads\n // performed when advancing their pipelines are done once per worker rather\n // than once per client group.\n const snapshotRowCache =\n config.snapshotRowCacheSize > 0\n ? new SnapshotRowCache(config.snapshotRowCacheSize)\n : undefined;\n\n // Shared by all of the view-syncers on this worker, which each hold their\n // own copy of the changes they are advancing through.\n const deferredWritesBudget = config.deferIvmWrites\n ? DeferredWritesBudget.forHeapProportion(\n config.deferIvmWritesHeapProportion,\n )\n : undefined;\n if (deferredWritesBudget) {\n lc.info?.(\n `Deferred IVM writes may hold up to ${deferredWritesBudget.maxRows} ` +\n `rows (~${(deferredWritesBudget.maxBytes / 1024 ** 2).toFixed(2)} MB) ` +\n `across client groups`,\n );\n getOrCreateGauge(\n 'sync',\n 'ivm.deferred-writes-reserved-rows',\n 'Rows reserved by the client groups of a sync worker to hold IVM ' +\n 'changes in memory (deferIvmWrites)',\n ).addCallback(o => o.observe(deferredWritesBudget.reservedRows));\n getOrCreateGauge('sync', 'ivm.deferred-writes-held-bytes', {\n description:\n 'Estimated bytes of the IVM changes that the client groups of a sync ' +\n 'worker hold in memory (deferIvmWrites)',\n unit: 'By',\n }).addCallback(o => o.observe(deferredWritesBudget.heldBytes));\n }\n\n // Shared by all of the view-syncers on this worker, so that the work done\n // for each query shape is logged once per worker per interval.\n const queryStats =\n config.log.queryStatsIntervalSeconds > 0 ? new QueryStats() : undefined;\n const stopQueryStats = queryStats?.start(\n lc\n .withContext('taskID', config.taskID)\n .withContext('component', 'view-syncer'),\n config.log.queryStatsIntervalSeconds * 1000,\n );\n\n const viewSyncerFactory = (\n id: string,\n sub: Subscription<ReplicaState>,\n drainCoordinator: DrainCoordinator,\n ) => {\n const logger = lc\n .withContext('taskID', config.taskID)\n .withContext('component', 'view-syncer')\n .withContext('appID', shard.appID)\n .withContext('shardNum', shard.shardNum)\n .withContext('clientGroupID', id)\n .withContext('instance', randomID());\n\n const customQueryTransformer =\n customQueryConfig && new CustomQueryTransformer(logger, shard);\n const connContextManager = new ConnectionContextManagerImpl(\n logger,\n config.auth.revalidateIntervalSeconds,\n config.auth.retransformIntervalSeconds,\n customQueryConfig,\n pushConfig,\n validateLegacyJWT,\n );\n\n lc.debug?.(\n `creating view syncer. Query Planner Enabled: ${config.enableQueryPlanner}`,\n );\n\n const inspectorDelegate = new InspectorDelegate(customQueryTransformer);\n\n const priorityOpRunningYieldThresholdMs = Math.max(\n config.yieldThresholdMs / 4,\n 2,\n );\n const normalYieldThresholdMs = Math.max(config.yieldThresholdMs, 2);\n\n return new ViewSyncerService(\n config,\n logger,\n shard,\n config.taskID,\n id,\n cvrDB,\n new PipelineDriver(\n logger,\n config.log,\n new Snapshotter(\n logger,\n replicaFile,\n shard,\n undefined,\n snapshotRowCache,\n ),\n shard,\n operatorStorage.createClientGroupStorage(id),\n id,\n inspectorDelegate,\n () =>\n isPriorityOpRunning()\n ? priorityOpRunningYieldThresholdMs\n : normalYieldThresholdMs,\n config.enableQueryPlanner,\n config,\n deferredWritesBudget,\n queryStats,\n ),\n sub,\n drainCoordinator,\n config.log.slowHydrateThreshold,\n inspectorDelegate,\n connContextManager,\n customQueryTransformer,\n runPriorityOp,\n );\n };\n\n const mutagenFactory = upstreamDB\n ? (id: string) =>\n new MutagenService(\n lc\n .withContext('component', 'mutagen')\n .withContext('clientGroupID', id),\n shard,\n id,\n upstreamDB,\n config,\n writeAuthzStorage,\n )\n : undefined;\n\n const pusherFactory =\n pushConfig === undefined\n ? undefined\n : (id: string, connContextManager: ConnectionContextManager) =>\n new PusherService(\n config,\n lc.withContext('clientGroupID', id),\n id,\n connContextManager,\n );\n\n const syncer = new Syncer(\n lc,\n config,\n viewSyncerFactory,\n mutagenFactory,\n pusherFactory,\n parent,\n validateLegacyJWT,\n );\n\n startAnonymousTelemetry(lc, config);\n\n void dbWarmup.then(() => parent.send(['ready', {ready: true}]));\n\n try {\n return await runUntilKilled(lc, parent, syncer);\n } finally {\n // Logs the work done since the last interval.\n stopQueryStats?.();\n }\n}\n\n// fork()\nif (!singleProcessMode()) {\n void exitAfter(\n () => lc,\n () => runWorker(must(parentWorker), process.env, ...process.argv.slice(2)),\n );\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"backfill-manager.d.ts","sourceRoot":"","sources":["../../../../../../../zero-cache/src/services/change-source/common/backfill-manager.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"backfill-manager.d.ts","sourceRoot":"","sources":["../../../../../../../zero-cache/src/services/change-source/common/backfill-manager.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,kBAAkB,CAAC;AAUjD,OAAO,EAAmB,KAAK,UAAU,EAAC,MAAM,wBAAwB,CAAC;AAKzE,OAAO,KAAK,EACV,iBAAiB,EACjB,eAAe,EACf,mBAAmB,EAEnB,eAAe,EAChB,MAAM,wBAAwB,CAAC;AAChC,OAAO,KAAK,EACV,UAAU,EACV,uBAAuB,EACvB,QAAQ,EACT,MAAM,gCAAgC,CAAC;AAMxC;;;;;GAKG;AACH,MAAM,MAAM,eAAe,GAAG;IAC5B,OAAO,EAAE,eAAe,GAAG,iBAAiB,CAAC;IAC7C,QAAQ,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF,KAAK,gBAAgB,GAAG,CACtB,GAAG,EAAE,eAAe,KACjB,cAAc,CAAC,eAAe,CAAC,CAAC;AAkFrC;;;;;;;;;;;;;;;GAeG;AACH,qBAAa,eAAgB,YAAW,UAAU,EAAE,QAAQ;;IA4C1D,YACE,EAAE,EAAE,UAAU,EACd,cAAc,EAAE,uBAAuB,EACvC,gBAAgB,EAAE,gBAAgB,EAClC,UAAU,GAAE,UAA6B,EACzC,YAAY,SAA0B,EACtC,YAAY,SAA0B,EACtC,oBAAoB,SAAyB,EAU9C;IAED,GAAG,CAAC,aAAa,EAAE,MAAM,EAAE,eAAe,EAAE,eAAe,EAAE,QAU5D;IA4bD;;;OAGG;IACH,QAAQ,CAAC,OAAO,EAAE,mBAAmB,GAAG,IAAI,CA6M3C;IAED,MAAM,IAAI,IAAI,CAab;CACF;AAED,uBAAe,mBAAoB,SAAQ,KAAK;IAC9C,YAAY,EAAE,EAAE,eAAe,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,OAAO,EAM5D;CACF;AA6BD;;;;;GAKG;AACH,qBAAa,0BAA2B,SAAQ,mBAAmB;IACjE,QAAQ,CAAC,IAAI,gCAAgC;IAE7C,YAAY,EAAE,EAAE,eAAe,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,OAAO,EAE5D;CACF"}
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import { assert } from "../../../../../shared/src/asserts.js";
|
|
2
|
+
import { AbortError } from "../../../../../shared/src/abort-error.js";
|
|
2
3
|
import { must } from "../../../../../shared/src/must.js";
|
|
4
|
+
import { promiseOrAbort } from "../../../../../shared/src/promise-race.js";
|
|
3
5
|
import { stringify } from "../../../../../shared/src/bigint-json.js";
|
|
4
6
|
import "../../../types/lite.js";
|
|
5
7
|
import { randInt } from "../../../../../shared/src/rand.js";
|
|
6
8
|
import { stateVersionFromString, stateVersionToString } from "../../../types/state-version.js";
|
|
9
|
+
import { Queue } from "../../../../../shared/src/queue.js";
|
|
7
10
|
import { CustomKeyMap } from "../../../../../shared/src/custom-key-map.js";
|
|
8
11
|
import { resolver } from "@rocicorp/resolver";
|
|
9
12
|
//#region ../zero-cache/src/services/change-source/common/backfill-manager.ts
|
|
@@ -16,6 +19,34 @@ function tableKey({ schema, name }) {
|
|
|
16
19
|
* COMMIT/checkpoint so it can't monopolize the replica.
|
|
17
20
|
*/
|
|
18
21
|
var COMMIT_THRESHOLD_BYTES = 8 * 1024 * 1024;
|
|
22
|
+
/**
|
|
23
|
+
* How many backfill messages the prefetch producer may read ahead of the
|
|
24
|
+
* reservation-holding consumer loop. This bounds the in-memory buffer (~this
|
|
25
|
+
* many {@link BackfillMessage}s, each up to the stream's flush threshold, e.g.
|
|
26
|
+
* 64 KiB) while giving the consumer a supply of ready messages so it never
|
|
27
|
+
* has to wait on the upstream COPY while holding the change-stream reservation.
|
|
28
|
+
*/
|
|
29
|
+
var BACKFILL_PREFETCH_CREDITS = 16;
|
|
30
|
+
/**
|
|
31
|
+
* While the backfill holds an open transaction but its prefetch queue is empty
|
|
32
|
+
* (i.e. the upstream is momentarily behind), the consumer polls for the next
|
|
33
|
+
* message at this interval so that a replication transaction that begins
|
|
34
|
+
* waiting mid-fetch can preempt the backfill within this bound, rather than
|
|
35
|
+
* being blocked for the entire (possibly slow) upstream read.
|
|
36
|
+
*
|
|
37
|
+
* If stats indicate that these stalls are highly correlative with replication
|
|
38
|
+
* lag (indicating that replication changes are waiting on these polls), we
|
|
39
|
+
* can address this by adding an API to the change stream multiplexer that
|
|
40
|
+
* would allow backfill to relinquish an in-progress commit on demand (i.e.
|
|
41
|
+
* only if replication asks for a reservation).
|
|
42
|
+
*/
|
|
43
|
+
var BACKFILL_WAITER_POLL_MS = 10;
|
|
44
|
+
/** Interval at which in-progress backfill timing stats are logged. */
|
|
45
|
+
var BACKFILL_STATS_LOG_INTERVAL_MS = 1e4;
|
|
46
|
+
/** Sentinel enqueued by the prefetch producer when the backfill stream ends. */
|
|
47
|
+
var DONE = Symbol("backfill-done");
|
|
48
|
+
/** Sentinel returned by a polled dequeue that timed out (see {@link BACKFILL_WAITER_POLL_MS}). */
|
|
49
|
+
var POLL = Symbol("backfill-poll");
|
|
19
50
|
var MIN_BACKOFF_INTERVAL_MS = 2e3;
|
|
20
51
|
var MAX_BACKOFF_INTERVAL_MS = 6e4;
|
|
21
52
|
/**
|
|
@@ -57,8 +88,16 @@ var BackfillManager = class {
|
|
|
57
88
|
/** The watermark of the current transaction in the change stream. */
|
|
58
89
|
#currentTxWatermark = null;
|
|
59
90
|
#commitThresholdBytes;
|
|
60
|
-
/**
|
|
61
|
-
|
|
91
|
+
/**
|
|
92
|
+
* Aborted when the change stream is canceled. No further backfills are run.
|
|
93
|
+
*
|
|
94
|
+
* TODO: Thread this into the backfillStream so that upstream I/O can be
|
|
95
|
+
* aborted.
|
|
96
|
+
*/
|
|
97
|
+
#controller = new AbortController();
|
|
98
|
+
get #canceled() {
|
|
99
|
+
return this.#controller.signal.aborted;
|
|
100
|
+
}
|
|
62
101
|
constructor(lc, changeStreamer, backfillStreamer, jsonFormat = "s", minBackoffMs = MIN_BACKOFF_INTERVAL_MS, maxBackoffMs = MAX_BACKOFF_INTERVAL_MS, commitThresholdBytes = COMMIT_THRESHOLD_BYTES) {
|
|
63
102
|
this.#lc = lc.withContext("component", "backfill-manager");
|
|
64
103
|
this.#changeStreamer = changeStreamer;
|
|
@@ -109,7 +148,8 @@ var BackfillManager = class {
|
|
|
109
148
|
const request = candidates[randInt(0, candidates.length - 1)];
|
|
110
149
|
const state = {
|
|
111
150
|
request,
|
|
112
|
-
minWatermark: ""
|
|
151
|
+
minWatermark: "",
|
|
152
|
+
stopped: new AbortController()
|
|
113
153
|
};
|
|
114
154
|
const lc = this.#lc.withContext("table", request.table.name);
|
|
115
155
|
this.#runningBackfill = state;
|
|
@@ -138,14 +178,27 @@ var BackfillManager = class {
|
|
|
138
178
|
const changeStream = this.#changeStreamer;
|
|
139
179
|
let backfillTx = null;
|
|
140
180
|
let uncommittedBytes = 0;
|
|
181
|
+
const stats = {
|
|
182
|
+
start: performance.now(),
|
|
183
|
+
lastLogged: performance.now(),
|
|
184
|
+
messages: 0,
|
|
185
|
+
bytes: 0,
|
|
186
|
+
commits: 0,
|
|
187
|
+
stalls: 0,
|
|
188
|
+
produceMs: 0,
|
|
189
|
+
pushMs: 0,
|
|
190
|
+
reserveMs: 0,
|
|
191
|
+
stallMs: 0,
|
|
192
|
+
waitMs: 0
|
|
193
|
+
};
|
|
141
194
|
/**
|
|
142
195
|
* @returns the new tx watermark, or null if backfill was cancelled
|
|
143
196
|
*/
|
|
144
197
|
const beginTxFor = async (msg) => {
|
|
145
198
|
assert(backfillTx === null, "Expected no active backfill transaction");
|
|
146
199
|
const lastWatermark = await changeStream.reserve("backfill");
|
|
147
|
-
if (state.
|
|
148
|
-
if (state.
|
|
200
|
+
if (state.stopped.signal.aborted || msg.tag === "backfill" && msg.watermark < state.minWatermark) {
|
|
201
|
+
if (!state.stopped.signal.aborted) {
|
|
149
202
|
assert(msg.tag === "backfill", "Expected backfill message tag");
|
|
150
203
|
this.#stopRunningBackfill(`row key change at ${state.minWatermark} postdates backfill watermark at ${msg.watermark}`, state);
|
|
151
204
|
}
|
|
@@ -157,7 +210,7 @@ var BackfillManager = class {
|
|
|
157
210
|
major,
|
|
158
211
|
minor: BigInt(minor) + 1n
|
|
159
212
|
});
|
|
160
|
-
if (
|
|
213
|
+
if (tx < msg.watermark) tx = msg.watermark;
|
|
161
214
|
changeStream.push([
|
|
162
215
|
"begin",
|
|
163
216
|
{
|
|
@@ -177,29 +230,138 @@ var BackfillManager = class {
|
|
|
177
230
|
{ watermark: backfillTx }
|
|
178
231
|
]);
|
|
179
232
|
changeStream.release(backfillTx);
|
|
233
|
+
stats.commits++;
|
|
180
234
|
}
|
|
181
235
|
backfillTx = null;
|
|
182
236
|
uncommittedBytes = 0;
|
|
183
237
|
};
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
238
|
+
const ready = new Queue();
|
|
239
|
+
const credits = new Queue();
|
|
240
|
+
for (let i = 0; i < BACKFILL_PREFETCH_CREDITS; i++) credits.enqueue(1);
|
|
241
|
+
let stopProducer = false;
|
|
242
|
+
const onCancel = () => ready.enqueueRejection(new AbortError("backfill canceled"));
|
|
243
|
+
if (!this.#canceled) this.#controller.signal.addEventListener("abort", onCancel);
|
|
244
|
+
const producer = (async () => {
|
|
245
|
+
try {
|
|
246
|
+
let t0 = performance.now();
|
|
247
|
+
for await (const value of this.#backfillStreamer(state.request)) {
|
|
248
|
+
stats.produceMs += performance.now() - t0;
|
|
249
|
+
await credits.dequeue();
|
|
250
|
+
if (stopProducer || this.#canceled) {
|
|
251
|
+
ready.enqueueRejection(new AbortError("backfill canceled"));
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
ready.enqueue(value);
|
|
255
|
+
t0 = performance.now();
|
|
256
|
+
}
|
|
257
|
+
ready.enqueue(DONE);
|
|
258
|
+
} catch (e) {
|
|
259
|
+
ready.enqueueRejection(e);
|
|
188
260
|
}
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
if (
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
this.#checkAndStartBackfill();
|
|
196
|
-
return;
|
|
261
|
+
})();
|
|
262
|
+
producer.catch(() => {});
|
|
263
|
+
const nextItem = async () => {
|
|
264
|
+
if (backfillTx === null || ready.size() > 0) {
|
|
265
|
+
const n = ready.dequeue();
|
|
266
|
+
return n instanceof Promise ? await n : n;
|
|
197
267
|
}
|
|
198
|
-
|
|
199
|
-
|
|
268
|
+
for (;;) {
|
|
269
|
+
if (backfillTx !== null && changeStream.waiterDelay() > 0) commitTx();
|
|
270
|
+
const n = backfillTx !== null ? ready.dequeue(POLL, BACKFILL_WAITER_POLL_MS) : ready.dequeue();
|
|
271
|
+
const t0 = performance.now();
|
|
272
|
+
const v = n instanceof Promise ? await n : n;
|
|
273
|
+
if (v === POLL) {
|
|
274
|
+
stats.stallMs += performance.now() - t0;
|
|
275
|
+
continue;
|
|
276
|
+
}
|
|
277
|
+
if (n instanceof Promise) {
|
|
278
|
+
stats.stallMs += performance.now() - t0;
|
|
279
|
+
stats.stalls++;
|
|
280
|
+
}
|
|
281
|
+
return v;
|
|
282
|
+
}
|
|
283
|
+
};
|
|
284
|
+
try {
|
|
285
|
+
for (;;) {
|
|
286
|
+
const item = await nextItem();
|
|
287
|
+
if (item === DONE) break;
|
|
288
|
+
credits.enqueue(1);
|
|
289
|
+
if (this.#canceled) {
|
|
290
|
+
lc.info?.(`backfill stream canceled: change stream canceled`);
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
const { message: msg, byteSize } = item;
|
|
294
|
+
const waitForChangeStream = this.#changeStreamReached(lc, msg.watermark);
|
|
295
|
+
if (backfillTx && (changeStream.waiterDelay() > 0 || waitForChangeStream || uncommittedBytes >= this.#commitThresholdBytes)) commitTx();
|
|
296
|
+
if (waitForChangeStream) {
|
|
297
|
+
const t0 = performance.now();
|
|
298
|
+
await promiseOrAbort(waitForChangeStream, state.stopped.signal);
|
|
299
|
+
const elapsed = performance.now() - t0;
|
|
300
|
+
stats.waitMs += elapsed;
|
|
301
|
+
lc.info?.(`change stream reached ${msg.watermark} after waiting ${Math.round(elapsed)} ms`);
|
|
302
|
+
}
|
|
303
|
+
if (msg.tag === "backfill" && msg.rowValues.length > 0 && msg.relation.rowKey.columns.length === 0) throw new MissingRowKeyError(state.request);
|
|
304
|
+
if (backfillTx === null) {
|
|
305
|
+
const t0 = performance.now();
|
|
306
|
+
backfillTx = await beginTxFor(msg);
|
|
307
|
+
stats.reserveMs += performance.now() - t0;
|
|
308
|
+
if (backfillTx === null) {
|
|
309
|
+
lc.info?.(`backfill stream canceled: ${state.stopped.signal.reason}`, state.request);
|
|
310
|
+
this.#checkAndStartBackfill();
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
const t0 = performance.now();
|
|
315
|
+
await changeStream.push(["data", msg]);
|
|
316
|
+
stats.pushMs += performance.now() - t0;
|
|
317
|
+
uncommittedBytes += byteSize;
|
|
318
|
+
stats.messages++;
|
|
319
|
+
stats.bytes += byteSize;
|
|
320
|
+
this.#maybeLogBackfillStats(lc, stats);
|
|
321
|
+
}
|
|
322
|
+
if (backfillTx) commitTx();
|
|
323
|
+
lc.debug?.(`backfill stream exited`);
|
|
324
|
+
} finally {
|
|
325
|
+
stopProducer = true;
|
|
326
|
+
credits.enqueue(1);
|
|
327
|
+
if (!this.#canceled) await producer;
|
|
328
|
+
this.#controller.signal.removeEventListener("abort", onCancel);
|
|
329
|
+
this.#logBackfillStats(lc, stats);
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
#maybeLogBackfillStats(lc, stats) {
|
|
333
|
+
const now = performance.now();
|
|
334
|
+
if (now - stats.lastLogged >= BACKFILL_STATS_LOG_INTERVAL_MS) {
|
|
335
|
+
this.#logBackfillStats(lc, stats);
|
|
336
|
+
stats.lastLogged = now;
|
|
200
337
|
}
|
|
201
|
-
|
|
202
|
-
|
|
338
|
+
}
|
|
339
|
+
/**
|
|
340
|
+
* Logs where the backfill spent its time. `produceMs` (upstream read +
|
|
341
|
+
* parse/decode) accrues on the prefetch producer concurrently with the
|
|
342
|
+
* consumer's `pushMs` (downstream backpressure), so the two can overlap and
|
|
343
|
+
* their sum can exceed `elapsedMs`. `reserveMs` is time spent (re)acquiring
|
|
344
|
+
* the change-stream reservation, and `stallMs` is time the consumer waited on
|
|
345
|
+
* an empty prefetch queue (i.e. blocked on the upstream) — a large `stallMs`
|
|
346
|
+
* indicates an upstream-bound backfill. `waitMs` is time spent waiting
|
|
347
|
+
* (without the reservation) for the change stream to reach the backfill
|
|
348
|
+
* watermark, during which the upstream snapshot is held open.
|
|
349
|
+
*/
|
|
350
|
+
#logBackfillStats(lc, stats) {
|
|
351
|
+
const elapsed = performance.now() - stats.start;
|
|
352
|
+
lc.info?.(`backfill timing`, {
|
|
353
|
+
messages: stats.messages,
|
|
354
|
+
bytes: stats.bytes,
|
|
355
|
+
commits: stats.commits,
|
|
356
|
+
stalls: stats.stalls,
|
|
357
|
+
elapsedMs: Math.round(elapsed),
|
|
358
|
+
produceMs: Math.round(stats.produceMs),
|
|
359
|
+
pushMs: Math.round(stats.pushMs),
|
|
360
|
+
reserveMs: Math.round(stats.reserveMs),
|
|
361
|
+
stallMs: Math.round(stats.stallMs),
|
|
362
|
+
waitMs: Math.round(stats.waitMs),
|
|
363
|
+
throughputKBps: elapsed > 0 ? Math.round(stats.bytes / 1024 / (elapsed / 1e3)) : 0
|
|
364
|
+
});
|
|
203
365
|
}
|
|
204
366
|
#backfillRunningFor(table) {
|
|
205
367
|
const state = this.#runningBackfill;
|
|
@@ -214,7 +376,7 @@ var BackfillManager = class {
|
|
|
214
376
|
#stopRunningBackfill(reason, instance) {
|
|
215
377
|
const backfill = this.#runningBackfill;
|
|
216
378
|
if (backfill && backfill === (instance ?? backfill)) {
|
|
217
|
-
backfill.
|
|
379
|
+
backfill.stopped.abort(reason);
|
|
218
380
|
this.#runningBackfill = null;
|
|
219
381
|
reason && this.#lc.info?.(`canceling backfill:`, reason);
|
|
220
382
|
}
|
|
@@ -278,7 +440,7 @@ var BackfillManager = class {
|
|
|
278
440
|
name,
|
|
279
441
|
metadata
|
|
280
442
|
},
|
|
281
|
-
columns: backfill
|
|
443
|
+
columns: Object.fromEntries(Object.entries(backfill).map(([col, id]) => [col, { id }]))
|
|
282
444
|
});
|
|
283
445
|
break;
|
|
284
446
|
}
|
|
@@ -317,7 +479,7 @@ var BackfillManager = class {
|
|
|
317
479
|
...table,
|
|
318
480
|
metadata
|
|
319
481
|
},
|
|
320
|
-
columns: { [column.name]: backfill }
|
|
482
|
+
columns: { [column.name]: { id: backfill } }
|
|
321
483
|
});
|
|
322
484
|
else this.#setRequiredBackfill(tag, {
|
|
323
485
|
...backfillRequest,
|
|
@@ -327,7 +489,7 @@ var BackfillManager = class {
|
|
|
327
489
|
},
|
|
328
490
|
columns: {
|
|
329
491
|
...backfillRequest.columns,
|
|
330
|
-
[column.name]: backfill
|
|
492
|
+
[column.name]: { id: backfill }
|
|
331
493
|
}
|
|
332
494
|
});
|
|
333
495
|
}
|
|
@@ -396,7 +558,7 @@ var BackfillManager = class {
|
|
|
396
558
|
}
|
|
397
559
|
}
|
|
398
560
|
cancel() {
|
|
399
|
-
this.#canceled
|
|
561
|
+
this.#controller.abort("canceled");
|
|
400
562
|
this.#stopRunningBackfill(`change stream canceled`);
|
|
401
563
|
clearTimeout(this.#backfillRetryTimer);
|
|
402
564
|
this.#backfillRetryTimer = void 0;
|