@rocicorp/zero 1.11.0-canary.12 → 1.11.0-canary.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (260) hide show
  1. package/out/ast-to-zql/src/ast-to-zql.d.ts +8 -1
  2. package/out/ast-to-zql/src/ast-to-zql.d.ts.map +1 -1
  3. package/out/ast-to-zql/src/ast-to-zql.js +25 -25
  4. package/out/ast-to-zql/src/ast-to-zql.js.map +1 -1
  5. package/out/otel/src/log-options.d.ts +16 -0
  6. package/out/otel/src/log-options.d.ts.map +1 -1
  7. package/out/otel/src/log-options.js +54 -1
  8. package/out/otel/src/log-options.js.map +1 -1
  9. package/out/replicache/src/get-kv-store-provider.d.ts +1 -1
  10. package/out/replicache/src/get-kv-store-provider.d.ts.map +1 -1
  11. package/out/replicache/src/get-kv-store-provider.js +4 -4
  12. package/out/replicache/src/get-kv-store-provider.js.map +1 -1
  13. package/out/replicache/src/kv/idb-store.d.ts +10 -1
  14. package/out/replicache/src/kv/idb-store.d.ts.map +1 -1
  15. package/out/replicache/src/kv/idb-store.js +18 -2
  16. package/out/replicache/src/kv/idb-store.js.map +1 -1
  17. package/out/replicache/src/kv/mem-fallback-store.d.ts +54 -0
  18. package/out/replicache/src/kv/mem-fallback-store.d.ts.map +1 -0
  19. package/out/replicache/src/kv/mem-fallback-store.js +177 -0
  20. package/out/replicache/src/kv/mem-fallback-store.js.map +1 -0
  21. package/out/replicache/src/kv/op-sqlite/store.js +3 -3
  22. package/out/replicache/src/kv/op-sqlite/store.js.map +1 -1
  23. package/out/replicache/src/kv/sqlite-store.d.ts +9 -0
  24. package/out/replicache/src/kv/sqlite-store.d.ts.map +1 -1
  25. package/out/replicache/src/kv/sqlite-store.js +86 -6
  26. package/out/replicache/src/kv/sqlite-store.js.map +1 -1
  27. package/out/replicache/src/new-client-channel.js.map +1 -1
  28. package/out/replicache/src/persist/collect-idb-databases.d.ts +17 -3
  29. package/out/replicache/src/persist/collect-idb-databases.d.ts.map +1 -1
  30. package/out/replicache/src/persist/collect-idb-databases.js +81 -11
  31. package/out/replicache/src/persist/collect-idb-databases.js.map +1 -1
  32. package/out/replicache/src/persist/idb-databases-store-db-name.js +1 -1
  33. package/out/replicache/src/persist/idb-databases-store-db-name.js.map +1 -1
  34. package/out/replicache/src/persist/persist.d.ts.map +1 -1
  35. package/out/replicache/src/persist/persist.js +6 -3
  36. package/out/replicache/src/persist/persist.js.map +1 -1
  37. package/out/replicache/src/replicache-impl.d.ts +43 -3
  38. package/out/replicache/src/replicache-impl.d.ts.map +1 -1
  39. package/out/replicache/src/replicache-impl.js +135 -17
  40. package/out/replicache/src/replicache-impl.js.map +1 -1
  41. package/out/replicache/src/storage-failure.d.ts +37 -0
  42. package/out/replicache/src/storage-failure.d.ts.map +1 -0
  43. package/out/replicache/src/storage-failure.js +31 -0
  44. package/out/replicache/src/storage-failure.js.map +1 -0
  45. package/out/shared/src/random-values.js +12 -1
  46. package/out/shared/src/random-values.js.map +1 -1
  47. package/out/zero/package.js +1 -1
  48. package/out/zero/package.js.map +1 -1
  49. package/out/zero/src/zero.js +2 -1
  50. package/out/zero-cache/src/config/zero-config.d.ts +26 -0
  51. package/out/zero-cache/src/config/zero-config.d.ts.map +1 -1
  52. package/out/zero-cache/src/config/zero-config.js +36 -0
  53. package/out/zero-cache/src/config/zero-config.js.map +1 -1
  54. package/out/zero-cache/src/observability/log-throttle.d.ts +44 -0
  55. package/out/zero-cache/src/observability/log-throttle.d.ts.map +1 -0
  56. package/out/zero-cache/src/observability/log-throttle.js +76 -0
  57. package/out/zero-cache/src/observability/log-throttle.js.map +1 -0
  58. package/out/zero-cache/src/scripts/permissions.d.ts +16 -0
  59. package/out/zero-cache/src/scripts/permissions.d.ts.map +1 -1
  60. package/out/zero-cache/src/server/runner/zero-dispatcher.d.ts.map +1 -1
  61. package/out/zero-cache/src/server/runner/zero-dispatcher.js +2 -0
  62. package/out/zero-cache/src/server/runner/zero-dispatcher.js.map +1 -1
  63. package/out/zero-cache/src/server/syncer.d.ts.map +1 -1
  64. package/out/zero-cache/src/server/syncer.js +20 -2
  65. package/out/zero-cache/src/server/syncer.js.map +1 -1
  66. package/out/zero-cache/src/services/change-source/common/backfill-manager.d.ts.map +1 -1
  67. package/out/zero-cache/src/services/change-source/common/backfill-manager.js +189 -27
  68. package/out/zero-cache/src/services/change-source/common/backfill-manager.js.map +1 -1
  69. package/out/zero-cache/src/services/change-source/common/replica-schema.d.ts +1 -0
  70. package/out/zero-cache/src/services/change-source/common/replica-schema.d.ts.map +1 -1
  71. package/out/zero-cache/src/services/change-source/common/replica-schema.js +15 -3
  72. package/out/zero-cache/src/services/change-source/common/replica-schema.js.map +1 -1
  73. package/out/zero-cache/src/services/change-source/pg/backfill-stream.d.ts +9 -0
  74. package/out/zero-cache/src/services/change-source/pg/backfill-stream.d.ts.map +1 -1
  75. package/out/zero-cache/src/services/change-source/pg/backfill-stream.js +204 -18
  76. package/out/zero-cache/src/services/change-source/pg/backfill-stream.js.map +1 -1
  77. package/out/zero-cache/src/services/change-source/pg/initial-sync.d.ts +11 -2
  78. package/out/zero-cache/src/services/change-source/pg/initial-sync.d.ts.map +1 -1
  79. package/out/zero-cache/src/services/change-source/pg/initial-sync.js +11 -2
  80. package/out/zero-cache/src/services/change-source/pg/initial-sync.js.map +1 -1
  81. package/out/zero-cache/src/services/change-source/protocol/backfill-progress.d.ts +68 -0
  82. package/out/zero-cache/src/services/change-source/protocol/backfill-progress.d.ts.map +1 -0
  83. package/out/zero-cache/src/services/change-source/protocol/backfill-progress.js +141 -0
  84. package/out/zero-cache/src/services/change-source/protocol/backfill-progress.js.map +1 -0
  85. package/out/zero-cache/src/services/change-source/protocol/current/data.d.ts +43 -0
  86. package/out/zero-cache/src/services/change-source/protocol/current/data.d.ts.map +1 -1
  87. package/out/zero-cache/src/services/change-source/protocol/current/data.js +10 -1
  88. package/out/zero-cache/src/services/change-source/protocol/current/data.js.map +1 -1
  89. package/out/zero-cache/src/services/change-source/protocol/current/downstream.d.ts +57 -0
  90. package/out/zero-cache/src/services/change-source/protocol/current/downstream.d.ts.map +1 -1
  91. package/out/zero-cache/src/services/change-source/protocol/current/upstream.d.ts +20 -6
  92. package/out/zero-cache/src/services/change-source/protocol/current/upstream.d.ts.map +1 -1
  93. package/out/zero-cache/src/services/change-source/protocol/current/upstream.js +17 -7
  94. package/out/zero-cache/src/services/change-source/protocol/current/upstream.js.map +1 -1
  95. package/out/zero-cache/src/services/change-source/protocol/current.js +2 -1
  96. package/out/zero-cache/src/services/change-streamer/backfill-state-tracker.d.ts +66 -0
  97. package/out/zero-cache/src/services/change-streamer/backfill-state-tracker.d.ts.map +1 -0
  98. package/out/zero-cache/src/services/change-streamer/backfill-state-tracker.js +154 -0
  99. package/out/zero-cache/src/services/change-streamer/backfill-state-tracker.js.map +1 -0
  100. package/out/zero-cache/src/services/change-streamer/backfill-state.d.ts +50 -0
  101. package/out/zero-cache/src/services/change-streamer/backfill-state.d.ts.map +1 -0
  102. package/out/zero-cache/src/services/change-streamer/backfill-state.js +268 -0
  103. package/out/zero-cache/src/services/change-streamer/backfill-state.js.map +1 -0
  104. package/out/zero-cache/src/services/change-streamer/change-streamer-http.js +5 -4
  105. package/out/zero-cache/src/services/change-streamer/change-streamer-http.js.map +1 -1
  106. package/out/zero-cache/src/services/change-streamer/change-streamer-service.d.ts.map +1 -1
  107. package/out/zero-cache/src/services/change-streamer/change-streamer-service.js +20 -4
  108. package/out/zero-cache/src/services/change-streamer/change-streamer-service.js.map +1 -1
  109. package/out/zero-cache/src/services/change-streamer/change-streamer.d.ts +26 -1
  110. package/out/zero-cache/src/services/change-streamer/change-streamer.d.ts.map +1 -1
  111. package/out/zero-cache/src/services/change-streamer/change-streamer.js.map +1 -1
  112. package/out/zero-cache/src/services/change-streamer/storer.d.ts.map +1 -1
  113. package/out/zero-cache/src/services/change-streamer/storer.js +1 -1
  114. package/out/zero-cache/src/services/change-streamer/storer.js.map +1 -1
  115. package/out/zero-cache/src/services/change-streamer/subscribe.d.ts +88 -0
  116. package/out/zero-cache/src/services/change-streamer/subscribe.d.ts.map +1 -1
  117. package/out/zero-cache/src/services/change-streamer/subscribe.js +9 -1
  118. package/out/zero-cache/src/services/change-streamer/subscribe.js.map +1 -1
  119. package/out/zero-cache/src/services/change-streamer/subscriber.d.ts +29 -0
  120. package/out/zero-cache/src/services/change-streamer/subscriber.d.ts.map +1 -1
  121. package/out/zero-cache/src/services/change-streamer/subscriber.js +34 -0
  122. package/out/zero-cache/src/services/change-streamer/subscriber.js.map +1 -1
  123. package/out/zero-cache/src/services/plannerz.d.ts +159 -0
  124. package/out/zero-cache/src/services/plannerz.d.ts.map +1 -0
  125. package/out/zero-cache/src/services/plannerz.js +263 -0
  126. package/out/zero-cache/src/services/plannerz.js.map +1 -0
  127. package/out/zero-cache/src/services/replicator/change-log-cookies.js +1 -1
  128. package/out/zero-cache/src/services/replicator/change-log-cookies.js.map +1 -1
  129. package/out/zero-cache/src/services/replicator/change-processor.d.ts.map +1 -1
  130. package/out/zero-cache/src/services/replicator/change-processor.js +48 -5
  131. package/out/zero-cache/src/services/replicator/change-processor.js.map +1 -1
  132. package/out/zero-cache/src/services/replicator/incremental-sync.d.ts.map +1 -1
  133. package/out/zero-cache/src/services/replicator/incremental-sync.js +4 -3
  134. package/out/zero-cache/src/services/replicator/incremental-sync.js.map +1 -1
  135. package/out/zero-cache/src/services/replicator/schema/backfilling.d.ts +23 -2
  136. package/out/zero-cache/src/services/replicator/schema/backfilling.d.ts.map +1 -1
  137. package/out/zero-cache/src/services/replicator/schema/backfilling.js +54 -2
  138. package/out/zero-cache/src/services/replicator/schema/backfilling.js.map +1 -1
  139. package/out/zero-cache/src/services/replicator/write-worker-client.d.ts +11 -3
  140. package/out/zero-cache/src/services/replicator/write-worker-client.d.ts.map +1 -1
  141. package/out/zero-cache/src/services/replicator/write-worker-client.js.map +1 -1
  142. package/out/zero-cache/src/services/replicator/write-worker.js +5 -1
  143. package/out/zero-cache/src/services/replicator/write-worker.js.map +1 -1
  144. package/out/zero-cache/src/services/view-syncer/cvr-store.d.ts +11 -0
  145. package/out/zero-cache/src/services/view-syncer/cvr-store.d.ts.map +1 -1
  146. package/out/zero-cache/src/services/view-syncer/cvr-store.js +20 -7
  147. package/out/zero-cache/src/services/view-syncer/cvr-store.js.map +1 -1
  148. package/out/zero-cache/src/services/view-syncer/cvr.d.ts.map +1 -1
  149. package/out/zero-cache/src/services/view-syncer/cvr.js +1 -1
  150. package/out/zero-cache/src/services/view-syncer/cvr.js.map +1 -1
  151. package/out/zero-cache/src/services/view-syncer/deferred-writes-budget.d.ts +81 -0
  152. package/out/zero-cache/src/services/view-syncer/deferred-writes-budget.d.ts.map +1 -0
  153. package/out/zero-cache/src/services/view-syncer/deferred-writes-budget.js +128 -0
  154. package/out/zero-cache/src/services/view-syncer/deferred-writes-budget.js.map +1 -0
  155. package/out/zero-cache/src/services/view-syncer/inspect-handler.js +1 -1
  156. package/out/zero-cache/src/services/view-syncer/pipeline-driver.d.ts +36 -1
  157. package/out/zero-cache/src/services/view-syncer/pipeline-driver.d.ts.map +1 -1
  158. package/out/zero-cache/src/services/view-syncer/pipeline-driver.js +393 -30
  159. package/out/zero-cache/src/services/view-syncer/pipeline-driver.js.map +1 -1
  160. package/out/zero-cache/src/services/view-syncer/plan-warnings.d.ts +7 -0
  161. package/out/zero-cache/src/services/view-syncer/plan-warnings.d.ts.map +1 -0
  162. package/out/zero-cache/src/services/view-syncer/plan-warnings.js +35 -0
  163. package/out/zero-cache/src/services/view-syncer/plan-warnings.js.map +1 -0
  164. package/out/zero-cache/src/services/view-syncer/query-shape.d.ts +25 -0
  165. package/out/zero-cache/src/services/view-syncer/query-shape.d.ts.map +1 -0
  166. package/out/zero-cache/src/services/view-syncer/query-shape.js +28 -0
  167. package/out/zero-cache/src/services/view-syncer/query-shape.js.map +1 -0
  168. package/out/zero-cache/src/services/view-syncer/query-stats.d.ts +77 -0
  169. package/out/zero-cache/src/services/view-syncer/query-stats.d.ts.map +1 -0
  170. package/out/zero-cache/src/services/view-syncer/query-stats.js +206 -0
  171. package/out/zero-cache/src/services/view-syncer/query-stats.js.map +1 -0
  172. package/out/zero-cache/src/services/view-syncer/snapshotter.d.ts +11 -1
  173. package/out/zero-cache/src/services/view-syncer/snapshotter.d.ts.map +1 -1
  174. package/out/zero-cache/src/services/view-syncer/snapshotter.js +7 -1
  175. package/out/zero-cache/src/services/view-syncer/snapshotter.js.map +1 -1
  176. package/out/zero-cache/src/services/view-syncer/view-syncer.d.ts.map +1 -1
  177. package/out/zero-cache/src/services/view-syncer/view-syncer.js +50 -3
  178. package/out/zero-cache/src/services/view-syncer/view-syncer.js.map +1 -1
  179. package/out/zero-client/src/client/options.d.ts +29 -0
  180. package/out/zero-client/src/client/options.d.ts.map +1 -1
  181. package/out/zero-client/src/client/options.js.map +1 -1
  182. package/out/zero-client/src/client/version.js +1 -1
  183. package/out/zero-client/src/client/zero.d.ts.map +1 -1
  184. package/out/zero-client/src/client/zero.js +8 -23
  185. package/out/zero-client/src/client/zero.js.map +1 -1
  186. package/out/zero-client/src/mod.d.ts +1 -0
  187. package/out/zero-client/src/mod.d.ts.map +1 -1
  188. package/out/zero-client/src/mod.js +1 -0
  189. package/out/zero-protocol/src/down.d.ts +2 -2
  190. package/out/zero-react/src/zero.js +1 -0
  191. package/out/zero-solid/src/zero.js +1 -0
  192. package/out/zql/src/builder/builder.d.ts +6 -0
  193. package/out/zql/src/builder/builder.d.ts.map +1 -1
  194. package/out/zql/src/builder/builder.js +5 -4
  195. package/out/zql/src/builder/builder.js.map +1 -1
  196. package/out/zql/src/ivm/flipped-join.d.ts +1 -1
  197. package/out/zql/src/ivm/flipped-join.d.ts.map +1 -1
  198. package/out/zql/src/ivm/flipped-join.js +19 -22
  199. package/out/zql/src/ivm/flipped-join.js.map +1 -1
  200. package/out/zql/src/ivm/join.d.ts +1 -1
  201. package/out/zql/src/ivm/join.d.ts.map +1 -1
  202. package/out/zql/src/ivm/join.js +34 -18
  203. package/out/zql/src/ivm/join.js.map +1 -1
  204. package/out/zql/src/ivm/memory-source.d.ts +13 -0
  205. package/out/zql/src/ivm/memory-source.d.ts.map +1 -1
  206. package/out/zql/src/ivm/memory-source.js +1 -1
  207. package/out/zql/src/ivm/memory-source.js.map +1 -1
  208. package/out/zql/src/ivm/take-gate.d.ts +23 -2
  209. package/out/zql/src/ivm/take-gate.d.ts.map +1 -1
  210. package/out/zql/src/ivm/take-gate.js +23 -1
  211. package/out/zql/src/ivm/take-gate.js.map +1 -1
  212. package/out/zql/src/planner/planner-builder.d.ts +12 -1
  213. package/out/zql/src/planner/planner-builder.d.ts.map +1 -1
  214. package/out/zql/src/planner/planner-builder.js +8 -1
  215. package/out/zql/src/planner/planner-builder.js.map +1 -1
  216. package/out/zql/src/planner/planner-connection.d.ts +43 -0
  217. package/out/zql/src/planner/planner-connection.d.ts.map +1 -1
  218. package/out/zql/src/planner/planner-connection.js +31 -4
  219. package/out/zql/src/planner/planner-connection.js.map +1 -1
  220. package/out/zql/src/planner/planner-graph.d.ts +10 -0
  221. package/out/zql/src/planner/planner-graph.d.ts.map +1 -1
  222. package/out/zql/src/planner/planner-graph.js +14 -0
  223. package/out/zql/src/planner/planner-graph.js.map +1 -1
  224. package/out/zql/src/planner/planner-warnings.d.ts +83 -0
  225. package/out/zql/src/planner/planner-warnings.d.ts.map +1 -0
  226. package/out/zql/src/planner/planner-warnings.js +78 -0
  227. package/out/zql/src/planner/planner-warnings.js.map +1 -0
  228. package/out/zql/src/query/measure-push-operator.d.ts.map +1 -1
  229. package/out/zql/src/query/measure-push-operator.js +30 -8
  230. package/out/zql/src/query/measure-push-operator.js.map +1 -1
  231. package/out/zqlite/src/pending-delta.d.ts +110 -0
  232. package/out/zqlite/src/pending-delta.d.ts.map +1 -0
  233. package/out/zqlite/src/pending-delta.js +278 -0
  234. package/out/zqlite/src/pending-delta.js.map +1 -0
  235. package/out/zqlite/src/query-delegate.d.ts.map +1 -1
  236. package/out/zqlite/src/query-delegate.js +5 -1
  237. package/out/zqlite/src/query-delegate.js.map +1 -1
  238. package/out/zqlite/src/sqlite-cost-model.d.ts.map +1 -1
  239. package/out/zqlite/src/sqlite-cost-model.js +52 -1
  240. package/out/zqlite/src/sqlite-cost-model.js.map +1 -1
  241. package/out/zqlite/src/sqlite-stat-fanout.d.ts.map +1 -1
  242. package/out/zqlite/src/sqlite-stat-fanout.js +2 -26
  243. package/out/zqlite/src/sqlite-stat-fanout.js.map +1 -1
  244. package/out/zqlite/src/sqlite-stat4-sample.d.ts +37 -0
  245. package/out/zqlite/src/sqlite-stat4-sample.d.ts.map +1 -0
  246. package/out/zqlite/src/sqlite-stat4-sample.js +61 -0
  247. package/out/zqlite/src/sqlite-stat4-sample.js.map +1 -0
  248. package/out/zqlite/src/table-source.d.ts +68 -0
  249. package/out/zqlite/src/table-source.d.ts.map +1 -1
  250. package/out/zqlite/src/table-source.js +206 -7
  251. package/out/zqlite/src/table-source.js.map +1 -1
  252. package/package.json +1 -1
  253. package/out/replicache/src/kv/idb-store-with-mem-fallback.d.ts +0 -35
  254. package/out/replicache/src/kv/idb-store-with-mem-fallback.d.ts.map +0 -1
  255. package/out/replicache/src/kv/idb-store-with-mem-fallback.js +0 -83
  256. package/out/replicache/src/kv/idb-store-with-mem-fallback.js.map +0 -1
  257. package/out/zero-cache/src/services/change-source/pg/backfill-resume.d.ts +0 -156
  258. package/out/zero-cache/src/services/change-source/pg/backfill-resume.d.ts.map +0 -1
  259. package/out/zero-cache/src/services/change-source/pg/backfill-resume.js +0 -15
  260. 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;AAOjD,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;AAuBrC;;;;;;;;;;;;;;;GAeG;AACH,qBAAa,eAAgB,YAAW,UAAU,EAAE,QAAQ;;IAmC1D,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;IAgQD;;;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,9 +1,12 @@
