@rocicorp/zero 1.11.0-canary.13 → 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.
Files changed (249) hide show
  1. package/out/analyze-query/src/analyze-cli.js +2 -2
  2. package/out/analyze-query/src/analyze-cli.js.map +1 -1
  3. package/out/ast-to-zql/src/ast-to-zql.d.ts +8 -1
  4. package/out/ast-to-zql/src/ast-to-zql.d.ts.map +1 -1
  5. package/out/ast-to-zql/src/ast-to-zql.js +25 -25
  6. package/out/ast-to-zql/src/ast-to-zql.js.map +1 -1
  7. package/out/otel/src/log-options.d.ts +16 -0
  8. package/out/otel/src/log-options.d.ts.map +1 -1
  9. package/out/otel/src/log-options.js +54 -1
  10. package/out/otel/src/log-options.js.map +1 -1
  11. package/out/replicache/src/get-kv-store-provider.d.ts +1 -1
  12. package/out/replicache/src/get-kv-store-provider.d.ts.map +1 -1
  13. package/out/replicache/src/get-kv-store-provider.js +4 -4
  14. package/out/replicache/src/get-kv-store-provider.js.map +1 -1
  15. package/out/replicache/src/kv/idb-store.d.ts +10 -1
  16. package/out/replicache/src/kv/idb-store.d.ts.map +1 -1
  17. package/out/replicache/src/kv/idb-store.js +18 -2
  18. package/out/replicache/src/kv/idb-store.js.map +1 -1
  19. package/out/replicache/src/kv/mem-fallback-store.d.ts +54 -0
  20. package/out/replicache/src/kv/mem-fallback-store.d.ts.map +1 -0
  21. package/out/replicache/src/kv/mem-fallback-store.js +177 -0
  22. package/out/replicache/src/kv/mem-fallback-store.js.map +1 -0
  23. package/out/replicache/src/kv/op-sqlite/store.js +3 -3
  24. package/out/replicache/src/kv/op-sqlite/store.js.map +1 -1
  25. package/out/replicache/src/kv/sqlite-store.d.ts +9 -0
  26. package/out/replicache/src/kv/sqlite-store.d.ts.map +1 -1
  27. package/out/replicache/src/kv/sqlite-store.js +86 -6
  28. package/out/replicache/src/kv/sqlite-store.js.map +1 -1
  29. package/out/replicache/src/new-client-channel.js.map +1 -1
  30. package/out/replicache/src/persist/collect-idb-databases.d.ts +17 -3
  31. package/out/replicache/src/persist/collect-idb-databases.d.ts.map +1 -1
  32. package/out/replicache/src/persist/collect-idb-databases.js +81 -11
  33. package/out/replicache/src/persist/collect-idb-databases.js.map +1 -1
  34. package/out/replicache/src/persist/idb-databases-store-db-name.js +1 -1
  35. package/out/replicache/src/persist/idb-databases-store-db-name.js.map +1 -1
  36. package/out/replicache/src/persist/persist.d.ts.map +1 -1
  37. package/out/replicache/src/persist/persist.js +6 -3
  38. package/out/replicache/src/persist/persist.js.map +1 -1
  39. package/out/replicache/src/replicache-impl.d.ts +43 -3
  40. package/out/replicache/src/replicache-impl.d.ts.map +1 -1
  41. package/out/replicache/src/replicache-impl.js +135 -17
  42. package/out/replicache/src/replicache-impl.js.map +1 -1
  43. package/out/replicache/src/storage-failure.d.ts +37 -0
  44. package/out/replicache/src/storage-failure.d.ts.map +1 -0
  45. package/out/replicache/src/storage-failure.js +31 -0
  46. package/out/replicache/src/storage-failure.js.map +1 -0
  47. package/out/shared/src/random-values.js +12 -1
  48. package/out/shared/src/random-values.js.map +1 -1
  49. package/out/zero/package.js +1 -1
  50. package/out/zero/package.js.map +1 -1
  51. package/out/zero/src/zero.js +2 -1
  52. package/out/zero-cache/src/config/zero-config.d.ts +26 -0
  53. package/out/zero-cache/src/config/zero-config.d.ts.map +1 -1
  54. package/out/zero-cache/src/config/zero-config.js +36 -0
  55. package/out/zero-cache/src/config/zero-config.js.map +1 -1
  56. package/out/zero-cache/src/observability/log-throttle.d.ts +44 -0
  57. package/out/zero-cache/src/observability/log-throttle.d.ts.map +1 -0
  58. package/out/zero-cache/src/observability/log-throttle.js +76 -0
  59. package/out/zero-cache/src/observability/log-throttle.js.map +1 -0
  60. package/out/zero-cache/src/scripts/permissions.d.ts +16 -0
  61. package/out/zero-cache/src/scripts/permissions.d.ts.map +1 -1
  62. package/out/zero-cache/src/server/runner/zero-dispatcher.d.ts.map +1 -1
  63. package/out/zero-cache/src/server/runner/zero-dispatcher.js +2 -0
  64. package/out/zero-cache/src/server/runner/zero-dispatcher.js.map +1 -1
  65. package/out/zero-cache/src/server/syncer.d.ts.map +1 -1
  66. package/out/zero-cache/src/server/syncer.js +20 -2
  67. package/out/zero-cache/src/server/syncer.js.map +1 -1
  68. package/out/zero-cache/src/services/change-source/common/backfill-manager.d.ts.map +1 -1
  69. package/out/zero-cache/src/services/change-source/common/backfill-manager.js +27 -15
  70. package/out/zero-cache/src/services/change-source/common/backfill-manager.js.map +1 -1
  71. package/out/zero-cache/src/services/change-source/common/replica-schema.d.ts +1 -0
  72. package/out/zero-cache/src/services/change-source/common/replica-schema.d.ts.map +1 -1
  73. package/out/zero-cache/src/services/change-source/common/replica-schema.js +15 -3
  74. package/out/zero-cache/src/services/change-source/common/replica-schema.js.map +1 -1
  75. package/out/zero-cache/src/services/change-source/custom/change-source.js +2 -2
  76. package/out/zero-cache/src/services/change-source/custom/change-source.js.map +1 -1
  77. package/out/zero-cache/src/services/change-source/pg/backfill-stream.d.ts +9 -0
  78. package/out/zero-cache/src/services/change-source/pg/backfill-stream.d.ts.map +1 -1
  79. package/out/zero-cache/src/services/change-source/pg/backfill-stream.js +204 -18
  80. package/out/zero-cache/src/services/change-source/pg/backfill-stream.js.map +1 -1
  81. package/out/zero-cache/src/services/change-source/pg/initial-sync.d.ts +11 -2
  82. package/out/zero-cache/src/services/change-source/pg/initial-sync.d.ts.map +1 -1
  83. package/out/zero-cache/src/services/change-source/pg/initial-sync.js +11 -2
  84. package/out/zero-cache/src/services/change-source/pg/initial-sync.js.map +1 -1
  85. package/out/zero-cache/src/services/change-source/protocol/backfill-progress.d.ts +68 -0
  86. package/out/zero-cache/src/services/change-source/protocol/backfill-progress.d.ts.map +1 -0
  87. package/out/zero-cache/src/services/change-source/protocol/backfill-progress.js +141 -0
  88. package/out/zero-cache/src/services/change-source/protocol/backfill-progress.js.map +1 -0
  89. package/out/zero-cache/src/services/change-source/protocol/current/data.d.ts +43 -0
  90. package/out/zero-cache/src/services/change-source/protocol/current/data.d.ts.map +1 -1
  91. package/out/zero-cache/src/services/change-source/protocol/current/data.js +10 -1
  92. package/out/zero-cache/src/services/change-source/protocol/current/data.js.map +1 -1
  93. package/out/zero-cache/src/services/change-source/protocol/current/downstream.d.ts +57 -0
  94. package/out/zero-cache/src/services/change-source/protocol/current/downstream.d.ts.map +1 -1
  95. package/out/zero-cache/src/services/change-source/protocol/current/upstream.d.ts +20 -6
  96. package/out/zero-cache/src/services/change-source/protocol/current/upstream.d.ts.map +1 -1
  97. package/out/zero-cache/src/services/change-source/protocol/current/upstream.js +17 -7
  98. package/out/zero-cache/src/services/change-source/protocol/current/upstream.js.map +1 -1
  99. package/out/zero-cache/src/services/change-source/protocol/current.js +2 -1
  100. package/out/zero-cache/src/services/change-streamer/backfill-state-tracker.d.ts +66 -0
  101. package/out/zero-cache/src/services/change-streamer/backfill-state-tracker.d.ts.map +1 -0
  102. package/out/zero-cache/src/services/change-streamer/backfill-state-tracker.js +154 -0
  103. package/out/zero-cache/src/services/change-streamer/backfill-state-tracker.js.map +1 -0
  104. package/out/zero-cache/src/services/change-streamer/backfill-state.d.ts +50 -0
  105. package/out/zero-cache/src/services/change-streamer/backfill-state.d.ts.map +1 -0
  106. package/out/zero-cache/src/services/change-streamer/backfill-state.js +268 -0
  107. package/out/zero-cache/src/services/change-streamer/backfill-state.js.map +1 -0
  108. package/out/zero-cache/src/services/change-streamer/change-streamer-http.js +7 -6
  109. package/out/zero-cache/src/services/change-streamer/change-streamer-http.js.map +1 -1
  110. package/out/zero-cache/src/services/change-streamer/change-streamer-service.d.ts.map +1 -1
  111. package/out/zero-cache/src/services/change-streamer/change-streamer-service.js +20 -4
  112. package/out/zero-cache/src/services/change-streamer/change-streamer-service.js.map +1 -1
  113. package/out/zero-cache/src/services/change-streamer/change-streamer.d.ts +26 -1
  114. package/out/zero-cache/src/services/change-streamer/change-streamer.d.ts.map +1 -1
  115. package/out/zero-cache/src/services/change-streamer/change-streamer.js.map +1 -1
  116. package/out/zero-cache/src/services/change-streamer/storer.d.ts.map +1 -1
  117. package/out/zero-cache/src/services/change-streamer/storer.js +1 -1
  118. package/out/zero-cache/src/services/change-streamer/storer.js.map +1 -1
  119. package/out/zero-cache/src/services/change-streamer/subscribe.d.ts +88 -0
  120. package/out/zero-cache/src/services/change-streamer/subscribe.d.ts.map +1 -1
  121. package/out/zero-cache/src/services/change-streamer/subscribe.js +9 -1
  122. package/out/zero-cache/src/services/change-streamer/subscribe.js.map +1 -1
  123. package/out/zero-cache/src/services/change-streamer/subscriber.d.ts +29 -0
  124. package/out/zero-cache/src/services/change-streamer/subscriber.d.ts.map +1 -1
  125. package/out/zero-cache/src/services/change-streamer/subscriber.js +34 -0
  126. package/out/zero-cache/src/services/change-streamer/subscriber.js.map +1 -1
  127. package/out/zero-cache/src/services/plannerz.d.ts +159 -0
  128. package/out/zero-cache/src/services/plannerz.d.ts.map +1 -0
  129. package/out/zero-cache/src/services/plannerz.js +263 -0
  130. package/out/zero-cache/src/services/plannerz.js.map +1 -0
  131. package/out/zero-cache/src/services/replicator/change-log-cookies.js +1 -1
  132. package/out/zero-cache/src/services/replicator/change-log-cookies.js.map +1 -1
  133. package/out/zero-cache/src/services/replicator/change-processor.d.ts.map +1 -1
  134. package/out/zero-cache/src/services/replicator/change-processor.js +44 -12
  135. package/out/zero-cache/src/services/replicator/change-processor.js.map +1 -1
  136. package/out/zero-cache/src/services/replicator/incremental-sync.d.ts.map +1 -1
  137. package/out/zero-cache/src/services/replicator/incremental-sync.js +4 -3
  138. package/out/zero-cache/src/services/replicator/incremental-sync.js.map +1 -1
  139. package/out/zero-cache/src/services/replicator/schema/backfilling.d.ts +23 -2
  140. package/out/zero-cache/src/services/replicator/schema/backfilling.d.ts.map +1 -1
  141. package/out/zero-cache/src/services/replicator/schema/backfilling.js +54 -2
  142. package/out/zero-cache/src/services/replicator/schema/backfilling.js.map +1 -1
  143. package/out/zero-cache/src/services/replicator/write-worker-client.d.ts +11 -3
  144. package/out/zero-cache/src/services/replicator/write-worker-client.d.ts.map +1 -1
  145. package/out/zero-cache/src/services/replicator/write-worker-client.js.map +1 -1
  146. package/out/zero-cache/src/services/replicator/write-worker.js +5 -1
  147. package/out/zero-cache/src/services/replicator/write-worker.js.map +1 -1
  148. package/out/zero-cache/src/services/view-syncer/deferred-writes-budget.d.ts +81 -0
  149. package/out/zero-cache/src/services/view-syncer/deferred-writes-budget.d.ts.map +1 -0
  150. package/out/zero-cache/src/services/view-syncer/deferred-writes-budget.js +128 -0
  151. package/out/zero-cache/src/services/view-syncer/deferred-writes-budget.js.map +1 -0
  152. package/out/zero-cache/src/services/view-syncer/inspect-handler.js +1 -1
  153. package/out/zero-cache/src/services/view-syncer/pipeline-driver.d.ts +36 -1
  154. package/out/zero-cache/src/services/view-syncer/pipeline-driver.d.ts.map +1 -1
  155. package/out/zero-cache/src/services/view-syncer/pipeline-driver.js +362 -20
  156. package/out/zero-cache/src/services/view-syncer/pipeline-driver.js.map +1 -1
  157. package/out/zero-cache/src/services/view-syncer/plan-warnings.d.ts +7 -0
  158. package/out/zero-cache/src/services/view-syncer/plan-warnings.d.ts.map +1 -0
  159. package/out/zero-cache/src/services/view-syncer/plan-warnings.js +35 -0
  160. package/out/zero-cache/src/services/view-syncer/plan-warnings.js.map +1 -0
  161. package/out/zero-cache/src/services/view-syncer/query-shape.d.ts +25 -0
  162. package/out/zero-cache/src/services/view-syncer/query-shape.d.ts.map +1 -0
  163. package/out/zero-cache/src/services/view-syncer/query-shape.js +28 -0
  164. package/out/zero-cache/src/services/view-syncer/query-shape.js.map +1 -0
  165. package/out/zero-cache/src/services/view-syncer/query-stats.d.ts +77 -0
  166. package/out/zero-cache/src/services/view-syncer/query-stats.d.ts.map +1 -0
  167. package/out/zero-cache/src/services/view-syncer/query-stats.js +206 -0
  168. package/out/zero-cache/src/services/view-syncer/query-stats.js.map +1 -0
  169. package/out/zero-cache/src/services/view-syncer/snapshotter.d.ts +11 -1
  170. package/out/zero-cache/src/services/view-syncer/snapshotter.d.ts.map +1 -1
  171. package/out/zero-cache/src/services/view-syncer/snapshotter.js +7 -1
  172. package/out/zero-cache/src/services/view-syncer/snapshotter.js.map +1 -1
  173. package/out/zero-cache/src/services/view-syncer/view-syncer.d.ts.map +1 -1
  174. package/out/zero-cache/src/services/view-syncer/view-syncer.js +49 -3
  175. package/out/zero-cache/src/services/view-syncer/view-syncer.js.map +1 -1
  176. package/out/zero-cache/src/workers/connection.js +3 -3
  177. package/out/zero-cache/src/workers/connection.js.map +1 -1
  178. package/out/zero-cache/src/workers/syncer.js +2 -2
  179. package/out/zero-cache/src/workers/syncer.js.map +1 -1
  180. package/out/zero-client/src/client/options.d.ts +29 -0
  181. package/out/zero-client/src/client/options.d.ts.map +1 -1
  182. package/out/zero-client/src/client/options.js.map +1 -1
  183. package/out/zero-client/src/client/version.js +1 -1
  184. package/out/zero-client/src/client/zero.d.ts.map +1 -1
  185. package/out/zero-client/src/client/zero.js +8 -23
  186. package/out/zero-client/src/client/zero.js.map +1 -1
  187. package/out/zero-client/src/mod.d.ts +1 -0
  188. package/out/zero-client/src/mod.d.ts.map +1 -1
  189. package/out/zero-client/src/mod.js +1 -0
  190. package/out/zero-protocol/src/down.d.ts +2 -2
  191. package/out/zero-react/src/zero.js +1 -0
  192. package/out/zero-solid/src/zero.js +1 -0
  193. package/out/zql/src/builder/builder.d.ts +6 -0
  194. package/out/zql/src/builder/builder.d.ts.map +1 -1
  195. package/out/zql/src/builder/builder.js +4 -3
  196. package/out/zql/src/builder/builder.js.map +1 -1
  197. package/out/zql/src/ivm/memory-source.d.ts +13 -0
  198. package/out/zql/src/ivm/memory-source.d.ts.map +1 -1
  199. package/out/zql/src/ivm/memory-source.js +1 -1
  200. package/out/zql/src/ivm/memory-source.js.map +1 -1
  201. package/out/zql/src/planner/planner-builder.d.ts +12 -1
  202. package/out/zql/src/planner/planner-builder.d.ts.map +1 -1
  203. package/out/zql/src/planner/planner-builder.js +8 -1
  204. package/out/zql/src/planner/planner-builder.js.map +1 -1
  205. package/out/zql/src/planner/planner-connection.d.ts +43 -0
  206. package/out/zql/src/planner/planner-connection.d.ts.map +1 -1
  207. package/out/zql/src/planner/planner-connection.js +31 -4
  208. package/out/zql/src/planner/planner-connection.js.map +1 -1
  209. package/out/zql/src/planner/planner-graph.d.ts +10 -0
  210. package/out/zql/src/planner/planner-graph.d.ts.map +1 -1
  211. package/out/zql/src/planner/planner-graph.js +14 -0
  212. package/out/zql/src/planner/planner-graph.js.map +1 -1
  213. package/out/zql/src/planner/planner-warnings.d.ts +83 -0
  214. package/out/zql/src/planner/planner-warnings.d.ts.map +1 -0
  215. package/out/zql/src/planner/planner-warnings.js +78 -0
  216. package/out/zql/src/planner/planner-warnings.js.map +1 -0
  217. package/out/zql/src/query/measure-push-operator.d.ts.map +1 -1
  218. package/out/zql/src/query/measure-push-operator.js +30 -8
  219. package/out/zql/src/query/measure-push-operator.js.map +1 -1
  220. package/out/zqlite/src/pending-delta.d.ts +110 -0
  221. package/out/zqlite/src/pending-delta.d.ts.map +1 -0
  222. package/out/zqlite/src/pending-delta.js +278 -0
  223. package/out/zqlite/src/pending-delta.js.map +1 -0
  224. package/out/zqlite/src/query-delegate.d.ts.map +1 -1
  225. package/out/zqlite/src/query-delegate.js +5 -1
  226. package/out/zqlite/src/query-delegate.js.map +1 -1
  227. package/out/zqlite/src/sqlite-cost-model.d.ts.map +1 -1
  228. package/out/zqlite/src/sqlite-cost-model.js +52 -1
  229. package/out/zqlite/src/sqlite-cost-model.js.map +1 -1
  230. package/out/zqlite/src/sqlite-stat-fanout.d.ts.map +1 -1
  231. package/out/zqlite/src/sqlite-stat-fanout.js +2 -26
  232. package/out/zqlite/src/sqlite-stat-fanout.js.map +1 -1
  233. package/out/zqlite/src/sqlite-stat4-sample.d.ts +37 -0
  234. package/out/zqlite/src/sqlite-stat4-sample.d.ts.map +1 -0
  235. package/out/zqlite/src/sqlite-stat4-sample.js +61 -0
  236. package/out/zqlite/src/sqlite-stat4-sample.js.map +1 -0
  237. package/out/zqlite/src/table-source.d.ts +68 -0
  238. package/out/zqlite/src/table-source.d.ts.map +1 -1
  239. package/out/zqlite/src/table-source.js +206 -7
  240. package/out/zqlite/src/table-source.js.map +1 -1
  241. package/package.json +1 -1
  242. package/out/replicache/src/kv/idb-store-with-mem-fallback.d.ts +0 -35
  243. package/out/replicache/src/kv/idb-store-with-mem-fallback.d.ts.map +0 -1
  244. package/out/replicache/src/kv/idb-store-with-mem-fallback.js +0 -83
  245. package/out/replicache/src/kv/idb-store-with-mem-fallback.js.map +0 -1
  246. package/out/zero-cache/src/services/change-source/pg/backfill-resume.d.ts +0 -156
  247. package/out/zero-cache/src/services/change-source/pg/backfill-resume.d.ts.map +0 -1
  248. package/out/zero-cache/src/services/change-source/pg/backfill-resume.js +0 -15
  249. 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;;;;;;;;;;;;;;;;;;;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
