@rocicorp/zero 1.11.0-canary.5 → 1.11.0-canary.7

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 (164) 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/replicache/src/kv/expo-sqlite/store.d.ts.map +1 -1
  4. package/out/replicache/src/kv/expo-sqlite/store.js +27 -4
  5. package/out/replicache/src/kv/expo-sqlite/store.js.map +1 -1
  6. package/out/replicache/src/kv/sqlite-store.d.ts +11 -0
  7. package/out/replicache/src/kv/sqlite-store.d.ts.map +1 -1
  8. package/out/replicache/src/kv/sqlite-store.js +43 -3
  9. package/out/replicache/src/kv/sqlite-store.js.map +1 -1
  10. package/out/shared/src/error.d.ts +11 -0
  11. package/out/shared/src/error.d.ts.map +1 -1
  12. package/out/shared/src/error.js +35 -1
  13. package/out/shared/src/error.js.map +1 -1
  14. package/out/shared/src/map.d.ts +7 -2
  15. package/out/shared/src/map.d.ts.map +1 -1
  16. package/out/shared/src/map.js.map +1 -1
  17. package/out/zero/package.js +2 -2
  18. package/out/zero/package.js.map +1 -1
  19. package/out/zero-cache/src/config/normalize.d.ts.map +1 -1
  20. package/out/zero-cache/src/config/normalize.js +7 -1
  21. package/out/zero-cache/src/config/normalize.js.map +1 -1
  22. package/out/zero-cache/src/config/zero-config.d.ts +9 -2
  23. package/out/zero-cache/src/config/zero-config.d.ts.map +1 -1
  24. package/out/zero-cache/src/config/zero-config.js +26 -7
  25. package/out/zero-cache/src/config/zero-config.js.map +1 -1
  26. package/out/zero-cache/src/server/change-streamer.d.ts.map +1 -1
  27. package/out/zero-cache/src/server/change-streamer.js +15 -6
  28. package/out/zero-cache/src/server/change-streamer.js.map +1 -1
  29. package/out/zero-cache/src/server/main.d.ts.map +1 -1
  30. package/out/zero-cache/src/server/main.js +8 -1
  31. package/out/zero-cache/src/server/main.js.map +1 -1
  32. package/out/zero-cache/src/server/mutator.js +2 -0
  33. package/out/zero-cache/src/server/mutator.js.map +1 -1
  34. package/out/zero-cache/src/server/reaper.d.ts.map +1 -1
  35. package/out/zero-cache/src/server/reaper.js +2 -0
  36. package/out/zero-cache/src/server/reaper.js.map +1 -1
  37. package/out/zero-cache/src/server/replicator.d.ts.map +1 -1
  38. package/out/zero-cache/src/server/replicator.js +2 -0
  39. package/out/zero-cache/src/server/replicator.js.map +1 -1
  40. package/out/zero-cache/src/server/runner/zero-dispatcher.d.ts.map +1 -1
  41. package/out/zero-cache/src/server/runner/zero-dispatcher.js +3 -0
  42. package/out/zero-cache/src/server/runner/zero-dispatcher.js.map +1 -1
  43. package/out/zero-cache/src/server/syncer.d.ts.map +1 -1
  44. package/out/zero-cache/src/server/syncer.js +2 -0
  45. package/out/zero-cache/src/server/syncer.js.map +1 -1
  46. package/out/zero-cache/src/services/change-source/common/backfill-manager.d.ts.map +1 -1
  47. package/out/zero-cache/src/services/change-source/common/backfill-manager.js +14 -1
  48. package/out/zero-cache/src/services/change-source/common/backfill-manager.js.map +1 -1
  49. package/out/zero-cache/src/services/change-source/common/change-stream-multiplexer.d.ts.map +1 -1
  50. package/out/zero-cache/src/services/change-source/common/change-stream-multiplexer.js +15 -3
  51. package/out/zero-cache/src/services/change-source/common/change-stream-multiplexer.js.map +1 -1
  52. package/out/zero-cache/src/services/change-source/custom/change-source.js +2 -2
  53. package/out/zero-cache/src/services/change-source/custom/change-source.js.map +1 -1
  54. package/out/zero-cache/src/services/change-source/pg/initial-sync.js +1 -1
  55. package/out/zero-cache/src/services/change-source/pg/schema/index-predicate.d.ts.map +1 -1
  56. package/out/zero-cache/src/services/change-source/pg/schema/index-predicate.js +68 -8
  57. package/out/zero-cache/src/services/change-source/pg/schema/index-predicate.js.map +1 -1
  58. package/out/zero-cache/src/services/change-streamer/backup-monitor.d.ts.map +1 -1
  59. package/out/zero-cache/src/services/change-streamer/backup-monitor.js +58 -7
  60. package/out/zero-cache/src/services/change-streamer/backup-monitor.js.map +1 -1
  61. package/out/zero-cache/src/services/change-streamer/change-log-codec.d.ts +19 -4
  62. package/out/zero-cache/src/services/change-streamer/change-log-codec.d.ts.map +1 -1
  63. package/out/zero-cache/src/services/change-streamer/change-log-codec.js +28 -8
  64. package/out/zero-cache/src/services/change-streamer/change-log-codec.js.map +1 -1
  65. package/out/zero-cache/src/services/change-streamer/change-streamer-http.d.ts +3 -0
  66. package/out/zero-cache/src/services/change-streamer/change-streamer-http.d.ts.map +1 -1
  67. package/out/zero-cache/src/services/change-streamer/change-streamer-http.js +6 -4
  68. package/out/zero-cache/src/services/change-streamer/change-streamer-http.js.map +1 -1
  69. package/out/zero-cache/src/services/change-streamer/change-streamer-service.d.ts +6 -0
  70. package/out/zero-cache/src/services/change-streamer/change-streamer-service.d.ts.map +1 -1
  71. package/out/zero-cache/src/services/change-streamer/change-streamer-service.js +86 -51
  72. package/out/zero-cache/src/services/change-streamer/change-streamer-service.js.map +1 -1
  73. package/out/zero-cache/src/services/change-streamer/schema/tables.d.ts +1 -1
  74. package/out/zero-cache/src/services/change-streamer/schema/tables.d.ts.map +1 -1
  75. package/out/zero-cache/src/services/change-streamer/schema/tables.js +4 -4
  76. package/out/zero-cache/src/services/change-streamer/schema/tables.js.map +1 -1
  77. package/out/zero-cache/src/services/change-streamer/snapshot.d.ts +3 -1
  78. package/out/zero-cache/src/services/change-streamer/snapshot.d.ts.map +1 -1
  79. package/out/zero-cache/src/services/change-streamer/snapshot.js +54 -20
  80. package/out/zero-cache/src/services/change-streamer/snapshot.js.map +1 -1
  81. package/out/zero-cache/src/services/change-streamer/sqlite-change-log-writer.d.ts +6 -1
  82. package/out/zero-cache/src/services/change-streamer/sqlite-change-log-writer.d.ts.map +1 -1
  83. package/out/zero-cache/src/services/change-streamer/sqlite-change-log-writer.js +27 -9
  84. package/out/zero-cache/src/services/change-streamer/sqlite-change-log-writer.js.map +1 -1
  85. package/out/zero-cache/src/services/change-streamer/storer.d.ts +2 -2
  86. package/out/zero-cache/src/services/change-streamer/storer.d.ts.map +1 -1
  87. package/out/zero-cache/src/services/change-streamer/storer.js +8 -5
  88. package/out/zero-cache/src/services/change-streamer/storer.js.map +1 -1
  89. package/out/zero-cache/src/services/litestream/litestream-checkpointer.js +51 -24
  90. package/out/zero-cache/src/services/litestream/litestream-checkpointer.js.map +1 -1
  91. package/out/zero-cache/src/services/mutagen/mutagen.d.ts +5 -0
  92. package/out/zero-cache/src/services/mutagen/mutagen.d.ts.map +1 -1
  93. package/out/zero-cache/src/services/mutagen/mutagen.js +7 -1
  94. package/out/zero-cache/src/services/mutagen/mutagen.js.map +1 -1
  95. package/out/zero-cache/src/services/mutagen/pusher.js +2 -1
  96. package/out/zero-cache/src/services/mutagen/pusher.js.map +1 -1
  97. package/out/zero-cache/src/services/profz.d.ts +7 -0
  98. package/out/zero-cache/src/services/profz.d.ts.map +1 -0
  99. package/out/zero-cache/src/services/profz.js +105 -0
  100. package/out/zero-cache/src/services/profz.js.map +1 -0
  101. package/out/zero-cache/src/services/replicator/change-log-stream-writer.d.ts +5 -4
  102. package/out/zero-cache/src/services/replicator/change-log-stream-writer.d.ts.map +1 -1
  103. package/out/zero-cache/src/services/replicator/change-log-stream-writer.js +20 -22
  104. package/out/zero-cache/src/services/replicator/change-log-stream-writer.js.map +1 -1
  105. package/out/zero-cache/src/services/replicator/sqlite-change-log-observability.d.ts +6 -6
  106. package/out/zero-cache/src/services/replicator/sqlite-change-log-observability.d.ts.map +1 -1
  107. package/out/zero-cache/src/services/replicator/sqlite-change-log-observability.js +19 -14
  108. package/out/zero-cache/src/services/replicator/sqlite-change-log-observability.js.map +1 -1
  109. package/out/zero-cache/src/services/runner.d.ts.map +1 -1
  110. package/out/zero-cache/src/services/runner.js +1 -1
  111. package/out/zero-cache/src/services/runner.js.map +1 -1
  112. package/out/zero-cache/src/services/view-syncer/cvr.d.ts +17 -0
  113. package/out/zero-cache/src/services/view-syncer/cvr.d.ts.map +1 -1
  114. package/out/zero-cache/src/services/view-syncer/cvr.js +59 -0
  115. package/out/zero-cache/src/services/view-syncer/cvr.js.map +1 -1
  116. package/out/zero-cache/src/services/view-syncer/hydration-circuit-breaker.d.ts +51 -0
  117. package/out/zero-cache/src/services/view-syncer/hydration-circuit-breaker.d.ts.map +1 -0
  118. package/out/zero-cache/src/services/view-syncer/hydration-circuit-breaker.js +78 -0
  119. package/out/zero-cache/src/services/view-syncer/hydration-circuit-breaker.js.map +1 -0
  120. package/out/zero-cache/src/services/view-syncer/pipeline-driver.d.ts.map +1 -1
  121. package/out/zero-cache/src/services/view-syncer/pipeline-driver.js +18 -6
  122. package/out/zero-cache/src/services/view-syncer/pipeline-driver.js.map +1 -1
  123. package/out/zero-cache/src/services/view-syncer/view-syncer.d.ts.map +1 -1
  124. package/out/zero-cache/src/services/view-syncer/view-syncer.js +174 -6
  125. package/out/zero-cache/src/services/view-syncer/view-syncer.js.map +1 -1
  126. package/out/zero-cache/src/types/processes.d.ts +32 -0
  127. package/out/zero-cache/src/types/processes.d.ts.map +1 -1
  128. package/out/zero-cache/src/types/processes.js +43 -4
  129. package/out/zero-cache/src/types/processes.js.map +1 -1
  130. package/out/zero-cache/src/types/profiler.d.ts +12 -0
  131. package/out/zero-cache/src/types/profiler.d.ts.map +1 -1
  132. package/out/zero-cache/src/types/profiler.js +46 -1
  133. package/out/zero-cache/src/types/profiler.js.map +1 -1
  134. package/out/zero-cache/src/workers/connection.js +3 -3
  135. package/out/zero-cache/src/workers/connection.js.map +1 -1
  136. package/out/zero-cache/src/workers/syncer-ws-message-handler.d.ts.map +1 -1
  137. package/out/zero-cache/src/workers/syncer-ws-message-handler.js +21 -16
  138. package/out/zero-cache/src/workers/syncer-ws-message-handler.js.map +1 -1
  139. package/out/zero-cache/src/workers/syncer.d.ts.map +1 -1
  140. package/out/zero-cache/src/workers/syncer.js +10 -1
  141. package/out/zero-cache/src/workers/syncer.js.map +1 -1
  142. package/out/zero-client/src/client/connection-manager.d.ts.map +1 -1
  143. package/out/zero-client/src/client/connection-manager.js +46 -0
  144. package/out/zero-client/src/client/connection-manager.js.map +1 -1
  145. package/out/zero-client/src/client/version.js +1 -1
  146. package/out/zero-client/src/client/zero.d.ts.map +1 -1
  147. package/out/zero-client/src/client/zero.js +12 -8
  148. package/out/zero-client/src/client/zero.js.map +1 -1
  149. package/out/zero-protocol/src/down.d.ts +27 -27
  150. package/out/zero-react/src/use-query.d.ts.map +1 -1
  151. package/out/zero-react/src/use-query.js +50 -3
  152. package/out/zero-react/src/use-query.js.map +1 -1
  153. package/out/zero-solid/src/use-query.js +1 -2
  154. package/out/zero-solid/src/use-query.js.map +1 -1
  155. package/out/zql/src/ivm/data.d.ts.map +1 -1
  156. package/out/zql/src/ivm/data.js +8 -3
  157. package/out/zql/src/ivm/data.js.map +1 -1
  158. package/out/zql/src/ivm/view-apply-change.js +6 -1
  159. package/out/zql/src/ivm/view-apply-change.js.map +1 -1
  160. package/out/zqlite/src/db.d.ts +9 -1
  161. package/out/zqlite/src/db.d.ts.map +1 -1
  162. package/out/zqlite/src/db.js +8 -2
  163. package/out/zqlite/src/db.js.map +1 -1
  164. package/package.json +2 -2