1
1
  import { assert } from "../../../../../shared/src/asserts.js";
2
+ import { AbortError } from "../../../../../shared/src/abort-error.js";
2
3
  import { must } from "../../../../../shared/src/must.js";
4
+ import { promiseOrAbort } from "../../../../../shared/src/promise-race.js";
3
5
  import { stringify } from "../../../../../shared/src/bigint-json.js";
4
6
  import "../../../types/lite.js";
5
7
  import { randInt } from "../../../../../shared/src/rand.js";
6
8
  import { stateVersionFromString, stateVersionToString } from "../../../types/state-version.js";
9
+ import { Queue } from "../../../../../shared/src/queue.js";
7
10
  import { CustomKeyMap } from "../../../../../shared/src/custom-key-map.js";
8
11
  import { resolver } from "@rocicorp/resolver";
9
12
  //#region ../zero-cache/src/services/change-source/common/backfill-manager.ts
@@ -16,6 +19,34 @@ function tableKey({ schema, name }) {
16
19
  * COMMIT/checkpoint so it can't monopolize the replica.
17
20
  */
18
21
  var COMMIT_THRESHOLD_BYTES = 8 * 1024 * 1024;
22
+ /**
23
+ * How many backfill messages the prefetch producer may read ahead of the
24
+ * reservation-holding consumer loop. This bounds the in-memory buffer (~this
25
+ * many {@link BackfillMessage}s, each up to the stream's flush threshold, e.g.
26
+ * 64 KiB) while giving the consumer a supply of ready messages so it never
27
+ * has to wait on the upstream COPY while holding the change-stream reservation.
28
+ */
29
+ var BACKFILL_PREFETCH_CREDITS = 16;
30
+ /**
31
+ * While the backfill holds an open transaction but its prefetch queue is empty
32
+ * (i.e. the upstream is momentarily behind), the consumer polls for the next
33
+ * message at this interval so that a replication transaction that begins
34
+ * waiting mid-fetch can preempt the backfill within this bound, rather than
35
+ * being blocked for the entire (possibly slow) upstream read.
36
+ *
37
+ * If stats indicate that these stalls are highly correlative with replication
38
+ * lag (indicating that replication changes are waiting on these polls), we
39
+ * can address this by adding an API to the change stream multiplexer that
40
+ * would allow backfill to relinquish an in-progress commit on demand (i.e.
41
+ * only if replication asks for a reservation).
42
+ */
43
+ var BACKFILL_WAITER_POLL_MS = 10;
44
+ /** Interval at which in-progress backfill timing stats are logged. */
45
+ var BACKFILL_STATS_LOG_INTERVAL_MS = 1e4;
46
+ /** Sentinel enqueued by the prefetch producer when the backfill stream ends. */
47
+ var DONE = Symbol("backfill-done");
48
+ /** Sentinel returned by a polled dequeue that timed out (see {@link BACKFILL_WAITER_POLL_MS}). */
49
+ var POLL = Symbol("backfill-poll");
19
50
  var MIN_BACKOFF_INTERVAL_MS = 2e3;
20
51
  var MAX_BACKOFF_INTERVAL_MS = 6e4;
21
52
  /**
@@ -57,8 +88,16 @@ var BackfillManager = class {
57
88
  /** The watermark of the current transaction in the change stream. */
58
89
  #currentTxWatermark = null;
59
90
  #commitThresholdBytes;
60
- /** Set when the change stream is canceled. No further backfills are run. */
61
- #canceled = false;
91
+ /**
92
+ * Aborted when the change stream is canceled. No further backfills are run.
93
+ *
94
+ * TODO: Thread this into the backfillStream so that upstream I/O can be
95
+ * aborted.
96
+ */
97
+ #controller = new AbortController();
98
+ get #canceled() {
99
+ return this.#controller.signal.aborted;
100
+ }
62
101
  constructor(lc, changeStreamer, backfillStreamer, jsonFormat = "s", minBackoffMs = MIN_BACKOFF_INTERVAL_MS, maxBackoffMs = MAX_BACKOFF_INTERVAL_MS, commitThresholdBytes = COMMIT_THRESHOLD_BYTES) {
63
102
  this.#lc = lc.withContext("component", "backfill-manager");
64
103
  this.#changeStreamer = changeStreamer;
@@ -109,7 +148,8 @@ var BackfillManager = class {
109
148
  const request = candidates[randInt(0, candidates.length - 1)];
110
149
  const state = {
111
150
  request,
112
- minWatermark: ""
151
+ minWatermark: "",
152
+ stopped: new AbortController()
113
153
  };
114
154
  const lc = this.#lc.withContext("table", request.table.name);
115
155
  this.#runningBackfill = state;
@@ -138,14 +178,27 @@ var BackfillManager = class {
138
178
  const changeStream = this.#changeStreamer;
139
179
  let backfillTx = null;
140
180
  let uncommittedBytes = 0;
181
+ const stats = {
182
+ start: performance.now(),
183
+ lastLogged: performance.now(),
184
+ messages: 0,
185
+ bytes: 0,
186
+ commits: 0,
187
+ stalls: 0,
188
+ produceMs: 0,
189
+ pushMs: 0,
190
+ reserveMs: 0,
191
+ stallMs: 0,
192
+ waitMs: 0
193
+ };
141
194
  /**
142
195
  * @returns the new tx watermark, or null if backfill was cancelled
143
196
  */
144
197
  const beginTxFor = async (msg) => {
145
198
  assert(backfillTx === null, "Expected no active backfill transaction");
146
199
  const lastWatermark = await changeStream.reserve("backfill");
147
- if (state.canceledReason || msg.tag === "backfill" && msg.watermark < state.minWatermark) {
148
- if (state.canceledReason === void 0) {
200
+ if (state.stopped.signal.aborted || msg.tag === "backfill" && msg.watermark < state.minWatermark) {
201
+ if (!state.stopped.signal.aborted) {
149
202
  assert(msg.tag === "backfill", "Expected backfill message tag");
150
203
  this.#stopRunningBackfill(`row key change at ${state.minWatermark} postdates backfill watermark at ${msg.watermark}`, state);
151
204
  }
@@ -157,7 +210,7 @@ var BackfillManager = class {
157
210
  major,
158
211
  minor: BigInt(minor) + 1n
159
212
  });
160
- if (msg.tag === "backfill-completed" && tx < msg.watermark) tx = msg.watermark;
213
+ if (tx < msg.watermark) tx = msg.watermark;
161
214
  changeStream.push([
162
215
  "begin",
163
216
  {
@@ -177,29 +230,138 @@ var BackfillManager = class {
177
230
  { watermark: backfillTx }
178
231
  ]);
179
232
  changeStream.release(backfillTx);
233
+ stats.commits++;
180
234
  }
181
235
  backfillTx = null;
182
236
  uncommittedBytes = 0;
183
237
  };
184
- for await (const { message: msg, byteSize } of this.#backfillStreamer(state.request)) {
185
- if (this.#canceled) {
186
- lc.info?.(`backfill stream canceled: change stream canceled`);
187
- return;
238
+ const ready = new Queue();
239
+ const credits = new Queue();
240
+ for (let i = 0; i < BACKFILL_PREFETCH_CREDITS; i++) credits.enqueue(1);
241
+ let stopProducer = false;
242
+ const onCancel = () => ready.enqueueRejection(new AbortError("backfill canceled"));
243
+ if (!this.#canceled) this.#controller.signal.addEventListener("abort", onCancel);
244
+ const producer = (async () => {
245
+ try {
246
+ let t0 = performance.now();
247
+ for await (const value of this.#backfillStreamer(state.request)) {
248
+ stats.produceMs += performance.now() - t0;
249
+ await credits.dequeue();
250
+ if (stopProducer || this.#canceled) {
251
+ ready.enqueueRejection(new AbortError("backfill canceled"));
252
+ return;
253
+ }
254
+ ready.enqueue(value);
255
+ t0 = performance.now();
256
+ }
257
+ ready.enqueue(DONE);
258
+ } catch (e) {
259
+ ready.enqueueRejection(e);
188
260
  }
189
- const mustWaitBeforeFlush = msg.tag === "backfill-completed" && (this.#changeStreamReached(lc, msg.watermark) || backfillTx !== null && backfillTx < msg.watermark);
190
- if (backfillTx && (changeStream.waiterDelay() > 0 || mustWaitBeforeFlush || uncommittedBytes >= this.#commitThresholdBytes)) commitTx();
191
- mustWaitBeforeFlush && await mustWaitBeforeFlush;
192
- if (msg.tag === "backfill" && msg.rowValues.length > 0 && msg.relation.rowKey.columns.length === 0) throw new MissingRowKeyError(state.request);
193
- if ((backfillTx ??= await beginTxFor(msg)) === null) {
194
- lc.info?.(`backfill stream canceled: ${state.canceledReason}`, state.request);
195
- this.#checkAndStartBackfill();
196
- return;
261
+ })();
262
+ producer.catch(() => {});
263
+ const nextItem = async () => {
264
+ if (backfillTx === null || ready.size() > 0) {
265
+ const n = ready.dequeue();
266
+ return n instanceof Promise ? await n : n;
197
267
  }
198
- await changeStream.push(["data", msg]);
199
- uncommittedBytes += byteSize;
268
+ for (;;) {
269
+ if (backfillTx !== null && changeStream.waiterDelay() > 0) commitTx();
270
+ const n = backfillTx !== null ? ready.dequeue(POLL, BACKFILL_WAITER_POLL_MS) : ready.dequeue();
271
+ const t0 = performance.now();
272
+ const v = n instanceof Promise ? await n : n;
273
+ if (v === POLL) {
274
+ stats.stallMs += performance.now() - t0;
275
+ continue;
276
+ }
277
+ if (n instanceof Promise) {
278
+ stats.stallMs += performance.now() - t0;
279
+ stats.stalls++;
280
+ }
281
+ return v;
282
+ }
283
+ };
284
+ try {
285
+ for (;;) {
286
+ const item = await nextItem();
287
+ if (item === DONE) break;
288
+ credits.enqueue(1);
289
+ if (this.#canceled) {
290
+ lc.info?.(`backfill stream canceled: change stream canceled`);
291
+ return;
292
+ }
293
+ const { message: msg, byteSize } = item;
294
+ const waitForChangeStream = this.#changeStreamReached(lc, msg.watermark);
295
+ if (backfillTx && (changeStream.waiterDelay() > 0 || waitForChangeStream || uncommittedBytes >= this.#commitThresholdBytes)) commitTx();
296
+ if (waitForChangeStream) {
297
+ const t0 = performance.now();
298
+ await promiseOrAbort(waitForChangeStream, state.stopped.signal);
299
+ const elapsed = performance.now() - t0;
300
+ stats.waitMs += elapsed;
301
+ lc.info?.(`change stream reached ${msg.watermark} after waiting ${Math.round(elapsed)} ms`);
302
+ }
303
+ if (msg.tag === "backfill" && msg.rowValues.length > 0 && msg.relation.rowKey.columns.length === 0) throw new MissingRowKeyError(state.request);
304
+ if (backfillTx === null) {
305
+ const t0 = performance.now();
306
+ backfillTx = await beginTxFor(msg);
307
+ stats.reserveMs += performance.now() - t0;
308
+ if (backfillTx === null) {
309
+ lc.info?.(`backfill stream canceled: ${state.stopped.signal.reason}`, state.request);
310
+ this.#checkAndStartBackfill();
311
+ return;
312
+ }
313
+ }
314
+ const t0 = performance.now();
315
+ await changeStream.push(["data", msg]);
316
+ stats.pushMs += performance.now() - t0;
317
+ uncommittedBytes += byteSize;
318
+ stats.messages++;
319
+ stats.bytes += byteSize;
320
+ this.#maybeLogBackfillStats(lc, stats);
321
+ }
322
+ if (backfillTx) commitTx();
323
+ lc.debug?.(`backfill stream exited`);
324
+ } finally {
325
+ stopProducer = true;
326
+ credits.enqueue(1);
327
+ if (!this.#canceled) await producer;
328
+ this.#controller.signal.removeEventListener("abort", onCancel);
329
+ this.#logBackfillStats(lc, stats);
330
+ }
331
+ }
332
+ #maybeLogBackfillStats(lc, stats) {
333
+ const now = performance.now();
334
+ if (now - stats.lastLogged >= BACKFILL_STATS_LOG_INTERVAL_MS) {
335
+ this.#logBackfillStats(lc, stats);
336
+ stats.lastLogged = now;
200
337
  }
201
- backfillTx && commitTx();
202
- lc.debug?.(`backfill stream exited`, state.canceledReason ?? "");
338
+ }
339
+ /**
340
+ * Logs where the backfill spent its time. `produceMs` (upstream read +
341
+ * parse/decode) accrues on the prefetch producer concurrently with the
342
+ * consumer's `pushMs` (downstream backpressure), so the two can overlap and
343
+ * their sum can exceed `elapsedMs`. `reserveMs` is time spent (re)acquiring
344
+ * the change-stream reservation, and `stallMs` is time the consumer waited on
345
+ * an empty prefetch queue (i.e. blocked on the upstream) — a large `stallMs`
346
+ * indicates an upstream-bound backfill. `waitMs` is time spent waiting
347
+ * (without the reservation) for the change stream to reach the backfill
348
+ * watermark, during which the upstream snapshot is held open.
349
+ */
350
+ #logBackfillStats(lc, stats) {
351
+ const elapsed = performance.now() - stats.start;
352
+ lc.info?.(`backfill timing`, {
353
+ messages: stats.messages,
354
+ bytes: stats.bytes,
355
+ commits: stats.commits,
356
+ stalls: stats.stalls,
357
+ elapsedMs: Math.round(elapsed),
358
+ produceMs: Math.round(stats.produceMs),
359
+ pushMs: Math.round(stats.pushMs),
360
+ reserveMs: Math.round(stats.reserveMs),
361
+ stallMs: Math.round(stats.stallMs),
362
+ waitMs: Math.round(stats.waitMs),
363
+ throughputKBps: elapsed > 0 ? Math.round(stats.bytes / 1024 / (elapsed / 1e3)) : 0
364
+ });
203
365
  }
204
366
  #backfillRunningFor(table) {
205
367
  const state = this.#runningBackfill;
@@ -214,7 +376,7 @@ var BackfillManager = class {
214
376
  #stopRunningBackfill(reason, instance) {
215
377
  const backfill = this.#runningBackfill;
216
378
  if (backfill && backfill === (instance ?? backfill)) {
217
- backfill.canceledReason = reason;
379
+ backfill.stopped.abort(reason);
218
380
  this.#runningBackfill = null;
219
381
  reason && this.#lc.info?.(`canceling backfill:`, reason);
220
382
  }
@@ -278,7 +440,7 @@ var BackfillManager = class {
278
440
  name,
279
441
  metadata
280
442
  },
281
- columns: backfill
443
+ columns: Object.fromEntries(Object.entries(backfill).map(([col, id]) => [col, { id }]))
282
444
  });
283
445
  break;
284
446
  }
@@ -317,7 +479,7 @@ var BackfillManager = class {
317
479
  ...table,
318
480
  metadata
319
481
  },
320
- columns: { [column.name]: backfill }
482
+ columns: { [column.name]: { id: backfill } }
321
483
  });
322
484
  else this.#setRequiredBackfill(tag, {
323
485
  ...backfillRequest,
@@ -327,7 +489,7 @@ var BackfillManager = class {
327
489
  },
328
490
  columns: {
329
491
  ...backfillRequest.columns,
330
- [column.name]: backfill
492
+ [column.name]: { id: backfill }
331
493
  }
332
494
  });
333
495
  }
@@ -396,7 +558,7 @@ var BackfillManager = class {
396
558
  }
397
559
  }
398
560
  cancel() {
399
- this.#canceled = true;
561
+ this.#controller.abort("canceled");
400
562
  this.#stopRunningBackfill(`change stream canceled`);
401
563
  clearTimeout(this.#backfillRetryTimer);
402
564
  this.#backfillRetryTimer = void 0;