+ {"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;AAOzE,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,EAmBrB;IAED,UAAmB,QAAQ,SAE1B;CAYF"}
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":";;;;;;AAgBA,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,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
+ {"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":"AAkCA,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,CAgOf"}
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
- return runUntilKilled(lc, parent, syncer);
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;AASjD,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;AA6ErC;;;;;;;;;;;;;;;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;IAwaD;;;OAGG;IACH,QAAQ,CAAC,OAAO,EAAE,mBAAmB,GAAG,IAAI,CA2M3C;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
+ {"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,6 +1,7 @@
1
1
  import { assert } from "../../../../../shared/src/asserts.js";
2
2
  import { AbortError } from "../../../../../shared/src/abort-error.js";
3
3
  import { must } from "../../../../../shared/src/must.js";
4
+ import { promiseOrAbort } from "../../../../../shared/src/promise-race.js";
4
5
  import { stringify } from "../../../../../shared/src/bigint-json.js";
5
6
  import "../../../types/lite.js";
6
7
  import { randInt } from "../../../../../shared/src/rand.js";
@@ -147,7 +148,8 @@ var BackfillManager = class {
147
148
  const request = candidates[randInt(0, candidates.length - 1)];
148
149
  const state = {
149
150
  request,
150
- minWatermark: ""
151
+ minWatermark: "",
152
+ stopped: new AbortController()
151
153
  };
152
154
  const lc = this.#lc.withContext("table", request.table.name);
153
155
  this.#runningBackfill = state;
@@ -186,7 +188,8 @@ var BackfillManager = class {
186
188
  produceMs: 0,
187
189
  pushMs: 0,
188
190
  reserveMs: 0,
189
- stallMs: 0
191
+ stallMs: 0,
192
+ waitMs: 0
190
193
  };
191
194
  /**
192
195
  * @returns the new tx watermark, or null if backfill was cancelled
@@ -194,8 +197,8 @@ var BackfillManager = class {
194
197
  const beginTxFor = async (msg) => {
195
198
  assert(backfillTx === null, "Expected no active backfill transaction");
196
199
  const lastWatermark = await changeStream.reserve("backfill");
197
- if (state.canceledReason || msg.tag === "backfill" && msg.watermark < state.minWatermark) {
198
- if (state.canceledReason === void 0) {
200
+ if (state.stopped.signal.aborted || msg.tag === "backfill" && msg.watermark < state.minWatermark) {
201
+ if (!state.stopped.signal.aborted) {
199
202
  assert(msg.tag === "backfill", "Expected backfill message tag");
200
203
  this.#stopRunningBackfill(`row key change at ${state.minWatermark} postdates backfill watermark at ${msg.watermark}`, state);
201
204
  }
@@ -207,7 +210,7 @@ var BackfillManager = class {
207
210
  major,
208
211
  minor: BigInt(minor) + 1n
209
212
  });
210
- if (msg.tag === "backfill-completed" && tx < msg.watermark) tx = msg.watermark;
213
+ if (tx < msg.watermark) tx = msg.watermark;
211
214
  changeStream.push([
212
215
  "begin",
213
216
  {
@@ -288,16 +291,22 @@ var BackfillManager = class {
288
291
  return;
289
292
  }
290
293
  const { message: msg, byteSize } = item;
291
- const mustWaitBeforeFlush = msg.tag === "backfill-completed" && (this.#changeStreamReached(lc, msg.watermark) || backfillTx !== null && backfillTx < msg.watermark);
292
- if (backfillTx && (changeStream.waiterDelay() > 0 || mustWaitBeforeFlush || uncommittedBytes >= this.#commitThresholdBytes)) commitTx();
293
- if (mustWaitBeforeFlush) await mustWaitBeforeFlush;
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
+ }
294
303
  if (msg.tag === "backfill" && msg.rowValues.length > 0 && msg.relation.rowKey.columns.length === 0) throw new MissingRowKeyError(state.request);
295
304
  if (backfillTx === null) {
296
305
  const t0 = performance.now();
297
306
  backfillTx = await beginTxFor(msg);
298
307
  stats.reserveMs += performance.now() - t0;
299
308
  if (backfillTx === null) {
300
- lc.info?.(`backfill stream canceled: ${state.canceledReason}`, state.request);
309
+ lc.info?.(`backfill stream canceled: ${state.stopped.signal.reason}`, state.request);
301
310
  this.#checkAndStartBackfill();
302
311
  return;
303
312
  }
@@ -311,7 +320,7 @@ var BackfillManager = class {
311
320
  this.#maybeLogBackfillStats(lc, stats);
312
321
  }
313
322
  if (backfillTx) commitTx();
314
- lc.debug?.(`backfill stream exited`, state.canceledReason ?? "");
323
+ lc.debug?.(`backfill stream exited`);
315
324
  } finally {
316
325
  stopProducer = true;
317
326
  credits.enqueue(1);
@@ -334,7 +343,9 @@ var BackfillManager = class {
334
343
  * their sum can exceed `elapsedMs`. `reserveMs` is time spent (re)acquiring
335
344
  * the change-stream reservation, and `stallMs` is time the consumer waited on
336
345
  * an empty prefetch queue (i.e. blocked on the upstream) — a large `stallMs`
337
- * indicates an upstream-bound backfill.
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.
338
349
  */
339
350
  #logBackfillStats(lc, stats) {
340
351
  const elapsed = performance.now() - stats.start;
@@ -348,6 +359,7 @@ var BackfillManager = class {
348
359
  pushMs: Math.round(stats.pushMs),
349
360
  reserveMs: Math.round(stats.reserveMs),
350
361
  stallMs: Math.round(stats.stallMs),
362
+ waitMs: Math.round(stats.waitMs),
351
363
  throughputKBps: elapsed > 0 ? Math.round(stats.bytes / 1024 / (elapsed / 1e3)) : 0
352
364
  });
353
365
  }