@@ -1 +1 @@
1
- {"version":3,"file":"main.js","names":[],"sources":["../../../../../zero-cache/src/server/main.ts"],"sourcesContent":["import path from 'node:path';\nimport {consoleLogSink, LogContext} from '@rocicorp/logger';\nimport {resolver} from '@rocicorp/resolver';\nimport {must} from '../../../shared/src/must.ts';\nimport {runsChangeStreamer} from '../config/normalize.ts';\nimport {getNormalizedZeroConfig} from '../config/zero-config.ts';\nimport {registerSQLiteCorruptionDiagnosticTarget} from '../db/sqlite-corruption.ts';\nimport {initEventSink} from '../observability/events.ts';\nimport {\n exitAfter,\n ProcessManager,\n recordStartupDurationMs,\n runUntilKilled,\n type WorkerType,\n} from '../services/life-cycle.ts';\nimport {\n childWorker,\n parentWorker,\n singleProcessMode,\n type Worker,\n} from '../types/processes.ts';\nimport {\n createNotifierFrom,\n handleSubscriptionsFrom,\n type ReplicaFileMode,\n subscribeTo,\n} from '../workers/replicator.ts';\nimport {createLogContext} from './logging.ts';\nimport {startOtelAuto} from './otel-start.ts';\nimport {WorkerDispatcher} from './worker-dispatcher.ts';\nimport {\n CHANGE_STREAMER_URL,\n MUTATOR_URL,\n REAPER_URL,\n REPLICATOR_URL,\n SHADOW_SYNCER_URL,\n SYNCER_URL,\n} from './worker-urls.ts';\n\nconst clientConnectionBifurcated = false;\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): Promise<void> {\n const startMs = Date.now();\n const config = getNormalizedZeroConfig({env});\n\n startOtelAuto(\n createLogContext(config, 'dispatcher', 0, false),\n 'dispatcher',\n 0,\n );\n lc = createLogContext(config, 'dispatcher');\n registerSQLiteCorruptionDiagnosticTarget(\n {\n debugName: 'dispatcher replica',\n dbPath: config.replica.file,\n },\n config.sqliteCorruptionChecks,\n );\n initEventSink(lc, config);\n\n const processes = new ProcessManager(lc, parent);\n\n const {numSyncWorkers: numSyncers} = config;\n if (config.enableCrudMutations && config.upstream.maxConns < numSyncers) {\n throw new Error(\n `Insufficient upstream connections (${config.upstream.maxConns}) for ${numSyncers} syncers.` +\n `Increase ZERO_UPSTREAM_MAX_CONNS or decrease ZERO_NUM_SYNC_WORKERS (which defaults to available cores).`,\n );\n }\n if (config.cvr.maxConns < numSyncers) {\n throw new Error(\n `Insufficient cvr connections (${config.cvr.maxConns}) for ${numSyncers} syncers.` +\n `Increase ZERO_CVR_MAX_CONNS or decrease ZERO_NUM_SYNC_WORKERS (which defaults to available cores).`,\n );\n }\n\n const internalFlags: string[] =\n numSyncers === 0\n ? []\n : [\n '--upstream-max-conns-per-worker',\n String(Math.floor(config.upstream.maxConns / numSyncers)),\n '--cvr-max-conns-per-worker',\n String(Math.floor(config.cvr.maxConns / numSyncers)),\n ];\n\n function loadWorker(\n moduleUrl: URL,\n type: WorkerType,\n id?: string | number,\n ...args: string[]\n ): Worker {\n const worker = childWorker(moduleUrl, env, ...args, ...internalFlags);\n const name = path.basename(moduleUrl.pathname) + (id ? ` (${id})` : '');\n return processes.addWorker(worker, type, name);\n }\n\n const {taskID, litestream} = config;\n const runChangeStreamer = runsChangeStreamer(config);\n const sqliteChangeLogEnabled =\n config.changeStreamer.sqliteChangeLogMode !== 'off';\n if (sqliteChangeLogEnabled && !runChangeStreamer) {\n // A multi-node deployment configures every task from one environment, so\n // the change log's options reach the view-syncers too. The log lives in\n // the change-streamer, and a task that connects to one simply never writes\n // a log. Warning rather than refusing to start is what makes the\n // fleet-wide environment -- often the only knob an operator has -- able to\n // express the rollout at all.\n lc.warn?.(\n `ignoring --change-streamer-sqlite-change-log-mode=` +\n `${config.changeStreamer.sqliteChangeLogMode}: this task connects to ` +\n `a change-streamer rather than running one, and the SQLite change log ` +\n `lives in the change-streamer`,\n );\n }\n\n let changeStreamer: Worker | undefined;\n\n if (runChangeStreamer) {\n const {promise: changeStreamerReady, resolve: changeStreamerStarted} =\n resolver();\n changeStreamer = loadWorker(CHANGE_STREAMER_URL, 'supporting').once(\n 'message',\n changeStreamerStarted,\n );\n // Wait for the change-streamer to be ready to guarantee that a replica\n // file is present.\n await changeStreamerReady;\n }\n\n if (numSyncers > 0) {\n const {promise: reaperReady, resolve: reaperStarted} = resolver();\n loadWorker(REAPER_URL, 'supporting').once('message', reaperStarted);\n // Before starting the view-syncers, ensure that the reaper has started\n // up, indicating that any CVR db migrations have been performed.\n await reaperReady;\n }\n\n // Only run the shadow-sync canary on the replication-manager (or in\n // single-node mode, where it also owns upstream). Running on every\n // view-syncer would hammer the upstream with N redundant canaries.\n if (config.shadowSync.enabled && runChangeStreamer) {\n const {promise: shadowReady, resolve: shadowStarted} = resolver();\n loadWorker(SHADOW_SYNCER_URL, 'supporting').once('message', shadowStarted);\n await shadowReady;\n }\n\n const syncers: Worker[] = [];\n if (numSyncers) {\n const mode: ReplicaFileMode =\n runChangeStreamer && litestream.backupURL ? 'serving-copy' : 'serving';\n const {promise: replicaReady, resolve} = resolver();\n const replicator = loadWorker(\n REPLICATOR_URL,\n 'supporting',\n mode,\n mode,\n ).once('message', () => {\n subscribeTo(lc, replicator);\n resolve();\n });\n await replicaReady;\n\n const notifier = createNotifierFrom(lc, replicator);\n for (let i = 0; i < numSyncers; i++) {\n syncers.push(loadWorker(SYNCER_URL, 'user-facing', i, mode, String(i)));\n }\n syncers.forEach(syncer => handleSubscriptionsFrom(lc, syncer, notifier));\n }\n let mutator: Worker | undefined;\n if (clientConnectionBifurcated) {\n mutator = loadWorker(MUTATOR_URL, 'supporting', 'mutator');\n }\n\n lc.info?.('waiting for workers to be ready ...');\n const logWaiting = setInterval(\n () => lc.info?.(`still waiting for ${processes.initializing().join(', ')}`),\n 10_000,\n );\n await processes.allWorkersReady();\n clearInterval(logWaiting);\n const startupDurationMs = Date.now() - startMs;\n lc.info?.(`all workers ready (${startupDurationMs} ms)`);\n recordStartupDurationMs(startupDurationMs);\n\n parent.send(['ready', {ready: true}]);\n\n try {\n await runUntilKilled(\n lc,\n parent,\n new WorkerDispatcher(\n lc,\n taskID,\n parent,\n syncers,\n mutator,\n changeStreamer,\n ),\n );\n } catch (err) {\n processes.logErrorAndExit(err, 'dispatcher');\n } finally {\n await processes.shutdown();\n }\n}\n\nif (!singleProcessMode()) {\n void exitAfter(\n () => lc,\n () => runWorker(must(parentWorker), process.env),\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"main.js","names":[],"sources":["../../../../../zero-cache/src/server/main.ts"],"sourcesContent":["import path from 'node:path';\nimport {consoleLogSink, LogContext} from '@rocicorp/logger';\nimport {resolver} from '@rocicorp/resolver';\nimport {must} from '../../../shared/src/must.ts';\nimport {runsChangeStreamer} from '../config/normalize.ts';\nimport {getNormalizedZeroConfig} from '../config/zero-config.ts';\nimport {registerSQLiteCorruptionDiagnosticTarget} from '../db/sqlite-corruption.ts';\nimport {initEventSink} from '../observability/events.ts';\nimport {\n exitAfter,\n ProcessManager,\n recordStartupDurationMs,\n runUntilKilled,\n type WorkerType,\n} from '../services/life-cycle.ts';\nimport {\n childWorker,\n parentWorker,\n singleProcessMode,\n type ProfileMessage,\n type ProfileResponseMessage,\n type Worker,\n} from '../types/processes.ts';\nimport {\n createNotifierFrom,\n handleSubscriptionsFrom,\n type ReplicaFileMode,\n subscribeTo,\n} from '../workers/replicator.ts';\nimport {createLogContext} from './logging.ts';\nimport {startOtelAuto} from './otel-start.ts';\nimport {WorkerDispatcher} from './worker-dispatcher.ts';\nimport {\n CHANGE_STREAMER_URL,\n MUTATOR_URL,\n REAPER_URL,\n REPLICATOR_URL,\n SHADOW_SYNCER_URL,\n SYNCER_URL,\n} from './worker-urls.ts';\n\nconst clientConnectionBifurcated = false;\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): Promise<void> {\n const startMs = Date.now();\n const config = getNormalizedZeroConfig({env});\n\n startOtelAuto(\n createLogContext(config, 'dispatcher', 0, false),\n 'dispatcher',\n 0,\n );\n lc = createLogContext(config, 'dispatcher');\n registerSQLiteCorruptionDiagnosticTarget(\n {\n debugName: 'dispatcher replica',\n dbPath: config.replica.file,\n },\n config.sqliteCorruptionChecks,\n );\n initEventSink(lc, config);\n\n const processes = new ProcessManager(lc, parent);\n\n const {numSyncWorkers: numSyncers} = config;\n if (config.enableCrudMutations && config.upstream.maxConns < numSyncers) {\n throw new Error(\n `Insufficient upstream connections (${config.upstream.maxConns}) for ${numSyncers} syncers.` +\n `Increase ZERO_UPSTREAM_MAX_CONNS or decrease ZERO_NUM_SYNC_WORKERS (which defaults to available cores).`,\n );\n }\n if (config.cvr.maxConns < numSyncers) {\n throw new Error(\n `Insufficient cvr connections (${config.cvr.maxConns}) for ${numSyncers} syncers.` +\n `Increase ZERO_CVR_MAX_CONNS or decrease ZERO_NUM_SYNC_WORKERS (which defaults to available cores).`,\n );\n }\n\n const internalFlags: string[] =\n numSyncers === 0\n ? []\n : [\n '--upstream-max-conns-per-worker',\n String(Math.floor(config.upstream.maxConns / numSyncers)),\n '--cvr-max-conns-per-worker',\n String(Math.floor(config.cvr.maxConns / numSyncers)),\n ];\n\n const allSubWorkers: Worker[] = [];\n function loadWorker(\n moduleUrl: URL,\n type: WorkerType,\n id?: string | number,\n ...args: string[]\n ): Worker {\n const worker = childWorker(moduleUrl, env, ...args, ...internalFlags);\n const name = path.basename(moduleUrl.pathname) + (id ? ` (${id})` : '');\n const w = processes.addWorker(worker, type, name);\n allSubWorkers.push(w);\n w.onMessageType<ProfileResponseMessage>('profileResponse', res =>\n parent.send(['profileResponse', res]),\n );\n return w;\n }\n\n const {taskID, litestream} = config;\n const runChangeStreamer = runsChangeStreamer(config);\n const sqliteChangeLogEnabled =\n config.changeStreamer.sqliteChangeLogMode !== 'off';\n if (sqliteChangeLogEnabled && !runChangeStreamer) {\n // A multi-node deployment configures every task from one environment, so\n // the change log's options reach the view-syncers too. The log lives in\n // the change-streamer, and a task that connects to one simply never writes\n // a log. Warning rather than refusing to start is what makes the\n // fleet-wide environment -- often the only knob an operator has -- able to\n // express the rollout at all.\n lc.warn?.(\n `ignoring --change-streamer-sqlite-change-log-mode=` +\n `${config.changeStreamer.sqliteChangeLogMode}: this task connects to ` +\n `a change-streamer rather than running one, and the SQLite change log ` +\n `lives in the change-streamer`,\n );\n }\n\n let changeStreamer: Worker | undefined;\n\n if (runChangeStreamer) {\n const {promise: changeStreamerReady, resolve: changeStreamerStarted} =\n resolver();\n changeStreamer = loadWorker(CHANGE_STREAMER_URL, 'supporting').once(\n 'message',\n changeStreamerStarted,\n );\n // Wait for the change-streamer to be ready to guarantee that a replica\n // file is present.\n await changeStreamerReady;\n }\n\n if (numSyncers > 0) {\n const {promise: reaperReady, resolve: reaperStarted} = resolver();\n loadWorker(REAPER_URL, 'supporting').once('message', reaperStarted);\n // Before starting the view-syncers, ensure that the reaper has started\n // up, indicating that any CVR db migrations have been performed.\n await reaperReady;\n }\n\n // Only run the shadow-sync canary on the replication-manager (or in\n // single-node mode, where it also owns upstream). Running on every\n // view-syncer would hammer the upstream with N redundant canaries.\n if (config.shadowSync.enabled && runChangeStreamer) {\n const {promise: shadowReady, resolve: shadowStarted} = resolver();\n loadWorker(SHADOW_SYNCER_URL, 'supporting').once('message', shadowStarted);\n await shadowReady;\n }\n\n const syncers: Worker[] = [];\n if (numSyncers) {\n const mode: ReplicaFileMode =\n runChangeStreamer && litestream.backupURL ? 'serving-copy' : 'serving';\n const {promise: replicaReady, resolve} = resolver();\n const replicator = loadWorker(\n REPLICATOR_URL,\n 'supporting',\n mode,\n mode,\n ).once('message', () => {\n subscribeTo(lc, replicator);\n resolve();\n });\n await replicaReady;\n\n const notifier = createNotifierFrom(lc, replicator);\n for (let i = 0; i < numSyncers; i++) {\n syncers.push(loadWorker(SYNCER_URL, 'user-facing', i, mode, String(i)));\n }\n syncers.forEach(syncer => handleSubscriptionsFrom(lc, syncer, notifier));\n }\n let mutator: Worker | undefined;\n if (clientConnectionBifurcated) {\n mutator = loadWorker(MUTATOR_URL, 'supporting', 'mutator');\n }\n\n lc.info?.('waiting for workers to be ready ...');\n const logWaiting = setInterval(\n () => lc.info?.(`still waiting for ${processes.initializing().join(', ')}`),\n 10_000,\n );\n await processes.allWorkersReady();\n clearInterval(logWaiting);\n const startupDurationMs = Date.now() - startMs;\n lc.info?.(`all workers ready (${startupDurationMs} ms)`);\n recordStartupDurationMs(startupDurationMs);\n\n parent.send(['ready', {ready: true}]);\n parent.onMessageType<ProfileMessage>('profile', req => {\n for (const w of allSubWorkers) {\n w.send(['profile', req]);\n }\n });\n\n try {\n await runUntilKilled(\n lc,\n parent,\n new WorkerDispatcher(\n lc,\n taskID,\n parent,\n syncers,\n mutator,\n changeStreamer,\n ),\n );\n } catch (err) {\n processes.logErrorAndExit(err, 'dispatcher');\n } finally {\n await processes.shutdown();\n }\n}\n\nif (!singleProcessMode()) {\n void exitAfter(\n () => lc,\n () => runWorker(must(parentWorker), process.env),\n );\n}\n"],"mappings":"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"}
@@ -3,6 +3,7 @@ import { parentWorker, singleProcessMode } from "../types/processes.js";
3
3
  import { getNormalizedZeroConfig } from "../config/zero-config.js";
4
4
  import { exitAfter, runUntilKilled } from "../services/life-cycle.js";
5
5
  import { createLogContext } from "./logging.js";
6
+ import { installProfileHandler } from "../types/profiler.js";
6
7
  import { initEventSink } from "../observability/events.js";
7
8
  import { startOtelAuto } from "./otel-start.js";
8
9
  import { Mutator } from "../workers/mutator.js";
@@ -10,6 +11,7 @@ import { LogContext, consoleLogSink } from "@rocicorp/logger";
10
11
  //#region ../zero-cache/src/server/mutator.ts
11
12
  var lc = new LogContext("info", {}, consoleLogSink);
12
13
  function runWorker(parent, env, ...args) {
14
+ installProfileHandler(parent, "mutator");
13
15
  const config = getNormalizedZeroConfig({
14
16
  env,
15
17
  argv: args.slice(1)
@@ -1 +1 @@
1
- {"version":3,"file":"mutator.js","names":[],"sources":["../../../../../zero-cache/src/server/mutator.ts"],"sourcesContent":["import {consoleLogSink, LogContext} from '@rocicorp/logger';\nimport {must} from '../../../shared/src/must.ts';\nimport {getNormalizedZeroConfig} from '../config/zero-config.ts';\nimport {initEventSink} from '../observability/events.ts';\nimport {exitAfter, runUntilKilled} from '../services/life-cycle.ts';\nimport {\n parentWorker,\n singleProcessMode,\n type Worker,\n} from '../types/processes.ts';\nimport {Mutator} from '../workers/mutator.ts';\nimport {createLogContext} from './logging.ts';\nimport {startOtelAuto} from './otel-start.ts';\n\n// Default LogContext, overridden in runWorker\nlet lc = new LogContext('info', {}, consoleLogSink);\n\nfunction runWorker(\n parent: Worker,\n env: NodeJS.ProcessEnv,\n ...args: string[]\n): Promise<void> {\n const config = getNormalizedZeroConfig({env, argv: args.slice(1)});\n startOtelAuto(createLogContext(config, 'mutator', 0, false), 'mutator', 0);\n lc = createLogContext(config, 'mutator');\n initEventSink(lc, config);\n\n // TODO: create `PusherFactory`\n return runUntilKilled(lc, parent, new Mutator());\n}\n\nif (!singleProcessMode()) {\n void exitAfter(\n () => lc,\n () => runWorker(must(parentWorker), process.env, ...process.argv.slice(2)),\n );\n}\n"],"mappings":";;;;;;;;;;AAeA,IAAI,KAAK,IAAI,WAAW,QAAQ,CAAC,GAAG,cAAc;AAElD,SAAS,UACP,QACA,KACA,GAAG,MACY;CACf,MAAM,SAAS,wBAAwB;EAAC;EAAK,MAAM,KAAK,MAAM,CAAC;CAAC,CAAC;CACjE,cAAc,iBAAiB,QAAQ,WAAW,GAAG,KAAK,GAAG,WAAW,CAAC;CACzE,KAAK,iBAAiB,QAAQ,SAAS;CACvC,cAAc,IAAI,MAAM;CAGxB,OAAO,eAAe,IAAI,QAAQ,IAAI,QAAQ,CAAC;AACjD;AAEA,IAAI,CAAC,kBAAkB,GACrB,gBACQ,UACA,UAAU,KAAK,YAAY,GAAG,QAAQ,KAAK,GAAG,QAAQ,KAAK,MAAM,CAAC,CAAC,CAC3E"}
1
+ {"version":3,"file":"mutator.js","names":[],"sources":["../../../../../zero-cache/src/server/mutator.ts"],"sourcesContent":["import {consoleLogSink, LogContext} from '@rocicorp/logger';\nimport {must} from '../../../shared/src/must.ts';\nimport {getNormalizedZeroConfig} from '../config/zero-config.ts';\nimport {initEventSink} from '../observability/events.ts';\nimport {exitAfter, runUntilKilled} from '../services/life-cycle.ts';\nimport {\n parentWorker,\n singleProcessMode,\n type Worker,\n} from '../types/processes.ts';\nimport {installProfileHandler} from '../types/profiler.ts';\nimport {Mutator} from '../workers/mutator.ts';\nimport {createLogContext} from './logging.ts';\nimport {startOtelAuto} from './otel-start.ts';\n\n// Default LogContext, overridden in runWorker\nlet lc = new LogContext('info', {}, consoleLogSink);\n\nfunction runWorker(\n parent: Worker,\n env: NodeJS.ProcessEnv,\n ...args: string[]\n): Promise<void> {\n installProfileHandler(parent, 'mutator');\n const config = getNormalizedZeroConfig({env, argv: args.slice(1)});\n startOtelAuto(createLogContext(config, 'mutator', 0, false), 'mutator', 0);\n lc = createLogContext(config, 'mutator');\n initEventSink(lc, config);\n\n // TODO: create `PusherFactory`\n return runUntilKilled(lc, parent, new Mutator());\n}\n\nif (!singleProcessMode()) {\n void exitAfter(\n () => lc,\n () => runWorker(must(parentWorker), process.env, ...process.argv.slice(2)),\n );\n}\n"],"mappings":";;;;;;;;;;;AAgBA,IAAI,KAAK,IAAI,WAAW,QAAQ,CAAC,GAAG,cAAc;AAElD,SAAS,UACP,QACA,KACA,GAAG,MACY;CACf,sBAAsB,QAAQ,SAAS;CACvC,MAAM,SAAS,wBAAwB;EAAC;EAAK,MAAM,KAAK,MAAM,CAAC;CAAC,CAAC;CACjE,cAAc,iBAAiB,QAAQ,WAAW,GAAG,KAAK,GAAG,WAAW,CAAC;CACzE,KAAK,iBAAiB,QAAQ,SAAS;CACvC,cAAc,IAAI,MAAM;CAGxB,OAAO,eAAe,IAAI,QAAQ,IAAI,QAAQ,CAAC;AACjD;AAEA,IAAI,CAAC,kBAAkB,GACrB,gBACQ,UACA,UAAU,KAAK,YAAY,GAAG,QAAQ,KAAK,GAAG,QAAQ,KAAK,MAAM,CAAC,CAAC,CAC3E"}
@@ -1 +1 @@
1
- {"version":3,"file":"reaper.d.ts","sourceRoot":"","sources":["../../../../../zero-cache/src/server/reaper.ts"],"names":[],"mappings":"AASA,OAAO,EAGL,KAAK,MAAM,EACZ,MAAM,uBAAuB,CAAC;AAW/B,wBAA8B,SAAS,CACrC,MAAM,EAAE,MAAM,EACd,GAAG,EAAE,MAAM,CAAC,UAAU,EACtB,GAAG,IAAI,EAAE,MAAM,EAAE,GAChB,OAAO,CAAC,IAAI,CAAC,CA+Bf"}
1
+ {"version":3,"file":"reaper.d.ts","sourceRoot":"","sources":["../../../../../zero-cache/src/server/reaper.ts"],"names":[],"mappings":"AASA,OAAO,EAGL,KAAK,MAAM,EACZ,MAAM,uBAAuB,CAAC;AAY/B,wBAA8B,SAAS,CACrC,MAAM,EAAE,MAAM,EACd,GAAG,EAAE,MAAM,CAAC,UAAU,EACtB,GAAG,IAAI,EAAE,MAAM,EAAE,GAChB,OAAO,CAAC,IAAI,CAAC,CAgCf"}
@@ -5,6 +5,7 @@ import { getNormalizedZeroConfig } from "../config/zero-config.js";
5
5
  import { connectPgClient } from "../types/pg.js";
6
6
  import { exitAfter, runUntilKilled } from "../services/life-cycle.js";
7
7
  import { createLogContext } from "./logging.js";
8
+ import { installProfileHandler } from "../types/profiler.js";
8
9
  import { initEventSink } from "../observability/events.js";
9
10
  import { startOtelAuto } from "./otel-start.js";
10
11
  import { startAnonymousTelemetry } from "./anonymous-otel-start.js";
@@ -16,6 +17,7 @@ import { LogContext, consoleLogSink } from "@rocicorp/logger";
16
17
  var MS_PER_HOUR = 1e3 * 60 * 60;
17
18
  var lc = new LogContext("info", {}, consoleLogSink);
18
19
  async function runWorker(parent, env, ...argv) {
20
+ installProfileHandler(parent, "reaper");
19
21
  const config = getNormalizedZeroConfig({
20
22
  env,
21
23
  argv
@@ -1 +1 @@
1
- {"version":3,"file":"reaper.js","names":[],"sources":["../../../../../zero-cache/src/server/reaper.ts"],"sourcesContent":["import {consoleLogSink, LogContext} from '@rocicorp/logger';\nimport {must} from '../../../shared/src/must.ts';\nimport {getNormalizedZeroConfig} from '../config/zero-config.ts';\nimport {initEventSink} from '../observability/events.ts';\nimport {exitAfter, runUntilKilled} from '../services/life-cycle.ts';\nimport {ActiveUsersGauge} from '../services/view-syncer/active-users-gauge.ts';\nimport {CVRPurger} from '../services/view-syncer/cvr-purger.ts';\nimport {initViewSyncerSchema} from '../services/view-syncer/schema/init.ts';\nimport {connectPgClient} from '../types/pg.ts';\nimport {\n parentWorker,\n singleProcessMode,\n type Worker,\n} from '../types/processes.ts';\nimport {getShardID} from '../types/shards.ts';\nimport {startAnonymousTelemetry} from './anonymous-otel-start.ts';\nimport {createLogContext} from './logging.ts';\nimport {startOtelAuto} from './otel-start.ts';\n\nconst MS_PER_HOUR = 1000 * 60 * 60;\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 ...argv: string[]\n): Promise<void> {\n const config = getNormalizedZeroConfig({env, argv});\n\n startOtelAuto(createLogContext(config, 'reaper', 0, false), 'reaper', 0);\n lc = createLogContext(config, 'reaper');\n initEventSink(lc, config);\n startAnonymousTelemetry(lc, config);\n\n const {cvr} = config;\n const shard = getShardID(config);\n const cvrDB = await connectPgClient(lc, cvr.db, `sync-cvr-purger`, {\n max: 1,\n });\n await initViewSyncerSchema(lc, cvrDB, shard);\n parent.send(['ready', {ready: true}]);\n\n return runUntilKilled(\n lc,\n parent,\n new CVRPurger(lc, cvrDB, shard, {\n inactivityThresholdMs:\n cvr.garbageCollectionInactivityThresholdHours * MS_PER_HOUR,\n initialBatchSize: cvr.garbageCollectionInitialBatchSize,\n initialIntervalMs: cvr.garbageCollectionInitialIntervalSeconds * 1000,\n }),\n // Periodically computes and exports active users gauge to anonymous telemetry\n new ActiveUsersGauge(lc, cvrDB, shard, {\n // Default 10minutes refresh; can be made configurable later if needed\n updateIntervalMs: 10 * 60 * 1000,\n }),\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":";;;;;;;;;;;;;;;AAmBA,IAAM,cAAc,MAAO,KAAK;AAGhC,IAAI,KAAK,IAAI,WAAW,QAAQ,CAAC,GAAG,cAAc;AAElD,eAA8B,UAC5B,QACA,KACA,GAAG,MACY;CACf,MAAM,SAAS,wBAAwB;EAAC;EAAK;CAAI,CAAC;CAElD,cAAc,iBAAiB,QAAQ,UAAU,GAAG,KAAK,GAAG,UAAU,CAAC;CACvE,KAAK,iBAAiB,QAAQ,QAAQ;CACtC,cAAc,IAAI,MAAM;CACxB,wBAAwB,IAAI,MAAM;CAElC,MAAM,EAAC,QAAO;CACd,MAAM,QAAQ,WAAW,MAAM;CAC/B,MAAM,QAAQ,MAAM,gBAAgB,IAAI,IAAI,IAAI,mBAAmB,EACjE,KAAK,EACP,CAAC;CACD,MAAM,qBAAqB,IAAI,OAAO,KAAK;CAC3C,OAAO,KAAK,CAAC,SAAS,EAAC,OAAO,KAAI,CAAC,CAAC;CAEpC,OAAO,eACL,IACA,QACA,IAAI,UAAU,IAAI,OAAO,OAAO;EAC9B,uBACE,IAAI,4CAA4C;EAClD,kBAAkB,IAAI;EACtB,mBAAmB,IAAI,0CAA0C;CACnE,CAAC,GAED,IAAI,iBAAiB,IAAI,OAAO,OAAO,EAErC,kBAAkB,MAAU,IAC9B,CAAC,CACH;AACF;AAGA,IAAI,CAAC,kBAAkB,GACrB,gBACQ,UACA,UAAU,KAAK,YAAY,GAAG,QAAQ,KAAK,GAAG,QAAQ,KAAK,MAAM,CAAC,CAAC,CAC3E"}
1
+ {"version":3,"file":"reaper.js","names":[],"sources":["../../../../../zero-cache/src/server/reaper.ts"],"sourcesContent":["import {consoleLogSink, LogContext} from '@rocicorp/logger';\nimport {must} from '../../../shared/src/must.ts';\nimport {getNormalizedZeroConfig} from '../config/zero-config.ts';\nimport {initEventSink} from '../observability/events.ts';\nimport {exitAfter, runUntilKilled} from '../services/life-cycle.ts';\nimport {ActiveUsersGauge} from '../services/view-syncer/active-users-gauge.ts';\nimport {CVRPurger} from '../services/view-syncer/cvr-purger.ts';\nimport {initViewSyncerSchema} from '../services/view-syncer/schema/init.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 {startAnonymousTelemetry} from './anonymous-otel-start.ts';\nimport {createLogContext} from './logging.ts';\nimport {startOtelAuto} from './otel-start.ts';\n\nconst MS_PER_HOUR = 1000 * 60 * 60;\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 ...argv: string[]\n): Promise<void> {\n installProfileHandler(parent, 'reaper');\n const config = getNormalizedZeroConfig({env, argv});\n\n startOtelAuto(createLogContext(config, 'reaper', 0, false), 'reaper', 0);\n lc = createLogContext(config, 'reaper');\n initEventSink(lc, config);\n startAnonymousTelemetry(lc, config);\n\n const {cvr} = config;\n const shard = getShardID(config);\n const cvrDB = await connectPgClient(lc, cvr.db, `sync-cvr-purger`, {\n max: 1,\n });\n await initViewSyncerSchema(lc, cvrDB, shard);\n parent.send(['ready', {ready: true}]);\n\n return runUntilKilled(\n lc,\n parent,\n new CVRPurger(lc, cvrDB, shard, {\n inactivityThresholdMs:\n cvr.garbageCollectionInactivityThresholdHours * MS_PER_HOUR,\n initialBatchSize: cvr.garbageCollectionInitialBatchSize,\n initialIntervalMs: cvr.garbageCollectionInitialIntervalSeconds * 1000,\n }),\n // Periodically computes and exports active users gauge to anonymous telemetry\n new ActiveUsersGauge(lc, cvrDB, shard, {\n // Default 10minutes refresh; can be made configurable later if needed\n updateIntervalMs: 10 * 60 * 1000,\n }),\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":";;;;;;;;;;;;;;;;AAoBA,IAAM,cAAc,MAAO,KAAK;AAGhC,IAAI,KAAK,IAAI,WAAW,QAAQ,CAAC,GAAG,cAAc;AAElD,eAA8B,UAC5B,QACA,KACA,GAAG,MACY;CACf,sBAAsB,QAAQ,QAAQ;CACtC,MAAM,SAAS,wBAAwB;EAAC;EAAK;CAAI,CAAC;CAElD,cAAc,iBAAiB,QAAQ,UAAU,GAAG,KAAK,GAAG,UAAU,CAAC;CACvE,KAAK,iBAAiB,QAAQ,QAAQ;CACtC,cAAc,IAAI,MAAM;CACxB,wBAAwB,IAAI,MAAM;CAElC,MAAM,EAAC,QAAO;CACd,MAAM,QAAQ,WAAW,MAAM;CAC/B,MAAM,QAAQ,MAAM,gBAAgB,IAAI,IAAI,IAAI,mBAAmB,EACjE,KAAK,EACP,CAAC;CACD,MAAM,qBAAqB,IAAI,OAAO,KAAK;CAC3C,OAAO,KAAK,CAAC,SAAS,EAAC,OAAO,KAAI,CAAC,CAAC;CAEpC,OAAO,eACL,IACA,QACA,IAAI,UAAU,IAAI,OAAO,OAAO;EAC9B,uBACE,IAAI,4CAA4C;EAClD,kBAAkB,IAAI;EACtB,mBAAmB,IAAI,0CAA0C;CACnE,CAAC,GAED,IAAI,iBAAiB,IAAI,OAAO,OAAO,EAErC,kBAAkB,MAAU,IAC9B,CAAC,CACH;AACF;AAGA,IAAI,CAAC,kBAAkB,GACrB,gBACQ,UACA,UAAU,KAAK,YAAY,GAAG,QAAQ,KAAK,GAAG,QAAQ,KAAK,MAAM,CAAC,CAAC,CAC3E"}
@@ -1 +1 @@
1
- {"version":3,"file":"replicator.d.ts","sourceRoot":"","sources":["../../../../../zero-cache/src/server/replicator.ts"],"names":[],"mappings":"AAsCA,OAAO,EAGL,KAAK,MAAM,EACZ,MAAM,uBAAuB,CAAC;AAgB/B,wBAA8B,SAAS,CACrC,MAAM,EAAE,MAAM,EACd,GAAG,EAAE,MAAM,CAAC,UAAU,EACtB,GAAG,IAAI,EAAE,MAAM,EAAE,GAChB,OAAO,CAAC,IAAI,CAAC,CAoHf"}
1
+ {"version":3,"file":"replicator.d.ts","sourceRoot":"","sources":["../../../../../zero-cache/src/server/replicator.ts"],"names":[],"mappings":"AAsCA,OAAO,EAGL,KAAK,MAAM,EACZ,MAAM,uBAAuB,CAAC;AAiB/B,wBAA8B,SAAS,CACrC,MAAM,EAAE,MAAM,EACd,GAAG,EAAE,MAAM,CAAC,UAAU,EACtB,GAAG,IAAI,EAAE,MAAM,EAAE,GAChB,OAAO,CAAC,IAAI,CAAC,CAqHf"}
@@ -9,6 +9,7 @@ import { getOrCreateGauge } from "../observability/metrics.js";
9
9
  import { registerSQLiteCorruptionDiagnosticTarget } from "../db/sqlite-corruption.js";
10
10
  import { exitAfter, runUntilKilled } from "../services/life-cycle.js";
11
11
  import { createLogContext } from "./logging.js";
12
+ import { installProfileHandler } from "../types/profiler.js";
12
13
  import { initEventSink } from "../observability/events.js";
13
14
  import { ReplicationStatusPublisher } from "../services/replicator/replication-status.js";
14
15
  import { litestreamRestoreDuration, litestreamRestoreMetricAttrs, litestreamRestoreRuns } from "../services/litestream/metrics.js";
@@ -35,6 +36,7 @@ async function runWorker(parent, env, ...args) {
35
36
  const mode = fileMode === "backup" ? "backup" : "serving";
36
37
  const runningLocalChangeStreamer = config.changeStreamer.mode === "dedicated" && !config.changeStreamer.uri;
37
38
  const workerName = `${mode}-replicator`;
39
+ installProfileHandler(parent, workerName);
38
40
  startOtelAuto(createLogContext(config, workerName, 0, false), workerName, 0);
39
41
  lc = createLogContext(config, workerName);
40
42
  const unregisterInitialCorruptionDiagnosticTarget = registerSQLiteCorruptionDiagnosticTarget({
@@ -1 +1 @@
1
- {"version":3,"file":"replicator.js","names":[],"sources":["../../../../../zero-cache/src/server/replicator.ts"],"sourcesContent":["import {stat} from 'node:fs/promises';\nimport {pid} from 'node:process';\nimport type {ObservableCallback} from '@opentelemetry/api';\nimport {consoleLogSink, LogContext} from '@rocicorp/logger';\nimport {assert} from '../../../shared/src/asserts.ts';\nimport {must} from '../../../shared/src/must.ts';\nimport {sleep} from '../../../shared/src/sleep.ts';\nimport * as v from '../../../shared/src/valita.ts';\nimport type {\n LitestreamConfig,\n NormalizedZeroConfig,\n} from '../config/normalize.ts';\nimport {getNormalizedZeroConfig} from '../config/zero-config.ts';\nimport {registerSQLiteCorruptionDiagnosticTarget} from '../db/sqlite-corruption.ts';\nimport {initEventSink} from '../observability/events.ts';\nimport {getOrCreateGauge} from '../observability/metrics.ts';\nimport {ChangeStreamerHttpClient} from '../services/change-streamer/change-streamer-http.ts';\nimport {reserveAndGetSnapshotStatus} from '../services/change-streamer/snapshot.ts';\nimport {exitAfter, runUntilKilled} from '../services/life-cycle.ts';\nimport {\n tryRestore,\n type RestoreResult,\n} from '../services/litestream/commands.ts';\nimport {\n litestreamRestoreDuration,\n litestreamRestoreMetricAttrs,\n litestreamRestoreRuns,\n} from '../services/litestream/metrics.ts';\nimport {ReplicationStatusPublisher} from '../services/replicator/replication-status.ts';\nimport {\n ReplicatorService,\n type ReplicatorMode,\n} from '../services/replicator/replicator.ts';\nimport {sqliteFileBytes} from '../services/replicator/sqlite-change-log-observability.ts';\nimport {\n ThreadWriteWorkerClient,\n type ForceCheckpointConfig,\n} from '../services/replicator/write-worker-client.ts';\nimport {\n parentWorker,\n singleProcessMode,\n type Worker,\n} from '../types/processes.ts';\nimport {getShardConfig} from '../types/shards.ts';\nimport {\n deleteStaleChangeLog,\n getPragmaConfig,\n replicaFileModeSchema,\n setUpMessageHandlers,\n setupReplica,\n type WalMode,\n} from '../workers/replicator.ts';\nimport {createLogContext} from './logging.ts';\nimport {startOtelAuto} from './otel-start.ts';\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 > 0, `replicator mode not specified`);\n const fileMode = v.parse(args[0], replicaFileModeSchema);\n\n const config = getNormalizedZeroConfig({env, argv: args.slice(1)});\n const mode: ReplicatorMode = fileMode === 'backup' ? 'backup' : 'serving';\n const runningLocalChangeStreamer =\n config.changeStreamer.mode === 'dedicated' && !config.changeStreamer.uri;\n const workerName = `${mode}-replicator`;\n startOtelAuto(createLogContext(config, workerName, 0, false), workerName, 0);\n lc = createLogContext(config, workerName);\n const unregisterInitialCorruptionDiagnosticTarget =\n registerSQLiteCorruptionDiagnosticTarget(\n {\n debugName: `${workerName} replica`,\n dbPath: config.replica.file,\n },\n config.sqliteCorruptionChecks,\n );\n initEventSink(lc, config);\n\n // Remote view-syncers restore from the replication-manager, which supplies\n // the backup URL. A local change-streamer owns the canonical replica and\n // must not infer backup configuration from bundled executable paths.\n if (\n fileMode === 'serving' &&\n !runningLocalChangeStreamer &&\n (config.litestream.executable || config.litestream.executableV5)\n ) {\n await restoreReplica(lc, config);\n }\n\n const {\n file: dbPath,\n walMode,\n pageSize,\n } = await setupReplica(lc, fileMode, config.replica);\n unregisterInitialCorruptionDiagnosticTarget();\n registerSQLiteCorruptionDiagnosticTarget(\n {\n debugName: `${workerName} replica`,\n dbPath,\n },\n config.sqliteCorruptionChecks,\n );\n\n // A file left behind by a run with the writer enabled would otherwise hold\n // local disk indefinitely. See `replicatorDeletesStaleChangeLog` for why\n // this is keyed on the config flag and nothing else.\n deleteStaleChangeLog(config.changeStreamer.sqliteChangeLogMode, dbPath);\n\n setupMetrics(lc, dbPath, walMode);\n\n // Create the write worker for async SQLite writes. Enable write-path\n // checkpoint backpressure only for the backup replicator on litestream v5,\n // which owns the replica litestream backs up.\n const pragmas = getPragmaConfig(fileMode);\n const checkpoint: ForceCheckpointConfig | null =\n mode === 'backup' &&\n config.litestream.backupURL &&\n config.litestream.backupUsingV5 &&\n config.litestream.forceCheckpointThresholdMB\n ? {\n checkpointThresholdPages:\n (config.litestream.forceCheckpointThresholdMB * 1024 ** 2) /\n pageSize,\n maxWalPages: !config.litestream.maxWalSizeMB\n ? undefined\n : (config.litestream.maxWalSizeMB * 1024 ** 2) / pageSize,\n }\n : null;\n const workerClient = new ThreadWriteWorkerClient();\n await workerClient.init(dbPath, mode, pragmas, config.log, checkpoint);\n\n const shard = getShardConfig(config);\n const {\n taskID,\n change,\n changeStreamer: {\n port,\n uri: changeStreamerURI = runningLocalChangeStreamer\n ? `http://localhost:${port}/`\n : undefined,\n },\n } = config;\n const changeStreamer = new ChangeStreamerHttpClient(\n lc,\n shard,\n change.db,\n changeStreamerURI,\n );\n\n const replicator = new ReplicatorService(\n lc,\n taskID,\n `${workerName}-${pid}`,\n mode,\n changeStreamer,\n workerClient,\n runningLocalChangeStreamer\n ? // publish ReplicationStatusEvents from backup-replicator only\n ReplicationStatusPublisher.forReplicaFile(dbPath)\n : null,\n );\n\n setUpMessageHandlers(lc, replicator, parent);\n\n const running = runUntilKilled(lc, parent, replicator);\n\n // Signal readiness once the first ReplicaVersionReady notification is received.\n for await (const _ of replicator.subscribe()) {\n parent.send(['ready', {ready: true}]);\n break;\n }\n\n return running;\n}\n\nfunction setupMetrics(lc: LogContext, file: string, walMode: WalMode) {\n getOrCreateGauge('replica', 'db_size', {\n description:\n `The size of the replica's main db file, ` +\n `which does not include the wal file(s)`,\n unit: 'bytes',\n }).addCallback(observeFileSize(lc, file));\n\n getOrCreateGauge('replica', 'wal_size', {\n description: `The size of the replica's wal file`,\n unit: 'bytes',\n }).addCallback(observeFileSize(lc, `${file}-wal`));\n\n if (walMode === 'wal2') {\n getOrCreateGauge('replica', 'wal2_size', {\n description: `The size of the replica's wal2 file`,\n unit: 'bytes',\n }).addCallback(observeFileSize(lc, `${file}-wal2`));\n }\n\n // A whole-database total rather than a per-file one. Its counterpart,\n // `sqlite_change_log.file_bytes`, is emitted by the change-streamer, which is\n // the process that writes the log.\n getOrCreateGauge('replica', 'file_bytes', {\n description:\n `The replica's total on-disk footprint: its main db file plus its ` +\n `wal sidecars.`,\n unit: 'By',\n }).addCallback(observeSQLiteFileBytes(lc, file));\n}\n\nfunction observeFileSize(lc: LogContext, file: string): ObservableCallback {\n return async o => {\n try {\n const stats = await stat(file);\n o.observe(stats.size);\n } catch (e) {\n lc.warn?.(`unable to stat ${file} for size metrics`, e);\n }\n };\n}\n\nfunction observeSQLiteFileBytes(\n lc: LogContext,\n file: string,\n): ObservableCallback {\n return async o => o.observe(await sqliteFileBytes(lc, file));\n}\n\nconst RETRY_INTERVAL_MS = 3000;\n\n// View-syncers (no replicaConstraints) wait indefinitely for the\n// replication-manager to publish a restorable backup. On a fresh stack the\n// first backup is not durable until the initial sync completes and litestream\n// uploads the initial snapshot, which can take many minutes for a large\n// replica. The platform's startup probe budget (which scales with replica\n// size) is the backstop, so restoreReplica must not impose its own shorter\n// cap and self-terminate while the backup is still being produced.\nasync function restoreReplica(lc: LogContext, config: NormalizedZeroConfig) {\n const start = performance.now();\n let backupURL: string | undefined;\n let result: RestoreResult | undefined;\n try {\n for (;;) {\n const snapshotStatus = await reserveAndGetSnapshotStatus(lc, config);\n // The backupURL comes from the replication-manager's snapshot response.\n ({backupURL} = snapshotStatus);\n const litestream: LitestreamConfig = {...config.litestream, backupURL};\n const attempt = await tryRestore(\n lc,\n litestream,\n config.replica.file,\n snapshotStatus,\n 'view_syncer',\n );\n if (attempt.restored) {\n result = attempt.result;\n return;\n }\n lc.info?.(\n `replica not found. retrying in ${RETRY_INTERVAL_MS / 1000} seconds`,\n );\n await sleep(RETRY_INTERVAL_MS);\n }\n } finally {\n const attrs = litestreamRestoreMetricAttrs(\n config.litestream,\n 'view_syncer',\n backupURL,\n );\n const labels = {...attrs, result: result ?? 'error'};\n litestreamRestoreRuns().add(1, labels);\n litestreamRestoreDuration().recordMs(performance.now() - start, labels);\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
+ {"version":3,"file":"replicator.js","names":[],"sources":["../../../../../zero-cache/src/server/replicator.ts"],"sourcesContent":["import {stat} from 'node:fs/promises';\nimport {pid} from 'node:process';\nimport type {ObservableCallback} from '@opentelemetry/api';\nimport {consoleLogSink, LogContext} from '@rocicorp/logger';\nimport {assert} from '../../../shared/src/asserts.ts';\nimport {must} from '../../../shared/src/must.ts';\nimport {sleep} from '../../../shared/src/sleep.ts';\nimport * as v from '../../../shared/src/valita.ts';\nimport type {\n LitestreamConfig,\n NormalizedZeroConfig,\n} from '../config/normalize.ts';\nimport {getNormalizedZeroConfig} from '../config/zero-config.ts';\nimport {registerSQLiteCorruptionDiagnosticTarget} from '../db/sqlite-corruption.ts';\nimport {initEventSink} from '../observability/events.ts';\nimport {getOrCreateGauge} from '../observability/metrics.ts';\nimport {ChangeStreamerHttpClient} from '../services/change-streamer/change-streamer-http.ts';\nimport {reserveAndGetSnapshotStatus} from '../services/change-streamer/snapshot.ts';\nimport {exitAfter, runUntilKilled} from '../services/life-cycle.ts';\nimport {\n tryRestore,\n type RestoreResult,\n} from '../services/litestream/commands.ts';\nimport {\n litestreamRestoreDuration,\n litestreamRestoreMetricAttrs,\n litestreamRestoreRuns,\n} from '../services/litestream/metrics.ts';\nimport {ReplicationStatusPublisher} from '../services/replicator/replication-status.ts';\nimport {\n ReplicatorService,\n type ReplicatorMode,\n} from '../services/replicator/replicator.ts';\nimport {sqliteFileBytes} from '../services/replicator/sqlite-change-log-observability.ts';\nimport {\n ThreadWriteWorkerClient,\n type ForceCheckpointConfig,\n} from '../services/replicator/write-worker-client.ts';\nimport {\n parentWorker,\n singleProcessMode,\n type Worker,\n} from '../types/processes.ts';\nimport {installProfileHandler} from '../types/profiler.ts';\nimport {getShardConfig} from '../types/shards.ts';\nimport {\n deleteStaleChangeLog,\n getPragmaConfig,\n replicaFileModeSchema,\n setUpMessageHandlers,\n setupReplica,\n type WalMode,\n} from '../workers/replicator.ts';\nimport {createLogContext} from './logging.ts';\nimport {startOtelAuto} from './otel-start.ts';\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 > 0, `replicator mode not specified`);\n const fileMode = v.parse(args[0], replicaFileModeSchema);\n\n const config = getNormalizedZeroConfig({env, argv: args.slice(1)});\n const mode: ReplicatorMode = fileMode === 'backup' ? 'backup' : 'serving';\n const runningLocalChangeStreamer =\n config.changeStreamer.mode === 'dedicated' && !config.changeStreamer.uri;\n const workerName = `${mode}-replicator`;\n installProfileHandler(parent, workerName);\n startOtelAuto(createLogContext(config, workerName, 0, false), workerName, 0);\n lc = createLogContext(config, workerName);\n const unregisterInitialCorruptionDiagnosticTarget =\n registerSQLiteCorruptionDiagnosticTarget(\n {\n debugName: `${workerName} replica`,\n dbPath: config.replica.file,\n },\n config.sqliteCorruptionChecks,\n );\n initEventSink(lc, config);\n\n // Remote view-syncers restore from the replication-manager, which supplies\n // the backup URL. A local change-streamer owns the canonical replica and\n // must not infer backup configuration from bundled executable paths.\n if (\n fileMode === 'serving' &&\n !runningLocalChangeStreamer &&\n (config.litestream.executable || config.litestream.executableV5)\n ) {\n await restoreReplica(lc, config);\n }\n\n const {\n file: dbPath,\n walMode,\n pageSize,\n } = await setupReplica(lc, fileMode, config.replica);\n unregisterInitialCorruptionDiagnosticTarget();\n registerSQLiteCorruptionDiagnosticTarget(\n {\n debugName: `${workerName} replica`,\n dbPath,\n },\n config.sqliteCorruptionChecks,\n );\n\n // A file left behind by a run with the writer enabled would otherwise hold\n // local disk indefinitely. See `replicatorDeletesStaleChangeLog` for why\n // this is keyed on the config flag and nothing else.\n deleteStaleChangeLog(config.changeStreamer.sqliteChangeLogMode, dbPath);\n\n setupMetrics(lc, dbPath, walMode);\n\n // Create the write worker for async SQLite writes. Enable write-path\n // checkpoint backpressure only for the backup replicator on litestream v5,\n // which owns the replica litestream backs up.\n const pragmas = getPragmaConfig(fileMode);\n const checkpoint: ForceCheckpointConfig | null =\n mode === 'backup' &&\n config.litestream.backupURL &&\n config.litestream.backupUsingV5 &&\n config.litestream.forceCheckpointThresholdMB\n ? {\n checkpointThresholdPages:\n (config.litestream.forceCheckpointThresholdMB * 1024 ** 2) /\n pageSize,\n maxWalPages: !config.litestream.maxWalSizeMB\n ? undefined\n : (config.litestream.maxWalSizeMB * 1024 ** 2) / pageSize,\n }\n : null;\n const workerClient = new ThreadWriteWorkerClient();\n await workerClient.init(dbPath, mode, pragmas, config.log, checkpoint);\n\n const shard = getShardConfig(config);\n const {\n taskID,\n change,\n changeStreamer: {\n port,\n uri: changeStreamerURI = runningLocalChangeStreamer\n ? `http://localhost:${port}/`\n : undefined,\n },\n } = config;\n const changeStreamer = new ChangeStreamerHttpClient(\n lc,\n shard,\n change.db,\n changeStreamerURI,\n );\n\n const replicator = new ReplicatorService(\n lc,\n taskID,\n `${workerName}-${pid}`,\n mode,\n changeStreamer,\n workerClient,\n runningLocalChangeStreamer\n ? // publish ReplicationStatusEvents from backup-replicator only\n ReplicationStatusPublisher.forReplicaFile(dbPath)\n : null,\n );\n\n setUpMessageHandlers(lc, replicator, parent);\n\n const running = runUntilKilled(lc, parent, replicator);\n\n // Signal readiness once the first ReplicaVersionReady notification is received.\n for await (const _ of replicator.subscribe()) {\n parent.send(['ready', {ready: true}]);\n break;\n }\n\n return running;\n}\n\nfunction setupMetrics(lc: LogContext, file: string, walMode: WalMode) {\n getOrCreateGauge('replica', 'db_size', {\n description:\n `The size of the replica's main db file, ` +\n `which does not include the wal file(s)`,\n unit: 'bytes',\n }).addCallback(observeFileSize(lc, file));\n\n getOrCreateGauge('replica', 'wal_size', {\n description: `The size of the replica's wal file`,\n unit: 'bytes',\n }).addCallback(observeFileSize(lc, `${file}-wal`));\n\n if (walMode === 'wal2') {\n getOrCreateGauge('replica', 'wal2_size', {\n description: `The size of the replica's wal2 file`,\n unit: 'bytes',\n }).addCallback(observeFileSize(lc, `${file}-wal2`));\n }\n\n // A whole-database total rather than a per-file one. Its counterpart,\n // `sqlite_change_log.file_bytes`, is emitted by the change-streamer, which is\n // the process that writes the log.\n getOrCreateGauge('replica', 'file_bytes', {\n description:\n `The replica's total on-disk footprint: its main db file plus its ` +\n `wal sidecars.`,\n unit: 'By',\n }).addCallback(observeSQLiteFileBytes(lc, file));\n}\n\nfunction observeFileSize(lc: LogContext, file: string): ObservableCallback {\n return async o => {\n try {\n const stats = await stat(file);\n o.observe(stats.size);\n } catch (e) {\n lc.warn?.(`unable to stat ${file} for size metrics`, e);\n }\n };\n}\n\nfunction observeSQLiteFileBytes(\n lc: LogContext,\n file: string,\n): ObservableCallback {\n return async o => o.observe(await sqliteFileBytes(lc, file));\n}\n\nconst RETRY_INTERVAL_MS = 3000;\n\n// View-syncers (no replicaConstraints) wait indefinitely for the\n// replication-manager to publish a restorable backup. On a fresh stack the\n// first backup is not durable until the initial sync completes and litestream\n// uploads the initial snapshot, which can take many minutes for a large\n// replica. The platform's startup probe budget (which scales with replica\n// size) is the backstop, so restoreReplica must not impose its own shorter\n// cap and self-terminate while the backup is still being produced.\nasync function restoreReplica(lc: LogContext, config: NormalizedZeroConfig) {\n const start = performance.now();\n let backupURL: string | undefined;\n let result: RestoreResult | undefined;\n try {\n for (;;) {\n const snapshotStatus = await reserveAndGetSnapshotStatus(lc, config);\n // The backupURL comes from the replication-manager's snapshot response.\n ({backupURL} = snapshotStatus);\n const litestream: LitestreamConfig = {...config.litestream, backupURL};\n const attempt = await tryRestore(\n lc,\n litestream,\n config.replica.file,\n snapshotStatus,\n 'view_syncer',\n );\n if (attempt.restored) {\n result = attempt.result;\n return;\n }\n lc.info?.(\n `replica not found. retrying in ${RETRY_INTERVAL_MS / 1000} seconds`,\n );\n await sleep(RETRY_INTERVAL_MS);\n }\n } finally {\n const attrs = litestreamRestoreMetricAttrs(\n config.litestream,\n 'view_syncer',\n backupURL,\n );\n const labels = {...attrs, result: result ?? 'error'};\n litestreamRestoreRuns().add(1, labels);\n litestreamRestoreDuration().recordMs(performance.now() - start, labels);\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":"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;AAGzE,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,EAarB;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;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,5 +1,6 @@
1
1
  import { handleHeapzRequest } from "../../services/heapz.js";
2
2
  import { HttpService } from "../../services/http-service.js";
3
+ import { handleProfrmzRequest, handleProfzRequest } from "../../services/profz.js";
3
4
  import { handleStatzRequest } from "../../services/statz.js";
4
5
  import { installWebSocketHandoff } from "../../types/websocket-handoff.js";
5
6
  //#region ../zero-cache/src/server/runner/zero-dispatcher.ts
@@ -11,6 +12,8 @@ var ZeroDispatcher = class extends HttpService {
11
12
  super(`zero-dispatcher`, lc, opts, (fastify) => {
12
13
  fastify.get("/statz", (req, res) => handleStatzRequest(lc, config, req, res));
13
14
  fastify.get("/heapz", (req, res) => handleHeapzRequest(lc, config, req, res));
15
+ fastify.get("/profz", (req, res) => handleProfzRequest(lc, config, req, res, this.#getWorker));
16
+ fastify.get("/profrmz", (req, res) => handleProfrmzRequest(lc, config, req, res, this.#getWorker));
14
17
  installWebSocketHandoff(lc, this.#handoff, fastify.server);
15
18
  });
16
19
  this.#getWorker = 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 {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 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":";;;;;AAYA,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,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 {\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 +1 @@
1
- {"version":3,"file":"syncer.d.ts","sourceRoot":"","sources":["../../../../../zero-cache/src/server/syncer.ts"],"names":[],"mappings":"AAiCA,OAAO,EAGL,KAAK,MAAM,EACZ,MAAM,uBAAuB,CAAC;AAoC/B,wBAA8B,SAAS,CACrC,MAAM,EAAE,MAAM,EACd,GAAG,EAAE,MAAM,CAAC,UAAU,EACtB,GAAG,IAAI,EAAE,MAAM,EAAE,GAChB,OAAO,CAAC,IAAI,CAAC,CAiNf"}
1
+ {"version":3,"file":"syncer.d.ts","sourceRoot":"","sources":["../../../../../zero-cache/src/server/syncer.ts"],"names":[],"mappings":"AAiCA,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,CAkNf"}
@@ -12,6 +12,7 @@ import { connectPgClient } from "../types/pg.js";
12
12
  import { registerSQLiteCorruptionDiagnosticTarget } from "../db/sqlite-corruption.js";
13
13
  import { exitAfter, runUntilKilled } from "../services/life-cycle.js";
14
14
  import { createLogContext } from "./logging.js";
15
+ import { installProfileHandler } from "../types/profiler.js";
15
16
  import { warmupConnections } from "../db/warmup.js";
16
17
  import { initEventSink } from "../observability/events.js";
17
18
  import { startOtelAuto } from "./otel-start.js";
@@ -53,6 +54,7 @@ async function runWorker(parent, env, ...args) {
53
54
  assert(args.length >= 2, `expected [fileMode, workerIndex, ...flags]`);
54
55
  const fileMode = parse(args[0], replicaFileModeSchema);
55
56
  const workerIndex = Number(args[1]);
57
+ installProfileHandler(parent, `syncer-${workerIndex}`, workerIndex);
56
58
  const config = getNormalizedZeroConfig({
57
59
  env,
58
60
  argv: args.slice(2)
@@ -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 {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 {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 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 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(logger, replicaFile, shard),\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 {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 {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 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(logger, replicaFile, shard),\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":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAiDA,SAAS,WAAW;CAClB,OAAO,QAAQ,GAAG,OAAO,gBAAgB,EAAE,SAAS,EAAE;AACxD;AAEA,SAAS,qBACP,QACA;CACA,MAAM,cAAc,OAAO,OAAO,MAAM,OAAO,QAAQ,OAAO;CAE9D,IAAI,CAAC,aAAa,KAChB;CAGF,OAAO;EACL,KAAK,YAAY;EACjB,QAAQ,YAAY;EACpB,sBAAsB,YAAY;EAClC,uBAAuB,YAAY;EACnC,gBAAgB,YAAY,kBAAkB;CAChD;AACF;AAGA,IAAI,KAAK,IAAI,WAAW,QAAQ,CAAC,GAAG,cAAc;AAElD,eAA8B,UAC5B,QACA,KACA,GAAG,MACY;CACf,OAAO,KAAK,UAAU,GAAG,4CAA4C;CACrE,MAAM,WAAW,MAAQ,KAAK,IAAI,qBAAqB;CACvD,MAAM,cAAc,OAAO,KAAK,EAAE;CAClC,sBAAsB,QAAQ,UAAU,eAAe,WAAW;CAClE,MAAM,SAAS,wBAAwB;EAAC;EAAK,MAAM,KAAK,MAAM,CAAC;CAAC,CAAC;CAEjE,cACE,iBAAiB,QAAQ,UAAU,aAAa,KAAK,GACrD,UACA,WACF;CACA,KAAK,iBAAiB,QAAQ,UAAU,WAAW;CACnD,cAAc,IAAI,MAAM;CAExB,MAAM,EAAC,KAAK,UAAU,wBAAuB;CAE7C,MAAM,cAAc,gBAAgB,OAAO,QAAQ,MAAM,QAAQ;CACjE,yCACE;EACE,WAAW;EACX,QAAQ;CACV,GACA,OAAO,sBACT;CACA,GAAG,QAAQ,0BAA0B,aAAa;CAElD,MAAM,QAAQ,MAAM,gBAAgB,IAAI,IAAI,IAAI,eAAe,IAAI,OAAO,EACxE,KAAK,KAAK,IAAI,mBAAmB,mCAAmC,EACtE,CAAC;CAED,MAAM,aACJ,uBAAuB,SAAS,SAAS,OACrC,MAAM,gBAAgB,IAAI,SAAS,IAAI,eAAe,IAAI,YAAY,EACpE,KAAK,KACH,SAAS,mBACT,wCACF,EACF,CAAC,IACD,KAAA;CAEN,MAAM,WAAW,QAAQ,WAAW,CAClC,kBAAkB,IAAI,OAAO,KAAK,GAClC,aAAa,kBAAkB,IAAI,YAAY,UAAU,IAAI,WAC/D,CAAC;CAED,MAAM,SAAS,OAAO,mBAAmB,OAAO;CAChD,MAAM,kBAAkB,gBAAgB,OACtC,IACA,KAAK,KAAK,QAAQ,eAAe,WAAW,GAAG,CACjD;CACA,MAAM,oBAAoB,gBAAgB,OACxC,IACA,KAAK,KAAK,QAAQ,WAAW,WAAW,GAAG,CAC7C;CAEA,MAAM,QAAQ,WAAW,MAAM;CAC/B,MAAM,oBAAoB,qBAAqB,MAAM;CACrD,MAAM,aACJ,OAAO,KAAK,QAAQ,KAAA,KAAa,OAAO,OAAO,QAAQ,KAAA,IACnD,KAAA,IACA;EACE,GAAG,OAAO;EACV,GAAG,OAAO;EACV,KAAK,KACH,OAAO,KAAK,OAAO,OAAO,OAAO,KACjC,kCACF;CACF;;CAGN,IAAI,oBAAmD,KAAA;CAGvD,IADqB,mBAAmB,OAAO,QAAQ,CAAC,CACpD,EAAa,WAAW,GAC1B,oBAAoB,OAAO,OAAO,EAAC,aAAY;EAC7C,IAAI,CAAC,QACH,MAAM,IAAI,uBACR;GACE,MAAM;GACN,SAAS;GACT,QAAQ;EACV,GACA,MACF;EAUF,OAAO;GACL,MAAM;GACN,KAAK;GACL,SAAA,MAVoB,YAAY,OAAO,MAAM,OAAO;IACpD,SAAS;IACT,GAAI,OAAO,MAAM,UAAU,EAAC,QAAQ,OAAO,KAAK,OAAM;IACtD,GAAI,OAAO,MAAM,YAAY,EAC3B,UAAU,OAAO,KAAK,SACxB;GACF,CAAC;EAKD;CACF;CAGF,MAAM,qBACJ,IACA,KACA,qBACG;EACH,MAAM,SAAS,GACZ,YAAY,UAAU,OAAO,MAAM,EACnC,YAAY,aAAa,aAAa,EACtC,YAAY,SAAS,MAAM,KAAK,EAChC,YAAY,YAAY,MAAM,QAAQ,EACtC,YAAY,iBAAiB,EAAE,EAC/B,YAAY,YAAY,SAAS,CAAC;EAErC,MAAM,yBACJ,qBAAqB,IAAI,uBAAuB,QAAQ,KAAK;EAC/D,MAAM,qBAAqB,IAAI,6BAC7B,QACA,OAAO,KAAK,2BACZ,OAAO,KAAK,4BACZ,mBACA,YACA,iBACF;EAEA,GAAG,QACD,gDAAgD,OAAO,oBACzD;EAEA,MAAM,oBAAoB,IAAI,kBAAkB,sBAAsB;EAEtE,MAAM,oCAAoC,KAAK,IAC7C,OAAO,mBAAmB,GAC1B,CACF;EACA,MAAM,yBAAyB,KAAK,IAAI,OAAO,kBAAkB,CAAC;EAElE,OAAO,IAAI,kBACT,QACA,QACA,OACA,OAAO,QACP,IACA,OACA,IAAI,eACF,QACA,OAAO,KACP,IAAI,YAAY,QAAQ,aAAa,KAAK,GAC1C,OACA,gBAAgB,yBAAyB,EAAE,GAC3C,IACA,yBAEE,oBAAoB,IAChB,oCACA,wBACN,OAAO,oBACP,MACF,GACA,KACA,kBACA,OAAO,IAAI,sBACX,mBACA,oBACA,wBACA,aACF;CACF;CA2BA,MAAM,SAAS,IAAI,OACjB,IACA,QACA,mBA5BqB,cAClB,OACC,IAAI,eACF,GACG,YAAY,aAAa,SAAS,EAClC,YAAY,iBAAiB,EAAE,GAClC,OACA,IACA,YACA,QACA,iBACF,IACF,KAAA,GAGF,eAAe,KAAA,IACX,KAAA,KACC,IAAY,uBACX,IAAI,cACF,QACA,GAAG,YAAY,iBAAiB,EAAE,GAClC,IACA,kBACF,GAQN,QACA,iBACF;CAEA,wBAAwB,IAAI,MAAM;CAElC,SAAc,WAAW,OAAO,KAAK,CAAC,SAAS,EAAC,OAAO,KAAI,CAAC,CAAC,CAAC;CAE9D,OAAO,eAAe,IAAI,QAAQ,MAAM;AAC1C;AAGA,IAAI,CAAC,kBAAkB,GACrB,gBACQ,UACA,UAAU,KAAK,YAAY,GAAG,QAAQ,KAAK,GAAG,QAAQ,KAAK,MAAM,CAAC,CAAC,CAC3E"}
@@ -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;;IAgC1D,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;IAqPD;;;OAGG;IACH,QAAQ,CAAC,OAAO,EAAE,mBAAmB,GAAG,IAAI,CA2M3C;IAED,MAAM,IAAI,IAAI,CAGb;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;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;IAiQD;;;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"}
@@ -57,6 +57,8 @@ var BackfillManager = class {
57
57
  /** The watermark of the current transaction in the change stream. */
58
58
  #currentTxWatermark = null;
59
59
  #commitThresholdBytes;
60
+ /** Set when the change stream is canceled. No further backfills are run. */
61
+ #canceled = false;
60
62
  constructor(lc, changeStreamer, backfillStreamer, jsonFormat = "s", minBackoffMs = MIN_BACKOFF_INTERVAL_MS, maxBackoffMs = MAX_BACKOFF_INTERVAL_MS, commitThresholdBytes = COMMIT_THRESHOLD_BYTES) {
61
63
  this.#lc = lc.withContext("component", "backfill-manager");
62
64
  this.#changeStreamer = changeStreamer;
@@ -102,7 +104,7 @@ var BackfillManager = class {
102
104
  #retryDelayMs;
103
105
  #backfillRetryTimer;
104
106
  #checkAndStartBackfill() {
105
- if (!this.#backfillRetryTimer && !this.#runningBackfill && this.#requiredBackfills.size) {
107
+ if (!this.#canceled && !this.#backfillRetryTimer && !this.#runningBackfill && this.#requiredBackfills.size) {
106
108
  const candidates = [...this.#requiredBackfills.values()];
107
109
  const request = candidates[randInt(0, candidates.length - 1)];
108
110
  const state = {
@@ -121,6 +123,10 @@ var BackfillManager = class {
121
123
  }
122
124
  }
123
125
  #retryBackfillWithBackoff(e) {
126
+ if (this.#canceled) {
127
+ this.#lc.debug?.(`not retrying backfill: change stream canceled`, e);
128
+ return;
129
+ }
124
130
  const log = this.#retryDelayMs === this.#maxBackoffMs ? "error" : "warn";
125
131
  this.#lc[log]?.(`Error running backfill. Retrying in ${this.#retryDelayMs} ms`, e);
126
132
  this.#backfillRetryTimer = setTimeout(() => {
@@ -177,6 +183,10 @@ var BackfillManager = class {
177
183
  uncommittedBytes = 0;
178
184
  };
179
185
  for await (const { message: msg, byteSize } of this.#backfillStreamer(state.request)) {
186
+ if (this.#canceled) {
187
+ lc.info?.(`backfill stream canceled: change stream canceled`);
188
+ return;
189
+ }
180
190
  const mustWaitBeforeFlush = msg.tag === "backfill-completed" && (this.#changeStreamReached(lc, msg.watermark) || backfillTx !== null && backfillTx < msg.watermark);
181
191
  if (backfillTx && (changeStream.waiterDelay() > 0 || mustWaitBeforeFlush || uncommittedBytes >= this.#commitThresholdBytes)) commitTx();
182
192
  mustWaitBeforeFlush && await mustWaitBeforeFlush;
@@ -387,8 +397,11 @@ var BackfillManager = class {
387
397
  }
388
398
  }
389
399
  cancel() {
400
+ this.#canceled = true;
390
401
  this.#stopRunningBackfill(`change stream canceled`);
391
402
  clearTimeout(this.#backfillRetryTimer);
403
+ this.#backfillRetryTimer = void 0;
404
+ for (const { reached } of this.#awaitingStatusWatermarks.splice(0)) reached();
392
405
  }
393
406
  };
394
407
  var BackfillStreamError = class extends Error {
@@ -1 +1 @@
1
- {"version":3,"file":"backfill-manager.js","names":["#lc","#requiredBackfills","#changeStreamer","#backfillStreamer","#jsonFormat","#awaitingStatusWatermarks","#commitThresholdBytes","#minBackoffMs","#maxBackoffMs","#retryDelayMs","#lastStatusWatermark","#setRequiredBackfill","#checkAndStartBackfill","#backfillRetryTimer","#runningBackfill","#runBackfill","#stopRunningBackfill","#retryBackfillWithBackoff","#changeStreamReached","#currentTxWatermark","#setLastStatusWatermark","#backfillRunningFor","#deleteRequiredBackfill"],"sources":["../../../../../../../zero-cache/src/services/change-source/common/backfill-manager.ts"],"sourcesContent":["import type {LogContext} from '@rocicorp/logger';\nimport {resolver} from '@rocicorp/resolver';\nimport {assert} from '../../../../../shared/src/asserts.ts';\nimport {stringify} from '../../../../../shared/src/bigint-json.ts';\nimport {CustomKeyMap} from '../../../../../shared/src/custom-key-map.ts';\nimport {must} from '../../../../../shared/src/must.ts';\nimport {randInt} from '../../../../../shared/src/rand.ts';\nimport {JSON_STRINGIFIED, type JSONFormat} from '../../../types/lite.ts';\nimport {\n stateVersionFromString,\n stateVersionToString,\n} from '../../../types/state-version.ts';\nimport type {\n BackfillCompleted,\n BackfillRequest,\n ChangeStreamMessage,\n Identifier,\n MessageBackfill,\n} from '../protocol/current.ts';\nimport type {\n Cancelable,\n ChangeStreamMultiplexer,\n Listener,\n} from './change-stream-multiplexer.ts';\n\nfunction tableKey({schema, name}: Identifier) {\n return `${schema}.${name}`;\n}\n\n/**\n * A {@link MessageBackfill} or {@link BackfillCompleted} paired with the\n * approximate number of upstream bytes it represents. The BackfillManager uses\n * `byteSize` to bound the size of each replica transaction, committing and\n * reopening once {@link COMMIT_THRESHOLD_BYTES} is reached.\n */\nexport type BackfillMessage = {\n message: MessageBackfill | BackfillCompleted;\n byteSize: number;\n};\n\ntype BackfillStreamer = (\n req: BackfillRequest,\n) => AsyncGenerator<BackfillMessage>;\n\n/**\n * Commit (and reopen) the current backfill transaction once this many bytes of\n * backfill data have accumulated in it. Bounds the size of a single replica\n * COMMIT/checkpoint so it can't monopolize the replica.\n */\nconst COMMIT_THRESHOLD_BYTES = 8 * 1024 * 1024;\n\ntype RunningBackfillState = {\n request: BackfillRequest;\n canceledReason?: string | undefined;\n minWatermark: string;\n};\n\nconst MIN_BACKOFF_INTERVAL_MS = 2_000;\nconst MAX_BACKOFF_INTERVAL_MS = 60_000;\n\ntype AwaitingStatusWatermark = {\n watermark: string;\n reached: () => void;\n};\n\n/**\n * The BackfillManager initiates backfills for BackfillRequests from the\n * change-streamer (i.e. unfinished backfills from previous sessions)\n * or for new backfills signaled by `create-table` or `add-column` messages\n * from the change-source.\n *\n * The BackfillManager registers itself as a change stream listener in order\n * to track necessary backfills, and potentially invalidate the in-progress\n * backfill (e.g. due to a schema change) so that it can be retried at a\n * new snapshot.\n *\n * The manager also handles low priority streaming of the backfill messages\n * using the {@link ChangeStreamMultiplexer}, implementing a policy of always\n * releasing its reservation if another producer (i.e. the main change stream)\n * has messages to stream.\n */\nexport class BackfillManager implements Cancelable, Listener {\n readonly #lc: LogContext;\n\n /**\n * Tracks the metadata of required backfills based on schema changes\n * and initial backfill requests.\n */\n readonly #requiredBackfills = new CustomKeyMap<Identifier, BackfillRequest>(\n tableKey,\n );\n readonly #changeStreamer: ChangeStreamMultiplexer;\n readonly #backfillStreamer: BackfillStreamer;\n readonly #jsonFormat: JSONFormat;\n\n /**\n * The current running backfill. The backfill request is always also in\n * `#requiredBackfills` (technically, it can be a subset of what's in\n * `#requiredBackfills`); the request is removed from `#requiredBackfills`\n * upon completion.\n */\n #runningBackfill: RunningBackfillState | null = null;\n\n /** The last seen watermark in the change stream. */\n #lastStatusWatermark: string | null = null;\n\n readonly #awaitingStatusWatermarks: AwaitingStatusWatermark[] = [];\n\n /** The watermark of the current transaction in the change stream. */\n #currentTxWatermark: string | null = null;\n\n readonly #commitThresholdBytes: number;\n\n constructor(\n lc: LogContext,\n changeStreamer: ChangeStreamMultiplexer,\n backfillStreamer: BackfillStreamer,\n jsonFormat: JSONFormat = JSON_STRINGIFIED,\n minBackoffMs = MIN_BACKOFF_INTERVAL_MS,\n maxBackoffMs = MAX_BACKOFF_INTERVAL_MS,\n commitThresholdBytes = COMMIT_THRESHOLD_BYTES,\n ) {\n this.#lc = lc.withContext('component', 'backfill-manager');\n this.#changeStreamer = changeStreamer;\n this.#backfillStreamer = backfillStreamer;\n this.#jsonFormat = jsonFormat;\n this.#minBackoffMs = minBackoffMs;\n this.#maxBackoffMs = maxBackoffMs;\n this.#retryDelayMs = minBackoffMs;\n this.#commitThresholdBytes = commitThresholdBytes;\n }\n\n run(lastWatermark: string, initialRequests: BackfillRequest[]) {\n this.#lc.info?.(\n `starting backfill manager with ${initialRequests.length} initial requests`,\n {requests: initialRequests},\n );\n this.#lastStatusWatermark = lastWatermark;\n initialRequests.forEach(req =>\n this.#setRequiredBackfill('initial-request', req),\n );\n this.#checkAndStartBackfill();\n }\n\n #setLastStatusWatermark({watermark}: {watermark: string}) {\n // Only allow the watermark to move forward. This prevents a backfill\n // transaction (whose watermark is unrelated to change-stream state)\n // from moving the watermark backwards.\n if ((this.#lastStatusWatermark ?? '') < watermark) {\n this.#lastStatusWatermark = watermark;\n for (let i = this.#awaitingStatusWatermarks.length - 1; i >= 0; i--) {\n const awaiting = this.#awaitingStatusWatermarks[i];\n if (watermark >= awaiting.watermark) {\n awaiting.reached();\n this.#awaitingStatusWatermarks.splice(i, 1);\n }\n }\n }\n }\n\n #changeStreamReached(\n lc: LogContext,\n watermark: string,\n ): Promise<void> | null {\n if ((this.#lastStatusWatermark ?? '') < watermark) {\n const {promise, resolve: reached} = resolver();\n this.#awaitingStatusWatermarks.push({watermark, reached});\n lc.info?.(\n `waiting for change stream (at ${this.#lastStatusWatermark}) to reach ${watermark}`,\n );\n return promise;\n }\n return null;\n }\n\n readonly #minBackoffMs: number;\n readonly #maxBackoffMs: number;\n #retryDelayMs: number;\n #backfillRetryTimer: NodeJS.Timeout | undefined;\n\n #checkAndStartBackfill() {\n if (\n !this.#backfillRetryTimer &&\n !this.#runningBackfill &&\n this.#requiredBackfills.size\n ) {\n // Pick a random backfill to avoid head-of-line blocking by a\n // problematic backfill (e.g. awaiting a primary key). This is\n // simpler that adding logic to classify (and declassify)\n // problematic backfills.\n const candidates = [...this.#requiredBackfills.values()];\n const request = candidates[randInt(0, candidates.length - 1)];\n const state = {request, minWatermark: ''};\n const lc = this.#lc.withContext('table', request.table.name);\n\n this.#runningBackfill = state;\n void this.#runBackfill(lc, state)\n .then(() => {\n this.#stopRunningBackfill('backfill exited', state);\n this.#retryDelayMs = this.#minBackoffMs; // reset on success\n })\n // For unexpected errors (e.g. upstream replication slot\n // unavailability), retry with exponential backoff.\n .catch(e => {\n this.#stopRunningBackfill(String(e), state);\n this.#retryBackfillWithBackoff(e);\n });\n }\n }\n\n #retryBackfillWithBackoff(e: unknown) {\n const log = this.#retryDelayMs === this.#maxBackoffMs ? 'error' : 'warn';\n this.#lc[log]?.(\n `Error running backfill. Retrying in ${this.#retryDelayMs} ms`,\n e,\n );\n this.#backfillRetryTimer = setTimeout(() => {\n this.#backfillRetryTimer = undefined;\n this.#checkAndStartBackfill();\n }, this.#retryDelayMs);\n\n this.#retryDelayMs = Math.min(this.#retryDelayMs * 2, this.#maxBackoffMs);\n }\n\n async #runBackfill(lc: LogContext, state: RunningBackfillState) {\n const changeStream = this.#changeStreamer; // Purely for readability\n\n // backfillTx is set if and only if a changeStreamer reservation has been\n // acquired and the backfill stream is inside a transaction.\n let backfillTx: string | null = null;\n let uncommittedBytes = 0;\n\n /**\n * @returns the new tx watermark, or null if backfill was cancelled\n */\n const beginTxFor = async (\n msg: MessageBackfill | BackfillCompleted,\n ): Promise<string | null> => {\n assert(backfillTx === null, 'Expected no active backfill transaction');\n const lastWatermark = await changeStream.reserve('backfill');\n\n // After obtaining the changeStream reservation, check if the stream\n // had changes that resulted in invalidating / canceling this backfill.\n if (\n state.canceledReason ||\n (msg.tag === 'backfill' && msg.watermark < state.minWatermark)\n ) {\n if (state.canceledReason === undefined) {\n assert(msg.tag === 'backfill', 'Expected backfill message tag'); // TypeScript should have figured this out.\n this.#stopRunningBackfill(\n `row key change at ${state.minWatermark} ` +\n `postdates backfill watermark at ${msg.watermark}`,\n state,\n );\n }\n changeStream.release(lastWatermark);\n return null;\n }\n\n const {major, minor = 0n} = stateVersionFromString(lastWatermark);\n let tx = stateVersionToString({\n major,\n minor: BigInt(minor) + 1n,\n });\n\n if (msg.tag === 'backfill-completed' && tx < msg.watermark) {\n // At this point it must be the case that the #changeStreamReached() the\n // backfill watermark. Given that guarantee, ensure that the version of the\n // transaction containing the backfill-completed message is at least up\n // to the backfill watermark, so that the final database state version is\n // never earlier than the version of any backfilled rows.\n tx = msg.watermark;\n }\n\n void changeStream.push([\n 'begin',\n {tag: 'begin', json: this.#jsonFormat, skipAck: true},\n {commitWatermark: tx},\n ]);\n return (backfillTx = tx);\n };\n\n const commitTx = () => {\n if (backfillTx) {\n void changeStream.push([\n 'commit',\n {tag: 'commit'},\n {watermark: backfillTx},\n ]);\n changeStream.release(backfillTx);\n }\n backfillTx = null;\n uncommittedBytes = 0;\n };\n\n for await (const {message: msg, byteSize} of this.#backfillStreamer(\n state.request,\n )) {\n // Before sending `backfill-completed`, the main replication stream\n // may need to catch up, and/or the current transaction may need to be\n // committed to open a new transaction that's up to backfill watermark.\n const mustWaitBeforeFlush =\n msg.tag === 'backfill-completed' &&\n (this.#changeStreamReached(lc, msg.watermark) ||\n (backfillTx !== null && backfillTx < msg.watermark));\n\n // Commit (and later reopen) the transaction if the main stream is\n // waiting on the reservation, if we must catch up before completing, or\n // if the size of current transaction has reached the commit threshold.\n if (\n backfillTx &&\n (changeStream.waiterDelay() > 0 ||\n mustWaitBeforeFlush ||\n uncommittedBytes >= this.#commitThresholdBytes)\n ) {\n commitTx();\n }\n\n mustWaitBeforeFlush && (await mustWaitBeforeFlush);\n\n if (\n msg.tag === 'backfill' &&\n msg.rowValues.length > 0 &&\n msg.relation.rowKey.columns.length === 0\n ) {\n throw new MissingRowKeyError(state.request);\n }\n\n // Reserve the changeStreamer if not in a transaction.\n if ((backfillTx ??= await beginTxFor(msg)) === null) {\n lc.info?.(\n `backfill stream canceled: ${state.canceledReason}`,\n state.request,\n );\n this.#checkAndStartBackfill(); // start the next backfill if present\n return; // this backfill is canceled\n }\n\n // `await` to allow the change streamer to exert back pressure\n // on backfills.\n await changeStream.push(['data', msg]);\n uncommittedBytes += byteSize;\n }\n\n // Flush any final tx and release the stream.\n backfillTx && commitTx();\n lc.debug?.(`backfill stream exited`, state.canceledReason ?? '');\n }\n\n #backfillRunningFor(table: Identifier): RunningBackfillState | null {\n const state = this.#runningBackfill;\n return state?.request.table.schema === table.schema &&\n state.request.table.name === table.name\n ? state\n : null;\n }\n\n /**\n * Stops the running backfill for the specified `reason`. If `instance` is\n * specified, the running backfill is stopped only if it is that instance.\n * This allows the running backfill itself to clear backfill state without\n * accidentally stopping a different (e.g. subsequent) backfill.\n */\n #stopRunningBackfill(reason?: string, instance?: RunningBackfillState) {\n const backfill = this.#runningBackfill;\n if (backfill && backfill === (instance ?? backfill)) {\n backfill.canceledReason = reason;\n this.#runningBackfill = null;\n reason && this.#lc.info?.(`canceling backfill:`, reason);\n }\n }\n\n #setRequiredBackfill(source: string, req: BackfillRequest) {\n const action = this.#requiredBackfills.has(req.table) ? 'updated' : 'added';\n this.#lc.info?.(`Backfill ${action}: ${source}`, {backfill: req});\n this.#requiredBackfills.set(req.table, req);\n }\n\n #deleteRequiredBackfill(source: string, id: Identifier) {\n const req = this.#requiredBackfills.get(id);\n if (req) {\n const action = source === 'backfill-completed' ? 'completed' : 'dropped';\n this.#lc.info?.(`Backfill ${action}: ${source}`, {backfill: req});\n this.#requiredBackfills.delete(id);\n }\n }\n\n /**\n * Implements {@link Listener.onChange()}, invoked by the\n * {@link ChangeStreamMultiplexer}.\n */\n onChange(message: ChangeStreamMessage): void {\n if (message[0] === 'begin') {\n this.#currentTxWatermark = message[2].commitWatermark;\n return;\n }\n if (message[0] === 'commit') {\n this.#currentTxWatermark = null;\n this.#setLastStatusWatermark(message[2]);\n // Every commit is a candidate for starting the next backfill\n // (if one is not currently running).\n this.#checkAndStartBackfill();\n return;\n }\n if (message[0] === 'status') {\n this.#setLastStatusWatermark(message[2]);\n return;\n }\n if (message[0] !== 'data') {\n return;\n }\n const change = message[1];\n const {tag} = change;\n switch (tag) {\n case 'update-table-metadata': {\n const {table, new: metadata} = change;\n const backfillRequest = this.#requiredBackfills.get(table);\n if (backfillRequest) {\n this.#setRequiredBackfill(tag, {\n ...backfillRequest,\n table: {...backfillRequest.table, metadata},\n });\n if (this.#backfillRunningFor(table)) {\n this.#stopRunningBackfill(`TableMetadata updated`);\n }\n }\n break;\n }\n case 'create-table': {\n const {\n spec: {schema, name},\n metadata = null,\n backfill,\n } = change;\n\n if (backfill) {\n this.#setRequiredBackfill(tag, {\n table: {schema, name, metadata},\n columns: backfill,\n });\n }\n break;\n }\n case 'rename-table': {\n const {old, new: newTable} = change;\n const backfillRequest = this.#requiredBackfills.get(old);\n if (backfillRequest) {\n const {schema, name} = newTable;\n this.#deleteRequiredBackfill(tag, old);\n this.#setRequiredBackfill(tag, {\n ...backfillRequest,\n table: {...backfillRequest.table, schema, name},\n });\n if (this.#backfillRunningFor(old)) {\n this.#stopRunningBackfill(`table renamed`);\n }\n }\n break;\n }\n case 'drop-table': {\n const {id} = change;\n const backfillRequest = this.#requiredBackfills.get(id);\n if (backfillRequest) {\n this.#deleteRequiredBackfill(tag, id);\n if (this.#backfillRunningFor(id)) {\n this.#stopRunningBackfill(`table dropped`);\n }\n }\n break;\n }\n case 'add-column': {\n const {\n table,\n tableMetadata: metadata = null,\n column,\n backfill,\n } = change;\n if (backfill) {\n const backfillRequest = this.#requiredBackfills.get(table);\n if (!backfillRequest) {\n this.#setRequiredBackfill(tag, {\n table: {...table, metadata},\n columns: {[column.name]: backfill},\n });\n } else {\n this.#setRequiredBackfill(tag, {\n ...backfillRequest,\n table: {...backfillRequest.table, metadata},\n columns: {\n ...backfillRequest.columns,\n [column.name]: backfill,\n },\n });\n // Note: The running backfill need not be canceled if a\n // new column is added. The new column will be backfilled\n // by its own stream after the current backfill completes.\n }\n }\n break;\n }\n case 'update-column': {\n const {\n table,\n old: {name: oldName},\n new: {name: newName},\n } = change;\n if (oldName !== newName) {\n const backfillRequest = this.#requiredBackfills.get(table);\n if (backfillRequest && oldName in backfillRequest.columns) {\n const {[oldName]: colSpec, ...otherCols} = backfillRequest.columns;\n this.#setRequiredBackfill(tag, {\n ...backfillRequest,\n columns: {...otherCols, [newName]: colSpec},\n });\n const backfill = this.#backfillRunningFor(table);\n if (backfill && oldName in backfill.request.columns) {\n this.#stopRunningBackfill(`column renamed`);\n }\n }\n }\n break;\n }\n case 'drop-column': {\n const {table, column} = change;\n const backfillRequest = this.#requiredBackfills.get(table);\n if (backfillRequest && column in backfillRequest.columns) {\n const {[column]: _excluded, ...remaining} = backfillRequest.columns;\n if (Object.keys(remaining).length === 0) {\n this.#deleteRequiredBackfill(tag, table);\n } else {\n this.#setRequiredBackfill(tag, {\n ...backfillRequest,\n columns: remaining,\n });\n }\n const backfill = this.#backfillRunningFor(table);\n if (backfill && column in backfill.request.columns) {\n this.#stopRunningBackfill(`column dropped`);\n }\n }\n break;\n }\n case 'update': {\n const {relation, key, new: row} = change;\n const backfill = this.#backfillRunningFor(relation);\n const txWatermark = must(this.#currentTxWatermark, `not in a tx`);\n if (backfill?.request.table.metadata && key !== null) {\n // A corner case that backfill is unable to correctly handle is\n // when a row's key changes; this is decomposed into a delete\n // of the old key and a set of the new key in the replica change\n // log, at which point the backfill algorithm assumes that the\n // (old) row is deleted but does not know to backfill the new row.\n // In these corner cases, the current backfill is canceled and\n // retried if its version precedes this update.\n for (const col of Object.keys(\n backfill.request.table.metadata.rowKey,\n )) {\n if (key[col] !== row[col]) {\n backfill.minWatermark = txWatermark;\n this.#lc.info?.(\n `key for row as changed (col: ${col}). ` +\n `backfill data must not predate ${backfill.minWatermark}`,\n );\n break;\n }\n }\n }\n break;\n }\n case 'backfill-completed': {\n const {relation, columns} = change;\n const backfillRequest = this.#requiredBackfills.get(relation);\n assert(\n backfillRequest,\n () => `No BackfillRequest completed backfill ${stringify(change)}`,\n );\n const remaining = Object.entries(backfillRequest.columns).filter(\n ([col]) =>\n !(columns.includes(col) || relation.rowKey.columns.includes(col)),\n );\n if (remaining.length === 0) {\n this.#deleteRequiredBackfill(tag, relation);\n } else {\n this.#setRequiredBackfill(tag, {\n ...backfillRequest,\n columns: Object.fromEntries(remaining),\n });\n }\n // Technically the backfill is already stopping, but this method\n // cleans up the state that tracks it.\n this.#stopRunningBackfill();\n break;\n }\n }\n }\n\n cancel(): void {\n this.#stopRunningBackfill(`change stream canceled`);\n clearTimeout(this.#backfillRetryTimer);\n }\n}\n\nabstract class BackfillStreamError extends Error {\n constructor(bf: BackfillRequest, msg: string, cause?: unknown) {\n super(\n `Cannot backfill ${bf.table.schema}.${bf.table.name}` +\n `[${Object.keys(bf.columns).join(',')}]: ${msg}`,\n {cause},\n );\n }\n}\n\n/**\n * Background: The zero-cache supports replication of tables without a\n * PRIMARY KEY to facilitate the onboarding process. These rows can be\n * INSERT'ed, but postgres will rightfully prohibit UPDATEs and DELETEs\n * on such tables because the rows cannot be identified by a key. Supporting\n * this mode of replication allows the user to \"fix\" the setup by adding the\n * primary key, after which the table can be published downstream without\n * requiring a resync of the data.\n *\n * In terms of backfill, however, non-empty tables without a row key **cannot**\n * be backfilled, because backfill retries would result in writing duplicating\n * rows. (Empty tables, on the other hand, are fine because there is no data\n * to be deduped.)\n *\n * The MissingRowKeyError is used to signal that the table cannot be backfilled\n * in its current state. For simplicity, it is handled like runtime errors and\n * retried with backoff, with which it can eventually succeed if (1) a primary\n * key is added or (2) the table is emptied, e.g. via a TRUNCATE.\n */\nclass MissingRowKeyError extends BackfillStreamError {\n readonly name = 'MissingRowKeyError';\n\n constructor(bf: BackfillRequest, cause?: unknown) {\n super(bf, `\"${bf.table.name}\" is missing a PRIMARY KEY`, cause);\n }\n}\n\n/**\n * Error type for backfill stream implementations to throw indicating that\n * the backfill request failed due to a schema incompatibility error. This\n * type of error does not need exponential backoff, as the retry happens\n * naturally once the invalidating schema change is processed and committed.\n */\nexport class SchemaIncompatibilityError extends BackfillStreamError {\n readonly name = 'SchemaIncompatibilityError';\n\n constructor(bf: BackfillRequest, msg: string, cause?: unknown) {\n super(bf, msg, cause);\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"backfill-manager.js","names":["#lc","#requiredBackfills","#changeStreamer","#backfillStreamer","#jsonFormat","#awaitingStatusWatermarks","#commitThresholdBytes","#minBackoffMs","#maxBackoffMs","#retryDelayMs","#lastStatusWatermark","#setRequiredBackfill","#checkAndStartBackfill","#canceled","#backfillRetryTimer","#runningBackfill","#runBackfill","#stopRunningBackfill","#retryBackfillWithBackoff","#changeStreamReached","#currentTxWatermark","#setLastStatusWatermark","#backfillRunningFor","#deleteRequiredBackfill"],"sources":["../../../../../../../zero-cache/src/services/change-source/common/backfill-manager.ts"],"sourcesContent":["import type {LogContext} from '@rocicorp/logger';\nimport {resolver} from '@rocicorp/resolver';\nimport {assert} from '../../../../../shared/src/asserts.ts';\nimport {stringify} from '../../../../../shared/src/bigint-json.ts';\nimport {CustomKeyMap} from '../../../../../shared/src/custom-key-map.ts';\nimport {must} from '../../../../../shared/src/must.ts';\nimport {randInt} from '../../../../../shared/src/rand.ts';\nimport {JSON_STRINGIFIED, type JSONFormat} from '../../../types/lite.ts';\nimport {\n stateVersionFromString,\n stateVersionToString,\n} from '../../../types/state-version.ts';\nimport type {\n BackfillCompleted,\n BackfillRequest,\n ChangeStreamMessage,\n Identifier,\n MessageBackfill,\n} from '../protocol/current.ts';\nimport type {\n Cancelable,\n ChangeStreamMultiplexer,\n Listener,\n} from './change-stream-multiplexer.ts';\n\nfunction tableKey({schema, name}: Identifier) {\n return `${schema}.${name}`;\n}\n\n/**\n * A {@link MessageBackfill} or {@link BackfillCompleted} paired with the\n * approximate number of upstream bytes it represents. The BackfillManager uses\n * `byteSize` to bound the size of each replica transaction, committing and\n * reopening once {@link COMMIT_THRESHOLD_BYTES} is reached.\n */\nexport type BackfillMessage = {\n message: MessageBackfill | BackfillCompleted;\n byteSize: number;\n};\n\ntype BackfillStreamer = (\n req: BackfillRequest,\n) => AsyncGenerator<BackfillMessage>;\n\n/**\n * Commit (and reopen) the current backfill transaction once this many bytes of\n * backfill data have accumulated in it. Bounds the size of a single replica\n * COMMIT/checkpoint so it can't monopolize the replica.\n */\nconst COMMIT_THRESHOLD_BYTES = 8 * 1024 * 1024;\n\ntype RunningBackfillState = {\n request: BackfillRequest;\n canceledReason?: string | undefined;\n minWatermark: string;\n};\n\nconst MIN_BACKOFF_INTERVAL_MS = 2_000;\nconst MAX_BACKOFF_INTERVAL_MS = 60_000;\n\ntype AwaitingStatusWatermark = {\n watermark: string;\n reached: () => void;\n};\n\n/**\n * The BackfillManager initiates backfills for BackfillRequests from the\n * change-streamer (i.e. unfinished backfills from previous sessions)\n * or for new backfills signaled by `create-table` or `add-column` messages\n * from the change-source.\n *\n * The BackfillManager registers itself as a change stream listener in order\n * to track necessary backfills, and potentially invalidate the in-progress\n * backfill (e.g. due to a schema change) so that it can be retried at a\n * new snapshot.\n *\n * The manager also handles low priority streaming of the backfill messages\n * using the {@link ChangeStreamMultiplexer}, implementing a policy of always\n * releasing its reservation if another producer (i.e. the main change stream)\n * has messages to stream.\n */\nexport class BackfillManager implements Cancelable, Listener {\n readonly #lc: LogContext;\n\n /**\n * Tracks the metadata of required backfills based on schema changes\n * and initial backfill requests.\n */\n readonly #requiredBackfills = new CustomKeyMap<Identifier, BackfillRequest>(\n tableKey,\n );\n readonly #changeStreamer: ChangeStreamMultiplexer;\n readonly #backfillStreamer: BackfillStreamer;\n readonly #jsonFormat: JSONFormat;\n\n /**\n * The current running backfill. The backfill request is always also in\n * `#requiredBackfills` (technically, it can be a subset of what's in\n * `#requiredBackfills`); the request is removed from `#requiredBackfills`\n * upon completion.\n */\n #runningBackfill: RunningBackfillState | null = null;\n\n /** The last seen watermark in the change stream. */\n #lastStatusWatermark: string | null = null;\n\n readonly #awaitingStatusWatermarks: AwaitingStatusWatermark[] = [];\n\n /** The watermark of the current transaction in the change stream. */\n #currentTxWatermark: string | null = null;\n\n readonly #commitThresholdBytes: number;\n\n /** Set when the change stream is canceled. No further backfills are run. */\n #canceled = false;\n\n constructor(\n lc: LogContext,\n changeStreamer: ChangeStreamMultiplexer,\n backfillStreamer: BackfillStreamer,\n jsonFormat: JSONFormat = JSON_STRINGIFIED,\n minBackoffMs = MIN_BACKOFF_INTERVAL_MS,\n maxBackoffMs = MAX_BACKOFF_INTERVAL_MS,\n commitThresholdBytes = COMMIT_THRESHOLD_BYTES,\n ) {\n this.#lc = lc.withContext('component', 'backfill-manager');\n this.#changeStreamer = changeStreamer;\n this.#backfillStreamer = backfillStreamer;\n this.#jsonFormat = jsonFormat;\n this.#minBackoffMs = minBackoffMs;\n this.#maxBackoffMs = maxBackoffMs;\n this.#retryDelayMs = minBackoffMs;\n this.#commitThresholdBytes = commitThresholdBytes;\n }\n\n run(lastWatermark: string, initialRequests: BackfillRequest[]) {\n this.#lc.info?.(\n `starting backfill manager with ${initialRequests.length} initial requests`,\n {requests: initialRequests},\n );\n this.#lastStatusWatermark = lastWatermark;\n initialRequests.forEach(req =>\n this.#setRequiredBackfill('initial-request', req),\n );\n this.#checkAndStartBackfill();\n }\n\n #setLastStatusWatermark({watermark}: {watermark: string}) {\n // Only allow the watermark to move forward. This prevents a backfill\n // transaction (whose watermark is unrelated to change-stream state)\n // from moving the watermark backwards.\n if ((this.#lastStatusWatermark ?? '') < watermark) {\n this.#lastStatusWatermark = watermark;\n for (let i = this.#awaitingStatusWatermarks.length - 1; i >= 0; i--) {\n const awaiting = this.#awaitingStatusWatermarks[i];\n if (watermark >= awaiting.watermark) {\n awaiting.reached();\n this.#awaitingStatusWatermarks.splice(i, 1);\n }\n }\n }\n }\n\n #changeStreamReached(\n lc: LogContext,\n watermark: string,\n ): Promise<void> | null {\n if ((this.#lastStatusWatermark ?? '') < watermark) {\n const {promise, resolve: reached} = resolver();\n this.#awaitingStatusWatermarks.push({watermark, reached});\n lc.info?.(\n `waiting for change stream (at ${this.#lastStatusWatermark}) to reach ${watermark}`,\n );\n return promise;\n }\n return null;\n }\n\n readonly #minBackoffMs: number;\n readonly #maxBackoffMs: number;\n #retryDelayMs: number;\n #backfillRetryTimer: NodeJS.Timeout | undefined;\n\n #checkAndStartBackfill() {\n if (\n !this.#canceled &&\n !this.#backfillRetryTimer &&\n !this.#runningBackfill &&\n this.#requiredBackfills.size\n ) {\n // Pick a random backfill to avoid head-of-line blocking by a\n // problematic backfill (e.g. awaiting a primary key). This is\n // simpler that adding logic to classify (and declassify)\n // problematic backfills.\n const candidates = [...this.#requiredBackfills.values()];\n const request = candidates[randInt(0, candidates.length - 1)];\n const state = {request, minWatermark: ''};\n const lc = this.#lc.withContext('table', request.table.name);\n\n this.#runningBackfill = state;\n void this.#runBackfill(lc, state)\n .then(() => {\n this.#stopRunningBackfill('backfill exited', state);\n this.#retryDelayMs = this.#minBackoffMs; // reset on success\n })\n // For unexpected errors (e.g. upstream replication slot\n // unavailability), retry with exponential backoff.\n .catch(e => {\n this.#stopRunningBackfill(String(e), state);\n this.#retryBackfillWithBackoff(e);\n });\n }\n }\n\n #retryBackfillWithBackoff(e: unknown) {\n if (this.#canceled) {\n this.#lc.debug?.(`not retrying backfill: change stream canceled`, e);\n return;\n }\n const log = this.#retryDelayMs === this.#maxBackoffMs ? 'error' : 'warn';\n this.#lc[log]?.(\n `Error running backfill. Retrying in ${this.#retryDelayMs} ms`,\n e,\n );\n this.#backfillRetryTimer = setTimeout(() => {\n this.#backfillRetryTimer = undefined;\n this.#checkAndStartBackfill();\n }, this.#retryDelayMs);\n\n this.#retryDelayMs = Math.min(this.#retryDelayMs * 2, this.#maxBackoffMs);\n }\n\n async #runBackfill(lc: LogContext, state: RunningBackfillState) {\n const changeStream = this.#changeStreamer; // Purely for readability\n\n // backfillTx is set if and only if a changeStreamer reservation has been\n // acquired and the backfill stream is inside a transaction.\n let backfillTx: string | null = null;\n let uncommittedBytes = 0;\n\n /**\n * @returns the new tx watermark, or null if backfill was cancelled\n */\n const beginTxFor = async (\n msg: MessageBackfill | BackfillCompleted,\n ): Promise<string | null> => {\n assert(backfillTx === null, 'Expected no active backfill transaction');\n const lastWatermark = await changeStream.reserve('backfill');\n\n // After obtaining the changeStream reservation, check if the stream\n // had changes that resulted in invalidating / canceling this backfill.\n if (\n state.canceledReason ||\n (msg.tag === 'backfill' && msg.watermark < state.minWatermark)\n ) {\n if (state.canceledReason === undefined) {\n assert(msg.tag === 'backfill', 'Expected backfill message tag'); // TypeScript should have figured this out.\n this.#stopRunningBackfill(\n `row key change at ${state.minWatermark} ` +\n `postdates backfill watermark at ${msg.watermark}`,\n state,\n );\n }\n changeStream.release(lastWatermark);\n return null;\n }\n\n const {major, minor = 0n} = stateVersionFromString(lastWatermark);\n let tx = stateVersionToString({\n major,\n minor: BigInt(minor) + 1n,\n });\n\n if (msg.tag === 'backfill-completed' && tx < msg.watermark) {\n // At this point it must be the case that the #changeStreamReached() the\n // backfill watermark. Given that guarantee, ensure that the version of the\n // transaction containing the backfill-completed message is at least up\n // to the backfill watermark, so that the final database state version is\n // never earlier than the version of any backfilled rows.\n tx = msg.watermark;\n }\n\n void changeStream.push([\n 'begin',\n {tag: 'begin', json: this.#jsonFormat, skipAck: true},\n {commitWatermark: tx},\n ]);\n return (backfillTx = tx);\n };\n\n const commitTx = () => {\n if (backfillTx) {\n void changeStream.push([\n 'commit',\n {tag: 'commit'},\n {watermark: backfillTx},\n ]);\n changeStream.release(backfillTx);\n }\n backfillTx = null;\n uncommittedBytes = 0;\n };\n\n for await (const {message: msg, byteSize} of this.#backfillStreamer(\n state.request,\n )) {\n if (this.#canceled) {\n // Exiting the loop finalizes the backfill stream (and the upstream\n // resources it holds). The reservation, if held, does not need to be\n // released since the change stream is gone.\n lc.info?.(`backfill stream canceled: change stream canceled`);\n return;\n }\n // Before sending `backfill-completed`, the main replication stream\n // may need to catch up, and/or the current transaction may need to be\n // committed to open a new transaction that's up to backfill watermark.\n const mustWaitBeforeFlush =\n msg.tag === 'backfill-completed' &&\n (this.#changeStreamReached(lc, msg.watermark) ||\n (backfillTx !== null && backfillTx < msg.watermark));\n\n // Commit (and later reopen) the transaction if the main stream is\n // waiting on the reservation, if we must catch up before completing, or\n // if the size of current transaction has reached the commit threshold.\n if (\n backfillTx &&\n (changeStream.waiterDelay() > 0 ||\n mustWaitBeforeFlush ||\n uncommittedBytes >= this.#commitThresholdBytes)\n ) {\n commitTx();\n }\n\n mustWaitBeforeFlush && (await mustWaitBeforeFlush);\n\n if (\n msg.tag === 'backfill' &&\n msg.rowValues.length > 0 &&\n msg.relation.rowKey.columns.length === 0\n ) {\n throw new MissingRowKeyError(state.request);\n }\n\n // Reserve the changeStreamer if not in a transaction.\n if ((backfillTx ??= await beginTxFor(msg)) === null) {\n lc.info?.(\n `backfill stream canceled: ${state.canceledReason}`,\n state.request,\n );\n this.#checkAndStartBackfill(); // start the next backfill if present\n return; // this backfill is canceled\n }\n\n // `await` to allow the change streamer to exert back pressure\n // on backfills.\n await changeStream.push(['data', msg]);\n uncommittedBytes += byteSize;\n }\n\n // Flush any final tx and release the stream.\n backfillTx && commitTx();\n lc.debug?.(`backfill stream exited`, state.canceledReason ?? '');\n }\n\n #backfillRunningFor(table: Identifier): RunningBackfillState | null {\n const state = this.#runningBackfill;\n return state?.request.table.schema === table.schema &&\n state.request.table.name === table.name\n ? state\n : null;\n }\n\n /**\n * Stops the running backfill for the specified `reason`. If `instance` is\n * specified, the running backfill is stopped only if it is that instance.\n * This allows the running backfill itself to clear backfill state without\n * accidentally stopping a different (e.g. subsequent) backfill.\n */\n #stopRunningBackfill(reason?: string, instance?: RunningBackfillState) {\n const backfill = this.#runningBackfill;\n if (backfill && backfill === (instance ?? backfill)) {\n backfill.canceledReason = reason;\n this.#runningBackfill = null;\n reason && this.#lc.info?.(`canceling backfill:`, reason);\n }\n }\n\n #setRequiredBackfill(source: string, req: BackfillRequest) {\n const action = this.#requiredBackfills.has(req.table) ? 'updated' : 'added';\n this.#lc.info?.(`Backfill ${action}: ${source}`, {backfill: req});\n this.#requiredBackfills.set(req.table, req);\n }\n\n #deleteRequiredBackfill(source: string, id: Identifier) {\n const req = this.#requiredBackfills.get(id);\n if (req) {\n const action = source === 'backfill-completed' ? 'completed' : 'dropped';\n this.#lc.info?.(`Backfill ${action}: ${source}`, {backfill: req});\n this.#requiredBackfills.delete(id);\n }\n }\n\n /**\n * Implements {@link Listener.onChange()}, invoked by the\n * {@link ChangeStreamMultiplexer}.\n */\n onChange(message: ChangeStreamMessage): void {\n if (message[0] === 'begin') {\n this.#currentTxWatermark = message[2].commitWatermark;\n return;\n }\n if (message[0] === 'commit') {\n this.#currentTxWatermark = null;\n this.#setLastStatusWatermark(message[2]);\n // Every commit is a candidate for starting the next backfill\n // (if one is not currently running).\n this.#checkAndStartBackfill();\n return;\n }\n if (message[0] === 'status') {\n this.#setLastStatusWatermark(message[2]);\n return;\n }\n if (message[0] !== 'data') {\n return;\n }\n const change = message[1];\n const {tag} = change;\n switch (tag) {\n case 'update-table-metadata': {\n const {table, new: metadata} = change;\n const backfillRequest = this.#requiredBackfills.get(table);\n if (backfillRequest) {\n this.#setRequiredBackfill(tag, {\n ...backfillRequest,\n table: {...backfillRequest.table, metadata},\n });\n if (this.#backfillRunningFor(table)) {\n this.#stopRunningBackfill(`TableMetadata updated`);\n }\n }\n break;\n }\n case 'create-table': {\n const {\n spec: {schema, name},\n metadata = null,\n backfill,\n } = change;\n\n if (backfill) {\n this.#setRequiredBackfill(tag, {\n table: {schema, name, metadata},\n columns: backfill,\n });\n }\n break;\n }\n case 'rename-table': {\n const {old, new: newTable} = change;\n const backfillRequest = this.#requiredBackfills.get(old);\n if (backfillRequest) {\n const {schema, name} = newTable;\n this.#deleteRequiredBackfill(tag, old);\n this.#setRequiredBackfill(tag, {\n ...backfillRequest,\n table: {...backfillRequest.table, schema, name},\n });\n if (this.#backfillRunningFor(old)) {\n this.#stopRunningBackfill(`table renamed`);\n }\n }\n break;\n }\n case 'drop-table': {\n const {id} = change;\n const backfillRequest = this.#requiredBackfills.get(id);\n if (backfillRequest) {\n this.#deleteRequiredBackfill(tag, id);\n if (this.#backfillRunningFor(id)) {\n this.#stopRunningBackfill(`table dropped`);\n }\n }\n break;\n }\n case 'add-column': {\n const {\n table,\n tableMetadata: metadata = null,\n column,\n backfill,\n } = change;\n if (backfill) {\n const backfillRequest = this.#requiredBackfills.get(table);\n if (!backfillRequest) {\n this.#setRequiredBackfill(tag, {\n table: {...table, metadata},\n columns: {[column.name]: backfill},\n });\n } else {\n this.#setRequiredBackfill(tag, {\n ...backfillRequest,\n table: {...backfillRequest.table, metadata},\n columns: {\n ...backfillRequest.columns,\n [column.name]: backfill,\n },\n });\n // Note: The running backfill need not be canceled if a\n // new column is added. The new column will be backfilled\n // by its own stream after the current backfill completes.\n }\n }\n break;\n }\n case 'update-column': {\n const {\n table,\n old: {name: oldName},\n new: {name: newName},\n } = change;\n if (oldName !== newName) {\n const backfillRequest = this.#requiredBackfills.get(table);\n if (backfillRequest && oldName in backfillRequest.columns) {\n const {[oldName]: colSpec, ...otherCols} = backfillRequest.columns;\n this.#setRequiredBackfill(tag, {\n ...backfillRequest,\n columns: {...otherCols, [newName]: colSpec},\n });\n const backfill = this.#backfillRunningFor(table);\n if (backfill && oldName in backfill.request.columns) {\n this.#stopRunningBackfill(`column renamed`);\n }\n }\n }\n break;\n }\n case 'drop-column': {\n const {table, column} = change;\n const backfillRequest = this.#requiredBackfills.get(table);\n if (backfillRequest && column in backfillRequest.columns) {\n const {[column]: _excluded, ...remaining} = backfillRequest.columns;\n if (Object.keys(remaining).length === 0) {\n this.#deleteRequiredBackfill(tag, table);\n } else {\n this.#setRequiredBackfill(tag, {\n ...backfillRequest,\n columns: remaining,\n });\n }\n const backfill = this.#backfillRunningFor(table);\n if (backfill && column in backfill.request.columns) {\n this.#stopRunningBackfill(`column dropped`);\n }\n }\n break;\n }\n case 'update': {\n const {relation, key, new: row} = change;\n const backfill = this.#backfillRunningFor(relation);\n const txWatermark = must(this.#currentTxWatermark, `not in a tx`);\n if (backfill?.request.table.metadata && key !== null) {\n // A corner case that backfill is unable to correctly handle is\n // when a row's key changes; this is decomposed into a delete\n // of the old key and a set of the new key in the replica change\n // log, at which point the backfill algorithm assumes that the\n // (old) row is deleted but does not know to backfill the new row.\n // In these corner cases, the current backfill is canceled and\n // retried if its version precedes this update.\n for (const col of Object.keys(\n backfill.request.table.metadata.rowKey,\n )) {\n if (key[col] !== row[col]) {\n backfill.minWatermark = txWatermark;\n this.#lc.info?.(\n `key for row as changed (col: ${col}). ` +\n `backfill data must not predate ${backfill.minWatermark}`,\n );\n break;\n }\n }\n }\n break;\n }\n case 'backfill-completed': {\n const {relation, columns} = change;\n const backfillRequest = this.#requiredBackfills.get(relation);\n assert(\n backfillRequest,\n () => `No BackfillRequest completed backfill ${stringify(change)}`,\n );\n const remaining = Object.entries(backfillRequest.columns).filter(\n ([col]) =>\n !(columns.includes(col) || relation.rowKey.columns.includes(col)),\n );\n if (remaining.length === 0) {\n this.#deleteRequiredBackfill(tag, relation);\n } else {\n this.#setRequiredBackfill(tag, {\n ...backfillRequest,\n columns: Object.fromEntries(remaining),\n });\n }\n // Technically the backfill is already stopping, but this method\n // cleans up the state that tracks it.\n this.#stopRunningBackfill();\n break;\n }\n }\n }\n\n cancel(): void {\n this.#canceled = true;\n this.#stopRunningBackfill(`change stream canceled`);\n clearTimeout(this.#backfillRetryTimer);\n this.#backfillRetryTimer = undefined;\n\n // Wake up a backfill that is waiting for the change stream to reach a\n // watermark. The change stream never will, and the backfill must be\n // allowed to unwind (and release its upstream resources) rather than\n // remain suspended forever.\n for (const {reached} of this.#awaitingStatusWatermarks.splice(0)) {\n reached();\n }\n }\n}\n\nabstract class BackfillStreamError extends Error {\n constructor(bf: BackfillRequest, msg: string, cause?: unknown) {\n super(\n `Cannot backfill ${bf.table.schema}.${bf.table.name}` +\n `[${Object.keys(bf.columns).join(',')}]: ${msg}`,\n {cause},\n );\n }\n}\n\n/**\n * Background: The zero-cache supports replication of tables without a\n * PRIMARY KEY to facilitate the onboarding process. These rows can be\n * INSERT'ed, but postgres will rightfully prohibit UPDATEs and DELETEs\n * on such tables because the rows cannot be identified by a key. Supporting\n * this mode of replication allows the user to \"fix\" the setup by adding the\n * primary key, after which the table can be published downstream without\n * requiring a resync of the data.\n *\n * In terms of backfill, however, non-empty tables without a row key **cannot**\n * be backfilled, because backfill retries would result in writing duplicating\n * rows. (Empty tables, on the other hand, are fine because there is no data\n * to be deduped.)\n *\n * The MissingRowKeyError is used to signal that the table cannot be backfilled\n * in its current state. For simplicity, it is handled like runtime errors and\n * retried with backoff, with which it can eventually succeed if (1) a primary\n * key is added or (2) the table is emptied, e.g. via a TRUNCATE.\n */\nclass MissingRowKeyError extends BackfillStreamError {\n readonly name = 'MissingRowKeyError';\n\n constructor(bf: BackfillRequest, cause?: unknown) {\n super(bf, `\"${bf.table.name}\" is missing a PRIMARY KEY`, cause);\n }\n}\n\n/**\n * Error type for backfill stream implementations to throw indicating that\n * the backfill request failed due to a schema incompatibility error. This\n * type of error does not need exponential backoff, as the retry happens\n * naturally once the invalidating schema change is processed and committed.\n */\nexport class SchemaIncompatibilityError extends BackfillStreamError {\n readonly name = 'SchemaIncompatibilityError';\n\n constructor(bf: BackfillRequest, msg: string, cause?: unknown) {\n super(bf, msg, cause);\n }\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"change-stream-multiplexer.d.ts","sourceRoot":"","sources":["../../../../../../../zero-cache/src/services/change-source/common/change-stream-multiplexer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,kBAAkB,CAAC;AAGjD,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,2BAA2B,CAAC;AAEtD,OAAO,KAAK,EACV,mBAAmB,EACnB,uBAAuB,EACxB,MAAM,wBAAwB,CAAC;AAEhC,MAAM,MAAM,UAAU,GAAG;IACvB,MAAM,IAAI,IAAI,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,QAAQ,GAAG;IACrB,QAAQ,CAAC,MAAM,EAAE,mBAAmB,GAAG,IAAI,CAAC;CAC7C,CAAC;AAQF;;;GAGG;AACH,qBAAa,uBAAuB;;IAmBlC,YAAY,EAAE,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,EAMhD;IAED,YAAY,CAAC,GAAG,CAAC,EAAE,UAAU,EAAE,GAAG,IAAI,CAGrC;IAED,YAAY,CAAC,GAAG,CAAC,EAAE,QAAQ,EAAE,GAAG,IAAI,CAGnC;IAED;;;;;;;;;OASG;IACH,OAAO,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAelD;IAED;;;;OAIG;IACH,WAAW,IAAI,MAAM,CAKpB;IAED;;;OAGG;IACH,OAAO,CAAC,YAAY,EAAE,MAAM,QAY3B;IAED;;;;OAIG;IACH,UAAU,CAAC,OAAO,EAAE,uBAAuB,QAS1C;IAED;;;OAGG;IACH,IAAI,CAAC,OAAO,EAAE,mBAAmB,GAAG,OAAO,CAAC,OAAO,CAAC,CAOnD;IAED,IAAI,CAAC,GAAG,EAAE,KAAK,QAEd;IAED,QAAQ,IAAI,MAAM,CAAC,mBAAmB,CAAC,CAEtC;CACF"}
1
+ {"version":3,"file":"change-stream-multiplexer.d.ts","sourceRoot":"","sources":["../../../../../../../zero-cache/src/services/change-source/common/change-stream-multiplexer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,kBAAkB,CAAC;AAIjD,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,2BAA2B,CAAC;AAEtD,OAAO,KAAK,EACV,mBAAmB,EACnB,uBAAuB,EACxB,MAAM,wBAAwB,CAAC;AAEhC,MAAM,MAAM,UAAU,GAAG;IACvB,MAAM,IAAI,IAAI,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,QAAQ,GAAG;IACrB,QAAQ,CAAC,MAAM,EAAE,mBAAmB,GAAG,IAAI,CAAC;CAC7C,CAAC;AASF;;;GAGG;AACH,qBAAa,uBAAuB;;IAqBlC,YAAY,EAAE,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,EAMhD;IAmBD,YAAY,CAAC,GAAG,CAAC,EAAE,UAAU,EAAE,GAAG,IAAI,CAGrC;IAED,YAAY,CAAC,GAAG,CAAC,EAAE,QAAQ,EAAE,GAAG,IAAI,CAGnC;IAED;;;;;;;;;OASG;IACH,OAAO,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CA2BlD;IAED;;;;OAIG;IACH,WAAW,IAAI,MAAM,CAKpB;IAED;;;OAGG;IACH,OAAO,CAAC,YAAY,EAAE,MAAM,QAY3B;IAED;;;;OAIG;IACH,UAAU,CAAC,OAAO,EAAE,uBAAuB,QAS1C;IAED;;;OAGG;IACH,IAAI,CAAC,OAAO,EAAE,mBAAmB,GAAG,OAAO,CAAC,OAAO,CAAC,CAOnD;IAED,IAAI,CAAC,GAAG,EAAE,KAAK,QAEd;IAED,QAAQ,IAAI,MAAM,CAAC,mBAAmB,CAAC,CAEtC;CACF"}