@rocicorp/zero 1.10.0-canary.21 → 1.10.0-canary.23
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/bin-analyze.js +1 -1
- package/out/analyze-query/src/bin-analyze.js.map +1 -1
- package/out/replicache/src/dag/gc.d.ts.map +1 -1
- package/out/replicache/src/dag/gc.js +2 -1
- package/out/replicache/src/dag/gc.js.map +1 -1
- package/out/replicache/src/dag/invalid-ref-count-error.d.ts +12 -0
- package/out/replicache/src/dag/invalid-ref-count-error.d.ts.map +1 -0
- package/out/replicache/src/dag/invalid-ref-count-error.js +19 -0
- package/out/replicache/src/dag/invalid-ref-count-error.js.map +1 -0
- package/out/replicache/src/dag/store-impl.d.ts +12 -2
- package/out/replicache/src/dag/store-impl.d.ts.map +1 -1
- package/out/replicache/src/dag/store-impl.js +22 -8
- package/out/replicache/src/dag/store-impl.js.map +1 -1
- package/out/replicache/src/kv/expo-sqlite/store.d.ts.map +1 -1
- package/out/replicache/src/kv/expo-sqlite/store.js +1 -1
- package/out/replicache/src/kv/expo-sqlite/store.js.map +1 -1
- package/out/replicache/src/kv/idb-store-with-mem-fallback.d.ts +15 -5
- package/out/replicache/src/kv/idb-store-with-mem-fallback.d.ts.map +1 -1
- package/out/replicache/src/kv/idb-store-with-mem-fallback.js +25 -25
- package/out/replicache/src/kv/idb-store-with-mem-fallback.js.map +1 -1
- package/out/replicache/src/kv/idb-store.d.ts +9 -0
- package/out/replicache/src/kv/idb-store.d.ts.map +1 -1
- package/out/replicache/src/kv/idb-store.js +14 -2
- package/out/replicache/src/kv/idb-store.js.map +1 -1
- package/out/replicache/src/kv/mem-store.d.ts +1 -0
- package/out/replicache/src/kv/mem-store.d.ts.map +1 -1
- package/out/replicache/src/kv/mem-store.js +3 -0
- package/out/replicache/src/kv/mem-store.js.map +1 -1
- package/out/replicache/src/kv/op-sqlite/store.d.ts.map +1 -1
- package/out/replicache/src/kv/op-sqlite/store.js +1 -1
- package/out/replicache/src/kv/op-sqlite/store.js.map +1 -1
- package/out/replicache/src/kv/sqlite-store.d.ts +2 -1
- package/out/replicache/src/kv/sqlite-store.d.ts.map +1 -1
- package/out/replicache/src/kv/sqlite-store.js +6 -1
- package/out/replicache/src/kv/sqlite-store.js.map +1 -1
- package/out/replicache/src/kv/store.d.ts +6 -0
- package/out/replicache/src/kv/store.d.ts.map +1 -1
- package/out/replicache/src/persist/collect-idb-databases.d.ts +10 -5
- package/out/replicache/src/persist/collect-idb-databases.d.ts.map +1 -1
- package/out/replicache/src/persist/collect-idb-databases.js +16 -9
- package/out/replicache/src/persist/collect-idb-databases.js.map +1 -1
- package/out/replicache/src/replicache-impl.d.ts +14 -3
- package/out/replicache/src/replicache-impl.d.ts.map +1 -1
- package/out/replicache/src/replicache-impl.js +104 -9
- package/out/replicache/src/replicache-impl.js.map +1 -1
- package/out/shared/src/arrays.js +1 -15
- package/out/shared/src/arrays.js.map +1 -1
- package/out/shared/src/hash.d.ts +1 -0
- package/out/shared/src/hash.d.ts.map +1 -1
- package/out/shared/src/hash.js +10 -28
- package/out/shared/src/hash.js.map +1 -1
- package/out/shared/src/iterables.d.ts +2 -0
- package/out/shared/src/iterables.d.ts.map +1 -1
- package/out/shared/src/iterables.js +18 -1
- package/out/shared/src/iterables.js.map +1 -1
- package/out/shared/src/json.d.ts.map +1 -1
- package/out/shared/src/json.js +2 -2
- package/out/shared/src/json.js.map +1 -1
- package/out/shared/src/ring-buffer.d.ts +8 -0
- package/out/shared/src/ring-buffer.d.ts.map +1 -1
- package/out/shared/src/ring-buffer.js +22 -1
- package/out/shared/src/ring-buffer.js.map +1 -1
- package/out/shared/src/xxhash32.d.ts +36 -0
- package/out/shared/src/xxhash32.d.ts.map +1 -0
- package/out/shared/src/xxhash32.js +51 -0
- package/out/shared/src/xxhash32.js.map +1 -0
- package/out/z2s/src/compiler.js +1 -1
- package/out/zero/package.js +1 -1
- package/out/zero/package.js.map +1 -1
- package/out/zero-cache/src/auth/read-authorizer.js +1 -1
- package/out/zero-cache/src/config/zero-config.d.ts +8 -0
- package/out/zero-cache/src/config/zero-config.d.ts.map +1 -1
- package/out/zero-cache/src/config/zero-config.js +28 -1
- package/out/zero-cache/src/config/zero-config.js.map +1 -1
- package/out/zero-cache/src/custom-queries/transform-query.js +1 -1
- package/out/zero-cache/src/server/change-streamer.d.ts.map +1 -1
- package/out/zero-cache/src/server/change-streamer.js +11 -3
- package/out/zero-cache/src/server/change-streamer.js.map +1 -1
- package/out/zero-cache/src/server/inspector-delegate.d.ts +18 -2
- package/out/zero-cache/src/server/inspector-delegate.d.ts.map +1 -1
- package/out/zero-cache/src/server/inspector-delegate.js +23 -9
- package/out/zero-cache/src/server/inspector-delegate.js.map +1 -1
- package/out/zero-cache/src/server/main.d.ts.map +1 -1
- package/out/zero-cache/src/server/main.js +8 -1
- package/out/zero-cache/src/server/main.js.map +1 -1
- package/out/zero-cache/src/server/mutator.js +2 -0
- package/out/zero-cache/src/server/mutator.js.map +1 -1
- package/out/zero-cache/src/server/reaper.d.ts.map +1 -1
- package/out/zero-cache/src/server/reaper.js +2 -0
- package/out/zero-cache/src/server/reaper.js.map +1 -1
- package/out/zero-cache/src/server/replicator.d.ts.map +1 -1
- package/out/zero-cache/src/server/replicator.js +2 -0
- package/out/zero-cache/src/server/replicator.js.map +1 -1
- package/out/zero-cache/src/server/runner/zero-dispatcher.d.ts.map +1 -1
- package/out/zero-cache/src/server/runner/zero-dispatcher.js +3 -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 +2 -0
- package/out/zero-cache/src/server/syncer.js.map +1 -1
- package/out/zero-cache/src/services/analyze.d.ts.map +1 -1
- package/out/zero-cache/src/services/analyze.js +3 -2
- package/out/zero-cache/src/services/analyze.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 +14 -1
- package/out/zero-cache/src/services/change-source/common/backfill-manager.js.map +1 -1
- package/out/zero-cache/src/services/change-source/common/change-stream-multiplexer.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-source/common/change-stream-multiplexer.js +15 -3
- package/out/zero-cache/src/services/change-source/common/change-stream-multiplexer.js.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/initial-sync.js +1 -1
- package/out/zero-cache/src/services/change-streamer/change-streamer-http.d.ts +3 -0
- package/out/zero-cache/src/services/change-streamer/change-streamer-http.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-streamer/change-streamer-http.js +3 -1
- package/out/zero-cache/src/services/change-streamer/change-streamer-http.js.map +1 -1
- package/out/zero-cache/src/services/mutagen/mutagen.d.ts +5 -0
- package/out/zero-cache/src/services/mutagen/mutagen.d.ts.map +1 -1
- package/out/zero-cache/src/services/mutagen/mutagen.js +7 -1
- package/out/zero-cache/src/services/mutagen/mutagen.js.map +1 -1
- package/out/zero-cache/src/services/mutagen/pusher.js +3 -2
- package/out/zero-cache/src/services/mutagen/pusher.js.map +1 -1
- package/out/zero-cache/src/services/profz.d.ts +7 -0
- package/out/zero-cache/src/services/profz.d.ts.map +1 -0
- package/out/zero-cache/src/services/profz.js +103 -0
- package/out/zero-cache/src/services/profz.js.map +1 -0
- package/out/zero-cache/src/services/run-ast.d.ts +12 -0
- package/out/zero-cache/src/services/run-ast.d.ts.map +1 -1
- package/out/zero-cache/src/services/run-ast.js +27 -15
- package/out/zero-cache/src/services/run-ast.js.map +1 -1
- package/out/zero-cache/src/services/runner.d.ts.map +1 -1
- package/out/zero-cache/src/services/runner.js +1 -1
- package/out/zero-cache/src/services/runner.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/cvr.d.ts +45 -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 +133 -1
- package/out/zero-cache/src/services/view-syncer/cvr.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/drain-coordinator.d.ts +14 -1
- package/out/zero-cache/src/services/view-syncer/drain-coordinator.d.ts.map +1 -1
- package/out/zero-cache/src/services/view-syncer/drain-coordinator.js +30 -4
- package/out/zero-cache/src/services/view-syncer/drain-coordinator.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/hydration-circuit-breaker.d.ts +51 -0
- package/out/zero-cache/src/services/view-syncer/hydration-circuit-breaker.d.ts.map +1 -0
- package/out/zero-cache/src/services/view-syncer/hydration-circuit-breaker.js +78 -0
- package/out/zero-cache/src/services/view-syncer/hydration-circuit-breaker.js.map +1 -0
- package/out/zero-cache/src/services/view-syncer/inspect-handler.d.ts +1 -1
- package/out/zero-cache/src/services/view-syncer/inspect-handler.d.ts.map +1 -1
- package/out/zero-cache/src/services/view-syncer/inspect-handler.js +2 -2
- package/out/zero-cache/src/services/view-syncer/inspect-handler.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/pipeline-driver.d.ts +2 -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 +33 -18
- package/out/zero-cache/src/services/view-syncer/pipeline-driver.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/query-covering.d.ts.map +1 -1
- package/out/zero-cache/src/services/view-syncer/query-covering.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/snapshotter.d.ts +4 -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 +9 -3
- 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 +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 +470 -90
- package/out/zero-cache/src/services/view-syncer/view-syncer.js.map +1 -1
- package/out/zero-cache/src/types/processes.d.ts +32 -0
- package/out/zero-cache/src/types/processes.d.ts.map +1 -1
- package/out/zero-cache/src/types/processes.js +43 -4
- package/out/zero-cache/src/types/processes.js.map +1 -1
- package/out/zero-cache/src/types/profiler.d.ts +12 -0
- package/out/zero-cache/src/types/profiler.d.ts.map +1 -1
- package/out/zero-cache/src/types/profiler.js +46 -1
- package/out/zero-cache/src/types/profiler.js.map +1 -1
- package/out/zero-cache/src/workers/syncer-ws-message-handler.d.ts.map +1 -1
- package/out/zero-cache/src/workers/syncer-ws-message-handler.js +21 -16
- package/out/zero-cache/src/workers/syncer-ws-message-handler.js.map +1 -1
- package/out/zero-cache/src/workers/syncer.d.ts.map +1 -1
- package/out/zero-cache/src/workers/syncer.js +10 -1
- package/out/zero-cache/src/workers/syncer.js.map +1 -1
- package/out/zero-client/src/client/connection-manager.d.ts +20 -0
- package/out/zero-client/src/client/connection-manager.d.ts.map +1 -1
- package/out/zero-client/src/client/connection-manager.js +32 -14
- package/out/zero-client/src/client/connection-manager.js.map +1 -1
- package/out/zero-client/src/client/connection-status-enum.d.ts +2 -0
- package/out/zero-client/src/client/connection-status-enum.d.ts.map +1 -1
- package/out/zero-client/src/client/connection-status-enum.js +3 -1
- package/out/zero-client/src/client/connection-status-enum.js.map +1 -1
- package/out/zero-client/src/client/connection.d.ts +6 -0
- package/out/zero-client/src/client/connection.d.ts.map +1 -1
- package/out/zero-client/src/client/connection.js +2 -1
- package/out/zero-client/src/client/connection.js.map +1 -1
- package/out/zero-client/src/client/context.d.ts +22 -12
- package/out/zero-client/src/client/context.d.ts.map +1 -1
- package/out/zero-client/src/client/context.js +92 -25
- package/out/zero-client/src/client/context.js.map +1 -1
- package/out/zero-client/src/client/custom.js +2 -2
- package/out/zero-client/src/client/custom.js.map +1 -1
- package/out/zero-client/src/client/error.js +1 -1
- package/out/zero-client/src/client/ivm-branch.d.ts.map +1 -1
- package/out/zero-client/src/client/ivm-branch.js +20 -3
- package/out/zero-client/src/client/ivm-branch.js.map +1 -1
- package/out/zero-client/src/client/mutator-proxy.d.ts.map +1 -1
- package/out/zero-client/src/client/mutator-proxy.js +2 -1
- package/out/zero-client/src/client/mutator-proxy.js.map +1 -1
- package/out/zero-client/src/client/options.d.ts +3 -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/query-manager.d.ts +2 -0
- package/out/zero-client/src/client/query-manager.d.ts.map +1 -1
- package/out/zero-client/src/client/query-manager.js +48 -9
- package/out/zero-client/src/client/query-manager.js.map +1 -1
- package/out/zero-client/src/client/version.js +1 -1
- package/out/zero-client/src/client/zero-rep.d.ts.map +1 -1
- package/out/zero-client/src/client/zero-rep.js +1 -1
- package/out/zero-client/src/client/zero-rep.js.map +1 -1
- package/out/zero-client/src/client/zero.d.ts +18 -3
- package/out/zero-client/src/client/zero.d.ts.map +1 -1
- package/out/zero-client/src/client/zero.js +61 -14
- package/out/zero-client/src/client/zero.js.map +1 -1
- package/out/zero-client/src/types/query-result.d.ts +2 -0
- package/out/zero-client/src/types/query-result.d.ts.map +1 -1
- package/out/zero-protocol/src/ast.d.ts +64 -1
- package/out/zero-protocol/src/ast.d.ts.map +1 -1
- package/out/zero-protocol/src/ast.js +223 -51
- package/out/zero-protocol/src/ast.js.map +1 -1
- package/out/zero-protocol/src/error.d.ts +1 -1
- package/out/zero-protocol/src/query-hash-visitor.d.ts +81 -0
- package/out/zero-protocol/src/query-hash-visitor.d.ts.map +1 -0
- package/out/zero-protocol/src/query-hash-visitor.js +324 -0
- package/out/zero-protocol/src/query-hash-visitor.js.map +1 -0
- package/out/zero-protocol/src/query-hash.d.ts +7 -2
- package/out/zero-protocol/src/query-hash.d.ts.map +1 -1
- package/out/zero-protocol/src/query-hash.js +12 -9
- package/out/zero-protocol/src/query-hash.js.map +1 -1
- package/out/zero-react/src/use-query.d.ts +5 -4
- package/out/zero-react/src/use-query.d.ts.map +1 -1
- package/out/zero-react/src/use-query.js +86 -9
- package/out/zero-react/src/use-query.js.map +1 -1
- package/out/zero-solid/src/solid-view.d.ts +17 -0
- package/out/zero-solid/src/solid-view.d.ts.map +1 -1
- package/out/zero-solid/src/solid-view.js +39 -1
- package/out/zero-solid/src/solid-view.js.map +1 -1
- package/out/zero-solid/src/use-query.js +5 -3
- package/out/zero-solid/src/use-query.js.map +1 -1
- package/out/zql/src/builder/debug-delegate.d.ts +6 -1
- package/out/zql/src/builder/debug-delegate.d.ts.map +1 -1
- package/out/zql/src/builder/debug-delegate.js +14 -2
- package/out/zql/src/builder/debug-delegate.js.map +1 -1
- package/out/zql/src/ivm/array-view.d.ts +29 -0
- package/out/zql/src/ivm/array-view.d.ts.map +1 -1
- package/out/zql/src/ivm/array-view.js +58 -9
- package/out/zql/src/ivm/array-view.js.map +1 -1
- package/out/zql/src/ivm/cap.d.ts.map +1 -1
- package/out/zql/src/ivm/cap.js +5 -3
- package/out/zql/src/ivm/cap.js.map +1 -1
- package/out/zql/src/ivm/deferred-input.d.ts.map +1 -1
- package/out/zql/src/ivm/deferred-input.js +3 -2
- package/out/zql/src/ivm/deferred-input.js.map +1 -1
- package/out/zql/src/ivm/exists.d.ts +1 -1
- package/out/zql/src/ivm/exists.d.ts.map +1 -1
- package/out/zql/src/ivm/exists.js +1 -1
- package/out/zql/src/ivm/exists.js.map +1 -1
- package/out/zql/src/ivm/fan-in.d.ts +2 -2
- package/out/zql/src/ivm/fan-in.d.ts.map +1 -1
- package/out/zql/src/ivm/fan-in.js +6 -5
- package/out/zql/src/ivm/fan-in.js.map +1 -1
- package/out/zql/src/ivm/fan-out.d.ts +2 -2
- package/out/zql/src/ivm/fan-out.d.ts.map +1 -1
- package/out/zql/src/ivm/fan-out.js.map +1 -1
- package/out/zql/src/ivm/filter-operators.d.ts +4 -4
- package/out/zql/src/ivm/filter-operators.d.ts.map +1 -1
- package/out/zql/src/ivm/filter-operators.js +10 -8
- package/out/zql/src/ivm/filter-operators.js.map +1 -1
- package/out/zql/src/ivm/filter.d.ts +2 -2
- package/out/zql/src/ivm/filter.d.ts.map +1 -1
- package/out/zql/src/ivm/filter.js +7 -4
- package/out/zql/src/ivm/filter.js.map +1 -1
- package/out/zql/src/ivm/flipped-join.js +2 -2
- package/out/zql/src/ivm/flipped-join.js.map +1 -1
- package/out/zql/src/ivm/maybe-split-and-push-edit-change.d.ts +1 -1
- package/out/zql/src/ivm/maybe-split-and-push-edit-change.d.ts.map +1 -1
- package/out/zql/src/ivm/maybe-split-and-push-edit-change.js +6 -4
- package/out/zql/src/ivm/maybe-split-and-push-edit-change.js.map +1 -1
- package/out/zql/src/ivm/memory-source.d.ts +15 -3
- package/out/zql/src/ivm/memory-source.d.ts.map +1 -1
- package/out/zql/src/ivm/memory-source.js +35 -9
- package/out/zql/src/ivm/memory-source.js.map +1 -1
- package/out/zql/src/ivm/take.d.ts.map +1 -1
- package/out/zql/src/ivm/take.js +3 -4
- package/out/zql/src/ivm/take.js.map +1 -1
- package/out/zql/src/ivm/union-fan-in.d.ts.map +1 -1
- package/out/zql/src/ivm/union-fan-in.js +9 -7
- package/out/zql/src/ivm/union-fan-in.js.map +1 -1
- package/out/zql/src/ivm/view-apply-change.d.ts.map +1 -1
- package/out/zql/src/ivm/view-apply-change.js +3 -3
- package/out/zql/src/ivm/view-apply-change.js.map +1 -1
- package/out/zql/src/ivm/view.d.ts +15 -0
- package/out/zql/src/ivm/view.d.ts.map +1 -1
- package/out/zql/src/mutate/mutator-registry.d.ts.map +1 -1
- package/out/zql/src/mutate/mutator-registry.js +2 -2
- package/out/zql/src/mutate/mutator-registry.js.map +1 -1
- package/out/zql/src/query/query-delegate-base.d.ts +4 -2
- package/out/zql/src/query/query-delegate-base.d.ts.map +1 -1
- package/out/zql/src/query/query-delegate-base.js +69 -36
- package/out/zql/src/query/query-delegate-base.js.map +1 -1
- package/out/zql/src/query/query-delegate.d.ts +33 -6
- package/out/zql/src/query/query-delegate.d.ts.map +1 -1
- package/out/zql/src/query/query-impl.d.ts +25 -14
- package/out/zql/src/query/query-impl.d.ts.map +1 -1
- package/out/zql/src/query/query-impl.js +414 -94
- package/out/zql/src/query/query-impl.js.map +1 -1
- package/out/zql/src/query/query-internals.d.ts +21 -11
- package/out/zql/src/query/query-internals.d.ts.map +1 -1
- package/out/zql/src/query/query-internals.js.map +1 -1
- package/out/zql/src/query/query-transitions.d.ts +171 -0
- package/out/zql/src/query/query-transitions.d.ts.map +1 -0
- package/out/zql/src/query/query-transitions.js +207 -0
- package/out/zql/src/query/query-transitions.js.map +1 -0
- package/out/zql/src/query/query.d.ts +11 -2
- package/out/zql/src/query/query.d.ts.map +1 -1
- package/out/zql/src/query/runnable-query-impl.d.ts +3 -2
- package/out/zql/src/query/runnable-query-impl.d.ts.map +1 -1
- package/out/zql/src/query/runnable-query-impl.js +23 -2
- package/out/zql/src/query/runnable-query-impl.js.map +1 -1
- package/out/zql/src/query/typed-view.d.ts +1 -1
- package/out/zql/src/query/typed-view.d.ts.map +1 -1
- package/out/zqlite/src/db.d.ts +9 -1
- package/out/zqlite/src/db.d.ts.map +1 -1
- package/out/zqlite/src/db.js +8 -2
- package/out/zqlite/src/db.js.map +1 -1
- package/out/zqlite/src/table-source.d.ts +1 -0
- package/out/zqlite/src/table-source.d.ts.map +1 -1
- package/out/zqlite/src/table-source.js +10 -5
- package/out/zqlite/src/table-source.js.map +1 -1
- package/package.json +1 -1
|
@@ -11,12 +11,11 @@ import { manualSpan, startAsyncSpan, startSpan } from "../../../../otel/src/span
|
|
|
11
11
|
import { ZERO_VERSION_COLUMN_NAME } from "../replicator/schema/constants.js";
|
|
12
12
|
import { transformAndHashQuery } from "../../auth/read-authorizer.js";
|
|
13
13
|
import { randInt } from "../../../../shared/src/rand.js";
|
|
14
|
-
import { getOrCreateCounter, getOrCreateLatencyHistogram, getOrCreateNativeHistogram, getOrCreateUpDownCounter } from "../../observability/metrics.js";
|
|
14
|
+
import { getOrCreateCounter, getOrCreateLatencyHistogram, getOrCreateNativeHistogram, getOrCreateUpDownCounter, getOrCreateValueHistogram } from "../../observability/metrics.js";
|
|
15
15
|
import { rowIDString } from "../../types/row-key.js";
|
|
16
16
|
import "../replicator/schema/replication-state.js";
|
|
17
17
|
import { parseSignature } from "./row-set-signature.js";
|
|
18
18
|
import { ResetPipelinesSignal } from "./snapshotter.js";
|
|
19
|
-
import "./pipeline-driver.js";
|
|
20
19
|
import { Subscription } from "../../types/subscription.js";
|
|
21
20
|
import { CustomKeyMap } from "../../../../shared/src/custom-key-map.js";
|
|
22
21
|
import { ttlClockAsNumber, ttlClockFromNumber } from "./ttl-clock.js";
|
|
@@ -26,8 +25,10 @@ import { isAuthErrorBody } from "../../auth/auth.js";
|
|
|
26
25
|
import { ClientHandler, startPoke } from "./client-handler.js";
|
|
27
26
|
import { tracer } from "./tracer.js";
|
|
28
27
|
import { CVRStore, ClientNotFoundError } from "./cvr-store.js";
|
|
29
|
-
import { CVRConfigDrivenUpdater, CVRQueryDrivenUpdater, nextEvictionTime } from "./cvr.js";
|
|
28
|
+
import { CVRConfigDrivenUpdater, CVRQueryDrivenUpdater, classifyQueriesForHydration, nextEvictionTime } from "./cvr.js";
|
|
30
29
|
import { E2EServingLagTracker } from "./e2e-serving-lag.js";
|
|
30
|
+
import { HydrationBudget } from "./hydration-budget.js";
|
|
31
|
+
import { HydrationCircuitBreaker } from "./hydration-circuit-breaker.js";
|
|
31
32
|
import { handleInspect } from "./inspect-handler.js";
|
|
32
33
|
import { QueryCoveringIndex } from "./query-covering.js";
|
|
33
34
|
import { resolver } from "@rocicorp/resolver";
|
|
@@ -62,6 +63,7 @@ var ViewSyncerService = class {
|
|
|
62
63
|
#drainCoordinator;
|
|
63
64
|
#keepaliveMs;
|
|
64
65
|
#slowHydrateThreshold;
|
|
66
|
+
#hydrationCircuitBreaker;
|
|
65
67
|
#lastConnectTime = Date.now();
|
|
66
68
|
/**
|
|
67
69
|
* The TTL clock is used to determine the time at which queries are considered
|
|
@@ -89,14 +91,36 @@ var ViewSyncerService = class {
|
|
|
89
91
|
#lock = new Lock();
|
|
90
92
|
#cvrStore;
|
|
91
93
|
#stopped = resolver();
|
|
94
|
+
/**
|
|
95
|
+
* Set when {@link #cleanup} begins. Lock tasks that were in flight when the
|
|
96
|
+
* view-syncer was stopped may still complete after the timers have been
|
|
97
|
+
* cleared; this flag prevents them from scheduling new timers, which would
|
|
98
|
+
* otherwise outlive the service (and retain everything it references).
|
|
99
|
+
*/
|
|
100
|
+
#shuttingDown = false;
|
|
92
101
|
#initialized = resolver();
|
|
93
102
|
#cvr;
|
|
94
|
-
|
|
103
|
+
/**
|
|
104
|
+
* Indicates whether the query pipelines have completed initial catch-up and
|
|
105
|
+
* hydration with the CVR snapshot, and are ready for steady-state operation.
|
|
106
|
+
*
|
|
107
|
+
* - When `false`: The syncer is in its initial catch-up phase. The replica
|
|
108
|
+
* advances without diffs until it reaches `cvr.version.stateVersion`,
|
|
109
|
+
* after which `#maybeHydratePipelines` hydrates queries.
|
|
110
|
+
* - When `true`: Pipelines are fully populated and in steady-state; replica
|
|
111
|
+
* changes advance incrementally via `#advancePipelines`, and client query
|
|
112
|
+
* updates are processed via `#syncQueryPipelineSet`.
|
|
113
|
+
*
|
|
114
|
+
* Resets to `false` if `#advancePipelines` returns a `ResetPipelinesSignal`
|
|
115
|
+
* and pipelines must be reset and rehydrated.
|
|
116
|
+
*/
|
|
117
|
+
#pipelinesHydrated = false;
|
|
95
118
|
#servedVersion = null;
|
|
96
119
|
#e2eServingLagTracker = new E2EServingLagTracker();
|
|
97
120
|
#expiredQueriesTimer = 0;
|
|
98
121
|
#authMaintenanceTimer = 0;
|
|
99
122
|
#setTimeout;
|
|
123
|
+
#now;
|
|
100
124
|
#customQueryTransformer;
|
|
101
125
|
#queryReplacements = /* @__PURE__ */ new Map();
|
|
102
126
|
#activeClients = getOrCreateUpDownCounter("sync", "active-clients", "Number of active sync clients");
|
|
@@ -123,10 +147,64 @@ var ViewSyncerService = class {
|
|
|
123
147
|
#pipelineResets = getOrCreateCounter("sync", "pipeline-resets", "Number of pipeline resets");
|
|
124
148
|
#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).");
|
|
125
149
|
#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.");
|
|
150
|
+
#hydrationBudgetExhaustions = getOrCreateCounter("sync", "hydration_budget_exhaustions", {
|
|
151
|
+
description: "Number of hydration passes that exhaust their budget.",
|
|
152
|
+
unit: "{pass}"
|
|
153
|
+
});
|
|
154
|
+
#hydrationBudgetEvictions = getOrCreateCounter("sync", "hydration_budget_evictions", {
|
|
155
|
+
description: "Number of inactive queries removed after hydration budget exhaustion.",
|
|
156
|
+
unit: "{query}"
|
|
157
|
+
});
|
|
158
|
+
#hydrationBudgetElapsed = getOrCreateValueHistogram("sync", "hydration_budget_elapsed", {
|
|
159
|
+
description: "Elapsed milliseconds when optional query hydration stopped.",
|
|
160
|
+
unit: "ms",
|
|
161
|
+
bucketBoundaries: [
|
|
162
|
+
1,
|
|
163
|
+
2,
|
|
164
|
+
5,
|
|
165
|
+
10,
|
|
166
|
+
20,
|
|
167
|
+
50,
|
|
168
|
+
100,
|
|
169
|
+
200,
|
|
170
|
+
500,
|
|
171
|
+
1e3,
|
|
172
|
+
5e3
|
|
173
|
+
]
|
|
174
|
+
});
|
|
175
|
+
#hydrationBudgetOvershoot = getOrCreateValueHistogram("sync", "hydration_budget_overshoot", {
|
|
176
|
+
description: "Milliseconds elapsed beyond the configured hydration budget.",
|
|
177
|
+
unit: "ms",
|
|
178
|
+
bucketBoundaries: [
|
|
179
|
+
0,
|
|
180
|
+
1,
|
|
181
|
+
2,
|
|
182
|
+
5,
|
|
183
|
+
10,
|
|
184
|
+
20,
|
|
185
|
+
50,
|
|
186
|
+
100,
|
|
187
|
+
200,
|
|
188
|
+
500,
|
|
189
|
+
1e3
|
|
190
|
+
]
|
|
191
|
+
});
|
|
192
|
+
#queryEvictions = getOrCreateCounter("sync", "query_evictions", {
|
|
193
|
+
description: "Number of queries evicted from the CVR ahead of their removal by the client, grouped by reason. Inactive queries are evicted by ttl and hydration-budget; hydration-timeout and hydration-circuit-breaker evict active queries as well.",
|
|
194
|
+
unit: "{query}"
|
|
195
|
+
});
|
|
196
|
+
#hydrationTimeouts = getOrCreateCounter("sync", "hydration_timeouts", {
|
|
197
|
+
description: "Number of query hydrations aborted for exceeding the query hydration timeout.",
|
|
198
|
+
unit: "{query}"
|
|
199
|
+
});
|
|
200
|
+
#hydrationCircuitBreakerRejections = getOrCreateCounter("sync", "hydration_circuit_breaker_rejections", {
|
|
201
|
+
description: "Number of queries rejected without hydration because their hydration circuit breaker was open.",
|
|
202
|
+
unit: "{query}"
|
|
203
|
+
});
|
|
126
204
|
#inspectorDelegate;
|
|
127
205
|
#config;
|
|
128
206
|
#runPriorityOp;
|
|
129
|
-
constructor(config, lc, shard, taskID, clientGroupID, cvrDb, pipelineDriver, versionChanges, drainCoordinator, slowHydrateThreshold, inspectorDelegate, connContextManager, customQueryTransformer, runPriorityOp, keepaliveMs = DEFAULT_KEEPALIVE_MS, setTimeoutFn = setTimeout.bind(globalThis)) {
|
|
207
|
+
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)) {
|
|
130
208
|
this.#config = config;
|
|
131
209
|
this.id = clientGroupID;
|
|
132
210
|
this.connContextManager = connContextManager;
|
|
@@ -141,6 +219,8 @@ var ViewSyncerService = class {
|
|
|
141
219
|
this.#customQueryTransformer = customQueryTransformer;
|
|
142
220
|
this.#cvrStore = new CVRStore(lc, cvrDb, shard, taskID, clientGroupID, () => this.#stateChanges.cancel());
|
|
143
221
|
this.#setTimeout = setTimeoutFn;
|
|
222
|
+
this.#now = now;
|
|
223
|
+
this.#hydrationCircuitBreaker = new HydrationCircuitBreaker(config.viewSyncerQueryHydrationTimeoutMs ?? 0, void 0, now);
|
|
144
224
|
this.#runPriorityOp = runPriorityOp;
|
|
145
225
|
this.keepalive();
|
|
146
226
|
}
|
|
@@ -190,10 +270,20 @@ var ViewSyncerService = class {
|
|
|
190
270
|
});
|
|
191
271
|
}
|
|
192
272
|
readyState() {
|
|
193
|
-
return Promise
|
|
273
|
+
return new Promise((resolve, reject) => {
|
|
274
|
+
const unsubscribe = this.#drainCoordinator.onDraining(() => resolve("draining"));
|
|
275
|
+
this.#initialized.promise.then((state) => {
|
|
276
|
+
unsubscribe();
|
|
277
|
+
resolve(state);
|
|
278
|
+
}, (err) => {
|
|
279
|
+
unsubscribe();
|
|
280
|
+
reject(err);
|
|
281
|
+
});
|
|
282
|
+
});
|
|
194
283
|
}
|
|
195
284
|
async run() {
|
|
196
285
|
try {
|
|
286
|
+
this.#scheduleShutdown(this.#keepaliveMs);
|
|
197
287
|
if (await this.readyState() === "draining") {
|
|
198
288
|
this.#lc.debug?.(`draining view-syncer ${this.id} before running`);
|
|
199
289
|
this.stop();
|
|
@@ -214,26 +304,29 @@ var ViewSyncerService = class {
|
|
|
214
304
|
lc.info?.(`resetting CVR: ${message}`);
|
|
215
305
|
throw new ClientNotFoundError(message);
|
|
216
306
|
}
|
|
217
|
-
|
|
307
|
+
let previousQueries;
|
|
308
|
+
if (this.#pipelinesHydrated) {
|
|
218
309
|
const result = await this.#advancePipelines(lc, cvr);
|
|
219
310
|
if (result === "success") return;
|
|
220
311
|
lc.info?.(`resetting pipelines: ${result.message}`);
|
|
221
312
|
this.#pipelineResets.add(1, { reason: result.reason });
|
|
313
|
+
switch (result.reason) {
|
|
314
|
+
case "advancement-timeout":
|
|
315
|
+
case "scalar-subquery":
|
|
316
|
+
case "truncation":
|
|
317
|
+
case "schema-change":
|
|
318
|
+
if (!Object.values(cvr.queries).some((q) => q.type === "client")) previousQueries = new Map(this.#pipelines.queries());
|
|
319
|
+
break;
|
|
320
|
+
case "permissions-change": break;
|
|
321
|
+
default: unreachable(result.reason);
|
|
322
|
+
}
|
|
222
323
|
this.#pipelines.reset(clientSchema);
|
|
223
|
-
this.#
|
|
324
|
+
this.#pipelinesHydrated = false;
|
|
224
325
|
this.connContextManager.setSharedRetransformReady(false);
|
|
225
326
|
}
|
|
226
|
-
const
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
lc.debug?.(`replica@${version} is behind cvr@${cvrVer}`);
|
|
230
|
-
return;
|
|
231
|
-
}
|
|
232
|
-
lc.info?.(`init pipelines@${version} (cvr@${cvrVer})`);
|
|
233
|
-
const driftedQueryIDs = await this.#hydrateUnchangedQueries(lc, cvr);
|
|
234
|
-
await this.#syncQueryPipelineSet(lc, cvr, "missing", void 0, driftedQueryIDs);
|
|
235
|
-
this.#pipelinesSynced = true;
|
|
236
|
-
this.connContextManager.setSharedRetransformReady(true);
|
|
327
|
+
const backgroundConnCtx = this.connContextManager.getBackgroundConnectionContext();
|
|
328
|
+
if (backgroundConnCtx) await this.#maybeHydratePipelines(lc, cvr, backgroundConnCtx, previousQueries);
|
|
329
|
+
else lc.info?.("No validated background connection; deferring pipeline init");
|
|
237
330
|
});
|
|
238
331
|
}
|
|
239
332
|
if (this.#drainCoordinator.shouldDrain()) this.#drainCoordinator.drainNextIn(this.#totalHydrationTimeMs());
|
|
@@ -247,11 +340,57 @@ var ViewSyncerService = class {
|
|
|
247
340
|
this.#stopped.resolve();
|
|
248
341
|
}
|
|
249
342
|
}
|
|
343
|
+
/**
|
|
344
|
+
* Ensures that query pipelines are hydrated and ready for steady-state
|
|
345
|
+
* operation.
|
|
346
|
+
*
|
|
347
|
+
* Preconditions:
|
|
348
|
+
* 1. Must be called from within the `#lock`.
|
|
349
|
+
* 2. The caller must provide an active, validated `ConnectionContext` for
|
|
350
|
+
* evaluating custom query transforms and permissions.
|
|
351
|
+
*
|
|
352
|
+
* Readiness prerequisites:
|
|
353
|
+
* - The underlying `QueryPipelineDriver` must be initialized with the schema
|
|
354
|
+
* (i.e. `this.#pipelines.initialized()` is true).
|
|
355
|
+
* - The SQLite replica must have caught up to at least the CVR's
|
|
356
|
+
* `stateVersion` (`version >= cvr.version.stateVersion`).
|
|
357
|
+
*
|
|
358
|
+
* If either prerequisite is not met, this method returns early as a no-op,
|
|
359
|
+
* waiting for the replica to catch up or the driver to initialize.
|
|
360
|
+
*
|
|
361
|
+
* Once ready:
|
|
362
|
+
* - Hydrates unchanged queries from the CVR snapshot without full row diffing.
|
|
363
|
+
* - Syncs missing, errored, or drifted queries via `#syncQueryPipelineSet`.
|
|
364
|
+
* - Sets `#pipelinesHydrated = true` and enables shared retransformations.
|
|
365
|
+
*/
|
|
366
|
+
async #maybeHydratePipelines(lc, cvr, connCtx, previousQueries) {
|
|
367
|
+
if (!this.#pipelines.initialized()) return;
|
|
368
|
+
const version = this.#pipelines.advanceWithoutDiff();
|
|
369
|
+
const cvrVer = versionString(cvr.version);
|
|
370
|
+
if (version < cvr.version.stateVersion) {
|
|
371
|
+
lc.debug?.(`replica@${version} is behind cvr@${cvrVer}`);
|
|
372
|
+
return;
|
|
373
|
+
}
|
|
374
|
+
lc.info?.(`init pipelines@${version} (cvr@${cvrVer})`);
|
|
375
|
+
const hydrationBudget = new HydrationBudget(this.#config.viewSyncerHydrationBudgetMs ?? 0, this.#now);
|
|
376
|
+
const hydrationPassStats = {
|
|
377
|
+
activeHydratedQueries: 0,
|
|
378
|
+
inactiveHydratedQueries: 0
|
|
379
|
+
};
|
|
380
|
+
const driftedQueryIDs = await this.#hydrateUnchangedQueries(lc, cvr, connCtx, hydrationPassStats, hydrationBudget, previousQueries);
|
|
381
|
+
await this.#syncQueryPipelineSet(lc, cvr, "missing", connCtx, driftedQueryIDs, hydrationBudget, hydrationPassStats, previousQueries);
|
|
382
|
+
this.#pipelinesHydrated = true;
|
|
383
|
+
this.connContextManager.setSharedRetransformReady(true);
|
|
384
|
+
}
|
|
250
385
|
#removeExpiredQueries = async (lc, cvr) => {
|
|
251
386
|
if (hasExpiredQueries(cvr)) {
|
|
252
387
|
lc = lc.withContext("method", "#removeExpiredQueries");
|
|
253
388
|
lc.debug?.("Queries have expired");
|
|
254
|
-
if (this.#
|
|
389
|
+
if (this.#pipelinesHydrated) {
|
|
390
|
+
const connCtx = this.connContextManager.getBackgroundConnectionContext();
|
|
391
|
+
if (connCtx) await this.#syncQueryPipelineSet(lc, cvr, "missing", connCtx);
|
|
392
|
+
else lc.info?.("No validated background connection to remove expired queries; deferring");
|
|
393
|
+
}
|
|
255
394
|
}
|
|
256
395
|
this.#scheduleExpireEviction(lc, cvr);
|
|
257
396
|
};
|
|
@@ -333,7 +472,14 @@ var ViewSyncerService = class {
|
|
|
333
472
|
return true;
|
|
334
473
|
}
|
|
335
474
|
#shutdownTimer = null;
|
|
475
|
+
#stopShutdownTimer() {
|
|
476
|
+
if (this.#shutdownTimer !== null) {
|
|
477
|
+
clearTimeout(this.#shutdownTimer);
|
|
478
|
+
this.#shutdownTimer = null;
|
|
479
|
+
}
|
|
480
|
+
}
|
|
336
481
|
#scheduleShutdown(delayMs = 0) {
|
|
482
|
+
if (this.#shuttingDown) return;
|
|
337
483
|
this.#shutdownTimer ??= this.#setTimeout(() => {
|
|
338
484
|
this.#shutdownTimer = null;
|
|
339
485
|
this.#runInLockWithCVR(() => {}).catch((e) => this.#stateChanges.fail(e));
|
|
@@ -380,6 +526,7 @@ var ViewSyncerService = class {
|
|
|
380
526
|
*/
|
|
381
527
|
#scheduleAuthMaintenance(lc) {
|
|
382
528
|
this.#stopAuthMaintenanceTimer();
|
|
529
|
+
if (this.#shuttingDown) return;
|
|
383
530
|
const plan = this.connContextManager.planMaintenance();
|
|
384
531
|
if (plan.earliestDeadlineAt === void 0) {
|
|
385
532
|
lc.debug?.("No auth maintenance wakeup scheduled");
|
|
@@ -467,7 +614,7 @@ var ViewSyncerService = class {
|
|
|
467
614
|
}
|
|
468
615
|
lc.debug?.("Auth changed, re-validating and re-transforming queries");
|
|
469
616
|
const connCtx = this.connContextManager.mustGetConnectionContext(selector);
|
|
470
|
-
if (!this.#
|
|
617
|
+
if (!this.#pipelinesHydrated) {
|
|
471
618
|
if (!await this.#validateConnection(connCtx)) return;
|
|
472
619
|
}
|
|
473
620
|
return await this.#handleConfigUpdate(lc, clientID, {}, cvr, "all", void 0, connCtx);
|
|
@@ -496,6 +643,7 @@ var ViewSyncerService = class {
|
|
|
496
643
|
}
|
|
497
644
|
#startTTLClockInterval(lc) {
|
|
498
645
|
this.#stopTTLClockInterval();
|
|
646
|
+
if (this.#shuttingDown) return;
|
|
499
647
|
this.#ttlClockInterval = this.#setTimeout(() => {
|
|
500
648
|
this.#updateTTLClockInCVRWithoutLock(lc);
|
|
501
649
|
this.#startTTLClockInterval(lc);
|
|
@@ -529,7 +677,8 @@ var ViewSyncerService = class {
|
|
|
529
677
|
await pokers.end(newCVR.version);
|
|
530
678
|
});
|
|
531
679
|
}
|
|
532
|
-
if (this.#
|
|
680
|
+
if (!this.#pipelinesHydrated) await this.#maybeHydratePipelines(lc, this.#cvr, connCtx);
|
|
681
|
+
else await this.#syncQueryPipelineSet(lc, this.#cvr, customQueryTransformMode, connCtx);
|
|
533
682
|
return this.#cvr;
|
|
534
683
|
}
|
|
535
684
|
/**
|
|
@@ -625,6 +774,7 @@ var ViewSyncerService = class {
|
|
|
625
774
|
#scheduleExpireEviction(lc, cvr) {
|
|
626
775
|
const { ttlClock } = cvr;
|
|
627
776
|
this.#stopExpireTimer();
|
|
777
|
+
if (this.#shuttingDown) return;
|
|
628
778
|
const next = nextEvictionTime(cvr);
|
|
629
779
|
if (next === void 0) {
|
|
630
780
|
lc.debug?.("no inactive queries with ttl");
|
|
@@ -652,7 +802,7 @@ var ViewSyncerService = class {
|
|
|
652
802
|
*
|
|
653
803
|
* This must be called from within the #lock.
|
|
654
804
|
*/
|
|
655
|
-
async #hydrateUnchangedQueries(lc, cvr) {
|
|
805
|
+
async #hydrateUnchangedQueries(lc, cvr, connCtx, hydrationPassStats, hydrationBudget, previousQueries) {
|
|
656
806
|
assert(this.#pipelines.initialized(), "pipelines must be initialized");
|
|
657
807
|
const dbVersion = this.#pipelines.currentVersion();
|
|
658
808
|
const cvrVersion = cvr.version;
|
|
@@ -661,33 +811,34 @@ var ViewSyncerService = class {
|
|
|
661
811
|
return /* @__PURE__ */ new Set();
|
|
662
812
|
}
|
|
663
813
|
const gotQueries = Object.entries(cvr.queries).filter(([_, state]) => state.transformationHash !== void 0);
|
|
814
|
+
const { required: requiredGotQueries } = classifyQueriesForHydration(gotQueries.map(([, query]) => query));
|
|
664
815
|
const customQueries = /* @__PURE__ */ new Map();
|
|
665
816
|
const otherQueries = [];
|
|
666
|
-
|
|
667
|
-
for (const [, query] of gotQueries) {
|
|
668
|
-
if (query.type !== "internal" && Object.values(query.clientState).every(({ inactivatedAt }) => inactivatedAt !== void 0)) {
|
|
669
|
-
inactivatedCount++;
|
|
670
|
-
continue;
|
|
671
|
-
}
|
|
672
|
-
if (query.type === "custom") customQueries.set(query.id, query);
|
|
673
|
-
else otherQueries.push(query);
|
|
674
|
-
}
|
|
817
|
+
const inactivatedCount = gotQueries.length - requiredGotQueries.length;
|
|
675
818
|
const transformedQueries = [];
|
|
819
|
+
for (const query of requiredGotQueries) if (query.type === "custom") {
|
|
820
|
+
const previous = previousQueries?.get(query.id);
|
|
821
|
+
if (previous && previous.transformationHash === query.transformationHash) transformedQueries.push({
|
|
822
|
+
id: query.id,
|
|
823
|
+
transformationHash: previous.transformationHash,
|
|
824
|
+
transformedAst: previous.originalAst ?? previous.transformedAst
|
|
825
|
+
});
|
|
826
|
+
else customQueries.set(query.id, query);
|
|
827
|
+
} else otherQueries.push(query);
|
|
676
828
|
let customErrorCount = 0;
|
|
677
829
|
let customHashMismatchCount = 0;
|
|
678
830
|
let otherHashMismatchCount = 0;
|
|
679
831
|
if (customQueries.size > 0 && !this.#customQueryTransformer) lc.warn?.("Custom/named queries were requested but no `ZERO_QUERY_URL` is configured for Zero Cache.");
|
|
680
|
-
const backgroundContext = this.connContextManager.mustGetBackgroundConnectionContext();
|
|
681
832
|
const customQueryTransformer = this.#customQueryTransformer;
|
|
682
833
|
if (customQueryTransformer && customQueries.size > 0) {
|
|
683
|
-
const transformedCustomQueries = await this.#runPriorityOp(lc, "#hydrateUnchangedQueries transforming custom queries", () => customQueryTransformer.transform(
|
|
684
|
-
if (transformedCustomQueries.kind === "success" && !transformedCustomQueries.cached) this.connContextManager.validateConnection(
|
|
834
|
+
const transformedCustomQueries = await hydrationBudget.excluding(() => this.#runPriorityOp(lc, "#hydrateUnchangedQueries transforming custom queries", () => customQueryTransformer.transform(connCtx, customQueries.values())));
|
|
835
|
+
if (transformedCustomQueries.kind === "success" && !transformedCustomQueries.cached) this.connContextManager.validateConnection(connCtx, connCtx.revision, transformedCustomQueries.validation);
|
|
685
836
|
if (Array.isArray(transformedCustomQueries.result)) for (const q of transformedCustomQueries.result) if ("error" in q) customErrorCount++;
|
|
686
837
|
else if (q.transformationHash !== customQueries.get(q.id)?.transformationHash) customHashMismatchCount++;
|
|
687
838
|
else transformedQueries.push(q);
|
|
688
839
|
}
|
|
689
840
|
for (const q of otherQueries) {
|
|
690
|
-
const transformed = transformAndHashQuery(lc, q.id, q.ast, must(this.#pipelines.currentPermissions()).permissions ?? { tables: {} },
|
|
841
|
+
const transformed = transformAndHashQuery(lc, q.id, q.ast, must(this.#pipelines.currentPermissions()).permissions ?? { tables: {} }, connCtx.auth?.type === "jwt" ? connCtx.auth : void 0, q.type === "internal");
|
|
691
842
|
if (transformed.transformationHash === q.transformationHash) transformedQueries.push(transformed);
|
|
692
843
|
else otherHashMismatchCount++;
|
|
693
844
|
}
|
|
@@ -700,6 +851,10 @@ var ViewSyncerService = class {
|
|
|
700
851
|
for (const { id: queryID, transformationHash, transformedAst } of transformedQueries) {
|
|
701
852
|
const query = cvr.queries[queryID];
|
|
702
853
|
const queryName = query.type === "custom" ? query.name : void 0;
|
|
854
|
+
if (query.type !== "internal" && this.#hydrationCircuitBreaker.isOpen(transformationHash)) {
|
|
855
|
+
lc.info?.(`skipping hydration of ${queryID}: hydration circuit breaker open`);
|
|
856
|
+
continue;
|
|
857
|
+
}
|
|
703
858
|
const covered = queryCoveringIndex ? this.#findQueryCoverageShadowHit(queryCoveringIndex, queryID, transformationHash, transformedAst, queryName) : void 0;
|
|
704
859
|
totalHydratedQueries++;
|
|
705
860
|
if (covered) {
|
|
@@ -708,19 +863,35 @@ var ViewSyncerService = class {
|
|
|
708
863
|
}
|
|
709
864
|
const timer = new TimeSliceTimer(lc);
|
|
710
865
|
let count = 0;
|
|
866
|
+
let timedOut = false;
|
|
711
867
|
await startAsyncSpan(tracer, "vs.#hydrateUnchangedQueries.addQuery", async (span) => {
|
|
712
868
|
span.setAttribute("queryHash", queryID);
|
|
713
869
|
span.setAttribute("transformationHash", transformationHash);
|
|
714
870
|
span.setAttribute("table", transformedAst.table);
|
|
715
871
|
if (queryName !== void 0) span.setAttribute("queryName", queryName);
|
|
716
|
-
for (const change of this.#pipelines.addQuery(transformationHash, queryID, transformedAst, await timer.start(), queryName, "unchanged-query-rehydrate")) if (change === "yield")
|
|
717
|
-
|
|
872
|
+
for (const change of this.#pipelines.addQuery(transformationHash, queryID, transformedAst, await timer.start(), queryName, "unchanged-query-rehydrate")) if (change === "yield") {
|
|
873
|
+
if (query.type !== "internal" && this.#hydrationCircuitBreaker.exceeded(timer.totalElapsed())) {
|
|
874
|
+
timedOut = true;
|
|
875
|
+
await timer.yieldProcess("yield in hydrateUnchangedQueries");
|
|
876
|
+
break;
|
|
877
|
+
}
|
|
878
|
+
await timer.yieldProcess("yield in hydrateUnchangedQueries");
|
|
879
|
+
} else count++;
|
|
718
880
|
});
|
|
719
881
|
const elapsed = timer.totalElapsed();
|
|
882
|
+
if (timedOut) {
|
|
883
|
+
this.#recordHydrationTimeout(lc, {
|
|
884
|
+
id: queryID,
|
|
885
|
+
transformationHash,
|
|
886
|
+
name: queryName
|
|
887
|
+
}, elapsed, count);
|
|
888
|
+
continue;
|
|
889
|
+
}
|
|
890
|
+
hydrationPassStats.activeHydratedQueries++;
|
|
720
891
|
this.#hydrations.add(1);
|
|
721
892
|
this.#hydrationTime.recordMs(elapsed);
|
|
722
|
-
this.#addQueryMaterializationServerMetric(
|
|
723
|
-
this.#inspectorDelegate.addQuery(
|
|
893
|
+
this.#addQueryMaterializationServerMetric(queryID, elapsed);
|
|
894
|
+
this.#inspectorDelegate.addQuery(queryID, transformedAst);
|
|
724
895
|
lc.debug?.(`hydrated ${count} rows for ${queryID} (${elapsed} ms)`);
|
|
725
896
|
let drifted = false;
|
|
726
897
|
const storedSigHex = cvr.queries[queryID]?.rowSetSignature;
|
|
@@ -821,7 +992,10 @@ var ViewSyncerService = class {
|
|
|
821
992
|
*
|
|
822
993
|
* This must be called from within the #lock.
|
|
823
994
|
*/
|
|
824
|
-
#syncQueryPipelineSet(lc, cvr, customQueryTransformMode, connCtx, driftedQueryIDs = /* @__PURE__ */ new Set()) {
|
|
995
|
+
#syncQueryPipelineSet(lc, cvr, customQueryTransformMode, connCtx, driftedQueryIDs = /* @__PURE__ */ new Set(), hydrationBudget = new HydrationBudget(this.#config.viewSyncerHydrationBudgetMs ?? 0, this.#now), hydrationPassStats = {
|
|
996
|
+
activeHydratedQueries: 0,
|
|
997
|
+
inactiveHydratedQueries: 0
|
|
998
|
+
}, previousQueries) {
|
|
825
999
|
return startAsyncSpan(tracer, "vs.#syncQueryPipelineSet", async (span) => {
|
|
826
1000
|
const start = performance.now();
|
|
827
1001
|
span.setAttribute("clientGroupID", this.id);
|
|
@@ -830,38 +1004,45 @@ var ViewSyncerService = class {
|
|
|
830
1004
|
const now = Date.now();
|
|
831
1005
|
const ttlClock = this.#getTTLClock(now);
|
|
832
1006
|
const cvrQueryEntires = Object.entries(cvr.queries);
|
|
833
|
-
const customQueries = /* @__PURE__ */ new Map();
|
|
834
|
-
const otherQueries = [];
|
|
835
1007
|
const transformedQueries = [];
|
|
836
|
-
const
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
});
|
|
844
|
-
for (const { id, query: origQuery } of otherQueries) {
|
|
845
|
-
assert(id === origQuery.id, "query id mismatch");
|
|
846
|
-
const transformed = transformAndHashQuery(lc, origQuery.id, origQuery.ast, must(this.#pipelines.currentPermissions()).permissions ?? { tables: {} }, resolvedConnCtx.auth?.type === "jwt" ? resolvedConnCtx.auth : void 0, origQuery.type === "internal");
|
|
847
|
-
transformedQueries.push({
|
|
848
|
-
id,
|
|
849
|
-
origQuery,
|
|
850
|
-
transformed
|
|
851
|
-
});
|
|
852
|
-
}
|
|
853
|
-
if (customQueries.size > 0 && !this.#customQueryTransformer) lc.warn?.("Custom/named queries were requested but no `ZERO_QUERY_URL` is configured for Zero Cache.");
|
|
854
|
-
let erroredQueryIDs;
|
|
855
|
-
const customQueriesToTransform = customQueryTransformMode === "all" ? [...customQueries.values()] : customQueryTransformMode && [...customQueries.values()].filter((q) => !this.#pipelines.queries().has(q.id));
|
|
1008
|
+
const naturallyExpiredQueryIDs = new Set(cvrQueryEntires.map(([, query]) => query).filter((query) => expired(ttlClock, query)).map((query) => query.id));
|
|
1009
|
+
if (naturallyExpiredQueryIDs.size > 0) this.#queryEvictions.add(naturallyExpiredQueryIDs.size, { reason: "ttl" });
|
|
1010
|
+
const hydrationCandidates = cvrQueryEntires.map(([, query]) => query).filter((query) => !naturallyExpiredQueryIDs.has(query.id));
|
|
1011
|
+
const { required, optional } = classifyQueriesForHydration(hydrationCandidates);
|
|
1012
|
+
const requiredQueryIDs = new Set(required.map((query) => query.id));
|
|
1013
|
+
const optionalToHydrate = hydrationBudget.limitMs === 0 ? optional : optional.filter((query) => query.patchVersion !== void 0);
|
|
1014
|
+
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.");
|
|
856
1015
|
const customQueryTransformer = this.#customQueryTransformer;
|
|
857
|
-
|
|
1016
|
+
const erroredQueryIDs = [];
|
|
1017
|
+
let transformedCustomQueryCount = 0;
|
|
1018
|
+
const transformOtherQueries = (queries) => {
|
|
1019
|
+
for (const origQuery of queries) {
|
|
1020
|
+
if (origQuery.type === "custom") continue;
|
|
1021
|
+
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");
|
|
1022
|
+
transformedQueries.push({
|
|
1023
|
+
id: origQuery.id,
|
|
1024
|
+
origQuery,
|
|
1025
|
+
transformed
|
|
1026
|
+
});
|
|
1027
|
+
}
|
|
1028
|
+
};
|
|
1029
|
+
const shouldTransformCustomQuery = (query) => {
|
|
1030
|
+
if (customQueryTransformMode === "all") return true;
|
|
1031
|
+
if (this.#pipelines.queries().has(query.id)) return false;
|
|
1032
|
+
const previous = previousQueries?.get(query.id);
|
|
1033
|
+
if (previous && previous.transformationHash === query.transformationHash) return false;
|
|
1034
|
+
return true;
|
|
1035
|
+
};
|
|
1036
|
+
const transformCustomQueries = async (queries) => {
|
|
1037
|
+
if (!customQueryTransformer || queries.length === 0) return;
|
|
1038
|
+
transformedCustomQueryCount += queries.length;
|
|
858
1039
|
const transformStart = performance.now();
|
|
859
1040
|
let transformedCustomQueries;
|
|
860
1041
|
try {
|
|
861
|
-
transformedCustomQueries = await this.#runPriorityOp(lc, "#syncQueryPipelineSet transforming custom queries", () => customQueryTransformer.transform(
|
|
1042
|
+
transformedCustomQueries = await hydrationBudget.excluding(() => this.#runPriorityOp(lc, "#syncQueryPipelineSet transforming custom queries", () => customQueryTransformer.transform(connCtx, queries)));
|
|
862
1043
|
if (transformedCustomQueries.kind === "failed") throw new ProtocolErrorWithLevel(transformedCustomQueries.result, "warn");
|
|
863
1044
|
else {
|
|
864
|
-
if (!transformedCustomQueries.cached) this.connContextManager.validateConnection(
|
|
1045
|
+
if (!transformedCustomQueries.cached) this.connContextManager.validateConnection(connCtx, connCtx.revision, transformedCustomQueries.validation);
|
|
865
1046
|
this.#queryTransformations.add(1, { result: "success" });
|
|
866
1047
|
}
|
|
867
1048
|
} catch (e) {
|
|
@@ -872,8 +1053,9 @@ var ViewSyncerService = class {
|
|
|
872
1053
|
this.#queryTransformationTime.recordMs(transformDuration);
|
|
873
1054
|
}
|
|
874
1055
|
const successfullyTransformedCustomQueries = /* @__PURE__ */ new Map();
|
|
875
|
-
|
|
876
|
-
|
|
1056
|
+
const customQueryMap = new Map(queries.map((query) => [query.id, query]));
|
|
1057
|
+
erroredQueryIDs.push(...this.#processTransformedCustomQueries(lc, transformedCustomQueries.result, (q) => {
|
|
1058
|
+
const origQuery = customQueryMap.get(q.id);
|
|
877
1059
|
if (origQuery) {
|
|
878
1060
|
successfullyTransformedCustomQueries.set(q.id, q);
|
|
879
1061
|
transformedQueries.push({
|
|
@@ -882,7 +1064,7 @@ var ViewSyncerService = class {
|
|
|
882
1064
|
transformed: q
|
|
883
1065
|
});
|
|
884
1066
|
}
|
|
885
|
-
},
|
|
1067
|
+
}, customQueryMap));
|
|
886
1068
|
for (const [queryID, newTransform] of successfullyTransformedCustomQueries) {
|
|
887
1069
|
const existingTransformHash = cvr.queries[queryID]?.transformationHash;
|
|
888
1070
|
if (existingTransformHash) {
|
|
@@ -895,21 +1077,55 @@ var ViewSyncerService = class {
|
|
|
895
1077
|
} else this.#queryTransformationNoOps.add(1);
|
|
896
1078
|
}
|
|
897
1079
|
}
|
|
1080
|
+
};
|
|
1081
|
+
const queriesToTransform = [...required, ...optionalToHydrate];
|
|
1082
|
+
transformOtherQueries(queriesToTransform);
|
|
1083
|
+
if (customQueryTransformMode === "missing" && previousQueries) {
|
|
1084
|
+
for (const query of queriesToTransform) if (query.type === "custom" && !this.#pipelines.queries().has(query.id)) {
|
|
1085
|
+
const previous = previousQueries.get(query.id);
|
|
1086
|
+
if (previous && previous.transformationHash === query.transformationHash) {
|
|
1087
|
+
this.#queryTransformationNoOps.add(1);
|
|
1088
|
+
transformedQueries.push({
|
|
1089
|
+
id: query.id,
|
|
1090
|
+
origQuery: query,
|
|
1091
|
+
transformed: {
|
|
1092
|
+
id: query.id,
|
|
1093
|
+
transformationHash: previous.transformationHash,
|
|
1094
|
+
transformedAst: previous.originalAst ?? previous.transformedAst
|
|
1095
|
+
}
|
|
1096
|
+
});
|
|
1097
|
+
}
|
|
1098
|
+
}
|
|
898
1099
|
}
|
|
899
|
-
|
|
1100
|
+
await transformCustomQueries(queriesToTransform.filter((query) => query.type === "custom" && shouldTransformCustomQuery(query)));
|
|
1101
|
+
const circuitBrokenQueries = transformedQueries.filter(({ id, origQuery, transformed }) => origQuery.type !== "internal" && this.#pipelines.queries().get(id)?.transformationHash !== transformed.transformationHash && this.#hydrationCircuitBreaker.isOpen(transformed.transformationHash)).map(({ id, origQuery, transformed }) => ({
|
|
1102
|
+
id,
|
|
1103
|
+
transformationHash: transformed.transformationHash,
|
|
1104
|
+
name: origQuery.type === "custom" ? origQuery.name : void 0
|
|
1105
|
+
}));
|
|
1106
|
+
if (circuitBrokenQueries.length > 0) this.#rejectCircuitBrokenQueries(lc, cvr, circuitBrokenQueries);
|
|
1107
|
+
const removeQueriesQueryIds = new Set([
|
|
1108
|
+
...naturallyExpiredQueryIDs,
|
|
1109
|
+
...erroredQueryIDs,
|
|
1110
|
+
...circuitBrokenQueries.map(({ id }) => id)
|
|
1111
|
+
]);
|
|
900
1112
|
const addQueries = transformedQueries.map(({ id, origQuery, transformed }) => ({
|
|
901
1113
|
id,
|
|
902
1114
|
ast: transformed.transformedAst,
|
|
903
1115
|
transformationHash: transformed.transformationHash,
|
|
904
1116
|
name: origQuery.type === "custom" ? origQuery.name : void 0
|
|
905
1117
|
})).filter((q) => !removeQueriesQueryIds.has(q.id) && this.#pipelines.queries().get(q.id)?.transformationHash !== q.transformationHash);
|
|
906
|
-
|
|
1118
|
+
const hydrationOrder = new Map([...required, ...optional].map((query, index) => [query.id, index]));
|
|
1119
|
+
addQueries.sort((a, b) => must(hydrationOrder.get(a.id)) - must(hydrationOrder.get(b.id)));
|
|
1120
|
+
const requiredAddQueries = addQueries.filter((query) => requiredQueryIDs.has(query.id));
|
|
1121
|
+
const optionalAddQueries = addQueries.filter((query) => !requiredQueryIDs.has(query.id));
|
|
1122
|
+
lc.info?.(`syncQueryPipelineSet: ${cvrQueryEntires.length} CVR queries, ${transformedCustomQueryCount} custom re-transformed, ${erroredQueryIDs.length} errored, ${circuitBrokenQueries.length} circuit-broken, ${removeQueriesQueryIds.size} to remove, ${addQueries.length} to add`);
|
|
907
1123
|
for (const q of addQueries) {
|
|
908
1124
|
const orig = cvr.queries[q.id];
|
|
909
1125
|
lc.debug?.("ViewSyncer adding query", q.ast, "transformed from", orig.type === "custom" ? orig.name : orig.ast);
|
|
910
1126
|
}
|
|
911
1127
|
if (addQueries.length > 0 || removeQueriesQueryIds.size > 0) {
|
|
912
|
-
await this.#addAndRemoveQueries(lc, cvr,
|
|
1128
|
+
await this.#addAndRemoveQueries(lc, cvr, requiredAddQueries, optionalAddQueries, Array.from(removeQueriesQueryIds, (id) => ({ id })), hydrationBudget, hydrationPassStats, driftedQueryIDs);
|
|
913
1129
|
if (addQueries.length > 0) this.#viewSyncerHydration.recordMs(performance.now() - start);
|
|
914
1130
|
} else await this.#catchupClients(lc, cvr);
|
|
915
1131
|
});
|
|
@@ -942,8 +1158,100 @@ var ViewSyncerService = class {
|
|
|
942
1158
|
record.count++;
|
|
943
1159
|
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.`);
|
|
944
1160
|
}
|
|
945
|
-
|
|
1161
|
+
/**
|
|
1162
|
+
* Records that hydrating `query` was aborted for exceeding the query
|
|
1163
|
+
* hydration timeout, and opens its circuit breaker.
|
|
1164
|
+
*/
|
|
1165
|
+
#recordHydrationTimeout(lc, query, elapsedMs, rowCount) {
|
|
1166
|
+
this.#hydrationCircuitBreaker.trip(query.transformationHash);
|
|
1167
|
+
this.#hydrationTimeouts.add(1);
|
|
1168
|
+
lc.warn?.("Query hydration aborted for exceeding the hydration timeout", {
|
|
1169
|
+
clientGroupID: this.id,
|
|
1170
|
+
queryHash: query.id,
|
|
1171
|
+
transformationHash: query.transformationHash,
|
|
1172
|
+
...query.name !== void 0 && { queryName: query.name },
|
|
1173
|
+
hydrationTimeoutMs: this.#hydrationCircuitBreaker.timeoutMs,
|
|
1174
|
+
hydrationElapsedMs: elapsedMs,
|
|
1175
|
+
...rowCount !== void 0 && { hydrationRowCount: rowCount },
|
|
1176
|
+
circuitBreakerOpenMs: this.#hydrationCircuitBreaker.openMs
|
|
1177
|
+
});
|
|
1178
|
+
}
|
|
1179
|
+
/** Records that `query` was rejected without hydration by its open breaker. */
|
|
1180
|
+
#recordCircuitBreakerRejection(lc, query) {
|
|
1181
|
+
this.#hydrationCircuitBreakerRejections.add(1);
|
|
1182
|
+
lc.warn?.("Query rejected by its open hydration circuit breaker", {
|
|
1183
|
+
clientGroupID: this.id,
|
|
1184
|
+
queryHash: query.id,
|
|
1185
|
+
transformationHash: query.transformationHash,
|
|
1186
|
+
...query.name !== void 0 && { queryName: query.name },
|
|
1187
|
+
hydrationTimeoutMs: this.#hydrationCircuitBreaker.timeoutMs,
|
|
1188
|
+
circuitBreakerOpenMs: this.#hydrationCircuitBreaker.openMs
|
|
1189
|
+
});
|
|
1190
|
+
}
|
|
1191
|
+
/**
|
|
1192
|
+
* Handles queries whose hydration circuit breaker is open: they are counted,
|
|
1193
|
+
* logged, and errored to the affected clients. The caller removes them from
|
|
1194
|
+
* the CVR.
|
|
1195
|
+
*/
|
|
1196
|
+
#rejectCircuitBrokenQueries(lc, cvr, queries) {
|
|
1197
|
+
for (const query of queries) this.#recordCircuitBreakerRejection(lc, query);
|
|
1198
|
+
this.#queryEvictions.add(queries.length, { reason: "hydration-circuit-breaker" });
|
|
1199
|
+
this.#sendHydrationTimeoutErrors(cvr, queries);
|
|
1200
|
+
}
|
|
1201
|
+
/**
|
|
1202
|
+
* Sends a per-query error to every client that desires one of `queries`.
|
|
1203
|
+
* The error goes out as a `transformError` application error, which every
|
|
1204
|
+
* client understands as "this query errored" without affecting the
|
|
1205
|
+
* connection or the client's other queries.
|
|
1206
|
+
*/
|
|
1207
|
+
#sendHydrationTimeoutErrors(cvr, queries) {
|
|
1208
|
+
const timeoutMs = this.#hydrationCircuitBreaker.timeoutMs;
|
|
1209
|
+
const errorsByClient = /* @__PURE__ */ new Map();
|
|
1210
|
+
for (const { id, name } of queries) {
|
|
1211
|
+
const query = cvr.queries[id];
|
|
1212
|
+
if (query === void 0 || query.type === "internal") continue;
|
|
1213
|
+
const error = {
|
|
1214
|
+
error: "app",
|
|
1215
|
+
id,
|
|
1216
|
+
name: name ?? (query.type === "custom" ? query.name : "legacy"),
|
|
1217
|
+
message: `Query hydration exceeded the ${timeoutMs}ms limit (ZERO_VIEW_SYNCER_QUERY_HYDRATION_TIMEOUT_MS) and was aborted`,
|
|
1218
|
+
details: {
|
|
1219
|
+
kind: "HydrationTimeout",
|
|
1220
|
+
timeoutMs
|
|
1221
|
+
}
|
|
1222
|
+
};
|
|
1223
|
+
for (const clientID of Object.keys(query.clientState)) {
|
|
1224
|
+
let errors = errorsByClient.get(clientID);
|
|
1225
|
+
if (!errors) {
|
|
1226
|
+
errors = [];
|
|
1227
|
+
errorsByClient.set(clientID, errors);
|
|
1228
|
+
}
|
|
1229
|
+
errors.push(error);
|
|
1230
|
+
}
|
|
1231
|
+
}
|
|
1232
|
+
for (const [clientID, errors] of errorsByClient) this.#clients.get(clientID)?.sendQueryTransformApplicationErrors(errors);
|
|
1233
|
+
}
|
|
1234
|
+
#recordHydrationBudgetExhaustion(lc, hydrationBudget, activeHydratedQueries, inactiveHydratedQueries, inactiveEvictedQueryIDs) {
|
|
1235
|
+
const elapsedMs = hydrationBudget.exhaustedAtMs;
|
|
1236
|
+
if (elapsedMs === void 0) return;
|
|
1237
|
+
this.#hydrationBudgetExhaustions.add(1);
|
|
1238
|
+
this.#hydrationBudgetEvictions.add(inactiveEvictedQueryIDs.length);
|
|
1239
|
+
this.#hydrationBudgetElapsed.record(elapsedMs);
|
|
1240
|
+
this.#hydrationBudgetOvershoot.record(Math.max(0, elapsedMs - hydrationBudget.limitMs));
|
|
1241
|
+
if (inactiveEvictedQueryIDs.length > 0) this.#queryEvictions.add(inactiveEvictedQueryIDs.length, { reason: "hydration-budget" });
|
|
1242
|
+
lc.info?.("view-syncer hydration budget exhausted", {
|
|
1243
|
+
clientGroupID: this.id,
|
|
1244
|
+
hydrationBudgetMs: hydrationBudget.limitMs,
|
|
1245
|
+
hydrationElapsedMs: elapsedMs,
|
|
1246
|
+
activeHydratedQueries,
|
|
1247
|
+
inactiveHydratedQueries,
|
|
1248
|
+
inactiveEvictedQueries: inactiveEvictedQueryIDs.length,
|
|
1249
|
+
firstEvictedQueryHash: inactiveEvictedQueryIDs[0] ?? null
|
|
1250
|
+
});
|
|
1251
|
+
}
|
|
1252
|
+
#addAndRemoveQueries(lc, cvr, requiredQueries, optionalQueries, removeQueries, hydrationBudget, hydrationPassStats, driftedQueryIDs = /* @__PURE__ */ new Set()) {
|
|
946
1253
|
return startAsyncSpan(tracer, "vs.#addAndRemoveQueries", async () => {
|
|
1254
|
+
const addQueries = [...requiredQueries, ...optionalQueries];
|
|
947
1255
|
assert(addQueries.length > 0 || removeQueries.length > 0, "Must have queries to add or remove");
|
|
948
1256
|
const start = performance.now();
|
|
949
1257
|
const stateVersion = this.#pipelines.currentVersion();
|
|
@@ -960,6 +1268,7 @@ var ViewSyncerService = class {
|
|
|
960
1268
|
updater.ensureNewVersion();
|
|
961
1269
|
}
|
|
962
1270
|
const { queryPatches, newVersion } = updater.trackQueries(lc, addQueries, removeQueries);
|
|
1271
|
+
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");
|
|
963
1272
|
const pokers = startPoke(this.#getClients(), newVersion);
|
|
964
1273
|
for (const patch of queryPatches) await pokers.addPatch(patch);
|
|
965
1274
|
for (const q of removeQueries) {
|
|
@@ -976,22 +1285,54 @@ var ViewSyncerService = class {
|
|
|
976
1285
|
let totalHydratedQueries = 0;
|
|
977
1286
|
let coveredHydratedQueries = 0;
|
|
978
1287
|
let firstCoveredQuery;
|
|
1288
|
+
const hydratedQueryIDs = [];
|
|
1289
|
+
const budgetEvictedQueryIDs = [];
|
|
1290
|
+
const timedOutQueries = [];
|
|
1291
|
+
const rejectedQueries = [];
|
|
1292
|
+
const circuitBreaker = this.#hydrationCircuitBreaker;
|
|
979
1293
|
const self = this;
|
|
980
1294
|
await yieldProcess(lc);
|
|
981
|
-
function*
|
|
982
|
-
for (
|
|
1295
|
+
function* hydrateQueries(queries, optional, slowHydrateThreshold) {
|
|
1296
|
+
for (let i = 0; i < queries.length; i++) {
|
|
1297
|
+
if (optional && hydrationBudget.exhausted()) {
|
|
1298
|
+
budgetEvictedQueryIDs.push(...queries.slice(i).map((query) => query.id));
|
|
1299
|
+
return;
|
|
1300
|
+
}
|
|
1301
|
+
const q = must(queries[i]);
|
|
983
1302
|
let queryLC = lc.withContext("hash", q.id).withContext("queryHash", q.id).withContext("transformationHash", q.transformationHash);
|
|
984
1303
|
if (q.name !== void 0) queryLC = queryLC.withContext("queryName", q.name);
|
|
985
1304
|
queryLC.debug?.(`adding pipeline for query`, q.ast);
|
|
1305
|
+
const breakable = cvr.queries[q.id]?.type !== "internal";
|
|
1306
|
+
if (breakable && circuitBreaker.isOpen(q.transformationHash)) {
|
|
1307
|
+
rejectedQueries.push(q);
|
|
1308
|
+
self.#recordCircuitBreakerRejection(queryLC, q);
|
|
1309
|
+
continue;
|
|
1310
|
+
}
|
|
986
1311
|
const covered = queryCoveringIndex ? self.#findQueryCoverageShadowHit(queryCoveringIndex, q.id, q.transformationHash, q.ast, q.name) : void 0;
|
|
987
1312
|
totalHydratedQueries++;
|
|
988
1313
|
if (covered) {
|
|
989
1314
|
coveredHydratedQueries++;
|
|
990
1315
|
firstCoveredQuery ??= covered;
|
|
991
1316
|
}
|
|
992
|
-
|
|
1317
|
+
let timedOut = false;
|
|
1318
|
+
for (const change of pipelines.addQuery(q.transformationHash, q.id, q.ast, timer.startWithoutYielding(), q.name, "query-set-sync")) {
|
|
1319
|
+
if (change === "yield" && breakable && circuitBreaker.exceeded(timer.totalElapsed())) {
|
|
1320
|
+
timedOut = true;
|
|
1321
|
+
yield change;
|
|
1322
|
+
break;
|
|
1323
|
+
}
|
|
1324
|
+
yield change;
|
|
1325
|
+
}
|
|
993
1326
|
const elapsed = timer.stop();
|
|
994
1327
|
totalProcessTime += elapsed;
|
|
1328
|
+
if (timedOut) {
|
|
1329
|
+
timedOutQueries.push(q);
|
|
1330
|
+
self.#recordHydrationTimeout(queryLC, q, elapsed);
|
|
1331
|
+
continue;
|
|
1332
|
+
}
|
|
1333
|
+
hydratedQueryIDs.push(q.id);
|
|
1334
|
+
if (optional) hydrationPassStats.inactiveHydratedQueries++;
|
|
1335
|
+
else hydrationPassStats.activeHydratedQueries++;
|
|
995
1336
|
self.#addQueryMaterializationServerMetric(q.id, elapsed);
|
|
996
1337
|
self.#inspectorDelegate.addQuery(q.id, q.ast);
|
|
997
1338
|
queryCoveringIndex?.add(q.id, {
|
|
@@ -1005,18 +1346,43 @@ var ViewSyncerService = class {
|
|
|
1005
1346
|
transformationHash: q.transformationHash,
|
|
1006
1347
|
...q.name !== void 0 && { name: q.name }
|
|
1007
1348
|
});
|
|
1349
|
+
hydrations.add(1);
|
|
1350
|
+
hydrationTime.recordMs(elapsed);
|
|
1008
1351
|
}
|
|
1009
|
-
|
|
1010
|
-
|
|
1352
|
+
}
|
|
1353
|
+
function* generateRowChanges(slowHydrateThreshold) {
|
|
1354
|
+
yield* hydrateQueries(requiredQueries, false, slowHydrateThreshold);
|
|
1355
|
+
yield* hydrateQueries(optionalQueries, true, slowHydrateThreshold);
|
|
1011
1356
|
}
|
|
1012
1357
|
await this.#processChanges(lc, timer, generateRowChanges(this.#slowHydrateThreshold), updater, pokers);
|
|
1358
|
+
const abortedQueries = [...timedOutQueries, ...rejectedQueries];
|
|
1359
|
+
if (abortedQueries.length > 0) {
|
|
1360
|
+
const abortedQueryIDs = abortedQueries.map(({ id }) => id);
|
|
1361
|
+
for (const patch of await updater.abortExecutedQueries(lc, abortedQueryIDs)) await pokers.addPatch(patch);
|
|
1362
|
+
for (const queryID of abortedQueryIDs) {
|
|
1363
|
+
this.#pipelines.removeQuery(queryID);
|
|
1364
|
+
this.#inspectorDelegate.removeQuery(queryID);
|
|
1365
|
+
this.#queryReplacements.delete(queryID);
|
|
1366
|
+
}
|
|
1367
|
+
if (timedOutQueries.length > 0) this.#queryEvictions.add(timedOutQueries.length, { reason: "hydration-timeout" });
|
|
1368
|
+
if (rejectedQueries.length > 0) this.#queryEvictions.add(rejectedQueries.length, { reason: "hydration-circuit-breaker" });
|
|
1369
|
+
this.#sendHydrationTimeoutErrors(cvr, abortedQueries);
|
|
1370
|
+
}
|
|
1371
|
+
for (const patch of updater.removeTrackedQueries(budgetEvictedQueryIDs)) await pokers.addPatch(patch);
|
|
1372
|
+
for (const queryID of budgetEvictedQueryIDs) {
|
|
1373
|
+
this.#pipelines.removeQuery(queryID);
|
|
1374
|
+
this.#inspectorDelegate.removeQuery(queryID);
|
|
1375
|
+
this.#queryReplacements.delete(queryID);
|
|
1376
|
+
}
|
|
1377
|
+
this.#recordHydrationBudgetExhaustion(lc, hydrationBudget, hydrationPassStats.activeHydratedQueries, hydrationPassStats.inactiveHydratedQueries, budgetEvictedQueryIDs);
|
|
1013
1378
|
this.#logQueryCoverageShadowSummary(lc, "add", totalHydratedQueries, coveredHydratedQueries, firstCoveredQuery);
|
|
1014
1379
|
await startAsyncSpan(tracer, "vs.#syncQueryPipelineSet.deleteUnreferencedRows", async () => {
|
|
1015
1380
|
for (const patch of await updater.deleteUnreferencedRows(lc)) await pokers.addPatch(patch);
|
|
1016
1381
|
});
|
|
1017
1382
|
this.#cvr = await this.#flushUpdater(lc, updater);
|
|
1383
|
+
if (budgetEvictedQueryIDs.length > 0) this.#scheduleExpireEviction(lc, this.#cvr);
|
|
1018
1384
|
const finalVersion = this.#cvr.version;
|
|
1019
|
-
await this.#catchupClients(lc, cvr, finalVersion,
|
|
1385
|
+
await this.#catchupClients(lc, cvr, finalVersion, hydratedQueryIDs, pokers);
|
|
1020
1386
|
await startAsyncSpan(tracer, "vs.#syncQueryPipelineSet.pokeEnd", () => pokers.end(finalVersion));
|
|
1021
1387
|
this.#markVersionServed(stateVersion);
|
|
1022
1388
|
const wallTime = performance.now() - start;
|
|
@@ -1157,7 +1523,9 @@ var ViewSyncerService = class {
|
|
|
1157
1523
|
*
|
|
1158
1524
|
* Must be called from within the #lock.
|
|
1159
1525
|
*
|
|
1160
|
-
* Returns
|
|
1526
|
+
* Returns 'success' if changes were successfully processed and poked,
|
|
1527
|
+
* or a `ResetPipelinesSignal` if advancement aborted (e.g. schema change,
|
|
1528
|
+
* timeout) and pipelines need to be reset and rehydrated.
|
|
1161
1529
|
*/
|
|
1162
1530
|
#advancePipelines(lc, cvr) {
|
|
1163
1531
|
return startAsyncSpan(tracer, "vs.#advancePipelines", async (span) => {
|
|
@@ -1165,20 +1533,27 @@ var ViewSyncerService = class {
|
|
|
1165
1533
|
assert(this.#pipelines.initialized(), "pipelines must be initialized (advancePipelines");
|
|
1166
1534
|
const start = performance.now();
|
|
1167
1535
|
const timer = new TimeSliceTimer(lc);
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
lc.debug?.(`applying ${numChanges} to advance to ${version}`);
|
|
1536
|
+
let pokers;
|
|
1537
|
+
let updater;
|
|
1538
|
+
let version;
|
|
1539
|
+
let numChanges = 0;
|
|
1173
1540
|
try {
|
|
1174
|
-
|
|
1541
|
+
const advancement = this.#pipelines.advance(timer);
|
|
1542
|
+
version = advancement.version;
|
|
1543
|
+
numChanges = advancement.numChanges;
|
|
1544
|
+
lc = lc.withContext("newVersion", version);
|
|
1545
|
+
updater = new CVRQueryDrivenUpdater(this.#cvrStore, cvr, version, this.#pipelines.replicaVersion, (queryID) => this.#pipelines.rowSetSignature(queryID));
|
|
1546
|
+
pokers = startPoke(this.#getClients(cvr.version), updater.updatedVersion());
|
|
1547
|
+
lc.debug?.(`applying ${numChanges} to advance to ${version}`);
|
|
1548
|
+
await this.#processChanges(lc, await timer.start(), advancement.changes, updater, pokers);
|
|
1175
1549
|
} catch (e) {
|
|
1176
1550
|
if (e instanceof ResetPipelinesSignal) {
|
|
1177
|
-
await pokers
|
|
1551
|
+
await pokers?.cancel();
|
|
1178
1552
|
return e;
|
|
1179
1553
|
}
|
|
1180
1554
|
throw e;
|
|
1181
1555
|
}
|
|
1556
|
+
assert(updater && pokers && version !== void 0, "advancement state missing");
|
|
1182
1557
|
this.#cvr = await this.#flushUpdater(lc, updater);
|
|
1183
1558
|
const finalVersion = this.#cvr.version;
|
|
1184
1559
|
await startAsyncSpan(tracer, "vs.#advancePipelines.pokeEnd", () => pokers.end(finalVersion));
|
|
@@ -1199,7 +1574,7 @@ var ViewSyncerService = class {
|
|
|
1199
1574
|
clientID,
|
|
1200
1575
|
wsID: client.wsID
|
|
1201
1576
|
});
|
|
1202
|
-
return handleInspect(lc, body, cvr, client, this.#inspectorDelegate, this.id, this.#cvrStore, this.#config, connCtx);
|
|
1577
|
+
return handleInspect(lc, body, cvr, client, this.#inspectorDelegate, this.id, this, this.#cvrStore, this.#config, connCtx);
|
|
1203
1578
|
};
|
|
1204
1579
|
async #runBackgroundRetransform(lc) {
|
|
1205
1580
|
const attemptRetransform = async (connCtx) => {
|
|
@@ -1286,15 +1661,18 @@ var ViewSyncerService = class {
|
|
|
1286
1661
|
return this.#stopped.promise;
|
|
1287
1662
|
}
|
|
1288
1663
|
async #cleanup(err) {
|
|
1664
|
+
this.#shuttingDown = true;
|
|
1289
1665
|
this.connContextManager.setSharedRetransformReady(false);
|
|
1290
1666
|
this.#stopTTLClockInterval();
|
|
1291
1667
|
this.#stopExpireTimer();
|
|
1292
1668
|
this.#stopAuthMaintenanceTimer();
|
|
1669
|
+
this.#stopShutdownTimer();
|
|
1293
1670
|
this.#customQueryTransformer?.destroy();
|
|
1294
1671
|
for (const client of this.#clients.values()) if (err) client.fail(err);
|
|
1295
1672
|
else client.close(`closed clientGroupID=${this.id}`);
|
|
1296
1673
|
await this.#lock.withLock(() => {});
|
|
1297
1674
|
this.#pipelines.destroy();
|
|
1675
|
+
this.#inspectorDelegate.clearAuthenticated(this.id, this);
|
|
1298
1676
|
}
|
|
1299
1677
|
/**
|
|
1300
1678
|
* Test helper: Manually mark initialization as complete.
|
|
@@ -1330,8 +1708,10 @@ function isTransformFailedError(error) {
|
|
|
1330
1708
|
return error.errorBody.kind === "TransformFailed" && !isAuthErrorBody(error.errorBody);
|
|
1331
1709
|
}
|
|
1332
1710
|
/**
|
|
1333
|
-
* A query must be expired for all clients
|
|
1334
|
-
* expired.
|
|
1711
|
+
* Whether a query's TTL has elapsed. A query must be expired for all clients
|
|
1712
|
+
* in order to be considered expired.
|
|
1713
|
+
*
|
|
1714
|
+
* Exported for testing.
|
|
1335
1715
|
*/
|
|
1336
1716
|
function expired(ttlClock, q) {
|
|
1337
1717
|
if (q.type === "internal") return false;
|