@@ -364,7 +376,7 @@ var BackfillManager = class {
364
376
  #stopRunningBackfill(reason, instance) {
365
377
  const backfill = this.#runningBackfill;
366
378
  if (backfill && backfill === (instance ?? backfill)) {
367
- backfill.canceledReason = reason;
379
+ backfill.stopped.abort(reason);
368
380
  this.#runningBackfill = null;
369
381
  reason && this.#lc.info?.(`canceling backfill:`, reason);
370
382
  }
@@ -428,7 +440,7 @@ var BackfillManager = class {
428
440
  name,
429
441
  metadata
430
442
  },
431
- columns: backfill
443
+ columns: Object.fromEntries(Object.entries(backfill).map(([col, id]) => [col, { id }]))
432
444
  });
433
445
  break;
434
446
  }
@@ -467,7 +479,7 @@ var BackfillManager = class {
467
479
  ...table,
468
480
  metadata
469
481
  },
470
- columns: { [column.name]: backfill }
482
+ columns: { [column.name]: { id: backfill } }
471
483
  });
472
484
  else this.#setRequiredBackfill(tag, {
473
485
  ...backfillRequest,
@@ -477,7 +489,7 @@ var BackfillManager = class {
477
489
  },
478
490
  columns: {
479
491
  ...backfillRequest.columns,
480
- [column.name]: backfill
492
+ [column.name]: { id: backfill }
481
493
  }
482
494
  });
483
495
  }